文章
合集Pi Agent 源码阅读第 6 / 9 篇

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);

模型解析流程

  1. 确定用户想用哪些模型(--models sonnet,haiku 或设置文件里的默认值)
  2. resolveModelScope(),把模型模式(如 anthropic/*)解析成具体的模型对象
  3. 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() 做什么?

  1. 调用 createRuntime 工厂函数,创建运行时
  2. 处理可能的错误和重试
  3. 返回完整的运行时对象

应用 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 运行环境:

  • 配置已加载
  • 模型已连接
  • 资源已加载
  • 会话已准备好
  • 工具已注册

接下来进入第五阶段:最终检查与模式执行。