agent-session.ts(3159 行)是 Pi Coding Agent 的核心枢纽,位于 F:\Pi\packages\coding-agent\src\core\。它是所有运行模式(interactive、print、rpc)共享的 AgentSession 类,封装了 agent 生命周期的完整管理逻辑。
一句话定位:AgentSession 是所有模式在 pi-agent-core 之上叠加的会话管理层 — 连接了 agent 循环、扩展系统、会话持久化、模型管理、compaction、自动重试等所有主要子系统。
核心职责#
| 职责领域 | 说明 |
|---|
| Agent 生命周期 | 管理 prompt 发送、流式响应、消息持久化 |
| 扩展系统桥接 | 事件转发到 ExtensionRunner,处理扩展注册的工具/命令 |
| 会话管理 | 消息持久化、会话切换、分支导航、压缩 |
| 模型管理 | 模型选择、切换、thinking level 控制 |
| Compaction | 自动/手动上下文压缩,overflow 恢复 |
| 自动重试 | 可配置指数退避重试(429/500/网络错误) |
| Bash 执行 | 命令执行、结果记录、流式输出 |
| 消息队列 | steering/followUp 消息的排队与消费 |
架构位置与依赖关系#
数据流层次(按代码执行顺序)#
user message: /skill:name args 扩展
↓ (text expansion)
AgentSession.prompt()
↓ (input event → skill → template → streaming? → auth → message build)
_runAgentPrompt()
↓
agent.prompt() / agent.continue()
↓ (agent-core 内部循环:LLM → 工具 → LLM ...)
_handleAgentEvent() ←── agent event 回调
↓
_emitExtensionEvent() → ExtensionRunner
↓
_emit() → AgentSessionEventListener
↓
sessionManager.appendMessage() → 会话持久化
外部依赖全景#
agent-session.ts
├── @earendil-works/pi-agent-core (Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel)
├── @earendil-works/pi-ai/compat (AssistantMessage, ImageContent, Model, TextContent, streamSimple, isContextOverflow, clampThinkingLevel 等)
├── node:fs / node:path (文件读写)
│
├── ./extensions/index.ts (ExtensionRunner, 扩展事件类型, wrapRegisteredTools)
├── ./extensions/runner.ts (emitSessionShutdownEvent)
├── ./session-manager.ts (SessionManager, BranchSummaryEntry, CompactionEntry, SessionHeader)
├── ./model-registry.ts (ModelRegistry)
├── ./settings-manager.ts (SettingsManager)
├── ./resource-loader.ts (ResourceLoader, ResourceExtensionPaths)
│
├── ./compaction/index.ts (compact, prepareCompaction, shouldCompact, estimateContextTokens 等)
├── ./bash-executor.ts (executeBashWithOperations, BashResult)
├── ./messages.ts (CustomMessage, BashExecutionMessage)
├── ./system-prompt.ts (buildSystemPrompt, BuildSystemPromptOptions)
├── ./prompt-templates.ts (expandPromptTemplate, PromptTemplate)
├── ./slash-commands.ts (SlashCommandInfo)
├── ./source-info.ts (SourceInfo, createSyntheticSourceInfo)
├── ./tools/index.ts (createAllToolDefinitions)
├── ./tools/bash.ts (BashOperations, createLocalBashOperations)
├── ./tools/tool-definition-wrapper.ts (createToolDefinitionFromAgentTool)
│
├── ./auth-guidance.ts (formatNoApiKeyFoundMessage, formatNoModelSelectedMessage)
├── ./defaults.ts (DEFAULT_THINKING_LEVEL)
├── ./export-html/index.ts (exportSessionToHtml, ToolHtmlRenderer)
├── ./export-html/tool-renderer.ts (createToolHtmlRenderer)
├── ./keybindings.ts (KeybindingsConfig)
├── ./diagnostics.ts (ResourceDiagnostic)
│
├── ../utils/frontmatter.ts (stripFrontmatter)
├── ../utils/paths.ts (resolvePath)
├── ../utils/sleep.ts (sleep)
└── ../modes/interactive/theme/theme.ts (getThemeByName, theme)
类结构总览#
AgentSession
├── 构造函数 ──── _buildRuntime() → 初始化工具注册表、扩展系统
├── 公开方法
│ ├── prompt() → 主入口:处理用户输入
│ ├── steer() / followUp() → 流式状态下的消息排队
│ ├── sendCustomMessage() → 扩展发送自定义消息
│ ├── sendUserMessage() → 扩展发送用户消息
│ ├── setModel() / cycleModel() → 模型管理
│ ├── setThinkingLevel() / cycleThinkingLevel() → Thinking level
│ ├── compact() → 手动压缩
│ ├── navigateTree() → 会话树导航
│ ├── exportToHtml/Jsonl() → 导出
│ ├── executeBash() → Bash 执行
│ ├── subscribe() → 事件订阅
│ ├── bindExtensions() → 扩展绑定
│ └── dispose() → 资源清理
├── 内部方法
│ ├── _handleAgentEvent() → agent event 回调枢纽
│ ├── _emitExtensionEvent() → 转发到 ExtensionRunner
│ ├── _runAgentPrompt() → agent.prompt() + post-run 处理循环
│ ├── _handlePostAgentRun() → agent 停止后的处理(重试/compaction/队列)
│ ├── _checkCompaction() → 压缩检查(overflow + threshold)
│ ├── _runAutoCompaction() → 自动压缩执行
│ ├── _prepareRetry() → 指数退避重试
│ └── _buildRuntime() → 初始化工具注册表和扩展运行器
├── Getter 属性
│ ├── model / isStreaming / thinkingLevel / state
│ ├── sessionFile / sessionId / sessionName
│ ├── isCompacting / isRetrying / isBashRunning
│ ├── pendingMessageCount / activeToolNames
│ └── autoCompactionEnabled / autoRetryEnabled
└── 内部状态
├── _steeringMessages / _followUpMessages / _pendingNextTurnMessages
├── _compactionAbortController / _autoCompactionAbortController
├── _retryAbortController / _retryAttempt
├── _bashAbortController / _pendingBashMessages
└── _toolRegistry / _toolDefinitions / _toolPromptSnippets
逐段解读#
段 1:类型定义与常量(第 1-123 行)#
ParsedSkillBlock 与 parseSkillBlock()#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| export interface ParsedSkillBlock {
name: string;
location: string;
content: string;
userMessage: string | undefined;
}
export function parseSkillBlock(text: string): ParsedSkillBlock | null {
const match = text.match(
/^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/
);
if (!match) return null;
return { name: match[1], location: match[2], content: match[3], userMessage: match[4]?.trim() || undefined };
}
|
功能:从用户消息中解析 <skill> XML 块。正则匹配四个捕获组:name、location、content 和可选的 userMessage。用于将技能文件的内容嵌入用户消息上下文。
AgentSessionEvent — 自定义事件类型#
1
2
3
4
5
6
7
8
9
10
| export type AgentSessionEvent =
| Exclude<AgentEvent, { type: "agent_end" }>
| { type: "agent_end"; messages: AgentMessage[]; willRetry: boolean }
| { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
| { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
| { type: "compaction_end"; reason: ...; result: ...; aborted: boolean; willRetry: boolean; errorMessage?: string }
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
| { type: "session_info_changed"; name: string | undefined }
| { type: "thinking_level_changed"; level: ThinkingLevel };
|
语法要点 — Exclude<T, U>:从联合类型 T 中排除匹配 U 的成员。这里排除 agent-core 的 agent_end 类型,用自定义的(带 willRetry 字段)替换。
设计:新增的事件类型(compaction_start/end、auto_retry_start/end、queue_update 等)是 AgentSession 层独有的,agent-core 不知道它们的存在。
AgentSessionConfig — 构造函数配置#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| export interface AgentSessionConfig {
agent: Agent; // agent-core 的 Agent 实例
sessionManager: SessionManager; // 会话持久化管理器
settingsManager: SettingsManager; // 设置管理器
cwd: string;
scopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>; // --models 标志
resourceLoader: ResourceLoader;
customTools?: ToolDefinition[];
modelRegistry: ModelRegistry;
initialActiveToolNames?: string[];
allowedToolNames?: string[]; // 工具 allowlist
excludedToolNames?: string[]; // 工具 denylist
baseToolsOverride?: Record<string, AgentTool>;
extensionRunnerRef?: { current?: ExtensionRunner };
sessionStartEvent?: SessionStartEvent;
}
|
依赖注入:AgentSession 通过构造函数接收所有依赖,不做服务定位。这让它在不同运行模式(interactive、print、rpc)下可独立实例化,每个模式提供不同的实现。
段 2:类构造函数与初始化(第 259-340 行)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| export class AgentSession {
// ~30 个私有状态字段
private _steeringMessages: string[] = [];
private _followUpMessages: string[] = [];
private _pendingNextTurnMessages: CustomMessage[] = [];
private _compactionAbortController: AbortController | undefined;
private _retryAbortController: AbortController | undefined;
private _bashAbortController: AbortController | undefined;
// ...
constructor(config: AgentSessionConfig) {
// 1. 保存配置
this.agent = config.agent;
this.sessionManager = config.sessionManager;
// ...
// 2. 订阅 agent 事件
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
// 3. 安装工具钩子(beforeToolCall / afterToolCall)
this._installAgentToolHooks();
// 4. 构建运行时(工具注册表、ExtensionRunner、系统提示)
this._buildRuntime({ activeToolNames: this._initialActiveToolNames, includeAllExtensionTools: true });
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| private _installAgentToolHooks(): void {
this.agent.beforeToolCall = async ({ toolCall, args }) => {
const runner = this._extensionRunner;
if (!runner.hasHandlers("tool_call")) return undefined;
return await runner.emitToolCall({ type: "tool_call", toolName: toolCall.name, toolCallId: toolCall.id, input: args as Record<string, unknown> });
};
this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
const runner = this._extensionRunner;
if (!runner.hasHandlers("tool_result")) return undefined;
const hookResult = await runner.emitToolResult({ type: "tool_result", toolName: toolCall.name, ... });
if (!hookResult) return undefined;
return { content: hookResult.content, details: hookResult.details, isError: hookResult.isError ?? isError };
};
}
|
设计要点:钩子只安装一次,但每次调用时读取 this._extensionRunner 的最新值。这意味着扩展重载后新 runner 自动生效,无需重新安装钩子。
段 3:事件系统(第 340-580 行)#
事件订阅#
1
2
3
4
| subscribe(listener: AgentSessionEventListener): () => void {
this._eventListeners.push(listener);
return () => { const i = this._eventListeners.indexOf(listener); if (i !== -1) this._eventListeners.splice(i, 1); };
}
|
模式:返回取消函数(类似 DOM addEventListener 返回的移除函数)。每个 listener 独立管理。
_handleAgentEvent() — 事件处理枢纽#
收到 AgentEvent
│
├── Phase 1: 队列管理 — 从 steer/followUp 队列移除已消费消息
│
├── Phase 2: 扩展系统 — _emitExtensionEvent() 转发
│
├── Phase 3: 自定义 listener — _emit() 含 willRetry 增强
│
└── Phase 4: 消息持久化 — sessionManager.appendMessage()
└── 如果是 assistant → 重试计数器管理
_replaceMessageInPlace() — 原地替换消息#
1
2
3
4
5
6
| private _replaceMessageInPlace(target: AgentMessage, replacement: AgentMessage): void {
if (target === replacement) return;
const targetRecord = target as unknown as Record<string, unknown>;
for (const key of Object.keys(targetRecord)) delete targetRecord[key];
Object.assign(targetRecord, replacement);
}
|
语法要点 — 可变对象替换:当扩展的 message_end handler 修改消息时,使用此方法将新属性覆盖到原对象上,保持所有引用同步。删除现有属性后再 Object.assign,确保删除旧值中没有的属性。
_emitExtensionEvent() — 事件映射桥#
AgentEvent ExtensionEvent
────────── ──────────────
agent_start ────────────────── agent_start
agent_end ────────────────── agent_end
turn_start ───→ TurnStartEvent
turn_end ───→ TurnEndEvent
message_start ───→ MessageStartEvent
message_update ───→ MessageUpdateEvent
message_end ───→ MessageEndEvent(+ 替换消息)
tool_execution_start ──→ ToolExecutionStartEvent
tool_execution_update ──→ ToolExecutionUpdateEvent
tool_execution_end ──→ ToolExecutionEndEvent
段 4:Prompt 处理流程(第 580-800 行)#
prompt() — 7 步处理管道#
用户输入 text
│
Step 1: 扩展命令?(/command) → 直接执行,返回
│
Step 2: input 事件给扩展 → "handled" 返回 / "transform" 修改文本
│
Step 3: 展开 /skill:name 和提示模板
│
Step 4: 流式中?→ 排队(steer/followUp),返回
│
Step 5: 认证检查(模型 + API key)→ 无则抛错
│
Step 6: 预 prompt compaction → 有则 agent.continue()
│
Step 7: before_agent_start 事件 + 构建消息 → _runAgentPrompt()
_runAgentPrompt() / _handlePostAgentRun() — 后处理链#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| private async _runAgentPrompt(messages): Promise<void> {
try {
await this.agent.prompt(messages);
while (await this._handlePostAgentRun()) {
await this.agent.continue();
}
} finally {
this._flushPendingBashMessages();
}
}
private async _handlePostAgentRun(): Promise<boolean> {
// 1. 重试检查 → 是则准备重试,返回 true
if (this._isRetryableError(msg) && await this._prepareRetry(msg)) return true;
// 2. 压缩检查 → 是则执行压缩,返回 true(overflow 会继续重试)
if (await this._checkCompaction(msg)) return true;
// 3. 队列检查 → extension 在 agent_end 中排队了消息
return this.agent.hasQueuedMessages();
}
|
关键设计:每次 agent.continue() 后都重新跑整个后处理链。重试 → 压缩 → 队列消费,直到三者都不满足才结束循环。
段 5:Steer / FollowUp 消息队列(第 800-960 行)#
prompt("text") isStreaming=true
│
┌─────┴──────┐
│ streaming │
│ Behavior? │
└─────┬──────┘
┌─────┴───────┐
▼ ▼
steer() followUp()
│ │
▼ ▼
agent.steer() agent.followUp()
│ │
▼ ▼
中断当前 LLM 等待工具链完成
立即处理 LLM 空闲时消费
1
2
3
4
5
6
7
8
9
10
11
12
| async steer(text: string, images?: ImageContent[]): Promise<void> {
if (text.startsWith("/")) this._throwIfExtensionCommand(text); // 命令不能排队
let expandedText = this._expandSkillCommand(text);
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
await this._queueSteer(expandedText, images);
}
private async _queueSteer(text: string, images?: ImageContent[]): Promise<void> {
this._steeringMessages.push(text);
this._emitQueueUpdate();
this.agent.steer({ role: "user", content: [{ type: "text", text }, ...(images ?? [])], timestamp: Date.now() });
}
|
两种策略:steer 中断当前 LLM 立即处理,followUp 等当前工具链完成。模式有 "all"(全排)和 "one-at-a-time"(一次一个),由 settings 控制。
段 6:模型管理(第 960-1130 行)#
模型切换#
1
2
3
4
5
6
7
8
9
| async setModel(model: Model<any>): Promise<void> {
if (!this._modelRegistry.hasConfiguredAuth(model)) throw new Error(...);
const previousModel = this.model; // 保存旧模型
this.agent.state.model = model; // 设置新模型
this.sessionManager.appendModelChange(model.provider, model.id); // 持久化
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id); // 保存为默认
this.setThinkingLevel(thinkingLevel); // 重新钳位 thinking level
await this._emitModelSelect(model, previousModel, "set"); // 通知扩展
}
|
模型循环#
1
2
3
4
| async cycleModel(direction: "forward" | "backward"): Promise<ModelCycleResult | undefined> {
if (this._scopedModels.length > 0) return this._cycleScopedModel(direction); // --models 优先
return this._cycleAvailableModel(direction); // 回退到 ModelRegistry
}
|
两阶段:scopedModels(CLI --models)→ ModelRegistry(所有已认证模型)。
Thinking Level 钳位#
1
2
3
4
5
6
7
| setThinkingLevel(level: ThinkingLevel): void {
const effectiveLevel = this.getAvailableThinkingLevels().includes(level)
? level
: this._clampThinkingLevel(level, this.getAvailableThinkingLevels());
// 只在实际改变时持久化
if (effectiveLevel !== this.agent.state.thinkingLevel) { ... }
}
|
clampThinkingLevel() 来自 @earendil-works/pi-ai/compat,将请求的 level 适配到模型能力范围内。
段 7:Compaction 机制(第 1130-1600 行)#
两种触发场景#
场景 1: Context Overflow(溢出)
LLM 返回 stopReason=error + isContextOverflow()
→ 移除错误消息(保留 session 历史)
→ 压缩
→ 自动重试(一次,_overflowRecoveryAttempted 防循环)
场景 2: Context Threshold(超阈值)
assistant message usage > settings.threshold
→ 压缩
→ 不自动重试,用户手动继续
_checkCompaction() — 多层过滤#
1
2
3
4
5
6
7
8
9
10
11
12
| private async _checkCompaction(assistantMessage, skipAbortedCheck = true): Promise<boolean> {
// 1. 跳过中止消息
if (skipAbortedCheck && stopReason === "aborted") return false;
// 2. 跳过来自不同模型的消息(用户已切换模型)
if (!sameModel) return false;
// 3. 跳过最近一次压缩之前的消息(防重复触发)
if (assistantIsFromBeforeCompaction) return false;
// 4. Overflow → 压缩 + 重试
if (isContextOverflow(assistantMessage, contextWindow)) { ... }
// 5. Threshold → 压缩
if (shouldCompact(contextTokens, contextWindow, settings)) { ... }
}
|
_runAutoCompaction() — 自动压缩流程#
1. compaction_start 事件(reason: manual/threshold/overflow)
2. 获取 API key + auth
3. prepareCompaction() → 选择要压缩的条目
4. session_before_compact 事件 → 扩展可 cancel / 提供自己的摘要
5. compact() — LLM 生成摘要 或 扩展提供
6. sessionManager.appendCompaction()
7. sessionManager.buildSessionContext() → 更新 agent.state.messages
8. session_compact 事件
9. compaction_end 事件
10. willRetry? → true → agent.continue()
扩展的 3 种干涉方式:{ cancel: true } 阻止压缩;{ compaction: CompactionResult } 提供自己的摘要替代 LLM;{ customInstructions } 影响摘要方向。
段 8:自动重试机制(第 1600-1750 点)#
_isRetryableError() — 错误模式匹配#
1
2
3
4
5
6
7
8
| private _isRetryableError(message: AssistantMessage): boolean {
const err = message.errorMessage;
if (this._isNonRetryableProviderLimitError(err)) return false;
// 覆盖数百种错误:
return /overloaded|provider.?returned.?error|rate.?limit|too many requests|
429|500|502|503|504|service.?unavailable|connection.?error|connection.?refused|
websocket.?closed|fetch failed|socket hang up|timed? out|terminated|retry delay/i.test(err);
}
|
正则覆盖:HTTP 429/5xx、网络错误、WebSocket 断开、超时、terminated。通过 _isNonRetryableProviderLimitError 排除"不可重试的 provider limit"(如配额用尽)。
_prepareRetry() — 指数退避#
1
2
3
4
5
6
7
8
9
| private async _prepareRetry(message: AssistantMessage): Promise<boolean> {
this._retryAttempt++;
if (this._retryAttempt > settings.maxRetries) { this._retryAttempt--; return false; }
const delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
// auto_retry_start 事件 → 移除错误消息 → sleep(delayMs, abortable)
// 若 sleep 被中止 → auto_retry_end(success: false)
return true;
}
|
公式:baseDelayMs * 2^(attempt-1)。_retryAbortController 使等待可中断。
计数器重置:收到非错误的 assistant 响应时 _retryAttempt = 0,避免跨多轮 LLM 调用累积。
段 9:Bash 执行(第 1750-1900 行)#
1
2
3
4
5
6
7
8
9
10
11
| async executeBash(command, onChunk?, options?): Promise<BashResult> {
this._bashAbortController = new AbortController();
const result = await executeBashWithOperations(
resolvedCommand,
this.sessionManager.getCwd(),
options?.operations ?? createLocalBashOperations({ shellPath }),
{ onChunk, signal: this._bashAbortController.signal },
);
this.recordBashResult(command, result, options);
return result;
}
|
流式兼容:如果 agent 正在流式处理,bash 结果不立即加入 agent state(避免破坏 tool_use/tool_result 顺序),而是排队到 _pendingBashMessages,在 _flushPendingBashMessages()(_runAgentPrompt 的 finally 块中)统一刷新。
段 10:会话树导航(第 1900-2200 行)#
navigateTree() — 完整流程#
用户请求跳转到 entry X
│
Step 1: 检查是否已在目标 → 无操作
│
Step 2: collectEntriesForBranchSummary()
(从当前叶节点回溯到共同祖先)
│
Step 3: session_before_tree 事件
(扩展可 cancel / 提供摘要 / 覆盖指令/标签)
│
Step 4: 需要摘要?→ generateBranchSummary()(LLM)或扩展提供
│
Step 5: 确定新叶节点位置
├── user 消息 → 叶=parent(编辑器恢复文本)
├── custom_msg → 叶=parent(恢复内容到编辑器)
└── 其他 → 叶=选中节点
│
Step 6: sessionManager.branchWithSummary() / branch() / resetLeaf()
→ 附加标签(label)
│
Step 7: sessionManager.buildSessionContext() → agent.state.messages
│
Step 8: session_tree 事件
fork / session 切换#
1
2
3
4
| // ExtensionCommandContextActions 中定义,由各运行模式实现
newSession(options?: { parentSession?, setup?, withSession? })
fork(entryId, options?: { position?, withSession? })
switchSession(sessionPath, options?: { withSession? })
|
段 11:扩展系统集成(第 2200-2650 行)#
bindExtensions() → _bindExtensionCore()#
这是 AgentSession 和 ExtensionRunner 之间的核心桥接代码,将所有 ExtensionAPI 方法连接到 AgentSession 的内部实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| runner.bindCore(
{
// ExtensionActions — 扩展的 pi.sendMessage / pi.sendUserMessage 等
sendMessage: (message, options) => this.sendCustomMessage(message, options),
sendUserMessage: (content, options) => this.sendUserMessage(content, options),
appendEntry: (customType, data) => this.sessionManager.appendCustomEntry(customType, data),
setSessionName: (name) => this.setSessionName(name),
getSessionName: () => this.sessionManager.getSessionName(),
getActiveTools: () => this.getActiveToolNames(),
getAllTools: () => this.getAllTools(),
setActiveTools: (names) => this.setActiveToolsByName(names),
refreshTools: () => this._refreshToolRegistry(),
getCommands: () => [...extensionCommands, ...templates, ...skills],
setModel: async (model) => { ... await this.setModel(model); return true; },
getThinkingLevel: () => this.thinkingLevel,
setThinkingLevel: (level) => this.setThinkingLevel(level),
},
{ /* ExtensionContextActions — ctx.* 方法 */ },
{ /* Provider registration */ },
);
|
getCommands() 的三源合并:扩展命令(runner.getRegisteredCommands())+ 提示模板(this.promptTemplates)+ 技能(this._resourceLoader.getSkills().skills),统一返回用户可调用的斜杠命令列表。
段 12:工具注册与管理(第 2650-2900 行)#
_buildRuntime() — 初始化工具#
内置工具定义 (createAllToolDefinitions)
+ 基础工具覆盖 (baseToolsOverride)
+ SDK 自定义工具 (customTools)
+ 内置工具 AgentTool → ToolDefinition 转换
+ allowlist / denylist 过滤
+ 扩展注册的工具
│
▼
_toolDefinitions Map<string, ToolDefinitionEntry>
_toolRegistry Map<string, AgentTool> (给 agent-core 使用)
_toolPromptSnippets / _toolPromptGuidelines
│
▼
_rebuildSystemPrompt() → 构建系统提示
_bindExtensionCore() → 注册到 ExtensionRunner
1
2
3
4
5
6
7
8
9
10
| setActiveToolsByName(toolNames: string[]): void {
const tools: AgentTool[] = [];
for (const name of toolNames) {
const tool = this._toolRegistry.get(name);
if (tool) { tools.push(tool); }
}
this.agent.state.tools = tools; // 更新 agent
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames); // 重建提示
this.agent.state.systemPrompt = this._baseSystemPrompt;
}
|
段 13:上下文使用统计(第 2900-3050 行)#
getContextUsage() — 复杂计算#
1
2
3
4
5
6
7
8
9
10
11
| getContextUsage(): ContextUsage | undefined {
// 压缩后:只信任压缩边界之后的 assistant usage
// (压缩前的 usage 反映的是老的大上下文)
const latestCompaction = getLatestCompactionEntry(branchEntries);
if (latestCompaction && !hasPostCompactionUsage) {
return { tokens: null, contextWindow, percent: null }; // 未知
}
// 正常情况:估算 token 数
const estimate = estimateContextTokens(this.messages);
return { tokens: estimate.tokens, contextWindow, percent: (estimate.tokens / contextWindow) * 100 };
}
|
段 14:导出与其他工具(第 3050-3159 行)#
exportToHtml / exportToJsonl#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| async exportToHtml(outputPath?: string): Promise<string> {
// 创建工具渲染器(支持自定义工具 HTML 渲染)
const toolRenderer = createToolHtmlRenderer({ getToolDefinition: (name) => this.getToolDefinition(name), ... });
return await exportSessionToHtml(this.sessionManager, this.state, { outputPath, themeName, toolRenderer });
}
exportToJsonl(outputPath?: string): string {
// 线性化分支:重新建立 parentId 链,写入 JSONL 文件
let prevId: string | null = null;
for (const entry of branchEntries) {
const linear = { ...entry, parentId: prevId };
writeFileSync(filePath, `${lines.join("\n")}\n`);
prevId = entry.id;
}
}
|
createReplacedSessionContext()#
1
2
3
4
5
6
7
| createReplacedSessionContext(): ReplacedSessionContext {
// 使用 Object.defineProperties 复制惰性 getter
const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext())) as ReplacedSessionContext;
context.sendMessage = (message, options) => this.sendCustomMessage(message, options);
context.sendUserMessage = (content, options) => this.sendUserMessage(content, options);
return context;
}
|
语法要点 — Object.getOwnPropertyDescriptors():正确复制惰性 getter 的唯一方式。普通展开 { ...obj } 会立即求值所有 getter,丢失惰性特性。
关系总结#
引用与被引用关系#
agent-session.ts 被谁引用?
├── modes/interactive/InteractiveMode.ts (构造 AgentSession)
├── modes/print/PrintMode.ts (构造 AgentSession)
├── modes/rpc/RPCMode.ts (构造 AgentSession)
└── ... (其他使用 AgentSession 的模式文件)
agent-session.ts 引用了什么?
├── ./extensions/* → ExtensionRunner 和事件类型
├── ./session-manager.ts → 会话持久化
├── ./model-registry.ts → 模型注册中心
├── ./settings-manager.ts → 设置管理
├── ./resource-loader.ts → 技能/提示/主题加载
├── ./compaction/index.ts → 压缩逻辑
├── ./bash-executor.ts → Bash 执行
└── ... (共 ~25 个内部模块)
核心数据流#
用户输入
│
▼
AgentSession.prompt()
│
├── input event → ExtensionRunner (扩展可拦截/转换)
├── skill/template 展开
├── auth 检查 → ModelRegistry
│
▼
agent.prompt() / agent.continue()
│
▼ (agent-core 内部循环)
├── LLM 调用 (Model + ApiKey)
├── 工具执行 → beforeToolCall/afterToolCall → ExtensionRunner
│
▼
_handleAgentEvent()
│
├── _emitExtensionEvent() → ExtensionRunner → 扩展
├── _emit() → AgentSessionEventListener (UI/日志)
└── sessionManager.appendMessage() → 文件持久化
│
▼
_handlePostAgentRun()
├── 重试? → _prepareRetry() → 指数退避
├── 压缩? → _runAutoCompaction() → LLM 摘要
└── 队列? → agent.continue()
关键设计模式总结#
| 模式 | 位置 | 说明 |
|---|
| 桥接模式 | _emitExtensionEvent() | AgentEvent → ExtensionEvent 的类型转换与转发 |
| 责任链模式 | _handlePostAgentRun() | 重试 → 压缩 → 队列消费的链式检查 |
| 策略模式 | prompt() 中 streaming 分支 | steer/followUp 两种排队策略 |
| 观察者模式 | subscribe() / _emit() | 自定义 listener 订阅 AgentSession 事件 |
| AbortController | 多处 | 可中断的 sleep、compaction、bash、retry |
| 两阶段模型解析 | cycleModel() | –models 限定 → ModelRegistry 全局 |
| 惰性求值 | createReplacedSessionContext() | Object.getOwnPropertyDescriptors 复制 getter |
| 两阶段初始化 | _buildRuntime() → bindExtensions() | |