v2 phase 2: agent-runner — provider interface, poll loop, formatter
AgentProvider abstraction with Claude and Mock implementations. Poll loop reads messages_in, formats by kind, queries provider, writes results to messages_out. Concurrent polling pushes follow-up messages into active queries. - providers/types.ts: AgentProvider, AgentQuery, ProviderEvent - providers/claude.ts: wraps Agent SDK with MessageStream, hooks, transcript archiving - providers/mock.ts: canned responses with push() support - providers/factory.ts: createProvider() - formatter.ts: format by kind (chat/task/webhook/system), XML escaping, routing extraction - poll-loop.ts: poll → format → query → write, concurrent polling - mcp-tools.ts: MCP server with send_message tool - index-v2.ts: new entry point (config from env, enters poll loop) - 11 new tests, all 288 tests pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
66
container/agent-runner/src/providers/mock.ts
Normal file
66
container/agent-runner/src/providers/mock.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { AgentProvider, AgentQuery, ProviderEvent, QueryInput } from './types.js';
|
||||
|
||||
/**
|
||||
* Mock provider for testing. Returns canned responses.
|
||||
* Supports push() — queued messages produce additional results.
|
||||
*/
|
||||
export class MockProvider implements AgentProvider {
|
||||
private responseFactory: (prompt: string) => string;
|
||||
|
||||
constructor(responseFactory?: (prompt: string) => string) {
|
||||
this.responseFactory = responseFactory ?? ((prompt) => `Mock response to: ${prompt.slice(0, 100)}`);
|
||||
}
|
||||
|
||||
query(input: QueryInput): AgentQuery {
|
||||
const pending: string[] = [];
|
||||
let waiting: (() => void) | null = null;
|
||||
let ended = false;
|
||||
let aborted = false;
|
||||
const responseFactory = this.responseFactory;
|
||||
|
||||
const events: AsyncIterable<ProviderEvent> = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield { type: 'init', sessionId: `mock-session-${Date.now()}` };
|
||||
|
||||
// Process initial prompt
|
||||
yield { type: 'result', text: responseFactory(input.prompt) };
|
||||
|
||||
// Process any pushed follow-ups
|
||||
while (!ended && !aborted) {
|
||||
if (pending.length > 0) {
|
||||
const msg = pending.shift()!;
|
||||
yield { type: 'result', text: responseFactory(msg) };
|
||||
continue;
|
||||
}
|
||||
// Wait for push() or end()
|
||||
await new Promise<void>((resolve) => {
|
||||
waiting = resolve;
|
||||
});
|
||||
waiting = null;
|
||||
}
|
||||
|
||||
// Drain remaining
|
||||
while (pending.length > 0) {
|
||||
const msg = pending.shift()!;
|
||||
yield { type: 'result', text: responseFactory(msg) };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
push(message: string) {
|
||||
pending.push(message);
|
||||
waiting?.();
|
||||
},
|
||||
end() {
|
||||
ended = true;
|
||||
waiting?.();
|
||||
},
|
||||
events,
|
||||
abort() {
|
||||
aborted = true;
|
||||
waiting?.();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user