test(ai): promote MockLlmClient to a shared @mana/shared-ai export

The runPlannerLoop test file and the webapp's mission-runner test each
had their own inline scripted LLM mock — same interface, diverged
slightly. Consolidates into packages/shared-ai/src/planner/mock-llm.ts
and re-exports from the package root so any consumer can drive the
loop deterministically.

Both existing test files now use the shared client. 5 + 3 tests pass,
44 total in shared-ai still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-20 18:05:46 +02:00
parent e10c2436a6
commit 5b7564b3a4
5 changed files with 70 additions and 80 deletions

View file

@ -12,7 +12,7 @@ import { registerTools } from '../../tools/registry';
import { createMission, getMission } from './store';
import { runMission } from './runner';
import { MISSIONS_TABLE } from './types';
import type { LlmClient, LlmCompletionRequest, LlmCompletionResponse } from '@mana/shared-ai';
import { MockLlmClient } from '@mana/shared-ai';
let executed: { name: string; params: Record<string, unknown> }[] = [];
@ -34,33 +34,20 @@ beforeEach(async () => {
await db.table(MISSIONS_TABLE).clear();
});
/** Minimal LlmClient for runner tests scripts one or more assistant
* turns via enqueueToolCalls / enqueueStop. */
/** Builder for concise scripted LLM turns. Wraps the shared
* MockLlmClient from @mana/shared-ai so tests read top-down. */
function mockLlm(
turns: Array<
| { kind: 'tool_calls'; calls: Array<{ name: string; args: Record<string, unknown> }> }
| { kind: 'stop'; content?: string }
>
): LlmClient {
let i = 0;
return {
async complete(_req: LlmCompletionRequest): Promise<LlmCompletionResponse> {
const turn = turns[i++];
if (!turn) throw new Error('MockLlm exhausted');
if (turn.kind === 'stop') {
return { content: turn.content ?? null, toolCalls: [], finishReason: 'stop' };
}
return {
content: null,
toolCalls: turn.calls.map((c, n) => ({
id: `call_${i}_${n}`,
name: c.name,
arguments: c.args,
})),
finishReason: 'tool_calls',
};
},
};
) {
const m = new MockLlmClient();
for (const t of turns) {
if (t.kind === 'stop') m.enqueueStop(t.content ?? null);
else m.enqueueToolCalls(t.calls);
}
return m;
}
describe('runMission', () => {