Files
nanoclaw/container/agent-runner/src/providers/mock.ts
gavrielc 1f3b023a5a refactor(v2/providers): self-registration barrel + host container-config registry
Providers now mirror the channels pattern: each module calls
registerProvider() at top level, and providers/index.ts is a barrel of
side-effect imports. createProvider() becomes a thin registry lookup;
the closed ProviderName union is gone (now a string alias, since the
env var is a runtime string anyway).

Also adds a host-side provider-container-registry so providers can
declare their own mounts and env passthrough in src/providers/<name>.ts
instead of the container-runner having to know about each one. The
resolver runs once per spawn and threads provider + contribution
through buildMounts and buildContainerArgs so side effects (mkdir,
etc.) fire exactly once.

Both barrels are append-only — adding a new provider is a new file
+ one import line per barrel, no edits to existing files. The built-in
providers (claude, mock) don't need host-side config, so src/providers/
ships with an empty barrel; the container-side barrel imports both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:17:09 +03:00

78 lines
2.2 KiB
TypeScript

import { registerProvider } from './provider-registry.js';
import type { AgentProvider, AgentQuery, ProviderEvent, ProviderOptions, QueryInput } from './types.js';
/**
* Mock provider for testing. Returns canned responses.
* Supports push() — queued messages produce additional results.
*/
export class MockProvider implements AgentProvider {
readonly supportsNativeSlashCommands = false;
private responseFactory: (prompt: string) => string;
constructor(_options: ProviderOptions = {}, responseFactory?: (prompt: string) => string) {
this.responseFactory = responseFactory ?? ((prompt) => `Mock response to: ${prompt.slice(0, 100)}`);
}
isSessionInvalid(_err: unknown): boolean {
return false;
}
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: 'activity' };
yield { type: 'init', continuation: `mock-session-${Date.now()}` };
// Process initial prompt
yield { type: 'activity' };
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?.();
},
};
}
}
registerProvider('mock', (opts) => new MockProvider(opts));