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

179 lines (178 loc) • 8.49 kB
/** * Converts the JSON authoring format into the runtime * OpenAICompatCatalogEntry shape the registry loop consumes. All * knowledge about deriving env vars, template interpolation and error * rule matching lives HERE — the JSON stays declarative. */ import { CATALOG_JSON_ENTRIES } from "./index.generated.js"; import { DEFAULT_ERROR_RULES } from "../../utils/errorClassifier.js"; import { AuthenticationError, RateLimitError, InvalidModelError, NetworkError, ProviderError, } from "../../types/index.js"; const ERROR_CLASS_MAP = { authentication: AuthenticationError, "rate-limit": RateLimitError, "invalid-model": InvalidModelError, network: NetworkError, provider: ProviderError, }; // Duplicated from tools/codegen-catalog.ts intentionally: src/ must not // import from tools/ (build-time tooling depends on runtime source, not the // reverse). function toCamelCase(id) { return id.replace(/-([a-z])/g, (_m, c) => c.toUpperCase()); } // Resolves the NeurolinkCredentials key for an entry — respects a // credentialsKey override (required where the derived camelCase would // rename a pre-existing public credential field; see together-ai). Single // source of truth shared with codegen's own credentialsKeyFor() so the // generated NeurolinkCredentials keys and any runtime consumer (Task 7's // descriptors) can never drift from each other. export function catalogCredentialsKey(entry) { return entry.credentialsKey ?? toCamelCase(entry.id); } export function catalogEnvVar(entry, kind) { const override = entry.wire.envOverrides?.[kind]; if (override) { return override; } const base = entry.id.toUpperCase().replace(/-/g, "_"); return kind === "apiKey" ? `${base}_API_KEY` : kind === "baseURL" ? `${base}_BASE_URL` : `${base}_MODEL`; } function interpolate(template, entry, modelName, rawMessage) { return template .replace(/\{apiKeyEnvVar\}/g, catalogEnvVar(entry, "apiKey")) .replace(/\{setupUrl\}/g, entry.setup.url) .replace(/\{model\}/g, modelName ?? "") .replace(/\{message\}/g, rawMessage ?? ""); } export function buildCatalogConfigOptions(entry) { return { providerName: entry.displayName, envVarName: catalogEnvVar(entry, "apiKey"), setupUrl: entry.setup.url, description: entry.setup.description ?? "API key", instructions: entry.setup.instructions.map((line) => interpolate(line, entry)), ...(entry.wire.apiKeyFallbackEnvVars ? { fallbackEnvVars: entry.wire.apiKeyFallbackEnvVars } : {}), }; } function buildErrorRules(entry) { const bespoke = entry.errorRules.map((rule) => { const regex = rule.pattern ? new RegExp(rule.pattern, "i") : undefined; const needsContext = rule.message.includes("{model}") || rule.message.includes("{message}"); return { match: (ctx) => (rule.status !== undefined && ctx.statusCode === rule.status) || (regex !== undefined && (rule.patternStatuses === undefined || (ctx.statusCode !== undefined && rule.patternStatuses.includes(ctx.statusCode))) && regex.test(ctx.message)), errorClass: ERROR_CLASS_MAP[rule.class], message: needsContext ? (ctx) => interpolate(rule.message, entry, ctx.modelName, ctx.message) : interpolate(rule.message, entry), }; }); return [...bespoke, ...DEFAULT_ERROR_RULES]; } // Recursively freezes a value in place and returns it. Used only on a // structuredClone()d graph below — never on CATALOG_JSON_ENTRIES itself — // so this never touches the module-level singleton. function deepFreeze(value) { if (Array.isArray(value)) { for (const item of value) { deepFreeze(item); } return Object.freeze(value); } if (value !== null && typeof value === "object") { for (const key of Object.keys(value)) { deepFreeze(value[key]); } return Object.freeze(value); } return value; } // Returns a deep clone of the catalog graph, frozen at every level, never // the module-level singleton by reference or anything sharing its nested // objects/arrays — CATALOG_JSON_ENTRIES is reused on every call, so handing // out (a shallow copy of) the live array would let any one caller's // mutation corrupt catalog state for the rest of the process, and freezing // only the outer array left `entry.aliases`, `entry.models.fallbacks`, // `entry.models.catalog` etc. shared and mutable despite the `readonly` // return type. structuredClone() first (so the frozen graph never aliases // CATALOG_JSON_ENTRIES's own objects), then deepFreeze() the clone. All // current callers only .find/.map/.flatMap/.filter/iterate, so this is // behavior-preserving for them. export function getCatalogJsonEntries() { return deepFreeze(structuredClone(CATALOG_JSON_ENTRIES)); } export function buildCatalogEntries() { return CATALOG_JSON_ENTRIES.map((entry) => { const base = { providerName: entry.id, aliases: [entry.id, ...entry.aliases], apiKeyEnvVar: catalogEnvVar(entry, "apiKey"), configOptions: buildCatalogConfigOptions(entry), modelEnvVar: catalogEnvVar(entry, "model"), defaultModel: entry.models.default, registryDefaultModel: entry.models.registryDefaultModel ?? entry.models.default, registryDefaultModelChecksEnvVar: entry.quirks?.registryDefaultIgnoresModelEnvVar !== true, fallbackModelName: entry.models.fallbackModelName ?? entry.models.fallbacks[1] ?? entry.models.fallbacks[0], fallbackModels: [...entry.models.fallbacks], errorRules: buildErrorRules(entry), // "model-dependent" leaves this field unset entirely (not the string // itself, which OpenAICompatCatalogEntry.supportsTools types as // boolean|undefined) so ConfiguredOpenAICompatProvider.supportsTools() // falls through to the model-registry default, same as an entry that // never set this field at all. ...(typeof entry.capabilities.tools === "boolean" ? { supportsTools: entry.capabilities.tools } : {}), // Unlike `tools`, `capabilities.structuredOutputWithTools` is a plain // required boolean in the schema (no "model-dependent" member), so this // is a direct passthrough — `false` and `true` both carry meaning to // ConfiguredOpenAICompatProvider.suppressResponseFormatWithTools(). supportsStructuredOutputWithTools: entry.capabilities.structuredOutputWithTools, }; const { baseURLTemplate } = entry.wire; if (baseURLTemplate) { // Schema constrains extraCredentials to exactly one entry (Task 1) — // [0] is the whole list by construction, mirroring the deliberately // accountId-shaped runtime computedBaseURL type. const extra = entry.wire.extraCredentials?.[0] ?? "accountId"; base.computedBaseURL = { envVar: `${entry.id.toUpperCase().replace(/-/g, "_")}_${extra.replace(/([A-Z])/g, "_$1").toUpperCase()}`, missingValueMessage: entry.wire.missingCredentialMessage ?? `Missing ${extra} for ${entry.displayName}`, build: (value) => baseURLTemplate.replaceAll(`{${extra}}`, value), }; } else { base.baseURLEnvVar = catalogEnvVar(entry, "baseURL"); base.defaultBaseURL = entry.wire.baseURL; } if (entry.quirks?.messageContentFormat) { base.messageContentFormat = entry.quirks.messageContentFormat; } if (entry.quirks?.timeoutErrorClass === "provider") { base.timeoutErrorClass = ProviderError; } if (entry.quirks?.responseFormatDowngrade) { base.responseFormatDowngrade = entry.quirks.responseFormatDowngrade; } if (entry.quirks?.replayReasoningContent) { base.replayReasoningContent = true; } if (entry.quirks?.authHeaderStyle) { base.authHeaderStyle = entry.quirks.authHeaderStyle; } return base; }); }