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,
)
// ... 去重 ...数据流
装配顺序:
边界与失败
- 模型无法恢复:
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。