main.ts 第四阶段:创建运行时环境
概述
这是整个 main() 的核心,负责加载模型、工具、扩展,构建完整的 Agent 会话。这一阶段代码量最大,但逻辑是清晰的。
const resolvedExtensionPaths = resolveCliPaths(cwd, parsed.extensions);
const resolvedSkillPaths = resolveCliPaths(cwd, parsed.skills);
const resolvedPromptTemplatePaths = resolveCliPaths(cwd, parsed.promptTemplates);
const resolvedThemePaths = resolveCliPaths(cwd, parsed.themes);
const createRuntime: CreateAgentSessionRuntimeFactory = async ({
cwd,
agentDir,
sessionManager,
sessionStartEvent,
projectTrustContext,
}) => {
// ... 工厂函数内部 ...
};
time("createRuntime");
const runtime = await createAgentSessionRuntime(createRuntime, {
cwd: sessionManager.getCwd(),
agentDir,
sessionManager,
});
time("createAgentSessionRuntime");
const { services, session, modelFallbackMessage } = runtime;
const { settingsManager, modelRuntime, resourceLoader } = services;
applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
整体结构:工厂模式
第四阶段用了工厂模式,先定义一个工厂函数 createRuntime,然后调用它来创建运行时。
工厂函数的类型签名:
const createRuntime: CreateAgentSessionRuntimeFactory = async ({...}) => {...};
CreateAgentSessionRuntimeFactory 是一个函数类型,接收运行时参数,返回创建好的运行时对象。
工厂函数内部:步骤 1,确定项目信任状态
const isInitialRuntime = sessionStartEvent === undefined;
const projectTrustDiagnostics: AgentSessionRuntimeDiagnostic[] = [];
const cachedProjectTrust = projectTrustByCwd.get(cwd);
const hasTrustRequiringResources = hasTrustRequiringProjectResources(cwd);
const shouldResolveProjectTrust =
parsed.projectTrustOverride === undefined && cachedProjectTrust === undefined && hasTrustRequiringResources;
const projectTrusted = shouldResolveProjectTrust
? false
: (cachedProjectTrust ??
parsed.projectTrustOverride ??
(!hasTrustRequiringResources || trustStore.get(cwd) === true));
什么是"项目信任"?
Agent 能执行 bash 命令、读写文件。如果用户打开了一个恶意项目,项目里可能有 AGENTS.md 文件指示 Agent 执行危险操作。
信任机制:
- 项目里没有信任相关的资源(
AGENTS.md、CLAUDE.md)→ 自动信任 - 项目里有这些资源 → 需要用户确认
- 用户通过
--approve或--no-approve显式指定 → 直接采用
信任状态的决策树
parsed.projectTrustOverride 有值?
├─ Yes → 使用它的值(--approve 或 --no-approve)
└─ No → 检查缓存
├─ 有缓存 → 使用缓存值
└─ 无缓存 → 检查是否有信任相关资源
├─ 无资源 → 自动信任
└─ 有资源 → 检查 trustStore
├─ 已信任 → 信任
└─ 未信任 → 不信任
const runtimeSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
工厂函数内部:步骤 2,创建核心服务
const services = await createAgentSessionServices({
cwd,
agentDir,
settingsManager: runtimeSettingsManager,
modelRuntimeSignal: AbortSignal.timeout(15_000),
extensionFlagValues: parsed.unknownFlags,
resourceLoaderReloadOptions: shouldResolveProjectTrust
? {
resolveProjectTrust: async ({ extensionsResult }) => {
const trusted = await resolveProjectTrusted({
cwd,
trustStore,
trustOverride: parsed.projectTrustOverride,
defaultProjectTrust: startupSettingsManager.getDefaultProjectTrust(),
extensionsResult,
projectTrustContext:
projectTrustContext ??
createProjectTrustContext({
cwd,
mode: isInitialRuntime ? trustPromptMode : appMode,
settingsManager: startupSettingsManager,
hasUI: isInitialRuntime && trustPromptMode === "interactive",
}),
onExtensionError: (message) => projectTrustDiagnostics.push({ type: "warning", message }),
});
projectTrustByCwd.set(cwd, trusted);
return trusted;
},
}
: undefined,
resourceLoaderOptions: {
additionalExtensionPaths: resolvedExtensionPaths,
additionalSkillPaths: resolvedSkillPaths,
additionalPromptTemplatePaths: resolvedPromptTemplatePaths,
additionalThemePaths: resolvedThemePaths,
noExtensions: parsed.noExtensions,
noSkills: parsed.noSkills,
noPromptTemplates: parsed.noPromptTemplates,
noThemes: parsed.noThemes,
noContextFiles: parsed.noContextFiles,
systemPrompt: parsed.systemPrompt,
appendSystemPrompt: parsed.appendSystemPrompt,
extensionFactories,
},
});
const { settingsManager, modelRuntime, resourceLoader } = services;
这是最关键的一步,创建三大核心服务:
| 服务 | 职责 | 关键能力 |
|---|---|---|
settingsManager | 管理配置 | 读取主题、模型、重试策略等设置 |
modelRuntime | 管理 AI 模型 | 连接 API、发送请求、处理认证 |
resourceLoader | 管理资源 | 加载扩展、技能、提示词模板、主题 |
AbortSignal.timeout(15_000)
modelRuntimeSignal: AbortSignal.timeout(15_000),
给模型初始化设置 15 秒超时。如果模型 API 连不上,15 秒后自动失败,不让程序卡死。
扩展和资源的加载路径
resourceLoaderOptions: {
additionalExtensionPaths: resolvedExtensionPaths, // 用户指定的扩展路径
additionalSkillPaths: resolvedSkillPaths, // 用户指定的技能路径
additionalPromptTemplatePaths: resolvedPromptTemplatePaths,
additionalThemePaths: resolvedThemePaths,
noExtensions: parsed.noExtensions, // --no-extensions
noSkills: parsed.noSkills,
noPromptTemplates: parsed.noPromptTemplates,
noThemes: parsed.noThemes,
noContextFiles: parsed.noContextFiles, // --no-context-files
systemPrompt: parsed.systemPrompt,
appendSystemPrompt: parsed.appendSystemPrompt,
extensionFactories, // 内置扩展
},
resourceLoader 会从以下位置加载资源:
1. 内置资源(extensionFactories) ← 程序自带的
2. 全局资源(~/.pi/agent/extensions/) ← 用户安装的
3. 项目资源(.pi/extensions/) ← 项目级的
4. CLI 指定的(--extension /path) ← 用户临时指定的
项目信任的延迟解析
resourceLoaderReloadOptions 包含一个 resolveProjectTrust 回调函数。这是一个延迟执行的机制,只有在资源加载器需要信任判断时才调用它。
resolveProjectTrust: async ({ extensionsResult }) => {
const trusted = await resolveProjectTrusted({...});
projectTrustByCwd.set(cwd, trusted);
return trusted;
},
这样做的好处是:信任判断可能需要用户交互(弹出确认对话框),延迟到实际需要时才执行,避免过早阻塞。
工厂函数内部:步骤 3,收集诊断信息
const diagnostics: AgentSessionRuntimeDiagnostic[] = [
...projectTrustDiagnostics,
...services.diagnostics,
...collectSettingsDiagnostics(settingsManager, "runtime creation"),
...resourceLoader.getExtensions().errors.map(({ path, error }) => ({
type: "error" as const,
message: `Failed to load extension "${path}": ${error}`,
})),
];
把所有非致命问题收集起来,交给调用方决定如何处理(打印警告还是退出)。
工厂函数内部:步骤 4,解析模型
const modelPatterns = parsed.models ?? settingsManager.getEnabledModels();
const scopedModels =
modelPatterns && modelPatterns.length > 0
? await resolveModelScope(modelPatterns, modelRuntime, { signal: AbortSignal.timeout(15_000) })
: [];
const {
options: sessionOptions,
cliThinkingFromModel,
diagnostics: sessionOptionDiagnostics,
} = buildSessionOptions(
parsed,
scopedModels,
sessionManager.buildSessionContext().messages.length > 0,
modelRuntime,
settingsManager,
);
diagnostics.push(...sessionOptionDiagnostics);
模型解析流程
- 确定用户想用哪些模型(
--models sonnet,haiku或设置文件里的默认值) resolveModelScope(),把模型模式(如anthropic/*)解析成具体的模型对象buildSessionOptions(),根据模型、参数、设置,构建会话选项
buildSessionOptions() 的输出
{
options: {
model: claude-3-5-sonnet, // 选定的模型
thinkingLevel: "high", // 思考级别
scopedModels: [...], // 可切换的模型列表
tools: ["read", "bash", ...], // 启用的工具
excludeTools: [...], // 排除的工具
noTools: false, // 是否禁用所有工具
customTools: [...], // 自定义工具
},
cliThinkingFromModel: true, // 模型名称中是否包含思考级别
diagnostics: [...], // 诊断信息
}
工厂函数内部:步骤 5,设置 API Key
if (parsed.apiKey) {
if (!sessionOptions.model) {
diagnostics.push({
type: "error",
message: "--api-key requires a model to be specified via --model, --provider/--model, or --models",
});
} else {
await modelRuntime.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);
}
}
如果用户通过 --api-key 临时指定了密钥,直接注入到模型运行时中,覆盖默认的认证信息。
工厂函数内部:步骤 6,创建 Agent 会话
const created = await createAgentSessionFromServices({
services,
sessionManager,
sessionStartEvent,
model: sessionOptions.model,
thinkingLevel: sessionOptions.thinkingLevel,
scopedModels: sessionOptions.scopedModels,
tools: sessionOptions.tools,
excludeTools: sessionOptions.excludeTools,
noTools: sessionOptions.noTools,
customTools: sessionOptions.customTools,
});
const cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;
if (created.session.model && cliThinkingOverride) {
created.session.setThinkingLevel(created.session.thinkingLevel);
}
return {
...created,
services,
diagnostics,
};
这是最终的组装,把服务、会话、模型、工具全部组合成一个完整的 AgentSession 对象。
输入
| 参数 | 来源 | 作用 |
|---|---|---|
services | 步骤 2 创建 | 提供核心能力 |
sessionManager | 第三阶段创建 | 提供对话历史 |
model | 步骤 4 解析 | AI 模型 |
thinkingLevel | 步骤 4 解析 | 思考深度 |
tools | 步骤 4 解析 | 工具列表 |
customTools | 参数解析 | 用户自定义工具 |
思考级别的覆盖
const cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;
if (created.session.model && cliThinkingOverride) {
created.session.setThinkingLevel(created.session.thinkingLevel);
}
如果用户通过 --thinking high 或模型名称(如 sonnet:high)指定了思考级别,覆盖会话的默认值。
工厂函数外部:调用工厂
const runtime = await createAgentSessionRuntime(createRuntime, {
cwd: sessionManager.getCwd(),
agentDir,
sessionManager,
});
const { services, session, modelFallbackMessage } = runtime;
const { settingsManager, modelRuntime, resourceLoader } = services;
applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
createAgentSessionRuntime() 做什么?
- 调用
createRuntime工厂函数,创建运行时 - 处理可能的错误和重试
- 返回完整的运行时对象
应用 HTTP 配置
applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
- 如果用户配置了 HTTP 代理,应用到全局连接池
- 设置 HTTP 连接的空闲超时时间
运行时的最终结构
runtime = {
services: {
cwd: "/d/Projects/agent/pi",
agentDir: "~/.pi/agent",
settingsManager: {...}, // 配置
modelRuntime: {...}, // 模型
resourceLoader: {...}, // 资源
},
session: {
model: claude-3-5-sonnet,
thinkingLevel: "high",
tools: [read, bash, edit, write, ...],
messages: [...], // 历史消息
},
modelFallbackMessage: "...", // 模型不可用时的提示
}
关键模块关系
createAgentSessionRuntime()
│
├─ createAgentSessionServices()
│ ├─ SettingsManager ← 配置管理
│ ├─ ModelRuntime ← 模型管理(认证、请求、重试)
│ └─ ResourceLoader ← 资源加载(扩展、技能、主题)
│
└─ createAgentSessionFromServices()
├─ sessionManager ← 提供对话历史
├─ model ← AI 模型
├─ tools ← 工具定义
└─ customTools ← 自定义工具
第四阶段结束后的状态
运行时创建完成后,程序拥有了一个完整的 Agent 运行环境:
- 配置已加载
- 模型已连接
- 资源已加载
- 会话已准备好
- 工具已注册
接下来进入第五阶段:最终检查与模式执行。