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

353 lines (352 loc) • 15 kB
/** * Central configuration constants for NeuroLink * Single source of truth for all default values */ import { anthropicManifest } from "../models/manifests/anthropic.js"; import { openaiManifest } from "../models/manifests/openai.js"; import { googleAiManifest } from "../models/manifests/google-ai.js"; import { vertexManifest } from "../models/manifests/vertex.js"; import { bedrockManifest } from "../models/manifests/bedrock.js"; import { azureManifest } from "../models/manifests/azure.js"; import { mistralManifest } from "../models/manifests/mistral.js"; import { ollamaManifest } from "../models/manifests/ollama.js"; import { litellmManifest } from "../models/manifests/litellm.js"; // Image Generation Model Identifiers // Used to detect if a model is an image generation model (not text generation). // `isImageGenerationModel(name)` (below) does boundary-aware matching: // the modelName must equal an entry OR contain an entry followed by a // non-alphanumeric character (`-`, `_`, `:`, `/`, `.`, end-of-string). // This prevents short identifiers like `"V_1"` or `"gpt-image-1"` from // accidentally matching unrelated model names that happen to embed them. export const IMAGE_GENERATION_MODELS = [ // Gemini image models (Vertex AI, Google AI Studio) "gemini-3-pro-image-preview", "gemini-2.5-flash-image", "gemini-3.1-flash-image-preview", // Stability AI (direct provider — image-only) "stable-image-ultra", "stable-image-core", "sd3.5-large", "sd3.5-large-turbo", "sd3.5-medium", // Ideogram (direct provider — image-only). API uses model-version // identifiers like "V_1", "V_1_TURBO", "V_2", "V_2_TURBO", "V_2A", // "V_2A_TURBO", "V_3". Keep prefixes that won't collide with generic // text-model names. "V_1", "V_1_TURBO", "V_2", "V_2_TURBO", "V_2A", "V_2A_TURBO", "V_3", // Recraft (direct provider — image-only) "recraftv3", "recraftv2", // OpenAI image models (DALL-E + GPT-Image). Substring match keeps // version variants like "dall-e-3" / "dall-e-2" routed correctly. "gpt-image-1", "dall-e-3", "dall-e-2", // Replicate-hosted image models (matched on model slug prefix). // Replicate model slugs are passed through as-is via the LLM // provider; image-gen models trigger the image-gen path. "black-forest-labs/flux", "stability-ai/sdxl", "stability-ai/stable-diffusion", ]; /** * Boundary-aware test for whether `modelName` represents an image-generation * model. * * Matches when the model name **equals** an entry in * {@link IMAGE_GENERATION_MODELS} or contains the entry as a prefix followed * by a separator (`-`, `_`, `:`, `/`, `.`) or end-of-string. This avoids * accidental matches such as a custom fine-tune named `"my-V_1"` matching * `"V_1"` via plain substring inclusion. */ export function isImageGenerationModel(modelName) { if (!modelName) { return false; } for (const entry of IMAGE_GENERATION_MODELS) { if (modelName === entry) { return true; } const idx = modelName.indexOf(entry); if (idx === -1) { continue; } const before = idx === 0 ? "" : modelName[idx - 1]; const after = modelName[idx + entry.length] ?? ""; const isBoundary = (ch) => ch === "" || /[-_:./@]/.test(ch); if (isBoundary(before) && isBoundary(after)) { return true; } } return false; } // PDF Image Generation Models // Models that support generating images from PDFs export const PDF_IMAGE_GENERATION_MODELS = [ "gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview", ]; // Global Location Models // Models that require global location configuration (uses aiplatform.googleapis.com instead of region-specific endpoints) // Includes Gemini 3.x text and image models, which are only available via the global endpoint on Vertex AI. // IMAGE_GENERATION_MODELS is spread in to keep the two lists from drifting: // any new image-gen model added there is automatically routed to global here. export const GLOBAL_LOCATION_MODELS = [ // Image generation (sourced from IMAGE_GENERATION_MODELS) ...IMAGE_GENERATION_MODELS, // Gemini 3.1 text models (global-only) "gemini-3.1-pro-preview", "gemini-3.1-flash-lite-preview", "gemini-3.1-pro-preview-customtools", // Gemini 3 text models (global-only) "gemini-3-pro-preview", "gemini-3-pro-preview-11-2025", "gemini-3-pro-latest", "gemini-3-flash-preview", ]; // Core AI Generation Defaults export const DEFAULT_MAX_TOKENS = undefined; // Unlimited by default - let providers decide their own limits export const DEFAULT_TEMPERATURE = 0.7; export const DEFAULT_TIMEOUT = 60000; export const DEFAULT_MAX_STEPS = 200; export const DEFAULT_TOOL_MAX_RETRIES = 2; // Maximum retries per tool before permanently failing /** Defensive wall-clock ceiling for a native Gemini-3 agentic turn (generate + stream). */ export const DEFAULT_GEMINI_STREAM_TIMEOUT_MS = 300_000; /** * Default per-tool-execution timeout for native agentic loops. A tool that * exceeds it fails with an error tool_result and costs one step — the turn * continues instead of hanging on a wedged tool. Override per call with * `toolTimeoutMs`, or remove the bound entirely with `toolTimeoutMs: null`. */ export const DEFAULT_TOOL_EXECUTION_TIMEOUT_MS = 300_000; /** * Resolve a caller's `toolTimeoutMs` into the bound a loop should actually * apply: a number of milliseconds, or `null` for no bound at all. * * The three-way distinction is the whole point, and `??` cannot express it: * `undefined` means "no opinion, take the default", while `null` is a stated * choice to run tools unbounded — the pre-existing behaviour of the loops that * never had a per-tool timer, and the only way to say it, since a finite * number is always a ceiling and `Infinity` silently desugars to `setTimeout`'s * ~24.9-day cap. Every loop resolves it through here so `null` cannot come to * mean "the default" on one provider and "unbounded" on another. */ export function resolveToolTimeoutMs(toolTimeoutMs) { return toolTimeoutMs === null ? null : (toolTimeoutMs ?? DEFAULT_TOOL_EXECUTION_TIMEOUT_MS); } /** * Default wrap-up lead applied when `turnTimeoutMs` is set but * `wrapupTimeLeadMs` is not: with less than this much turn time remaining, * a wrap-up nudge rides the next tool-result turn. */ export const DEFAULT_WRAPUP_TIME_LEAD_MS = 120_000; /** * In-loop context guard threshold for native agentic loops: when the last * model call's actual prompt size (provider-reported usage) plus the * estimated growth from this step's tool results crosses this fraction of * the model's context window, the loop stops calling tools and synthesizes a * final answer instead of stepping into a provider 400 ("prompt is too * long") that would destroy the whole turn's work. */ export const DEFAULT_CONTEXT_GUARD_RATIO = 0.85; /** * Floor for the turn budget handed to the post-overflow recovery retry. * The retry inherits the REMAINING `turnTimeoutMs` (whole-turn semantics — * one generate() must not stack two full budgets), but never less than this, * so a compacted retry still gets a workable window. */ export const MIN_RECOVERY_TURN_BUDGET_MS = 30_000; // Fire-and-forget tool storage writes (Redis). 5s is generous for a single // Redis write; if breached, the .catch logs a warning. export const TOOL_STORAGE_TIMEOUT_MS = 5000; // Step execution limits export const STEP_LIMITS = { min: 1, max: 500, default: DEFAULT_MAX_STEPS, }; // Specialized Use Case Defaults export const DEFAULT_EVALUATION_MAX_TOKENS = 500; // Keep evaluation fast export const DEFAULT_ANALYSIS_MAX_TOKENS = 800; // For analysis tools export const DEFAULT_DOCUMENTATION_MAX_TOKENS = 12000; // For documentation generation // Provider-specific configurations export const PROVIDER_CONFIG = { evaluation: { maxTokens: DEFAULT_EVALUATION_MAX_TOKENS, model: "gemini-2.5-flash", temperature: 0.3, // Lower temperature for consistent evaluation }, analysis: { maxTokens: DEFAULT_ANALYSIS_MAX_TOKENS, temperature: 0.5, }, documentation: { maxTokens: DEFAULT_DOCUMENTATION_MAX_TOKENS, temperature: 0.4, }, }; /** * Per-model output-token overrides for a manifest, keyed by canonical model * id. The manifest's synthetic "_default" entry is excluded — the * provider-level `default` in PROVIDER_MAX_TOKENS already carries that role, * and is left as the hand-authored value below rather than replaced with * this data (a provider's `_default` entry, when present, is a minimal * catch-all sized for an unlisted/gateway model id, not a considered * provider-wide ceiling). */ function maxTokensOverridesFrom(manifest) { const overrides = {}; for (const [id, entry] of Object.entries(manifest.models)) { if (id === "_default") { continue; } overrides[id] = entry.maxOutputTokens; } return overrides; } // Provider-specific maxTokens limits. Each provider's `default` is the // original hand-authored ceiling (unchanged); per-model keys are generated // from that provider's manifest (src/lib/models/manifests/) so // getSafeMaxTokens()'s existing per-model lookup (tokenLimits.ts) actually // has data to find instead of always falling through to the coarse // `default`. This resolves the historical disagreement between // getSafeMaxTokens("anthropic", "claude-opus-4-6") (used to return the flat // 64000) and resolveClaudeMaxTokens("claude-opus-4-6") (tokenLimits.ts's // regex ladder, correctly 32000 for the Opus family) — the manifest's own // per-model maxOutputTokens now wins for every listed model, on every // provider below, not just Anthropic. // // Manifests are imported directly from src/lib/models/manifests/ rather // than through manifestRegistry.ts's aggregator: manifestRegistry.ts // imports PROVIDER_MAX_TOKENS from this file (to size the synthetic // `_default` entry for manifests that don't declare their own), so // importing the aggregator back here would form a constants.ts <-> // manifestRegistry.ts import cycle whose safety depends on which module a // given entrypoint happens to reach first — an ES module TDZ hazard, not // something a type-checker catches. The individual manifest files have no // dependency on this module (only on the ProviderModelManifest type), so // reaching past the aggregator avoids the cycle entirely. export const PROVIDER_MAX_TOKENS = { anthropic: { default: 64000, ...maxTokensOverridesFrom(anthropicManifest), }, openai: { default: 128000, ...maxTokensOverridesFrom(openaiManifest), }, "google-ai": { default: 64000, ...maxTokensOverridesFrom(googleAiManifest), }, vertex: { default: 64000, ...maxTokensOverridesFrom(vertexManifest), }, bedrock: { default: 64000, ...maxTokensOverridesFrom(bedrockManifest), }, azure: { default: 128000, ...maxTokensOverridesFrom(azureManifest), }, mistral: { default: 128000, ...maxTokensOverridesFrom(mistralManifest), }, ollama: { default: 64000, ...maxTokensOverridesFrom(ollamaManifest), }, litellm: { default: 128000, ...maxTokensOverridesFrom(litellmManifest), }, default: 64000, }; // CLI Validation Limits export const CLI_LIMITS = { maxTokens: { min: 1, max: 64000, default: DEFAULT_MAX_TOKENS, }, temperature: { min: 0, max: 2, default: DEFAULT_TEMPERATURE, }, }; // PDF Processing Limits export const PDF_LIMITS = { // Maximum PDF size for image conversion (20MB) MAX_SIZE_MB: 20, // Default maximum pages for image conversion DEFAULT_MAX_PAGES: 20, // Upper bound for the render scale factor. Above this, a single page can // allocate hundreds of MB; scale <= 0 produces a degenerate viewport. MAX_SCALE: 10, // Lower bound for the render scale factor (#297). Below this the render is // effectively unreadable; enforcing it makes the documented 0.1–10 range real // (previously only scale <= 0 was rejected). MIN_SCALE: 0.1, // Default render scale (#297). Lowered from 2 → 1.5 to roughly halve the // per-page canvas memory while staying legible for OCR/vision. DEFAULT_SCALE: 1.5, // Timeout (ms) for the accurate pdf-parse page-count probe (#287); on timeout // the processor falls back to the regex estimate rather than blocking. PAGE_COUNT_TIMEOUT_MS: 5000, // Default per-page pixel ceiling for image conversion (#260). 16.7M px × // 4 bytes RGBA ≈ 64 MB per page — safely bounded. A larger page is // uniformly downscaled to stay under this instead of allocating gigabytes. DEFAULT_MAX_CANVAS_PIXELS: 16_777_216, // Floor for the downscaled render scale (#260 follow-up). A crafted/malformed // MediaBox can drive the estimated pixel count toward Infinity, which would // otherwise collapse `effectiveScale` to 0 and hand the renderer a degenerate // viewport. Never let the downscale branch go below this. MIN_EFFECTIVE_SCALE: 0.1, }; // Performance and System Limits export const SYSTEM_LIMITS = { // Prompt size limits (baseProvider.ts magic number fix) MAX_PROMPT_LENGTH: 1000000, // 1M characters - prevents memory issues // Memory monitoring thresholds (performance.ts) HIGH_MEMORY_THRESHOLD: 100, // MB - when to warn about memory usage // Timeout warnings (baseProvider.ts) LONG_TIMEOUT_WARNING: 300000, // 5 minutes - when to warn about long timeouts // Concurrency control (neurolink.ts provider testing) DEFAULT_CONCURRENCY_LIMIT: 3, // Max parallel provider tests MAX_CONCURRENCY_LIMIT: 5, // Upper bound for concurrency // Retry system defaults (retryHandler.ts) DEFAULT_RETRY_ATTEMPTS: 3, DEFAULT_INITIAL_DELAY: 1000, // 1 second DEFAULT_MAX_DELAY: 30000, // 30 seconds DEFAULT_BACKOFF_MULTIPLIER: 2, }; // Pre-call tool routing: hard ceiling for the router LLM call before failing open export const DEFAULT_TOOL_ROUTING_TIMEOUT_MS = 15000; // Environment Variable Support (for future use) export const ENV_DEFAULTS = { maxTokens: (() => { if (!process.env.NEUROLINK_DEFAULT_MAX_TOKENS) { return undefined; } const n = parseInt(process.env.NEUROLINK_DEFAULT_MAX_TOKENS, 10); return Number.isFinite(n) ? n : undefined; })(), temperature: process.env.NEUROLINK_DEFAULT_TEMPERATURE ? (() => { const t = parseFloat(process.env.NEUROLINK_DEFAULT_TEMPERATURE); return Number.isFinite(t) ? t : DEFAULT_TEMPERATURE; })() : DEFAULT_TEMPERATURE, };