@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
TypeScript
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>;
};