import type { ProviderRequestOptions, ImageGenerationOptions, TextToSpeechOptions, SpeechToTextOptions, RerankingOptions, RerankingResult, FileUploadResult, ToolCall, FinishReason, TokenUsage } from './types.js'; /** * One scripted provider response in a multi-step agent loop. * * Pass an array of these to {@link AiFake.respondWithSequence} to script a * multi-turn run end-to-end (e.g. step 0 returns tool calls, step 1 returns * the final assistant text). Step indices are 0-based and align 1:1 with * provider `generate`/`stream` calls. * * - `text` — assistant message content (default: empty string) * - `toolCalls` — present when the assistant wants to call tools; the loop * will execute them and feed results into the next step * - `finishReason` — defaults to `'tool_calls'` when `toolCalls` is set, * otherwise `'stop'` */ export interface AiFakeStep { text?: string; toolCalls?: ToolCall[]; finishReason?: FinishReason; /** * Token usage to surface on the `'usage'` / `'finish'` chunks. Defaults * to all zeros — fine for most tests, but middlewares that depend on * realistic usage (e.g. budget enforcement) need this set. */ usage?: TokenUsage; } /** * Testing fake for @gemstack/ai-sdk. * * @example Single-response shorthand * const fake = AiFake.fake() * fake.respondWith('Mocked response') * * const response = await AI.prompt('Hello') * assert.strictEqual(response.text, 'Mocked response') * * fake.assertPrompted(input => input.includes('Hello')) * fake.restore() * * @example Multi-step sequence * const fake = AiFake.fake() * fake.respondWithSequence([ * { toolCalls: [{ id: 't1', name: 'lookup', arguments: { q: 'a' } }] }, * { text: 'Done.' }, * ]) * * @example Forced failure * const fake = AiFake.fake() * fake.failOnStep(0, new Error('Rate limited')) * // first provider call throws; useful for testing onError middleware */ export declare class AiFake { private readonly calls; private readonly imageCalls; private readonly ttsCalls; private readonly sttCalls; private readonly embedCalls; private readonly rerankCalls; private readonly fileCalls; private _response; private _sequence; private readonly _failures; private _preventStray; private _imageResponse; private _ttsResponse; private _sttResponse; private _embedResponse; private _rerankResponse; private _fileUploadResponse; /** Set the text response the fake will return */ respondWith(text: string): void; /** * Strict mode — throw when the agent makes a prompt the fake doesn't have a * scripted response for. * * Without this, an unscripted prompt silently falls back to the ambient * `respondWith` default (`'fake response'` if untouched). That behavior * makes tests pass even when they accidentally trigger an extra prompt the * test never asserted on. * * Under strict mode, only entries from {@link respondWithSequence} count * as valid responses; the ambient `respondWith` default is **not used**. * Force a single-step script via `respondWithSequence([{ text: '...' }])` * if you need an exact-one-prompt test that still returns content. * * @example * const fake = AiFake.fake().preventStrayPrompts() * fake.respondWithSequence([{ text: 'expected reply' }]) * * await new ChatAgent().prompt('hello') // OK * await new ChatAgent().prompt('again') // throws — no second script entry */ preventStrayPrompts(): this; /** * Script a multi-step provider response. Each entry in `steps` corresponds * to one provider call (the Nth `generate`/`stream` invocation consumes * `steps[N]`). When the sequence is exhausted, subsequent calls fall back * to {@link respondWith} (default: `'fake response'`). * * Pair with `tools` on an agent to drive a complete tool-call loop: * step 0 emits `toolCalls`, step 1 returns the final assistant text. * * Calling this resets the sequence and the provider-call counter so * scripted step indices are relative to this call. Registered failures * (see {@link failOnStep}) are left intact, so the order in which you * call `respondWithSequence` and `failOnStep` does not matter. */ respondWithSequence(steps: AiFakeStep[]): void; /** * Force the Nth provider call to throw. Useful for testing failover * (paired with multiple registered factories), error middleware, and * `onError` hooks without a real network failure. * * The step index is 0-based and counts every `generate`/`stream` call * the fake adapter receives. Failures take precedence over any scripted * sequence at the same index — the sequence step is NOT consumed. */ failOnStep(stepIndex: number, error: Error): void; /** Set the base64 image the fake will return */ respondWithImage(base64: string): void; /** Set the audio buffer the TTS fake will return */ respondWithAudio(audio: Buffer): void; /** Set the transcription text the STT fake will return */ respondWithTranscription(text: string): void; /** Set the embeddings the fake will return */ respondWithEmbedding(embeddings: number[][]): void; /** Set the reranking results the fake will return */ respondWithRanking(results: RerankingResult['results']): void; /** Set the file upload result the fake will return */ respondWithFileUpload(result: FileUploadResult): void; /** * Stub a `fileSearch` interaction. The hosted-OpenAI path runs the * search server-side and the result lands in the assistant message — * no agent-loop tool round-trip — so the simplest fake is a single * scripted response carrying the synthesized text. * * Pass `hits` to auto-format the text or pass `text` to control the * exact assistant reply. `usage` flows through to budget middleware. * * @example * fake.respondWithFileSearchResults({ text: 'The policy expires in 2027.' }) * * @example * fake.respondWithFileSearchResults({ * hits: [ * { text: 'Policies are reviewed annually.', source: 'policy.pdf', score: 0.92 }, * { text: 'The renewal date is March 1st.', source: 'renewal.pdf', score: 0.88 }, * ], * }) */ respondWithFileSearchResults(opts: { text?: string; hits?: Array<{ text: string; source?: string; score?: number; }>; usage?: TokenUsage; }): void; /** Install the fake — replaces all registered providers with a mock */ static fake(): AiFake; /** Assert at least one prompt was sent, optionally matching a predicate */ assertPrompted(predicate?: (input: string) => boolean): void; /** Assert no prompts were sent */ assertNothingPrompted(): void; /** Assert at least one image generation was made */ assertImageGenerated(predicate?: (prompt: string) => boolean): void; /** Assert at least one TTS generation was made */ assertAudioGenerated(predicate?: (text: string) => boolean): void; /** Assert at least one transcription was made */ assertTranscribed(predicate?: (opts: SpeechToTextOptions) => boolean): void; /** Get all recorded provider calls */ getCalls(): ProviderRequestOptions[]; /** Get all recorded image generation calls */ getImageCalls(): ImageGenerationOptions[]; /** Get all recorded TTS calls */ getTtsCalls(): TextToSpeechOptions[]; /** Get all recorded STT calls */ getSttCalls(): SpeechToTextOptions[]; /** Assert at least one embedding was made */ assertEmbedded(predicate?: (input: string | string[]) => boolean): void; /** Assert at least one reranking was made */ assertReranked(predicate?: (opts: RerankingOptions) => boolean): void; /** Get all recorded embedding calls */ getEmbedCalls(): Array<{ input: string | string[]; model: string; }>; /** Get all recorded reranking calls */ getRerankCalls(): RerankingOptions[]; /** Assert at least one file upload was made */ assertFileUploaded(predicate?: (filePath: string) => boolean): void; /** Get all recorded file operation calls */ getFileCalls(): Array<{ method: string; args: unknown; }>; /** Restore — clears the fake (user must re-register real providers) */ restore(): void; } //# sourceMappingURL=fake.d.ts.map