Skip to content

createAgentSession Wiring

源码版本v0.73.1

createAgentSession is the core entry point of the SDK. It takes external services — AuthStorage, ModelRegistry, SettingsManager, SessionManager, ResourceLoader — plus tool definitions and an extension runner, and assembles them into a usable AgentSession. main.ts doesn't call it directly; it goes through createAgentSessionRuntimecreateAgentSessionServices. But SDK callers (embedding pi into other programs) use this function directly.

Responsibilities

  1. Service resolution: each external service can be passed in or auto-created; default paths are based on agentDir (~/.pi/agent). See packages/coding-agent/src/core/sdk.ts:193-211.
  2. Model recovery: when session data exists, the model is read from the session header; otherwise the default is taken from settings. If the model can't be recovered, a modelFallbackMessage is produced. See packages/coding-agent/src/core/sdk.ts:214-248.
  3. Thinking level clamp: reads thinking level from session or settings, then clampThinkingLevel restricts it to the model's capability range. See packages/coding-agent/src/core/sdk.ts:250-269.
  4. Stream function injection: the Agent's streamFn doesn't call streamSimple directly. It first calls modelRegistry.getApiKeyAndHeaders to get auth, then merges in telemetry headers from getAttributionHeaders. See packages/coding-agent/src/core/sdk.ts:328-346.
  5. convertToLlm wrapper: when blockImages is on, all ImageContent is replaced with placeholder text; settings are read dynamically so mid-session changes take effect. See packages/coding-agent/src/core/sdk.ts:282-316.
  6. Attribution headers: OpenRouter and Cloudflare get provider-specific HTTP headers for attribution. See packages/coding-agent/src/core/sdk.ts:128-156.

Design Motivation

Why not have callers new Agent and then new AgentSession themselves? The wiring steps involve cross-dependencies among a dozen services: auth determines whether a model is usable, the model determines the thinking level range, settings determine retry/transport/steering, the resource loader loads extensions and injects into extensionRunnerRef. Exposing all of this to callers would make everyone re-step on the same mines. createAgentSession centralizes the wiring rules into one function; SDK callers only need to pass cwd and (optionally) model.

getAttributionHeaders is extracted separately because different providers need different headers: OpenRouter looks at HTTP-Referer + X-OpenRouter-*, Cloudflare looks at User-Agent. Centralizing this avoids scatter across the stream path.

Key Files

The streamFn injection shows how auth failure short-circuits and how attribution headers are merged:

typescript
// packages/coding-agent/src/core/sdk.ts:328-346
streamFn: async (model, context, options) => {
	const auth = await modelRegistry.getApiKeyAndHeaders(model);
	if (!auth.ok) {
		throw new Error(auth.error);
	}
	const providerRetrySettings = settingsManager.getProviderRetrySettings();
	const attributionHeaders = getAttributionHeaders(model, settingsManager);
	return streamSimple(model, context, {
		...options,
		apiKey: auth.apiKey,
		timeoutMs: options?.timeoutMs ?? providerRetrySettings.timeoutMs,
		// ...
		headers:
			attributionHeaders || auth.headers || options?.headers
				? { ...attributionHeaders, ...auth.headers, ...options?.headers }
				: undefined,
	});
},

The convertToLlm wrapper, when blockImages is on, replaces images with text placeholders and dedupes consecutive placeholders:

typescript
// packages/coding-agent/src/core/sdk.ts:282-298
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
	const converted = convertToLlm(messages);
	if (!settingsManager.getBlockImages()) {
		return converted;
	}
	return converted.map((msg) => {
		if (msg.role === "user" || msg.role === "toolResult") {
			const content = msg.content;
			if (Array.isArray(content)) {
				const hasImages = content.some((c) => c.type === "image");
				if (hasImages) {
					const filteredContent = content
						.map((c) =>
							c.type === "image" ? { type: "text" as const, text: "Image reading is disabled." } : c,
						)
						// ... dedupe ...

Data Flow

Wiring order:

Edges and Failures

  • Model can't be recovered: modelFallbackMessage covers both "saved model can't be restored" and "no configured models at all"; both cases return it, and the UI decides how to show it. See packages/coding-agent/src/core/sdk.ts:243-247.
  • Thinking off when no model: if (!model) thinkingLevel = "off", to avoid sending thinking params for an empty model.
  • Session exists but no thinking entry: on recovery, an appendThinkingLevelChange is appended so subsequent fork/resume can pick up state. See packages/coding-agent/src/core/sdk.ts:378-390.
  • Extension transformContext: if extensionRunnerRef.current doesn't exist, the original messages are returned directly instead of throwing, so the flow works even when extensions aren't bound.

Summary

createAgentSession centralizes the wiring rules for a dozen services into one function; SDK callers only pass cwd and an optional model. For the assembly internals, see AgentSession Orchestration Layer; for tool factories, see Tools: read/bash/edit/write/grep/find/ls; for message conversion, see Message Types and convertToLlm.