main.ts 第二阶段:解析参数与快速退出
概述
经过第一阶段的三个拦截后,程序确认用户要进入正常的 agent 流程。第二阶段正式解析参数,并处理几个可以快速完成就退出的场景。
const parsed = parseArgs(args);
if (parsed.diagnostics.length > 0) {
for (const d of parsed.diagnostics) {
const color = d.type === "error" ? chalk.red : chalk.yellow;
console.error(color(`${d.type === "error" ? "Error" : "Warning"}: ${d.message}`));
}
if (parsed.diagnostics.some((d) => d.type === "error")) {
process.exit(1);
}
}
time("parseArgs");
if (parsed.version) {
console.log(VERSION);
process.exit(0);
}
if (parsed.export) {
let result: string;
try {
const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
result = await exportFromFile(parsed.export, outputPath);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Failed to export session";
console.error(chalk.red(`Error: ${message}`));
process.exit(1);
}
console.log(`Exported to: ${result}`);
process.exit(0);
}
let appMode = resolveAppMode(parsed, process.stdin.isTTY, process.stdout.isTTY);
const shouldTakeOverStdout = appMode !== "interactive" && !isPlainRuntimeMetadataCommand(parsed);
if (shouldTakeOverStdout) {
takeOverStdout();
}
if (parsed.mode === "rpc" && parsed.fileArgs.length > 0) {
console.error(chalk.red("Error: @file arguments are not supported in RPC mode"));
process.exit(1);
}
validateForkFlags(parsed);
validateSessionIdFlags(parsed);
步骤 1:解析参数
const parsed = parseArgs(args);
调用 cli/args.ts 里的 parseArgs(),把命令行参数数组转成结构化的 Args 对象。详细实现见 pi-agent-cli-entry。
步骤 2:检查解析错误
if (parsed.diagnostics.length > 0) {
for (const d of parsed.diagnostics) {
const color = d.type === "error" ? chalk.red : chalk.yellow;
console.error(color(`${d.type === "error" ? "Error" : "Warning"}: ${d.message}`));
}
if (parsed.diagnostics.some((d) => d.type === "error")) {
process.exit(1);
}
}
解析时如果发现有问题(比如 --thinking invalid_level),会存入 diagnostics 数组。这里遍历打印出来:
error红色,且有 error 就退出(process.exit(1))warning黄色,只警告不退出
.some((d) => d.type === "error") 检查数组里是否存在至少一个 error 类型的诊断。
步骤 3:--version 快速退出
if (parsed.version) {
console.log(VERSION);
process.exit(0);
}
用户输入 pi --version,打印版本号(如 "1.2.3")就走,不创建任何会话。process.exit(0) 表示正常退出。
步骤 4:--export 导出会话
if (parsed.export) {
let result: string;
try {
const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;
result = await exportFromFile(parsed.export, outputPath);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Failed to export session";
console.error(chalk.red(`Error: ${message}`));
process.exit(1);
}
console.log(`Exported to: ${result}`);
process.exit(0);
}
用户输入类似:
pi --export ~/.pi/agent/sessions/xxx/session.jsonl output.html
把一个会话文件导出成 HTML 页面。这个操作不需要启动 AI 模型,只是一个文件转换,所以快速处理完就退出。
try/catch 用来捕获文件读写错误,给出友好的错误提示。error instanceof Error 检查错误类型,确保能安全访问 error.message。
步骤 5:决定运行模式
let appMode = resolveAppMode(parsed, process.stdin.isTTY, process.stdout.isTTY);
resolveAppMode() 根据三个条件决定用哪种模式:
| 条件 | 结果 |
|---|---|
--mode rpc | "rpc",JSON-RPC 模式 |
--print 或 stdin 不是终端 | "print",非交互模式 |
| 其他情况 | "interactive",有 UI 的交互模式 |
步骤 6:拦截 stdout
const shouldTakeOverStdout = appMode !== "interactive" && !isPlainRuntimeMetadataCommand(parsed);
if (shouldTakeOverStdout) {
takeOverStdout();
}
在非交互模式下,程序接管 stdout。这是为了防止 AI 模型的输出和程序自身的输出混在一起。takeOverStdout() 会拦截所有 console.log() 调用,确保输出格式可控。
isPlainRuntimeMetadataCommand() 检查是否为 --version、--help、--list-models 等元数据命令,这些命令不需要接管 stdout。
步骤 7:参数校验
if (parsed.mode === "rpc" && parsed.fileArgs.length > 0) {
console.error(chalk.red("Error: @file arguments are not supported in RPC mode"));
process.exit(1);
}
validateForkFlags(parsed);
validateSessionIdFlags(parsed);
RPC 模式不支持 @file 参数: RPC 模式通过 JSON-RPC 协议通信,不处理文件内容。
validateForkFlags(): 校验 --fork 的参数组合是否合法,比如不能同时 --fork 和 --session。
validateSessionIdFlags(): 校验 --session-id 的参数组合是否合法。
第二阶段结束后的状态
经过第二阶段:
- 参数解析完成,存入
parsed对象 --version、--export等快速场景已处理- 运行模式已确定(
appMode) - 参数合法性已验证
如果这些检查都通过了,程序进入第三阶段:基础设施初始化。