Files
openmaic/OpenMAIC/tests/lib/agent/runtime/director-transport-consumer.test.ts
2026-08-16 14:58:47 +08:00

244 lines
7.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { AgentConfig } from '@/lib/orchestration/registry/types';
import type { StatelessChatRequest, StatelessEvent } from '@/lib/types/chat';
const mocks = vi.hoisted(() => ({
streamLLM: vi.fn(),
injectOwnErrorWithOkStatus: false,
}));
vi.mock('@/lib/ai/llm', () => ({ streamLLM: mocks.streamLLM }));
vi.mock('@/lib/chat/pi/tools/read-scene', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/chat/pi/tools/read-scene')>();
return {
...actual,
buildReadSceneTool: vi.fn((options: Parameters<typeof actual.buildReadSceneTool>[0]) => {
const tool = actual.buildReadSceneTool(options);
const execute = tool.execute.bind(tool);
return {
...tool,
execute: async (...args: Parameters<typeof tool.execute>) => {
if (!mocks.injectOwnErrorWithOkStatus) return execute(...args);
return {
content: [{ type: 'text' as const, text: 'Synthetic own-marker failure.' }],
details: {
status: 'ok' as const,
sceneId: 'synthetic-marker',
source: 'request_start_snapshot' as const,
truncated: false as const,
},
isError: true,
};
},
};
}),
};
});
import { runPiDirectorLoop } from '@/lib/chat/pi/director-loop';
const ZERO_USAGE = {
inputTokens: 0,
outputTokens: 0,
inputTokenDetails: { cacheReadTokens: 0, cacheWriteTokens: 0 },
};
const resolvedModel = { provider: 'test.provider', modelId: 'director-model' };
const teacher: AgentConfig = {
id: 'teacher-1',
name: 'Teacher',
role: 'teacher',
persona: 'Teach clearly.',
avatar: '',
color: '#3366ff',
allowedActions: [],
priority: 10,
createdAt: new Date('2026-01-01T00:00:00Z'),
updatedAt: new Date('2026-01-01T00:00:00Z'),
isDefault: true,
};
function finish(finishReason: string) {
return { type: 'finish', finishReason, totalUsage: ZERO_USAGE };
}
function resultFrom(parts: Array<Record<string, unknown>>) {
return {
fullStream: (async function* () {
for (const part of parts) yield part;
})(),
usage: new Promise(() => {}),
};
}
function makeBody(): StatelessChatRequest {
return {
messages: [
{
id: 'user-1',
role: 'user',
parts: [{ type: 'text', text: 'Read the missing scene.' }],
},
],
storeState: {
stage: { id: 'stage-1', name: 'Transport test', createdAt: 1, updatedAt: 2 },
outlines: [],
scenes: [],
currentSceneId: null,
mode: 'autonomous',
whiteboardOpen: false,
},
config: { agentIds: [teacher.id], agentConfigs: [teacher] },
apiKey: '',
} as StatelessChatRequest;
}
function toolOutput(callIndex: number): { type?: string; value?: string } | undefined {
const params = mocks.streamLLM.mock.calls[callIndex]?.[0] as
| { messages?: Array<Record<string, unknown>> }
| undefined;
const toolMessage = params?.messages?.find((message) => message.role === 'tool') as
| { content?: Array<{ output?: { type?: string; value?: string } }> }
| undefined;
return toolMessage?.content?.[0]?.output;
}
describe('Director shared Pi transport consumer', () => {
beforeEach(() => {
mocks.streamLLM.mockReset();
mocks.injectOwnErrorWithOkStatus = false;
});
it('uses the resolved chat model and traces a normalized evidence failure unchanged', async () => {
mocks.streamLLM
.mockReturnValueOnce(
resultFrom([
{
type: 'tool-call',
toolCallId: 'director-read',
toolName: 'read_scene',
input: { sceneId: 'missing-scene' },
},
finish('tool-calls'),
]),
)
.mockReturnValueOnce(
resultFrom([
{ type: 'text-delta', text: 'The requested scene does not exist.' },
finish('stop'),
]),
);
const events: StatelessEvent[] = [];
const abortController = new AbortController();
await runPiDirectorLoop({
body: makeBody(),
agentConfigs: [teacher],
send: async (event) => {
events.push(event);
},
languageModel: resolvedModel as never,
thinkingConfig: { mode: 'disabled', enabled: false },
maxOutputTokens: 640,
abortSignal: abortController.signal,
signal: abortController.signal,
maxAgentTurns: 2,
maxActionsPerAgent: 1,
enableWhiteboardTools: false,
enableWebSearch: false,
});
expect(mocks.streamLLM).toHaveBeenCalledTimes(2);
expect(mocks.streamLLM.mock.calls[0]?.[0]).toEqual(
expect.objectContaining({ model: resolvedModel, maxOutputTokens: 640 }),
);
expect(mocks.streamLLM.mock.calls[0]?.[1]).toBe('pi-chat-director');
expect(toolOutput(1)).toEqual({
type: 'error-text',
value: expect.stringContaining('was not found in the request-start course snapshot'),
});
const done = events.find((event) => event.type === 'done');
expect(done?.type).toBe('done');
if (done?.type !== 'done') throw new Error('expected done event');
expect(done.data.directorToolTrace).toEqual([
expect.objectContaining({
sequence: 1,
toolName: 'read_scene',
args: { sceneId: 'missing-scene' },
isError: true,
resultPreview: expect.stringContaining('was not found'),
details: {
status: 'not_found',
sceneId: 'missing-scene',
source: 'request_start_snapshot',
truncated: false,
},
}),
]);
});
it('sees own-marker normalization before the Director evidence fallback', async () => {
mocks.injectOwnErrorWithOkStatus = true;
mocks.streamLLM
.mockReturnValueOnce(
resultFrom([
{
type: 'tool-call',
toolCallId: 'director-marker',
toolName: 'read_scene',
input: { sceneId: 'synthetic-marker' },
},
finish('tool-calls'),
]),
)
.mockReturnValueOnce(
resultFrom([{ type: 'text-delta', text: 'Failure acknowledged.' }, finish('stop')]),
);
const events: StatelessEvent[] = [];
const abortController = new AbortController();
await runPiDirectorLoop({
body: makeBody(),
agentConfigs: [teacher],
send: async (event) => {
events.push(event);
},
languageModel: resolvedModel as never,
thinkingConfig: { mode: 'disabled', enabled: false },
maxOutputTokens: 640,
abortSignal: abortController.signal,
signal: abortController.signal,
maxAgentTurns: 2,
maxActionsPerAgent: 1,
enableWhiteboardTools: false,
enableWebSearch: false,
});
expect(mocks.streamLLM).toHaveBeenCalledTimes(2);
expect(toolOutput(1)).toEqual({
type: 'error-text',
value: 'Synthetic own-marker failure.',
});
const done = events.find((event) => event.type === 'done');
expect(done?.type).toBe('done');
if (done?.type !== 'done') throw new Error('expected done event');
expect(done.data.directorToolTrace).toEqual([
expect.objectContaining({
sequence: 1,
toolName: 'read_scene',
args: { sceneId: 'synthetic-marker' },
isError: true,
resultPreview: 'Synthetic own-marker failure.',
details: {
status: 'ok',
sceneId: 'synthetic-marker',
source: 'request_start_snapshot',
truncated: false,
},
}),
]);
});
});