createAgentSession 組裝
createAgentSession 是 SDK 的核心入口。它把 AuthStorage、ModelRegistry、SettingsManager、SessionManager、ResourceLoader 這些外部服務,加上工具定義和擴充執行器,裝成一個可用的 AgentSession。main.ts 不直接呼叫它,而是透過 createAgentSessionRuntime 走 createAgentSessionServices,但 SDK 呼叫方(嵌入 pi 到別的程式)直接用這個函式。
職責
- 服務解析:每個外部服務都可以傳入或自動建立,預設路徑基於
agentDir(~/.pi/agent)。見packages/coding-agent/src/core/sdk.ts:193-211。 - 模型恢復:已有 session 資料時,從 session header 找回
model,否則從 settings 找預設;模型不能恢復時給modelFallbackMessage。見packages/coding-agent/src/core/sdk.ts:214-248。 - thinking level 鉗制:從 session 或 settings 取 thinking level,然後
clampThinkingLevel限制到模型能力範圍內。見packages/coding-agent/src/core/sdk.ts:250-269。 - stream 函式注入:
Agent的streamFn不直接呼叫streamSimple,而是先modelRegistry.getApiKeyAndHeaders拿 auth,再拼上getAttributionHeaders的 telemetry 標頭。見packages/coding-agent/src/core/sdk.ts:328-346。 - convertToLlm 包裝:
blockImages設定開啟時,把所有 ImageContent 替換成佔位文字,動態讀 settings 保證 session 中途改也生效。見packages/coding-agent/src/core/sdk.ts:282-316。 - attribution 標頭:OpenRouter 和 Cloudflare 加專屬 HTTP 標頭用於歸類。見
packages/coding-agent/src/core/sdk.ts:128-156。
設計動機
為什麼不讓呼叫方自己 new Agent 再 new 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 路徑。
關鍵檔案
packages/coding-agent/src/core/sdk.ts:33-80—CreateAgentSessionOptions,所有組裝參數(cwd、authStorage、modelRegistry、model、tools、scopedModels、customTools、resourceLoader、sessionManager、settingsManager)。packages/coding-agent/src/core/sdk.ts:82-90—CreateAgentSessionResult:session+extensionsResult+ 可選modelFallbackMessage。packages/coding-agent/src/core/sdk.ts:124-126—getDefaultAgentDir,預設全域設定目錄。packages/coding-agent/src/core/sdk.ts:128-156—getAttributionHeaders,provider 專屬 telemetry 標頭。packages/coding-agent/src/core/sdk.ts:193-211—createAgentSession開頭:服務解析與預設值。packages/coding-agent/src/core/sdk.ts:320-376—new Agent({...})建構,注入streamFn、onPayload、onResponse、transformContext、steeringMode、transport等所有執行時參數。packages/coding-agent/src/core/sdk.ts:392-413— 最後new AgentSession({...})與回傳CreateAgentSessionResult。packages/coding-agent/src/core/sdk.ts:108-120— re-export 工具工廠,SDK 呼叫方可以直接拿createCodingTools等。
streamFn 注入處可見 auth 失敗如何短路、attribution 標頭如何合併:
// 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 開啟時把圖片替換成文字佔位,且去重連續佔位:
// 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。