/** * 能力桥用例(任务 A3.4 / A3.11)。 * * 不启动 Pi:把扩展装到一个假的 Pi 注册表上,直接调它注册出来的工具, * 验证「Pi 工具调用 → 平台内部端点 → Core.invoke → 假 Provider → 回执」这条链路。 * 真引擎那一环另有一条需要 APS_PI_INTEGRATION=1 的用例(见 pi-runtime.test.mjs)。 */ import assert from 'node:assert/strict'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { test } from 'node:test'; import { pathToFileURL } from 'node:url'; const EXTENSION = pathToFileURL(join(process.cwd(), 'server/agent/pi/extension/aps-bridge.ts')).href; async function startPlatform() { process.env.APS_DEV_PROVIDER = '1'; const { startServer } = await import('../server/dist/index.js'); return startServer({ host: '127.0.0.1', port: 0, version: 'test', databasePath: ':memory:', engine: 'fake' }); } /** 用假 Pi 加载扩展,返回注册出来的工具表 */ async function loadBridge(configPath) { const previous = process.env.APS_BRIDGE_CONFIG; process.env.APS_BRIDGE_CONFIG = configPath; try { const module = await import(EXTENSION); const tools = new Map(); module.default({ registerTool(tool) { tools.set(tool.name, tool); }, }); return tools; } finally { if (previous === undefined) delete process.env.APS_BRIDGE_CONFIG; else process.env.APS_BRIDGE_CONFIG = previous; } } async function writeBridgeConfig(server, sessionId, capabilities) { const dir = await mkdtemp(join(tmpdir(), 'aps-bridge-')); const file = join(dir, 'bridge.json'); await writeFile( file, JSON.stringify({ baseUrl: server.url, internalToken: server.internalToken, sessionId, capabilities }), 'utf8', ); return { dir, file }; } test('扩展把平台能力注册成 Pi 工具,调用经 Core.invoke 返回回执', async () => { const server = await startPlatform(); const created = await ( await fetch(`${server.url}/v1/sessions`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ scope: 'project', projectId: 'p-default', title: '桥用例' }), }) ).json(); const sessionId = created.session.sessionId; const { dir, file } = await writeBridgeConfig(server, sessionId, [ { toolName: 'fake_echo', capabilityId: 'fake.echo', description: '回显输入', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] }, }, ]); try { const tools = await loadBridge(file); assert.ok(tools.has('fake_echo'), `应注册 fake_echo,实际:${[...tools.keys()].join(',')}`); const tool = tools.get('fake_echo'); const result = await tool.execute('call-abc', { text: '你好' }); const receipt = JSON.parse(result.content[0].text); assert.equal(receipt.status, 'succeeded'); assert.deepEqual(receipt.result, { echo: '你好' }); // 平台侧留下执行记录,且能经 HTTP 查到 const execution = await (await fetch(`${server.url}/v1/executions/${receipt.executionId}`)).json(); assert.equal(execution.status, 'succeeded'); const record = server.service.core.executions.get(receipt.executionId); assert.equal(record.sessionId, sessionId, '执行记录应归属发起会话'); assert.equal(record.capabilityId, 'fake.echo'); } finally { await server.close(); await rm(dir, { recursive: true, force: true }); delete process.env.APS_DEV_PROVIDER; } }); test('能力不存在时扩展抛错,Pi 会把它当工具错误(不会伪造成功)', async () => { const server = await startPlatform(); const created = await ( await fetch(`${server.url}/v1/sessions`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ scope: 'project', projectId: 'p-default', title: '失败路径' }), }) ).json(); const { dir, file } = await writeBridgeConfig(server, created.session.sessionId, [ { toolName: 'fake_nope', capabilityId: 'fake.__nope__', description: '不存在的能力', parameters: { type: 'object' } }, ]); try { const tools = await loadBridge(file); await assert.rejects(() => tools.get('fake_nope').execute('call-bad', {}), /执行失败|invalid_argument/); } finally { await server.close(); await rm(dir, { recursive: true, force: true }); delete process.env.APS_DEV_PROVIDER; } }); test('没有桥配置时不注册任何工具(宁可说做不到,也不给坏工具)', async () => { const tools = await loadBridge(join(tmpdir(), 'not-exists-bridge.json')); assert.equal(tools.size, 0); });