import type { PromptResult, StreamChunk, Result } from "smoltalk"; import type { AudioInput, DecideConfig, DecideResult, DecisionAnswer, DecisionQuestion, DecisionState, EmbedConfig, EmbedResult, ImageConfig, ImageGenResult, ImageInput, LLMClient, PromptConfig, SpeakConfig, SpeechResult, TranscribeConfig, TranscriptionResult } from "./llmClient.js"; /** Optional delays so tests can exercise mid-request cancellation of the audio * capabilities without touching the existing text/tool mock queues. */ export type DeterministicClientOptions = { transcriptionDelayMs?: number; speechDelayMs?: number; }; export type ReturnMock = { return: any; /** Milliseconds this mock "generates" before returning, honoring the * request's abort signal like a real provider: if the signal fires * mid-delay, the mock rejects with the signal's reason (or a plain * abort Error), which is exactly how a cancelled in-flight request * surfaces. Lets fixtures exercise mid-request cancellation — e.g. a * time guard tripping while a request is out — deterministically: * the only timing requirement is limit << delayMs. */ delayMs?: number; }; export type ToolCallMock = { toolCall: { name: string; args?: Record; }; }; /** Multi-tool variant: simulate the LLM returning multiple tool calls in * one round (used to test concurrent tool execution in `runPrompt`). * * `id` is optional: when omitted a synthetic `mock-tool-*` id is used. * Pass `id: ""` to reproduce providers (notably Google Gemini) whose * function-calling protocol returns no tool-call id — smoltalk defaults * the missing id to "". */ export type MultiToolCallMock = { toolCalls: Array<{ name: string; args?: Record; id?: string; }>; }; /** A decision model's answers for one `llm()` call that resolves to a * decision provider. Keyed by question name: `answer` for a bare * annotation, the field names for an object annotation. */ export type DecideMock = { decide: Record; }; export type LLMMock = ReturnMock | ToolCallMock | MultiToolCallMock | DecideMock; /** * Per-agent mock queues. Keys are matched against the executing module: * the exact module id ("lib/agents/optimize/mutatePrompt.agency"), then its * basename without the extension ("mutatePrompt"), then the "*" fallback * queue. Each queue is consumed in order, independently of the others, * so tests that span several agents (e.g. a full optimize iteration: * task agent + mutator + judge) don't have to predict the global * interleaving of llm() calls. */ export type ScopedLLMMocks = Record; export declare class DeterministicClient implements LLMClient { private queues; /** Array form: one anonymous queue, original error messages. */ private readonly scoped; private readonly speechOptions; constructor(mocks: LLMMock[] | ScopedLLMMocks, options?: DeterministicClientOptions); /** * Picks the mock queue for the module making the call. The runtime puts * that module's id on the request config (it is the callsite of the step * the call runs in); when the config has none, or no scope matches, the * "*" queue applies. */ private resolveQueue; text(config: PromptConfig): Promise>; textStream(config: PromptConfig): AsyncGenerator; /** Answers one request. Inside a parallel block the collector merges * several llm() calls into one request, each call's questions under a * `c_` prefix; those consume one mock per call, in call order, and * each mock is checked against its own call's names. */ decide(_state: DecisionState, questions: Record, config: DecideConfig, signal: AbortSignal): Promise>; /** The next mock as one llm() call's answers, checked against its questions. */ private nextDecideMock; embed(_input: string | string[], _config?: Partial): Promise>; image(_input: ImageInput, _config?: Partial): Promise>; transcribe(_source: AudioInput, _config: TranscribeConfig, signal: AbortSignal): Promise>; speak(_text: string, config: SpeakConfig, signal: AbortSignal): Promise>; }