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

500 lines • 20.4 kB
/** * Grok Build client configurator. * * Grok Build (`grok`, xAI's terminal agent) is a Codex-shaped TOML client * that speaks three wire formats. Built-in `grok-4.6` / `grok-4.5` stay on * xAI (`cli-chat-proxy.grok.com`, Responses API, 500k window). This writer * does not remap those. It adds the proxy's advertised catalog as extra * picker entries so Grok can send Claude traffic through `/v1/messages` * (passthrough) and Gemini/OpenAI traffic through `/v1/chat/completions` * (translation), each with a `context_window` that Grok's own compaction * will honour — the proxy does not truncate. * * Snapshot lives in `~/.neurolink/`, following Codex, never inside * `config.toml`. Grok's TOML parser is not a closed schema, but putting * bookkeeping keys in the user's file is how OpenCode was bricked, and new * writers do not rely on tolerance. * * Adaptive thinking: Grok's global `default_reasoning_effort = "xhigh"` * becomes Anthropic `thinking.type = "adaptive"`. Haiku 4.5 rejects that * with 400. Only Claude Opus/Sonnet 4.6 and 5.x keep reasoning enabled. * * Ownership is decided by content, never by the block markers alone. Grok * rewrites config.toml on its own saves (`/settings`, `grok mcp add`, ...) * through a serializer that drops every comment, the markers included, and * splits the inline `extra_headers` into a `[model.<id>.extra_headers]` * sub-table (measured on Grok Build 1.0.40). A marker-only writer then kept * the unmarked tables and appended a second copy of every id on the next * proxy start, and TOML rejects a table declared twice, so Grok would not * start. What marks an entry as ours is the placeholder key, a credential * only the proxy accepts, and that survives Grok's rewrite. */ import { readFileSync } from "fs"; import { homedir } from "os"; import { join } from "path"; import { logger } from "../../utils/logger.js"; import { getContextWindowSize } from "../../constants/contextWindows.js"; import { DEFAULT_PROXY_MODEL_IDS } from "../../constants/proxyModels.js"; import { defaultProxyConfigPath, parseProxyConfigString, } from "../../proxy/proxyConfig.js"; import { isUsableSnapshot, resolveSymlinkTarget, shouldCaptureSnapshot, writeFileAtomic, } from "./snapshot.js"; import { dottedTableIds, findDuplicateTable, inlineTablePairs, maskTomlStrings, maskedStringValue, ownedLineCount, parseMaskedAssignment, readStringValue, splitTomlSections, tomlCodeLines, } from "./tomlSections.js"; const GROK_BLOCK_BEGIN = "# >>> neurolink-proxy (managed) >>>"; const GROK_BLOCK_END = "# <<< neurolink-proxy (managed) <<<"; const GROK_BLOCK_NOTES = [ "# Proxy catalog. Built-in grok-4.6 / grok-4.5 stay on xAI.", "# context_window is Grok's compaction limit and must be <= upstream.", ]; const GROK_MANAGED_COMMENT_LINES = new Set([ GROK_BLOCK_BEGIN, GROK_BLOCK_END, ...GROK_BLOCK_NOTES, ]); const PLACEHOLDER_KEY = "neurolink-proxy"; const ANTHROPIC_VERSION = "2023-06-01"; function getGrokConfigDir() { const env = process.env.GROK_HOME; return env !== undefined && env.trim().length > 0 ? env.trim() : join(homedir(), ".grok"); } function getGrokConfigPath() { return join(getGrokConfigDir(), "config.toml"); } function getGrokSnapshotPath() { return join(homedir(), ".neurolink", "grok-proxy-snapshot.json"); } function tomlKey(id) { return /^[A-Za-z0-9_-]+$/.test(id) ? id : JSON.stringify(id); } function displayNameFor(id) { const base = id.replace(/-\d{8}$/, ""); const pretty = base .split("-") .map((part) => /^\d/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1)) .join(" ") .replace(/(\d) (\d)/g, "$1.$2"); return `${pretty} (NeuroLink)`; } function providerForModelId(id) { if (id.startsWith("claude-")) { return "anthropic"; } if (id.startsWith("gemini-")) { return "vertex"; } return "openai"; } function providerFromMapping(provider, fallbackId) { const normalized = provider.trim().toLowerCase(); if (normalized === "anthropic" || normalized === "claude") { return "anthropic"; } if (normalized === "openai") { return "openai"; } if (normalized === "vertex" || normalized === "google" || normalized === "google-ai" || normalized === "gemini") { return "vertex"; } return providerForModelId(fallbackId); } function isMissingFileError(error) { if (!error || typeof error !== "object" || !("code" in error)) { return false; } return error.code === "ENOENT"; } /** * Anthropic `thinking.type = "adaptive"` is what Grok emits for `xhigh`. * Measured: Haiku 4.5 returns 400 "adaptive thinking is not supported". * Opus/Sonnet 4.6 and the 5-series accept it. */ function supportsAdaptiveThinking(id) { return (/^claude-(opus|sonnet)-4-6$/.test(id) || /^claude-(opus|sonnet)-5/.test(id)); } function classifyGrokProxyModel(id, mapping) { const classifyId = mapping && mapping.to.trim().length > 0 ? mapping.to.trim() : id; const provider = mapping ? providerFromMapping(mapping.provider, classifyId) : providerForModelId(id); const windowProvider = mapping && mapping.provider.trim().length > 0 ? mapping.provider.trim() : provider; const apiBackend = provider === "anthropic" ? "messages" : "chat_completions"; const contextWindow = getContextWindowSize(windowProvider, classifyId); return { id, name: displayNameFor(id), apiBackend, contextWindow, maxCompletionTokens: contextWindow >= 1_000_000 ? 16_384 : 8_192, supportsReasoningEffort: supportsAdaptiveThinking(classifyId), }; } async function loadRoutedMappings(configPath) { const resolvedPath = configPath ?? defaultProxyConfigPath(); try { const config = await parseProxyConfigString(readFileSync(resolvedPath, "utf8")); return (config.routing?.modelMappings ?? []).filter((mapping) => mapping.from.trim().length > 0); } catch { return []; } } async function loadRoutedModelIds(configPath) { return (await loadRoutedMappings(configPath)).map((mapping) => mapping.from.trim()); } async function catalogGrokSpecs(configPath) { const seen = new Set(); const specs = []; const routed = await loadRoutedMappings(configPath); const routedByFrom = new Map(routed.map((mapping) => [mapping.from.trim(), mapping])); for (const id of [ ...DEFAULT_PROXY_MODEL_IDS, ...routed.map((mapping) => mapping.from.trim()), ]) { if (id.startsWith("grok-") || seen.has(id)) { continue; } seen.add(id); specs.push(classifyGrokProxyModel(id, routedByFrom.get(id))); } return specs; } async function catalogModelIds(configPath) { return (await catalogGrokSpecs(configPath)).map((spec) => spec.id); } function buildGrokModelBlock(spec, baseUrl) { const lines = [ `[model.${tomlKey(spec.id)}]`, `model = ${JSON.stringify(spec.id)}`, `name = ${JSON.stringify(spec.name)}`, `base_url = ${JSON.stringify(baseUrl)}`, `api_backend = ${JSON.stringify(spec.apiBackend)}`, `context_window = ${spec.contextWindow}`, `auto_compact_threshold_percent = 80`, `max_completion_tokens = ${spec.maxCompletionTokens}`, `supports_backend_search = false`, ]; if (!spec.supportsReasoningEffort) { lines.push("supports_reasoning_effort = false"); } if (spec.apiBackend === "messages") { lines.push(`extra_headers = { "x-api-key" = ${JSON.stringify(PLACEHOLDER_KEY)}, "anthropic-version" = ${JSON.stringify(ANTHROPIC_VERSION)} }`); } else { lines.push(`api_key = ${JSON.stringify(PLACEHOLDER_KEY)}`); } return lines.join("\n"); } /** * `userModelIds` are ids the user defines themselves. Writing the proxy's * entry beside theirs would declare the table twice, so theirs wins. */ async function buildGrokManagedBlock(baseUrl, configPath, userModelIds = new Set()) { const catalog = await catalogGrokSpecs(configPath); for (const spec of catalog) { if (userModelIds.has(spec.id)) { logger.debug(`[proxy] Grok: config.toml already defines [model.${tomlKey(spec.id)}]; keeping it and skipping the proxy's entry`); } } const body = catalog .filter((spec) => !userModelIds.has(spec.id)) .map((spec) => buildGrokModelBlock(spec, baseUrl)) .join("\n\n"); return [ GROK_BLOCK_BEGIN, ...GROK_BLOCK_NOTES, "", body, GROK_BLOCK_END, "", ].join("\n"); } function modelEntryId(section) { return section.path !== null && section.path.length >= 2 && section.path[0] === "model" ? section.path[1] : null; } /** Where a model entry carries the placeholder key, as a path within it. */ const PLACEHOLDER_KEY_PATHS = [ ["api_key"], ["extra_headers", "x-api-key"], ]; function isPlaceholderKey(path, value) { return (value === PLACEHOLDER_KEY && PLACEHOLDER_KEY_PATHS.some((keyPath) => keyPath.length === path.length && keyPath.every((part, index) => path[index] === part))); } /** * Whether a section of a model entry sends the placeholder key: `api_key` on * the model, or `x-api-key` in its `extra_headers`, spelled as a plain key, a * dotted key, an inline table or the sub-table Grok's own save writes. The * full key path must match, so a user's `legacy.api_key` is not the proxy's. */ function sectionSendsPlaceholderKey(section) { const prefix = section.path?.slice(2) ?? []; return tomlCodeLines(section).some(({ line, statement }) => { if (!statement) { return false; } const { masked, strings } = maskTomlStrings(line); const assignment = parseMaskedAssignment(masked, strings); if (!assignment) { return false; } const path = [...prefix, ...assignment.path]; return (isPlaceholderKey(path, maskedStringValue(assignment.value, strings)) || inlineTablePairs(assignment.value, strings).some((pair) => isPlaceholderKey([...path, ...pair.path], maskedStringValue(pair.value, strings)))); }); } /** * Which parts of a Grok config.toml the proxy owns. * * A model entry is a `[model.<id>]` table plus its `[model.<id>.*]` * sub-tables, wherever they sit: Grok's save keeps them together, hand edits * need not. It is the proxy's when it sends the placeholder key and is either * in the current catalog or still carries the exact name the proxy gave it, * which covers entries a later catalog dropped. A user's own model behind the * proxy, under their own name, is theirs. Keys and values are read as TOML, * never as raw text, so a string that merely mentions the placeholder cannot * make a user's table look like the proxy's. `remainder` is the text with * owned entries and the managed comment lines removed and every other byte * kept, so a missing, orphaned or doubled marker cannot make an apply delete * the user's content or leave an old copy of the catalog behind. */ function inspectGrokConfig(text, catalogIds) { const sections = splitTomlSections(text); // Models the user defines without a [model.<id>] table of their own. const definedElsewhere = new Set([ ...dottedTableIds(sections, "model"), ...sections.flatMap((section) => { const id = section.arrayTable ? modelEntryId(section) : null; return id !== null && section.path?.length === 2 ? [id] : []; }), ]); const ids = sections.map((section) => section.arrayTable ? null : modelEntryId(section)); const isModelTable = (index) => ids[index] !== null && sections[index].path?.length === 2; const tableFor = (index) => { if (ids[index] === null || isModelTable(index)) { return index; } const sameModel = (candidate) => isModelTable(candidate) && ids[candidate] === ids[index]; const before = sections.findLastIndex((_, candidate) => candidate < index && sameModel(candidate)); const after = sections.findIndex((_, candidate) => candidate > index && sameModel(candidate)); return before !== -1 ? before : after !== -1 ? after : index; }; const entryOf = sections.map((_, index) => tableFor(index)); const ownedEntries = new Set(entryOf.filter((entry) => { const id = ids[entry]; if (id === null) { return false; } const sendsPlaceholder = sections.some((section, index) => entryOf[index] === entry && ids[index] !== null && sectionSendsPlaceholderKey(section)); if (!sendsPlaceholder) { return false; } // A sub-table left without its table carries no name to check; it is // the proxy's unless the model is defined some other way. if (!isModelTable(entry)) { return !definedElsewhere.has(id); } return (catalogIds.has(id) || readStringValue(sections[entry], "name") === displayNameFor(id)); })); const owned = sections.map((_, index) => ids[index] !== null && ownedEntries.has(entryOf[index])); const userModelIds = new Set([ ...definedElsewhere, ...sections.flatMap((section, index) => { const id = modelEntryId(section); return id !== null && !owned[index] ? [id] : []; }), ]); const managedBaseUrls = [ ...new Set(sections.flatMap((section, index) => { const url = owned[index] ? readStringValue(section, "base_url") : undefined; return url === undefined ? [] : [url]; })), ]; const remainder = sections .flatMap((section, index) => owned[index] ? section.lines.slice(ownedLineCount(section, GROK_MANAGED_COMMENT_LINES)) : section.lines) .filter((line) => !GROK_MANAGED_COMMENT_LINES.has(line.trim())) .join(""); return { remainder, managedBaseUrls, userModelIds }; } async function readGrokSnapshot() { const fs = await import("fs"); let parsed; try { parsed = JSON.parse(fs.readFileSync(getGrokSnapshotPath(), "utf8")); } catch { return null; } if (!isUsableSnapshot(parsed, "originalExisted")) { logger.debug("[proxy] Grok: ignoring a malformed snapshot rather than treating it as empty"); return null; } const record = parsed; const originalExisted = record.originalExisted; const writtenBaseUrl = record.writtenBaseUrl; if (typeof originalExisted !== "boolean") { return null; } if (typeof writtenBaseUrl !== "string") { return null; } return { originalExisted, writtenBaseUrl }; } export async function setGrokProxySettings(baseUrl, options) { const fs = await import("fs"); try { fs.accessSync(getGrokConfigDir()); } catch { return false; } let original; try { original = fs.readFileSync(getGrokConfigPath(), "utf8"); } catch (error) { if (!isMissingFileError(error)) { logger.warn("[proxy] Grok: unable to read config.toml; leaving it untouched"); return false; } original = null; } const existingSnapshot = await readGrokSnapshot(); if (existingSnapshot === null && fs.existsSync(getGrokSnapshotPath())) { logger.warn("[proxy] Grok: snapshot file is unreadable; leaving config.toml untouched rather than overwriting with no way back"); return false; } const currentBlock = original ? original.includes(GROK_BLOCK_BEGIN) ? original.slice(original.indexOf(GROK_BLOCK_BEGIN), original.indexOf(GROK_BLOCK_END) === -1 ? original.length : original.indexOf(GROK_BLOCK_END) + GROK_BLOCK_END.length) : undefined : undefined; const inspected = original === null ? null : inspectGrokConfig(original, new Set(await catalogModelIds(options?.configPath))); const block = await buildGrokManagedBlock(baseUrl, options?.configPath, inspected?.userModelIds); const remainder = inspected?.remainder ?? ""; const next = remainder.trim().length === 0 ? block : `${remainder.replace(/\s*$/, "\n")}\n${block}`; // Grok refuses to load or save a config it cannot parse. Writing into one // would only bury the user's own error under ours. const duplicate = findDuplicateTable(next); if (duplicate !== undefined) { logger.warn(`[proxy] Grok: config.toml declares [${duplicate}] more than once; leaving it untouched`); return false; } if (existingSnapshot === null || shouldCaptureSnapshot({ hasSnapshot: existingSnapshot !== null, written: existingSnapshot?.writtenBaseUrl, current: inspected?.managedBaseUrls[0] ?? currentBlock, })) { fs.mkdirSync(join(homedir(), ".neurolink"), { recursive: true }); await writeFileAtomic(getGrokSnapshotPath(), JSON.stringify({ originalExisted: existingSnapshot?.originalExisted ?? original !== null, writtenBaseUrl: baseUrl, }, null, 2), 0o600); } // Every proxy start applies; an update starts one. Rewriting a file that // already matches only widens the window for racing Grok's own saves. if (next !== original) { await writeFileAtomic(getGrokConfigPath(), next, original === null ? 0o600 : undefined); } return true; } export async function clearGrokProxySettings(expectedBaseUrl) { const fs = await import("fs"); let current; try { current = fs.readFileSync(getGrokConfigPath(), "utf8"); } catch { return false; } const inspected = inspectGrokConfig(current, new Set(await catalogModelIds())); if (inspected.remainder === current) { return false; } // Another proxy's block is left alone. One entry the user repointed does // not make the rest foreign, whichever entry happens to come first. const configuredUrls = inspected.managedBaseUrls; if (expectedBaseUrl && configuredUrls.length > 0 && !configuredUrls.includes(expectedBaseUrl)) { logger.debug("[proxy] Grok clear: base URL is not the one we wrote, leaving it intact"); return false; } const snapshot = await readGrokSnapshot(); if (snapshot === null) { logger.warn("[proxy] Grok clear: no usable snapshot, leaving config.toml untouched rather than stripping a block we cannot prove we own"); return false; } const remainder = inspected.remainder.replace(/\s*$/, "\n"); if (!snapshot.originalExisted && remainder.trim().length === 0) { // Apply wrote through a symlink the user already had, so the link stays // and the file apply created behind it goes. fs.rmSync(await resolveSymlinkTarget(getGrokConfigPath()), { force: true, }); } else { await writeFileAtomic(getGrokConfigPath(), remainder); } try { fs.rmSync(getGrokSnapshotPath(), { force: true }); } catch { // next apply overwrites } return true; } export const grokConfigurator = { id: "grok", displayName: "Grok Build", detect: async () => { const fs = await import("fs"); try { fs.accessSync(getGrokConfigDir()); return true; } catch { return false; } }, // Grok appends `/messages` or `/chat/completions` to `base_url`, so it // takes the `/v1` door rather than the proxy root. apply: (proxyBaseUrl, options) => setGrokProxySettings(`${proxyBaseUrl}/v1`, options), restore: (proxyBaseUrl) => clearGrokProxySettings(`${proxyBaseUrl}/v1`), }; /** Test-only export (CLAUDE.md rule 15 determinism exception). See openCode.ts. */ export const __grokTestHooks = { getGrokConfigDir, getGrokConfigPath, getGrokSnapshotPath, setGrokProxySettings, clearGrokProxySettings, classifyGrokProxyModel, loadRoutedModelIds, catalogModelIds, buildGrokManagedBlock, }; //# sourceMappingURL=grok.js.map