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>
78 lines
2.2 KiB
TypeScript
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));
|