refactor(agent-runner): decouple provider interface from Claude specifics

Reshape AgentProvider so provider-specific assumptions stop leaking into
the generic layer. No change to what reaches sdkQuery() — same values,
different plumbing.

- QueryInput: opaque `continuation` replaces `sessionId` + `resumeAt`;
  `systemContext.instructions` replaces ambiguous `systemPrompt`;
  `mcpServers`, `env`, `additionalDirectories` move to `ProviderOptions`
  at construction time.
- AgentProvider gains `isSessionInvalid(err)` and
  `supportsNativeSlashCommands` so the poll-loop stops regex-matching
  Claude error strings and gates passthrough slash commands per provider.
- ClaudeProvider owns `CLAUDE_CODE_AUTO_COMPACT_WINDOW` and the
  stale-session regex internally.
- ProviderEvent.activity kept and documented as the liveness signal
  (fires on every SDK message so the idle timer stays honest during
  long tool runs); init carries `continuation` instead of `sessionId`.
- poll-loop drops mcpServers/env/systemPrompt from its config; admin
  user id now passed explicitly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-13 10:25:29 +03:00
parent e07158e194
commit b63dd186df
8 changed files with 156 additions and 114 deletions

View File

@@ -1,16 +1,22 @@
import type { AgentProvider, AgentQuery, ProviderEvent, QueryInput } from './types.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(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;
@@ -21,7 +27,7 @@ export class MockProvider implements AgentProvider {
const events: AsyncIterable<ProviderEvent> = {
async *[Symbol.asyncIterator]() {
yield { type: 'activity' };
yield { type: 'init', sessionId: `mock-session-${Date.now()}` };
yield { type: 'init', continuation: `mock-session-${Date.now()}` };
// Process initial prompt
yield { type: 'activity' };