/** * Gemini LLM transport — real implementation. * * Migrated from {@link GeminiBackend} (`backends/gemini.ts`), adapted to the * provider-neutral {@link LlmTransport} interface. Behavior is preserved * verbatim; only the constructor signature and I/O types change to match the * Phase-4 transport contract. * * Key behavioral invariants (each enforced with `@invariant` TSDoc at the * enforcement site): * * 1. `geminiCacheStore` singleton — module-level instance persists across calls. * 2. `GEMINI_ALLOWED_SCHEMA_KEYS` recursive sanitization — strips disallowed * keys from nested object/array schemas. * 3. `GEMINI_BLOCKED_FINISH_REASONS` — 3 distinct throw sites in `complete()`. * 4. `thinkingEffort` / `thinkingBudgetTokens` MUTEX — exactly one may be set. * 5. `maxOutputTokens` → `maxTokens` fallback — prefer `maxOutputTokens`. * * @module llm/transports/gemini * @task T9283 (W1a) * @epic T-LLM-CRED-CENTRALIZATION * @see ADR-072 §LlmTransport — pure wire level */ import type { NormalizedDelta, TransportContext } from '@cleocode/contracts/llm/interfaces.js'; import type { LlmTransport, NormalizedResponse, TransportRequest, TransportTool } from '@cleocode/contracts/llm/normalized-response.js'; import type { ApiMode } from '@cleocode/contracts/llm/provider-id.js'; import { repairResponseModelJson } from '../structured-output.js'; /** * Options accepted by {@link GeminiTransport}. * * Structurally identical to `AnthropicTransportOptions` so the role-resolver * can swap providers by swapping only the constructor reference. */ export interface GeminiTransportOptions { /** API key for the Google Generative AI SDK. */ apiKey: string; /** Override base URL (e.g. Vertex AI endpoint). */ baseUrl?: string; /** Extra headers merged into every SDK request. */ defaultHeaders?: Record; } /** * Real Gemini transport. * * Wraps `@google/generative-ai` and normalizes requests/responses to/from the * provider-neutral {@link LlmTransport} interface. Supports prompt caching via * Gemini cached-content API (best-effort; silently skips if SDK version does * not support it). * * @example * ```ts * const transport = new GeminiTransport({ apiKey: process.env.GEMINI_API_KEY! }); * const response = await transport.complete({ * model: 'gemini-1.5-pro', * messages: [{ role: 'user', content: 'Hello' }], * maxTokens: 1024, * }); * ``` */ export declare class GeminiTransport implements LlmTransport { /** Provider identifier — always `'gemini'`. */ readonly provider: "gemini"; /** * Wire protocol spoken by this transport — `'chat_completions'`. * * Gemini currently routes through the OpenAI-compatible shim. A native * `'gemini_native'` ApiMode is deferred to Phase 5 per ADR-072. * * @see ADR-072 §Type lock-in */ readonly apiMode: ApiMode; private readonly _client; /** * Optional model config carrying transport/model identity for cache-key * construction. When absent the transport + model from the request are used. */ private readonly _modelConfig?; /** * Create a `GeminiTransport`. * * @param options - API key, optional base URL, and optional extra headers. */ constructor(options: GeminiTransportOptions); /** * Execute a single completion call against the Gemini API. * * Maps provider-neutral {@link TransportRequest} to the `@google/generative-ai` * `generateContent` call, then normalizes the response to {@link NormalizedResponse}. * * @param request - Provider-neutral request parameters. * @param _ctx - Transport context (unused by this transport in W1a). * @returns Normalized response including content, tool calls, usage, and raw SDK object. */ complete(request: TransportRequest, _ctx?: TransportContext): Promise; /** * Stream a completion against the Gemini API. * * Yields {@link NormalizedDelta} chunks including incremental text deltas and * tool-call argument deltas. The final delta carries `stopReason` and `usage`. * * Tool-call streaming: Gemini does not stream function-call arguments * incrementally across chunks. When a chunk contains `functionCall` parts, * this transport emits the standard three-delta sequence for each call: * 1. Start marker: `toolCallDelta` with `{ index, name, argumentsChunk: '' }`. * 2. Args delta: `toolCallDelta` with `{ index, argumentsChunk: JSON.stringify(args) }`. * 3. End marker: `toolCallDelta` with `{ index, argumentsChunk: '' }` (no name). * * This matches the shape emitted by {@link AnthropicTransport} and * {@link ChatCompletionsTransport} so consumers can handle all providers uniformly. * * @invariant T9316 tool-call streaming parity — Gemini stream emits the same * toolCallDelta sequence (start / args / end) as the Anthropic transport. * * @param request - Provider-neutral request parameters. * @param _ctx - Transport context (unused by this transport in W1a). * @returns An async iterable of normalized delta chunks. */ stream(request: TransportRequest, _ctx: TransportContext): AsyncIterable; /** * Returns true when the error is a 401 (invalid key) or 429 (rate limit), * signalling the session layer should rotate to the next credential. * * @param err - The error thrown by the provider SDK. * @returns Whether a credential rotation should be attempted. */ shouldRotateCredential(err: unknown): boolean; /** * Build the Gemini generationConfig object from request parameters. * * @invariant thinkingEffort vs thinkingBudgetTokens MUTEX — set ONE; throws if BOTH set. * @invariant maxOutputTokens → maxTokens fallback — maxOutputTokens used when provided, * falls back to maxTokens (enforced at call-site via the BuildConfigParams shape). */ private _buildConfig; /** * Normalize a raw Gemini SDK response into {@link NormalizedResponse}. * * @param params.response - Raw Gemini response object. * @param params.modelName - Model identifier (for error messages). */ private _normalizeResponse; /** * Attach a Gemini cached-content handle to the generation config. * * Best-effort: if the SDK version does not support caching or the cache * creation call fails, the function returns without modifying `config`. * * @invariant geminiCacheStore singleton integration — uses the module-level * `geminiCacheStore` instance imported from `caching.ts`. The store persists * handles across calls for the lifetime of the process. */ private _attachCachedContent; /** * Convert provider-neutral messages to Gemini `contents` + `systemInstruction`. * * Separates system messages from the conversation, maps `assistant` → `model`, * and converts multi-block content arrays to Gemini `parts`. */ static _convertMessages(messages: Array>): { contents: Array>; systemInstruction: string | null; }; /** * Convert provider-neutral {@link TransportTool} array to Gemini * `function_declarations` format. * * Already-converted inputs (first element has `function_declarations` key) are * passed through unchanged to allow callers to pre-format tools. */ static _convertTools(tools: TransportTool[]): Array>; /** * Recursively strip JSON-Schema keys not in {@link GEMINI_ALLOWED_SCHEMA_KEYS}. * * Recurses into `properties` (per-property schemas) and `items` (array item * schemas) so nested objects are fully sanitized. * * @invariant GEMINI_ALLOWED_SCHEMA_KEYS recursive sanitization — this function * is called recursively for `properties` values and `items`, ensuring the full * schema tree is stripped of disallowed keys. */ static _sanitizeSchema(schema: unknown): unknown; /** * Convert provider-neutral `toolChoice` to Gemini `toolConfig` format. */ static _convertToolChoice(toolChoice: string | Record): Record; } export { repairResponseModelJson }; //# sourceMappingURL=gemini.d.ts.map