/** * 回合执行器按工具执行模式分流(A3.4)。 * * 两种模式必须都覆盖,否则 Pi 那条路只有真模型测试才碰得到: * platform —— 平台执行并回填(假引擎走这条) * runtime —— 引擎自己执行(Pi + 扩展桥),平台只观察事件、不得重复执行 */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { runTurn } from '@aps/server/app/turn-runner'; function stubSession(mode, events) { return { id: 'stub', toolExecution: mode, async *send() { for (const event of events) yield event; }, resolveToolResult() {}, abort() {}, async dispose() {}, }; } const toolCallEvents = [ { type: 'text', delta: '我来调用。' }, { type: 'tool_call', callId: 'c-1', capabilityId: 'test.echo', args: { q: 1 } }, { type: 'tool_result', callId: 'c-1', receipt: { executionId: 'exec-1', status: 'succeeded' } }, { type: 'turn_end', reason: 'stop' }, ]; function collect() { const events = []; return { events, publish: (event) => events.push(event) }; } test('runtime 模式:平台不执行工具,只观察 tool.call 与 tool.result', async () => { const { events, publish } = collect(); let invocations = 0; const result = await runTurn({ runtimeSession: stubSession('runtime', toolCallEvents), sessionId: 's-1', runId: 'r-1', assistantMessageId: 'm-1', text: '调用试试', publish, invoke: async () => { invocations += 1; return { executionId: 'platform-should-not-run', status: 'succeeded' }; }, signal: new AbortController().signal, }); assert.equal(invocations, 0, '引擎模式下平台不得再执行一次(否则等于重复调用)'); const types = events.map((event) => event.type); assert.deepEqual(types, ['run.state', 'message.delta', 'tool.call', 'tool.result', 'message.completed', 'run.state']); assert.equal(result.toolCalls, 1); assert.equal(result.finishReason, 'stop'); }); test('platform 模式:平台执行并把回执回填给运行时', async () => { const { events, publish } = collect(); const resolved = []; const session = stubSession('platform', toolCallEvents); session.resolveToolResult = (callId, receipt) => resolved.push({ callId, receipt }); let invocations = 0; await runTurn({ runtimeSession: session, sessionId: 's-1', runId: 'r-1', assistantMessageId: 'm-1', text: '调用试试', publish, invoke: async () => { invocations += 1; return { executionId: 'exec-platform', status: 'succeeded' }; }, signal: new AbortController().signal, }); assert.equal(invocations, 1, '平台模式必须执行一次'); assert.deepEqual(resolved, [{ callId: 'c-1', receipt: { executionId: 'exec-platform', status: 'succeeded' } }]); const types = events.map((event) => event.type); assert.ok(types.includes('tool.result'), types.join(',')); }); test('错误回合的结束原因不会被 turn_end 覆盖回 stop', async () => { const { events, publish } = collect(); const result = await runTurn({ runtimeSession: stubSession('runtime', [ { type: 'error', kind: 'forbidden', message: '401 无效密钥' }, { type: 'turn_end', reason: 'stop' }, ]), sessionId: 's-1', runId: 'r-1', assistantMessageId: 'm-1', text: '触发错误', publish, invoke: async () => ({ executionId: 'x', status: 'succeeded' }), signal: new AbortController().signal, }); assert.equal(result.finishReason, 'error'); assert.equal(events.find((event) => event.type === 'message.completed').data.finishReason, 'error'); });