/** * 平台能力桥(任务 A3.11):把平台的能力目录注册成 Pi 的工具。 * * 这个文件由平台以 `--extension ` 加载,运行在 Pi 子进程里(Pi 用 jiti 直接读 TS)。 * 它自己不判断权限、不碰凭据,只做一件事:把 Pi 的工具调用转成对平台内部回调端点的 HTTP 请求, * 平台侧收口到 Core.invoke(架构设计 5.1「每次都过 Core」)。 * * 配置来自环境变量 APS_BRIDGE_CONFIG 指向的 JSON 文件,由平台在建立会话时写入: * { baseUrl, internalToken, sessionId, capabilities: [{ toolName, capabilityId, description, parameters }] } */ import { readFileSync } from 'node:fs'; interface BridgeCapability { toolName: string; capabilityId: string; description: string; parameters: Record; } interface BridgeConfig { baseUrl: string; internalToken: string; sessionId: string; capabilities: BridgeCapability[]; } interface PlatformReceipt { executionId: string; status: 'accepted' | 'pending-confirmation' | 'succeeded' | 'failed'; result?: unknown; errorKind?: string; } /** Pi 扩展的注册接口(只声明用到的部分,避免扩展依赖 Pi 的类型包) */ interface PiExtensionApi { registerTool(tool: { name: string; label: string; description: string; parameters: Record; execute: (toolCallId: string, params: unknown, signal?: AbortSignal) => Promise<{ content: Array<{ type: 'text'; text: string }>; details: unknown }>; }): void; } export default function apsBridge(pi: PiExtensionApi): void { const configPath = process.env.APS_BRIDGE_CONFIG ?? ''; if (configPath === '') return; let config: BridgeConfig; try { config = JSON.parse(readFileSync(configPath, 'utf8')) as BridgeConfig; } catch { // 配置读不到就不注册任何工具:宁可让模型说做不到,也不要注册一个坏工具 return; } for (const capability of config.capabilities) { pi.registerTool({ name: capability.toolName, label: capability.capabilityId, description: capability.description, parameters: capability.parameters, async execute(toolCallId: string, params: unknown, signal?: AbortSignal) { const response = await fetch(`${config.baseUrl}/internal/tool-call`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-aps-internal-token': config.internalToken }, body: JSON.stringify({ sessionId: config.sessionId, capabilityId: capability.capabilityId, params, idempotencyKey: `call-${toolCallId}`, }), ...(signal === undefined ? {} : { signal }), }); if (!response.ok) { const detail = await response.text().catch(() => ''); throw new Error(`平台拒绝调用(HTTP ${response.status}):${detail.slice(0, 200)}`); } const receipt = (await response.json()) as PlatformReceipt; if (receipt.status === 'failed') { const detail = (receipt.result as { message?: string } | undefined)?.message ?? '未提供原因'; throw new Error(`能力 ${capability.capabilityId} 执行失败(${receipt.errorKind ?? 'unknown'}):${detail}`); } // 成功:把回执原样交给模型(含 executionId,便于它引用证据) return { content: [{ type: 'text', text: JSON.stringify(receipt) }], details: receipt }; }, }); } }