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

285 lines (284 loc) • 11.3 kB
import type { JsonObject } from "./common.js"; /** Options forwarded through `applyAllClients` into a configurator's `apply`. */ export type CliProxyClientApplyOptions = { /** * Absolute path of the proxy routing config this process loaded. * The Grok writer reads `routing.model-mappings` from this file so a * `proxy start --config` catalog matches `/v1/models`. Omit to use * `~/.neurolink/proxy-config.yaml`. */ configPath?: string; }; /** * One AI coding CLI the proxy can point at itself. * * Adding a CLI means adding one implementation of this type and one line in * `src/cli/proxy-clients/registry.ts`. Nothing else in the proxy should need * to know the client exists. */ export type CliProxyClientConfigurator = { /** Stable kebab-case identifier, e.g. "claude-code". */ id: string; /** Human-readable name used in CLI output, e.g. "Claude Code". */ displayName: string; /** * Whether this CLI appears to be installed. Configurators must not create * config files for a CLI the user never installed. */ detect: () => Promise<boolean>; /** * Point the CLI at the proxy. `proxyBaseUrl` is the bare proxy origin * (e.g. "http://127.0.0.1:55669"); the configurator appends whatever path * suffix its CLI needs. Returns false when the CLI is not left pointing at * the proxy, so callers never print a success message for work that did * not happen. A configurator whose file already matches may return true * without rewriting it. */ apply: (proxyBaseUrl: string, options?: CliProxyClientApplyOptions) => Promise<boolean>; /** * Restore the user's previous configuration. `proxyBaseUrl` is the same bare * origin; a configurator that finds a different URL configured must leave it * alone and return false. */ restore: (proxyBaseUrl: string) => Promise<boolean>; /** * Something the user must still do for apply() to take effect. * * Writing a file is not the same as being in effect. Copilot reads its * provider settings from the environment only, so its configurator writes a * script the user has to source; until they do, the proxy reports a green * check for a file nothing reads. Returning a string here lets a client say * "written, but not yet live, and here is the one line that fixes it". * Return null when nothing is outstanding. */ postApplyNote?: (proxyBaseUrl: string) => Promise<string | null>; }; /** Outcome of applying one configurator, for per-client CLI reporting. */ export type CliProxyClientApplyResult = { id: string; displayName: string; /** * True only when the CLI's configuration points at the proxy: written now, * or already written and left as it was. */ applied: boolean; /** * Set when the write landed but is not yet in effect — see * CliProxyClientConfigurator.postApplyNote. Callers must render this; a * silent note is the failure it exists to prevent. */ note?: string; /** Present when the configurator threw; the caller decides how loud to be. */ error?: Error; }; /** Outcome of restoring one configurator. */ export type CliProxyClientRestoreResult = { id: string; displayName: string; /** True only when a previous configuration was actually restored. */ restored: boolean; error?: Error; }; /** * Snapshot of the user's pre-existing OpenCode `provider.neurolink`. * * Persisted to `~/.neurolink/opencode-proxy-snapshot.json`, never inside * `opencode.json` — OpenCode validates against a closed schema and rejects * unknown top-level keys, so an in-file snapshot made the CLI unstartable. */ export type CliOpenCodeSnapshot = { /** The user's provider.neurolink before the proxy first touched it. */ original: unknown; /** What the writer last wrote, so apply() can recognise its own block. */ written?: unknown; }; /** * One Grok Build picker entry the proxy writer emits under `[model.<id>]`. * * `contextWindow` is Grok's compaction limit and must be <= the upstream * model's window. `supportsReasoningEffort` is false when Grok's `xhigh` * would become Anthropic adaptive thinking that the model rejects. */ export type CliGrokProxyModelSpec = { id: string; name: string; apiBackend: "messages" | "chat_completions"; contextWindow: number; maxCompletionTokens: number; supportsReasoningEffort: boolean; }; /** * Snapshot of whether `~/.grok/config.toml` existed before the proxy wrote * its managed block. Persisted to `~/.neurolink/grok-proxy-snapshot.json`. */ export type CliGrokSnapshot = { originalExisted: boolean; writtenBaseUrl: string; }; /** * One header-delimited run of a client's TOML config. `path` is the parsed * header key (`["model", "gemini-2.5-pro"]` for `[model."gemini-2.5-pro"]`), * or null for the keys before the first header. `lines` keep their own * terminators, so text the writer does not own round-trips byte for byte. */ export type CliTomlSection = { path: readonly string[] | null; arrayTable: boolean; lines: readonly string[]; }; /** * What a line of TOML leaves open for the next one: a multi-line string, or * brackets of an array or inline table. A `[` that starts a line is a table * header only when neither is open. */ export type CliTomlScanState = { multiline: '"""' | "'''" | null; depth: number; }; /** * Snapshot of the user's pre-existing Gemini CLI `~/.gemini/.env`. * * The whole file is kept rather than the managed keys alone: restoring must * reproduce the user's comments, ordering and unrelated variables exactly. */ export type CliGeminiSnapshot = { /** The whole prior `.env`, or null when the user had no such file. */ originalEnv: string | null; /** * What the writer last wrote for each managed variable. Compared against the * file on disk to detect a snapshot that has gone stale — one left behind by * a restore whose cleanup failed, or overtaken by a user edit. Reusing such a * record would make the next restore replay outdated values. */ written?: { baseUrl: string; apiKey: string; }; }; /** * Raw contents of a Qwen Code `settings.json`. Deliberately open-ended: the * configurator rewrites only `security.auth` and must round-trip every other * key the user has set, including ones this repo does not know about. */ export type CliQwenSettings = Record<string, unknown>; /** * Per-account token and cost totals derived from the proxy's own request log. * * `costUsd` is an **API-equivalent** figure: what the recorded tokens would * have cost at published per-token rates. Pooled OAuth accounts are billed by * subscription, so this is a value estimate, never an invoice. Consumers must * label it as such. */ export type CliAccountUsageTotals = { requests: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number; costUsd: number; /** Requests whose model carried no pricing row, so contributed no cost. */ unpricedRequests: number; /** Distinct models with no pricing row, so an operator can chase them. */ unpricedModels: string[]; /** * Same totals split by calling CLI, keyed by the derived client name. * * Empty for traffic logged before attribution existed — those rows carry no * User-Agent, and guessing one retroactively would invent history. */ byClient: Record<string, CliClientUsageTotals>; }; /** Per-CLI slice of an account's usage. See CliAccountUsageTotals.byClient. */ export type CliClientUsageTotals = { requests: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number; costUsd: number; }; /** One row of GET /accounts. */ export type CliAccountsRow = { /** * Bare label, e.g. "someone@example.com". Display only: two rows can share * it when one email is logged in to both engines. `key` is the identity. */ label: string; /** * Full pool key, e.g. "anthropic:someone@example.com" or * "codex:someone@example.com". Null only for plumbing rows. */ key: string | null; /** * Which pool engine owns this login. Absent on plumbing rows. Consumers * that key a list by row must key by `key`, not `label` — see above. * * `"vertex"` is not a login: native fallback legs are keyed by served * model, with no OAuth account behind them, so they carry no token state. */ provider?: "anthropic" | "codex" | "vertex"; /** * What this row actually is. Only "account" rows are real logins; the proxy * also tracks internal and translation pseudo-accounts, which have no quota * and should not be rendered as credentials. */ kind: "account" | "internal" | "translation"; type: string; status: string | null; cooling: boolean; allowed: boolean | null; expired: boolean | null; isPrimary: boolean; requests: number | null; errors: number | null; rateLimits: number | null; quotaRateLimits: number | null; /** Quota block from the limits snapshot, timestamps normalised to ms. */ quota: JsonObject | null; /** Today's usage from the request log, or null when the log is unreadable. */ usage: CliAccountUsageTotals | null; }; /** Response body of GET /accounts. */ export type CliAccountsResponse = { generatedAt: number; /** UTC date whose request log the usage totals cover. */ usageDate: string; /** True when quota came from the stored snapshot rather than a live fetch. */ quotaFromSnapshot: boolean; /** Set when the usage totals could not be read at all. */ usageError: string | null; /** Set when the quota snapshot could not be read; rows still carry status. */ quotaError: string | null; costBasis: "api-equivalent"; accounts: CliAccountsRow[]; }; /** One request as recorded in the proxy request log, reduced to what costing needs. */ export type ProxyLedgerEntry = { account: string; /** * Provider-qualified identity, "anthropic:<label>" or "codex:<label>". * Read from the log row when present; derived from `accountType` for rows * written before the pool logged it. This, not `account`, is the join key: * one email can be logged in to both engines. */ accountKey: string; /** Derived calling CLI; see CliAccountUsageTotals.byClient. */ clientApp: string; accountType: string; model: string; provider?: string; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number; /** 1h-TTL share of `cacheCreationTokens` (subset); 0 when not reported. */ cacheCreation1hTokens: number; }; /** Incremental read position and accumulated entries for one request-log file. */ export type ProxyLedgerFileCursor = { /** Byte offset just past the last complete line consumed. */ offset: number; size: number; /** requestId -> latest known entry, so a re-logged request cannot double count. */ entries: Map<string, ProxyLedgerEntry>; };