79 lines
5.1 KiB
TypeScript
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; });
|