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

数据流

装配顺序:

边界与失败

  • 模型无法恢复: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