UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

250 lines (249 loc) 11.8 kB
/** * Shared utilities for Gemini 3 native SDK support. * * Both GoogleAIStudioProvider and GoogleVertexProvider route Gemini 3 models * with tools to the native @google/genai SDK (bypassing the Vercel AI SDK) * in order to properly handle thought_signature in multi-turn tool calling. * * This module extracts the functions that are duplicated between the two * providers so they can share a single implementation. */ import { type Tool } from "ai"; import type { ThinkingConfig, CollectedChunkResult, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js"; export declare function sanitizeForGoogleFunctionName(name: string): string; /** * Resolve a sanitized Gemini tool name to one that is both unique within * the current request and at most 128 characters. When the candidate * collides with an already-used name we append `_2`, `_3`, … — but * reserve room for the suffix by truncating the base first so the * resolved name never exceeds Google's `function_declarations[].name` * limit. * * @param base The already-sanitized candidate name. * @param isTaken Predicate that returns true if `name` is already used. */ export declare function resolveUniqueGoogleFunctionName(base: string, isTaken: (name: string) => boolean): string; /** * Sanitize a JSON Schema for Gemini's proto-based API. * * Gemini cannot handle `anyOf`/`oneOf` union types in function declarations * because its proto format expects a single `type` field, not a list of types. * This function recursively converts unions to `string` type (the most * permissive primitive that can represent any value as text). * * Also removes `$schema`, `additionalProperties`, and `default` keys that * Gemini's proto format doesn't support. */ export declare function sanitizeSchemaForGemini(schema: Record<string, unknown>): Record<string, unknown>; /** * Sanitize Vercel AI SDK tools for Gemini compatibility. * * For the Vercel AI SDK path (non-native), tool parameters are Zod schemas that * get converted to JSON Schema internally by @ai-sdk/google. This conversion * doesn't sanitize union types (anyOf/oneOf), causing Gemini proto errors. * * This function pre-converts each tool's Zod parameters to sanitized JSON Schema * and re-wraps with the Vercel AI SDK's jsonSchema() helper. */ export declare function sanitizeToolsForGemini(tools: Record<string, Tool>): { tools: Record<string, Tool>; dropped: string[]; /** * Reverse map: Google-safe sanitized name → original consumer-supplied * name. Lets the calling layer translate tool-call results back so the * sanitization stays transport-only (see CodeRabbit thread, PR #1006). */ originalNameMap: Map<string, string>; }; export declare function normalizeToolsForJsonSchemaProvider(tools: Record<string, Tool>): { tools: Record<string, Tool>; normalized: string[]; }; /** * Convert Vercel AI SDK tools to @google/genai FunctionDeclarations and an execute map. * * This handles both Zod schemas and plain JSON Schema objects for tool parameters. */ export declare function buildNativeToolDeclarations(tools: Record<string, Tool>): NativeToolDeclarationsResult; /** * Build the native @google/genai config object shared by stream and generate. * * Caller is responsible for the tools-vs-JSON conflict resolution: Gemini's * function calling cannot be combined with `responseMimeType: * "application/json"`, and `responseSchema` requires that mime type. So * when tools are active, callers must NOT pass `wantsJsonOutput`/ * `responseSchema` here; when JSON/schema output is requested, callers * must omit `toolsConfig`. The AI Studio path enforces this by forcing * `disableTools: true` whenever JSON/schema output is requested. */ export declare function buildNativeConfig(options: { temperature?: number; maxTokens?: number; systemPrompt?: string; thinkingConfig?: ThinkingConfig; /** * When true (and `toolsConfig` is undefined), set * `responseMimeType: "application/json"` to enforce native JSON output. */ wantsJsonOutput?: boolean; /** * Pre-converted JSON Schema for native `responseSchema`. Implies * `wantsJsonOutput`. Ignored if `toolsConfig` is present. */ responseSchema?: Record<string, unknown>; }, toolsConfig?: NativeToolsConfig): Record<string, unknown>; /** * Compute a safe, clamped maxSteps value. */ export declare function computeMaxSteps(rawMaxSteps?: number): number; /** * Process stream chunks to extract raw response parts, function calls, and usage metadata. * * Consumes the full async iterable and returns all collected data. */ export declare function collectStreamChunks(stream: AsyncIterable<{ functionCalls?: NativeFunctionCall[]; [key: string]: unknown; }>): Promise<CollectedChunkResult>; /** * Create a push-based text channel that bridges a background producer * (the agentic tool-calling loop) with an async-iterable consumer. * * This enables truly incremental streaming: text parts are yielded to the * caller as they arrive from the network, rather than being buffered until * the model finishes generating. */ export declare function createTextChannel(): TextChannel; /** * Iterate a single stream step incrementally, pushing text parts to `channel` * as they arrive from the network while simultaneously accumulating the full * `CollectedChunkResult` needed for history and token accounting. * * Used for all steps (both intermediate tool-calling steps and the final * text-only step). Text parts are pushed to the channel as they arrive, * enabling truly incremental streaming. The complete `rawResponseParts` * (including thoughtSignature) are still returned at the end for use by * `pushModelResponseToHistory`. */ export declare function collectStreamChunksIncremental(stream: AsyncIterable<{ functionCalls?: NativeFunctionCall[]; [key: string]: unknown; }>, channel: TextChannel): Promise<CollectedChunkResult>; /** * Extract the thoughtSignature token from raw response parts. * Returns the last thoughtSignature found (each step may produce one). */ export declare function extractThoughtSignature(rawResponseParts: unknown[]): string | undefined; /** * Extract text from raw response parts, filtering out non-text parts * (thoughtSignature, functionCall) to avoid SDK warnings. */ export declare function extractTextFromParts(rawResponseParts: unknown[]): string; /** * Execute a batch of native function calls with retry tracking and permanent failure detection. * * @param logLabel - Label for log messages (e.g. "[GoogleAIStudio]" or "[GoogleVertex]") * @param stepFunctionCalls - The function calls from the model * @param executeMap - Map of tool name to execute function * @param failedTools - Mutable map tracking per-tool failure counts * @param allToolCalls - Mutable array accumulating all tool call records * @param options - Optional settings for execution tracking and cancellation, * plus an `originalNameMap` (Google-safe → consumer-supplied * identifier) so the sanitization stays transport-only and * consumers see the names they registered. * @returns Array of function responses for conversation history */ export declare function executeNativeToolCalls(logLabel: string, stepFunctionCalls: NativeFunctionCall[], executeMap: Map<string, Tool["execute"]>, failedTools: Map<string, { count: number; lastError: string; }>, allToolCalls: Array<{ toolName: string; args: Record<string, unknown>; }>, options?: { toolExecutions?: Array<{ name: string; input: Record<string, unknown>; output: unknown; }>; abortSignal?: AbortSignal; originalNameMap?: Map<string, string>; }): Promise<NativeFunctionResponse[]>; /** * Handle maxSteps termination by producing a final text when the model * was still calling tools when the step limit was reached. * * @param logLabel - Label for log messages (e.g. "[GoogleAIStudio]" or "[GoogleVertex]") */ export declare function handleMaxStepsTermination(logLabel: string, step: number, maxSteps: number, finalText: string, lastStepText: string): string; /** * Push model response parts to conversation history, preserving thoughtSignature * for Gemini 3 multi-turn tool calling. */ export declare function pushModelResponseToHistory(currentContents: Array<{ role: string; parts: unknown[]; }>, rawResponseParts: unknown[], stepFunctionCalls: NativeFunctionCall[]): void; /** * Convert a Zod schema (or AI SDK `jsonSchema()` wrapper) into the shape * `@google/genai` accepts as `responseSchema`. Mirrors the inline pipeline * the Vertex Gemini paths already use: * * convertZodToJsonSchema → inlineJsonSchema → strip `$schema` → ensure * every nested schema has a `type` (Vertex/Gemini reject schemas missing * that field, even on nested objects). * * Lives here so the AI Studio and Vertex paths can share the same * sanitization without duplicating the schema-conversion churn. */ export declare function buildGeminiResponseSchema(schema: unknown): Record<string, unknown>; /** * Map NeuroLink ChatMessage[] history into the @google/genai content format * and push the entries onto a contents array. * * Used by the native Vertex Gemini and Google AI Studio paths to honor * `options.conversationMessages` so multi-turn conversations (memory, loop * REPL, agent flows) actually carry prior turns into the request. * * Behavior notes: * - Only `user` and `assistant` roles are forwarded; system messages are * expected to be wired via `systemInstruction`, and tool-call / * tool-result roles only appear inside intra-call tool loops which build * their own model/function entries. * - String content is wrapped as a single `{ text }` part. Empty strings * are skipped to avoid sending empty parts that some Gemini regions * reject. * - The current user input should be appended AFTER calling this helper * so the prior turns appear first in chronological order. */ export declare function prependConversationMessages(contents: Array<{ role: string; parts: unknown[]; }>, conversationMessages?: Array<{ role: string; content: string; }>): void; /** * Build the `parts` array for the current user turn of a Gemini native * `generateContent` request, including inline image + PDF blobs. * * Both providers that hit the native `@google/genai` SDK — `GoogleVertex` * and `GoogleAIStudio` — need this. The previous AI Studio code only * pushed a single `{ text }` part, which silently dropped `input.images` * and `input.pdfFiles` on the floor: the model received text only and * legitimately reported "no image attached". Extracting this from the * Vertex copy keeps both providers on one definition. * * Accepted shapes per element (mirroring the runtime behaviour the Vertex * code already supported): * - `Buffer` → used as-is * - local file path → read via `readFileSync`, MIME guessed from extension * - `data:<mime>;base64,...` URL → mime parsed, data base64-decoded * - `http(s)://...` URL → fetched, mime from `content-type` * - any other string → assumed to be a base64-encoded payload * * Image MIME guessing is conservative — only known extensions override the * default `image/jpeg`. Fetch failures are logged and the offending entry * is skipped rather than aborting the entire request, matching prior * Vertex behaviour. */ export declare function buildUserPartsWithMultimodal(input: GeminiMultimodalInput | undefined, textOverride?: string, logPrefix?: string): Promise<VertexNativePart[]>;