Skip to content

createAgentSession 組裝

源码版本v0.73.1

createAgentSession 是 SDK 的核心入口。它把 AuthStorageModelRegistrySettingsManagerSessionManagerResourceLoader 這些外部服務,加上工具定義和擴充執行器,裝成一個可用的 AgentSessionmain.ts 不直接呼叫它,而是透過 createAgentSessionRuntimecreateAgentSessionServices,但 SDK 呼叫方(嵌入 pi 到別的程式)直接用這個函式。

職責

  1. 服務解析:每個外部服務都可以傳入或自動建立,預設路徑基於 agentDir(~/.pi/agent)。見 packages/coding-agent/src/core/sdk.ts:193-211
  2. 模型恢復:已有 session 資料時,從 session header 找回 model,否則從 settings 找預設;模型不能恢復時給 modelFallbackMessage。見 packages/coding-agent/src/core/sdk.ts:214-248
  3. thinking level 鉗制:從 session 或 settings 取 thinking level,然後 clampThinkingLevel 限制到模型能力範圍內。見 packages/coding-agent/src/core/sdk.ts:250-269
  4. stream 函式注入:AgentstreamFn 不直接呼叫 streamSimple,而是先 modelRegistry.getApiKeyAndHeaders 拿 auth,再拼上 getAttributionHeaders 的 telemetry 標頭。見 packages/coding-agent/src/core/sdk.ts:328-346
  5. convertToLlm 包裝:blockImages 設定開啟時,把所有 ImageContent 替換成佔位文字,動態讀 settings 保證 session 中途改也生效。見 packages/coding-agent/src/core/sdk.ts:282-316
  6. attribution 標頭:OpenRouter 和 Cloudflare 加專屬 HTTP 標頭用於歸類。見 packages/coding-agent/src/core/sdk.ts:128-156

設計動機

為什麼不讓呼叫方自己 new Agentnew AgentSession?因為組裝步驟涉及十多個服務的相互依賴:auth 決定 model 是否可用,model 決定 thinking level 範圍,settings 決定 retry/transport/steering,resource loader 載入擴充並注入 extensionRunnerRef。把這些暴露給呼叫方就是讓每個人都重新踩一遍坑。createAgentSession 把組裝規則集中到一個函式,SDK 呼叫方只管傳 cwd 和(可選)model

getAttributionHeaders 之所以單獨抽出來,是因為不同 provider 要的標頭不一樣:OpenRouter 看 HTTP-Referer + X-OpenRouter-*,Cloudflare 看 User-Agent。集中一處避免散落到 stream 路徑。

關鍵檔案

streamFn 注入處可見 auth 失敗如何短路、attribution 標頭如何合併:

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,
	});
},

convertToLlm 包裝層在 blockImages 開啟時把圖片替換成文字佔位,且去重連續佔位:

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,
						)
						// ... 去重 ...
	```

## 資料流

組裝順序:

```mermaid
graph TD
    A["createAgentSession options"] --> B["解析 cwd / agentDir"]
    B --> C["authStorage / modelRegistry / settingsManager / sessionManager"]
    C --> D["resourceLoader.reload"]
    D --> E["恢復或選定 model + thinkingLevel"]
    E --> F["包裝 convertToLlm"]
    F --> G["new Agent 注入 streamFn"]
    G --> H["sessionManager 寫入初始 model/thinking"]
    H --> I["new AgentSession"]
    I --> J["回傳 session + extensionsResult"]

邊界與失敗

  • 模型無法恢復:modelFallbackMessage 既提示「無法恢復保存的模型」也提示「沒有任何已設定模型」,兩者都回傳,UI 決定怎麼展示,見 packages/coding-agent/src/core/sdk.ts:243-247
  • 無 model 時 thinking off:if (!model) thinkingLevel = "off",避免給空模型發送 thinking 參數。
  • session 已存在但無 thinking entry:恢復時補一條 appendThinkingLevelChange,保證後續 fork/resume 能拿到狀態,見 packages/coding-agent/src/core/sdk.ts:378-390
  • 擴充 transformContext:如果 extensionRunnerRef.current 不存在就直接回傳原 messages,不拋錯,允許擴充未綁定時正常運作。

小結

createAgentSession 把十多個服務的組裝規則集中到一個函式,SDK 呼叫方只管傳 cwd 和可選 model。組裝細節往下看 AgentSession 編排層,工具工廠看 工具集 read/bash/edit/write/grep/find/ls,訊息轉換看 訊息類型與 convertToLlm