UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

99 lines (98 loc) • 4.66 kB
/** * Google `generateContent` wire format. * * The Gemini CLI honours `GOOGLE_GEMINI_BASE_URL`, so it can be pointed at this * proxy with no vendor cooperation — verified live: with the variable set the * CLI reached the proxy and failed with `ModelNotFoundError: 404`, which is the * right answer from a proxy that had no route to answer it. This module is the * missing half. * * Three differences from the Claude and OpenAI shapes drive everything here: * * - Roles are `user` / `model`, not `user` / `assistant`. * - The system prompt is a sibling `systemInstruction`, not a turn. * - Generation settings nest under `generationConfig`. * * Token counts also have their own names — `promptTokenCount`, * `candidatesTokenCount`, `totalTokenCount` — and the CLI reads them to render * its own usage line, so getting them wrong is visible to the user rather than * merely wrong in a log. */ import type { ParsedGeminiRequest, StreamSerializerAdapter } from "../types/index.js"; /** * Parse a `generateContent` body into the shape the translation engine takes. * * The final user turn becomes `prompt`, with Google's `model` role mapped to * `assistant` so downstream providers see a role they understand. * * `conversationMessages` carries EVERY turn, the final one included. That * looks redundant next to `prompt`, and it is the contract the shared engine * expects: `buildTranslationOptions` does `conversationMessages.slice(0, -1)` * to derive history, because the final turn is already being sent as `prompt`. * `claudeFormat` and `openaiFormat` both push unconditionally for that reason. * Excluding the last turn here — the intuitive reading of "history" — made the * engine's slice eat one real turn instead, so every multi-turn Gemini request * silently lost its most recent message. */ export declare function parseGeminiRequest(model: string, body: Record<string, unknown>, stream: boolean): ParsedGeminiRequest; /** Build a complete `generateContent` response body. */ /** * Render a tool call as text. * * The proxy does not forward tool calls in Google's `functionCall` part shape: * the CLI drives tools locally, so a `functionCall` it never asked for would be * an unresolvable pending call. Text is what it can act on. Both the streaming * serializer and the non-streaming builder go through here so the two paths * cannot drift. */ export declare function renderGeminiToolUse(name: string, input: unknown): string; export declare function buildGeminiResponse(text: string, finishReason: string, usage: { input: number; output: number; total: number; }, modelVersion: string, toolCalls?: ReadonlyArray<{ toolName: string; args: Record<string, unknown>; }>): Record<string, unknown>; /** Google's error envelope, which the CLI parses to classify failures. */ export declare function buildGeminiErrorResponse(status: number, message: string, statusText?: string): Response; /** * SSE serializer for `streamGenerateContent?alt=sse`. * * Google streams whole `GenerateContentResponse` objects, one per `data:` * frame, rather than the deltas Claude and OpenAI send. Each frame therefore * carries a complete `candidates[0].content.parts[0].text` holding only that * chunk's text — the CLI concatenates them — and the final frame is the one * that carries `finishReason` and `usageMetadata`. */ export declare class GeminiStreamSerializer { private readonly model; constructor(model: string); private frame; /** Google sends no preamble frame; the first delta is the first frame. */ start(): string[]; pushDelta(text: string): string[]; /** * Tool calls are surfaced as text. * * The translation engine can emit a tool call, but the Gemini CLI drives its * own tools locally and does not expect `functionCall` parts from a plain * `generateContent`. Emitting one would make the CLI wait for a tool result * that is never coming; rendering it as text keeps the turn terminating. */ pushToolUse(_id: string, name: string, input: unknown): string[]; finish(finishReason: string, usage: { input: number; output: number; total: number; }): string[]; /** * Errors ride the stream as a Google error object. * * Once headers are sent the status code is spent, so the CLI can only learn * about a mid-stream failure from the body. */ emitError(message: string): string[]; } /** Adapter so the translation engine can drive this like the other two. */ export declare function createGeminiSerializerAdapter(model: string): StreamSerializerAdapter;