ai-chatbot/scripts/zcode-agent/task-fixture.ts

79 lines
5.1 KiB
TypeScript

import assert from 'node:assert/strict';
import { tool } from 'ai';
import { z } from 'zod';
import { runZcodeCanvasTask } from '../../lib/ai/zcode-agent/task';
import { AgentToolRejectedError, newAgentRunSnapshot } from '../../lib/ai/canvas-agent-runtime/protocol';
import type { runNativeZcode } from '../../services/zcode-agent/runner.mjs';
type NativeOptions = Parameters<typeof runNativeZcode>[0];
function fixture(objective = '修改图片') {
const snapshot = newAgentRunSnapshot(objective, []); let writes = 0; const saved: string[] = [];
const schema = z.object({ nodeIds: z.array(z.string()).default([]) });
const definitions = { planCanvasTask: tool({ inputSchema: z.object({}) }), edit: tool({ inputSchema: schema }), readCanvas: tool({ inputSchema: z.object({}) }) };
const ports = { runId: crypto.randomUUID(), snapshot, definitions, instructions: '', context: {},
gateway: { modelId: 'fixture', apiType: 'openai-chat-completions', forward: async () => Response.json({}) }, signal: new AbortController().signal,
checkpoint: async (_snapshot: unknown, status: string) => { saved.push(status); }, consumeInputs: async () => ({ cancelled: false }), isCancelled: async () => false,
verify: async () => ({ passed: true, issues: [] as string[] }),
tools: { planCanvasTask: { title: '计划', readOnly: true, concurrentSafe: true, execute: async () => ({ steps: ['修改图片'] }) },
readCanvas: { title: '读取', readOnly: true, concurrentSafe: true, execute: async () => ({ nodes: [] }) },
edit: { title: '修改', readOnly: false, concurrentSafe: false, execute: async () => { writes++; return { updatedNodeIds: ['a'] }; } } },
native: async (options: NativeOptions) => { await options.execute('planCanvasTask', {}, 'plan', options.signal!); await options.execute('edit', { nodeIds: ['a'] }, 'edit', options.signal!); return result; }
};
return { ports, snapshot, saved, writes: () => writes };
}
const result = { type: 'result', sessionId: 'sess_fixture', response: '已完成' };
async function main() {
let checks = 0;
{
const h = fixture(); h.ports.native = async options => {
await options.execute('planCanvasTask', {}, 'plan', options.signal!);
await options.execute('edit', { nodeIds: ['a'] }, 'same', options.signal!);
await options.execute('edit', { nodeIds: ['a'] }, 'same', options.signal!);
return result;
};
assert.equal(await runZcodeCanvasTask(h.ports), 'completed'); assert.equal(h.writes(), 1); checks++;
}
{
const h = fixture(); h.ports.native = async options => {
await assert.rejects(options.execute('edit', { nodeIds: ['a'] }, 'early', options.signal!), /计划/);
await options.execute('planCanvasTask', {}, 'plan', options.signal!);
await assert.rejects(options.execute('edit', { nodeIds: 42 }, 'invalid', options.signal!), /参数无效/);
await options.execute('edit', { nodeIds: ['a'] }, 'valid', options.signal!); return result;
};
assert.equal(await runZcodeCanvasTask(h.ports), 'completed'); assert.equal(h.writes(), 1); checks++;
}
{
const h = fixture(); h.snapshot.calls.push({ id: 'unknown', name: 'edit', input: {}, status: 'started' });
assert.equal(await runZcodeCanvasTask(h.ports), 'needs_review'); assert.equal(h.writes(), 0); checks++;
}
{
const h = fixture(); h.snapshot.verification = { passed: false, issues: ['bad b'], retryable: true, failedNodeIds: ['b'] };
h.ports.native = async options => { await options.execute('planCanvasTask', {}, 'plan', options.signal!);
await assert.rejects(options.execute('edit', { nodeIds: ['a','b'] }, 'cross', options.signal!), /未通过/);
await options.execute('edit', { nodeIds: ['b'] }, 'only-failed', options.signal!); return result; };
assert.equal(await runZcodeCanvasTask(h.ports), 'completed'); assert.equal(h.writes(), 1); checks++;
}
{
const h = fixture('画布里有什么'); h.ports.native = async () => ({ ...result, response: '画布为空' });
assert.equal(await runZcodeCanvasTask(h.ports), 'completed'); assert.equal(h.snapshot.message, '画布为空'); checks++;
}
{
const h = fixture('抠图'); h.ports.native = async () => result;
assert.equal(await runZcodeCanvasTask(h.ports), 'needs_review'); assert.equal(h.writes(), 0); checks++;
}
{
const h = fixture(); h.ports.isCancelled = async () => true;
assert.equal(await runZcodeCanvasTask(h.ports), 'cancelled'); assert.equal(h.writes(), 1); checks++;
}
{
const h = fixture(); let attempts = 0;
h.ports.tools.edit.execute = async () => { attempts++; if (attempts === 1) throw new AgentToolRejectedError('参数未通过,未提交'); return { updatedNodeIds: ['a'] }; };
h.ports.native = async options => { await options.execute('planCanvasTask', {}, 'plan', options.signal!);
await assert.rejects(options.execute('edit', { nodeIds: ['a'] }, 'rejected', options.signal!));
await options.execute('edit', { nodeIds: ['a'] }, 'corrected', options.signal!); return result; };
assert.equal(await runZcodeCanvasTask(h.ports), 'completed'); assert.equal(h.snapshot.calls.find(c=>c.id==='rejected')?.uncertain,false); checks++;
}
console.log('NATIVE_TASK_ADAPTER_PASSED', { checks });
}
main().catch(error => { console.error(error); process.exitCode = 1; });