@juspay/neurolink 12.40.2 β†’ 12.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/browser/neurolink.min.js +447 -443
  3. package/dist/cli/commands/proxy.js +5 -1
  4. package/dist/proxy/accountLedger.js +3 -0
  5. package/dist/proxy/codexOutboundCache.d.ts +52 -0
  6. package/dist/proxy/codexOutboundCache.js +147 -0
  7. package/dist/proxy/codexOutboundFallback.d.ts +27 -2
  8. package/dist/proxy/codexOutboundFallback.js +147 -11
  9. package/dist/proxy/codexResponsesFormat.js +7 -1
  10. package/dist/proxy/codexToAnthropicFallback.js +6 -0
  11. package/dist/proxy/proxyAnalysis.js +4 -0
  12. package/dist/proxy/proxyConfig.js +120 -0
  13. package/dist/proxy/proxyTokenUsage.d.ts +15 -0
  14. package/dist/proxy/proxyTokenUsage.js +33 -0
  15. package/dist/proxy/proxyTracer.d.ts +16 -1
  16. package/dist/proxy/proxyTracer.js +26 -5
  17. package/dist/proxy/requestLogger.js +9 -0
  18. package/dist/proxy/runtimeConfig.js +20 -0
  19. package/dist/proxy/sseInterceptor.js +9 -0
  20. package/dist/proxy/vertexAnthropicFallback.js +9 -0
  21. package/dist/server/routes/claudeProxyRoutes.js +65 -1
  22. package/dist/server/routes/codexProxyRoutes.d.ts +39 -3
  23. package/dist/server/routes/codexProxyRoutes.js +792 -8
  24. package/dist/server/routes/index.d.ts +8 -0
  25. package/dist/server/routes/index.js +9 -1
  26. package/dist/types/analytics.d.ts +25 -0
  27. package/dist/types/codex.d.ts +45 -1
  28. package/dist/types/proxy.d.ts +40 -2
  29. package/dist/types/proxyClient.d.ts +2 -0
  30. package/dist/types/server.d.ts +15 -0
  31. package/dist/types/subscription.d.ts +22 -0
  32. package/dist/utils/anthropicCacheBreakpoints.d.ts +2 -2
  33. package/dist/utils/anthropicCacheBreakpoints.js +14 -3
  34. package/dist/utils/pricing.js +101 -24
  35. package/docs-site/static/search-index.json +17 -6
  36. package/package.json +4 -4
@@ -155,10 +155,11 @@
155
155
  {"objectID":"c47d3ea31b9350d8a7e39af83361be6a5c078cffb786893cc053a933cc4ca99b","title":"Share Your Use Cases","url":"/docs/about/vision#share-your-use-cases","content":"Tell us how you're using NeuroLink:\nEdge deployments: What works, what doesn't\nStreaming needs: Where continuous context matters\nPrivacy requirements: Compliance and security needs\nPerformance goals: Latency and cost targets\n\nGitHub Discussions - Join the conversation","hierarchy":{"lvl0":"About","lvl1":"NeuroLink Vision & Roadmap","lvl2":"Share Your Use Cases","lvl3":""}},
156
156
  {"objectID":"a01f19466002f1a4decfb4e57a7976034cd068b42a0beb84e7cbda9e6ff6cefa","title":"🎯 Join Us in Building This Future","url":"/docs/about/vision#-join-us-in-building-this-future","content":"NeuroLink started as a production tool at Juspay to solve today's AI integration problems. But we're building for tomorrowβ€”where AI is everywhere, costs nothing, and just works.","hierarchy":{"lvl0":"About","lvl1":"NeuroLink Vision & Roadmap","lvl2":"🎯 Join Us in Building This Future","lvl3":""}},
157
157
  {"objectID":"6d274f87d0b1b39d152ef548c717b98a5ca0af1e46da78fa7976343ebc419f3a","title":"If You Believe in This Vision:","url":"/docs/about/vision#if-you-believe-in-this-vision","content":"βœ… Use NeuroLink today for multi-provider AI\nβœ… Contribute to edge-first and streaming features\nβœ… Share your use cases to help us prioritize\nβœ… Join the community to shape the future of AI infrastructure\n\nThe future of AI is edge-first, streaming-native, and practically free.\n\nNeuroLink is building the infrastructure to power that future.\n\nWelcome aboard.\n\nDocument maintained by: NeuroLink Core Team\nLast updated: March 2026\nNext review: Q3 2026 (after Phase 3 planning)","hierarchy":{"lvl0":"About","lvl1":"NeuroLink Vision & Roadmap","lvl2":"If You Believe in This Vision:","lvl3":""}},
158
- {"objectID":"2c08ee20f6c6005a0d3d88aec0910caea8b54d8f9478d1e4bf7b7e73c9bbccf9","title":"Why the NeuroLink Core Stays Thin","url":"/docs/about/why-the-core-stays-thin","content":"Why the NeuroLink Core Stays Thin\n\nBreadth without bloat: what's actually in the package, and why unused capability costs you nothing at runtime\n\nNeuroLink's pitch is breadth: one install covers 17 capability domains β€” providers, MCP, evals,\nRAG, observability, voice, media, workflows, and more. Breadth claims like that earn a reasonable\nreflex: \"so it's another bloated framework that drags in everything whether I need it or not.\"\nThat reflex is fair, and it's the one framework abandonments over the last two years keep citing.\nSo instead of asking you to take breadth-without-bloat on faith, here's what's actually in the\npackage and why the surface doesn't have to cost you disk space, install time, or attack surface\nyou didn't ask for.\n\nProviders load on demand, not on import\n\nNeuroLink ships named provider integrations plus a generic OpenAI-compatible adapter. None\nof them run at import time. src/lib/factories/providerRegistry.ts resolves a provider name to a\ndynamic import() of that provider's module only when you actually request it β€” Anthropic's\nclient only loads if you call createBestAIProvider(\"anthropic\") or equivalent; Ollama, LiteLLM,\nHugging Face, Bedrock, Vertex, and the rest are each behind their own await import(\"../providers/<name>.js\")\nline, one per provider, all in that same file. Ask for OpenAI and only the OpenAI\nprovider module executes β€” the rest never get evaluated.\n\nHeavy media/document deps are optional and lazy\n\nThe 35 packages that do real work in image, video, audio, and document processing β€”\nsharp, ffmpeg-static/fluent-ffmpeg, pdf-parse, mammoth, exceljs,\npptxgenjs, the LiveKit voice-agent plugins, bullmq, fastify/koa/express server adapters β€”\nare declared in optionalDependencies, not dependencies, in package.json. Being optional means\na package manager can skip them entirely if install fails or if you opt out; being lazy on top of\nthat means the code that needs them doesn't touch them until the feature runs. sharp is a good\nexample: NeuroLink's video processor only calls (await import(\"sharp\")).default inside the\nframe-resize path (src/lib/processors/media/VideoProcessor.ts), not anywhere near startup. If you\nnever touch video, sharp's native bindings never load into your process.\n\nThe package exposes real subpath entry points, not one giant bundle\n\npackage.json's exports map lists separate entry points β€” ./voice, ./music, ./avatar,\n./image-gen, ./workflow, ./rag, ./files, ./processors/*, ./adapters/*, ./livekit β€”\nalongside the default . entry. That's what lets a bundler tree-shake: importing\n@juspay/neurolink/rag doesn't pull the voice stack into your bundle graph. The package also\ndeclares \"type\": \"module\" and a sideEffects array scoped to **/*.css plus the compiled\nvoice/music/avatar entry files β€” everything else is marked side-effect-free, which is the signal\nbundlers use to safely drop unused exports instead of keeping code \"just in case.\"\n\nOne interface, swappable implementations\n\nEvery provider β€” whether it's a first-party AI SDK wrapper or a bare HTTP client for something\nlike llama.cpp β€” implements the same AIProvider contract defined against BaseProvider\n(src/lib/core/baseProvider.ts). Generation, streaming, tool calls, and lifecycle hooks are all\nexpressed once, at the interface level, so adding provider #25 is additive (a new file + a new\nimport() branch in the registry), not a change to the surface every existing provider has to pay\nfor.\n\nWhere we're heavy today, honestly\n\nLazy import() stops code from running until it's needed, but it doesn't stop npm install\nfrom downloading everything in dependencies (as opposed to optionalDependencies).\n@aws-sdk/client-bedrock, @aws-sdk/client-bedrock-runtime, @aws-sdk/client-sagemaker-runtime,\n@aws-sdk/credential-provider-node, @google-cloud/vertexai, @google-cloud/text-to-speech, and\n@huggingface/inference now live in optionalDependencies β€” matching the treatment\nmedia/document deps already get.\n\nOptional doesn't mean skipped by default: a plain npm install still downloads all seven, same as\nit always has for sharp or pdf-parse. What optional buys you is the ability to opt out β€”\nnpm install --omit=optional, or an npm client falling back gracefully when one of them fails to\nbuild on an unsupported platform, drops them without breaking anything else. We verified that\ndirectly: a clean --omit=optional install still runs import('@juspay/neurolink'),\nneurolink --version, and the CLI entry point directly (node dist/cli/index.js --version)\ncorrectly, and asking for a provider whose SDK got skipped β€” Bedrock or SageMaker, say β€” fails\nwith a plain, actionable error at the moment you request that provider, not a crash at import\ntime.\n\nThat safety only holds because all seven are genuinely lazy β€” nothing reachable from\nimport '@juspay/neurolink' (or from the CLI's startup path) touches them at load time. Three of\nthem weren't, until the audits that found it: @google-cloud/vertexai was a top-of-file import in\nthe built-in websearchGrounding","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"","lvl3":""}},
158
+ {"objectID":"2c08ee20f6c6005a0d3d88aec0910caea8b54d8f9478d1e4bf7b7e73c9bbccf9","title":"Why the NeuroLink Core Stays Thin","url":"/docs/about/why-the-core-stays-thin","content":"Why the NeuroLink Core Stays Thin\n\nBreadth without bloat: what's actually in the package, and why unused capability costs you nothing at runtime\n\nNeuroLink's pitch is breadth: one install covers 17 capability domains β€” providers, MCP, evals,\nRAG, observability, voice, media, workflows, and more. Breadth claims like that earn a reasonable\nreflex: \"so it's another bloated framework that drags in everything whether I need it or not.\"\nThat reflex is fair, and it's the one framework abandonments over the last two years keep citing.\nSo instead of asking you to take breadth-without-bloat on faith, here's what's actually in the\npackage and why the surface doesn't have to cost you disk space, install time, or attack surface\nyou didn't ask for.\n\nProviders load on demand, not on import\n\nNeuroLink ships named provider integrations plus a generic OpenAI-compatible adapter. None\nof them run at import time. src/lib/factories/providerRegistry.ts resolves a provider name to a\ndynamic import() of that provider's module only when you actually request it β€” Anthropic's\nclient only loads if you call createBestAIProvider(\"anthropic\") or equivalent; Ollama, LiteLLM,\nHugging Face, Bedrock, Vertex, and the rest are each behind their own await import(\"../providers/<name>.js\")\nline, one per provider, all in that same file. Ask for OpenAI and only the OpenAI\nprovider module executes β€” the rest never get evaluated.\n\nHeavy media/document deps are optional and lazy\n\nThe 35 packages that do real work in image, video, audio, and document processing β€”\nsharp, ffmpeg-static/fluent-ffmpeg, pdf-parse, mammoth, exceljs,\npptxgenjs, the LiveKit voice-agent plugins, bullmq, fastify/koa/express server adapters β€”\nare declared in optionalDependencies, not dependencies, in package.json. Being optional means\na package manager can skip them entirely if install fails or if you opt out; being lazy on top of\nthat means the code that needs them doesn't touch them until the feature runs. sharp is a good\nexample: NeuroLink's video processor only calls (await import(\"sharp\")).default inside the\nframe-resize path (src/lib/processors/media/VideoProcessor.ts), not anywhere near startup. If you\nnever touch video, sharp's native bindings never load into your process.\n\nSchema conversion loads on first use, not on import\n\nConverting between Zod schemas and JSON Schema β€” for a Zod 3 tool's parameters, or a plain\nJSON Schema parameters/inputSchema object β€” needs zod-to-json-schema or\njson-schema-to-zod respectively (src/lib/utils/schemaConversion.ts). Neither is a top-level\nimport: both load via require(), through createRequire(import.meta.url), the first time a\nconversion actually runs, so importing the SDK β€” or converting only Zod 4 schemas, which use\nZod's own built-in z.toJSONSchema() and never touch either package β€” never pulls them in. A\nrequire() made this way is not one a bundler follows, so nothing on the SDK's import graph\npulls either package in ahead of use.\n\nThe flip side is that a bundler cannot resolve this load for you. It does not see the\nrequire(), so bundling the SDK does not make these two packages available to it, and listing\nthem as external changes nothing about it. They have to be resolvable at run time from where\nthe bundle runs, so ship zod-to-json-schema and json-schema-to-zod in the node_modules\nnext to it. When they are not, the require() fails. That failure degrades rather than\ncrashes β€” a schema or tool that needed the missing package gets an empty object schema instead,\nlogged once per package at error level β€” but the model then sees no parameters for it, so treat\nthe error log as actionable, not just informational. The one bundle we ship ourselves, the\nbrowser artifact (dist/browser/neurolink.min.js), is handled in its build:\nscripts/build-browser.mjs bundles both packages in and serves them through its module stub.\n\nThe package exposes real subpath entry points, not one giant bundle\n\npackage.json's exports map lists separate entry points β€” ./voice, ./music, ./avatar,\n./image-gen, ./workflow, ./rag, ./files, ./processors/*, ./adapters/*, ./livekit β€”\nalongside the default . entry. That's what lets a bundler tree-shake: importing\n@juspay/neurolink/rag doesn't pull the voice stack into your bundle graph. The package also\ndeclares \"type\": \"module\" and a sideEffects array scoped to **/*.css plus the compiled\nvoice/music/avatar entry files β€” everything else is marked side-effect-free, which is the signal\nbundlers use to safely drop unused exports instead of keeping code \"just in case.\"\n\nOne interface, swappable implementations\n\nEvery provider β€” whether it's a first-party AI SDK wrapper or a bare HTTP client for something\nlike llama.cpp β€” implements the same AIProvider contract defined against BaseProvider\n(src/lib/core/baseProvider.ts). Generation, streaming, tool calls, and lifecycle hooks are all\nexpressed once, at the interface level, so adding provider #25 is additive (a new file + a new\nimport() branch in the registry), not a change to the","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"","lvl3":""}},
159
159
  {"objectID":"aa2a97d1fbf62d32d4c2b89cedf6e8756457ec9552abdcc39df8e49353dc0a4a","title":"Why the NeuroLink Core Stays Thin","url":"/docs/about/why-the-core-stays-thin#why-the-neurolink-core-stays-thin","content":"Breadth without bloat: what's actually in the package, and why unused capability costs you nothing at runtime\n\nNeuroLink's pitch is breadth: one install covers 17 capability domains β€” providers, MCP, evals,\nRAG, observability, voice, media, workflows, and more. Breadth claims like that earn a reasonable\nreflex: \"so it's another bloated framework that drags in everything whether I need it or not.\"\nThat reflex is fair, and it's the one framework abandonments over the last two years keep citing.\nSo instead of asking you to take breadth-without-bloat on faith, here's what's actually in the\npackage and why the surface doesn't have to cost you disk space, install time, or attack surface\nyou didn't ask for.","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"Why the NeuroLink Core Stays Thin","lvl3":""}},
160
160
  {"objectID":"ea24a1ae9ea7219b37355473fc345224ee1e97305283ff930e1f36b603bb38ea","title":"Providers load on demand, not on import","url":"/docs/about/why-the-core-stays-thin#providers-load-on-demand-not-on-import","content":"NeuroLink ships named provider integrations plus a generic OpenAI-compatible adapter. None\nof them run at import time. src/lib/factories/providerRegistry.ts resolves a provider name to a\ndynamic import() of that provider's module only when you actually request it β€” Anthropic's\nclient only loads if you call createBestAIProvider(\"anthropic\") or equivalent; Ollama, LiteLLM,\nHugging Face, Bedrock, Vertex, and the rest are each behind their own await import(\"../providers/<name>.js\")\nline, one per provider, all in that same file. Ask for OpenAI and only the OpenAI\nprovider module executes β€” the rest never get evaluated.","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"Providers load on demand, not on import","lvl3":""}},
161
161
  {"objectID":"8d37817e98f3351bfb570122b7aeeacd5673de163c03e3fdc2249f22ba153b01","title":"Heavy media/document deps are optional and lazy","url":"/docs/about/why-the-core-stays-thin#heavy-mediadocument-deps-are-optional-and-lazy","content":"The 35 packages that do real work in image, video, audio, and document processing β€”\nsharp, ffmpeg-static/fluent-ffmpeg, pdf-parse, mammoth, exceljs,\npptxgenjs, the LiveKit voice-agent plugins, bullmq, fastify/koa/express server adapters β€”\nare declared in optionalDependencies, not dependencies, in package.json. Being optional means\na package manager can skip them entirely if install fails or if you opt out; being lazy on top of\nthat means the code that needs them doesn't touch them until the feature runs. sharp is a good\nexample: NeuroLink's video processor only calls (await import(\"sharp\")).default inside the\nframe-resize path (src/lib/processors/media/VideoProcessor.ts), not anywhere near startup. If you\nnever touch video, sharp's native bindings never load into your process.","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"Heavy media/document deps are optional and lazy","lvl3":""}},
162
+ {"objectID":"eab25db03796822c25591a11b08f10b769de826cf0eefe3c09223afa3ee22adf","title":"Schema conversion loads on first use, not on import","url":"/docs/about/why-the-core-stays-thin#schema-conversion-loads-on-first-use-not-on-import","content":"Converting between Zod schemas and JSON Schema β€” for a Zod 3 tool's parameters, or a plain\nJSON Schema parameters/inputSchema object β€” needs zod-to-json-schema or\njson-schema-to-zod respectively (src/lib/utils/schemaConversion.ts). Neither is a top-level\nimport: both load via require(), through createRequire(import.meta.url), the first time a\nconversion actually runs, so importing the SDK β€” or converting only Zod 4 schemas, which use\nZod's own built-in z.toJSONSchema() and never touch either package β€” never pulls them in. A\nrequire() made this way is not one a bundler follows, so nothing on the SDK's import graph\npulls either package in ahead of use.\n\nThe flip side is that a bundler cannot resolve this load for you. It does not see the\nrequire(), so bundling the SDK does not make these two packages available to it, and listing\nthem as external changes nothing about it. They have to be resolvable at run time from where\nthe bundle runs, so ship zod-to-json-schema and json-schema-to-zod in the node_modules\nnext to it. When they are not, the require() fails. That failure degrades rather than\ncrashes β€” a schema or tool that needed the missing package gets an empty object schema instead,\nlogged once per package at error level β€” but the model then sees no parameters for it, so treat\nthe error log as actionable, not just informational. The one bundle we ship ourselves, the\nbrowser artifact (dist/browser/neurolink.min.js), is handled in its build:\nscripts/build-browser.mjs bundles both packages in and serves them through its module stub.","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"Schema conversion loads on first use, not on import","lvl3":""}},
162
163
  {"objectID":"3539bbad3542a55c88f34d80ddd1bc1fce27889c57cf8aca66e6273e6f344bba","title":"The package exposes real subpath entry points, not one giant bundle","url":"/docs/about/why-the-core-stays-thin#the-package-exposes-real-subpath-entry-points-not-one-giant-bundle","content":"package.json's exports map lists separate entry points β€” ./voice, ./music, ./avatar,\n./image-gen, ./workflow, ./rag, ./files, ./processors/*, ./adapters/*, ./livekit β€”\nalongside the default . entry. That's what lets a bundler tree-shake: importing\n@juspay/neurolink/rag doesn't pull the voice stack into your bundle graph. The package also\ndeclares \"type\": \"module\" and a sideEffects array scoped to **/*.css plus the compiled\nvoice/music/avatar entry files β€” everything else is marked side-effect-free, which is the signal\nbundlers use to safely drop unused exports instead of keeping code \"just in case.\"","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"The package exposes real subpath entry points, not one giant bundle","lvl3":""}},
163
164
  {"objectID":"becaa9cd1d3ca9248464018e639eac6f6c2b7cd66931086213e50c253aa4d607","title":"One interface, swappable implementations","url":"/docs/about/why-the-core-stays-thin#one-interface-swappable-implementations","content":"Every provider β€” whether it's a first-party AI SDK wrapper or a bare HTTP client for something\nlike llama.cpp β€” implements the same AIProvider contract defined against BaseProvider\n(src/lib/core/baseProvider.ts). Generation, streaming, tool calls, and lifecycle hooks are all\nexpressed once, at the interface level, so adding provider #25 is additive (a new file + a new\nimport() branch in the registry), not a change to the surface every existing provider has to pay\nfor.","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"One interface, swappable implementations","lvl3":""}},
164
165
  {"objectID":"273fa6d89ab0536369235962641702748840e0eded987d1ca9520284907a5d1b","title":"Where we're heavy today, honestly","url":"/docs/about/why-the-core-stays-thin#where-were-heavy-today-honestly","content":"Lazy import() stops code from running until it's needed, but it doesn't stop npm install\nfrom downloading everything in dependencies (as opposed to optionalDependencies).\n@aws-sdk/client-bedrock, @aws-sdk/client-bedrock-runtime, @aws-sdk/client-sagemaker-runtime,\n@aws-sdk/credential-provider-node, @google-cloud/vertexai, @google-cloud/text-to-speech, and\n@huggingface/inference now live in optionalDependencies β€” matching the treatment\nmedia/document deps already get.\n\nOptional doesn't mean skipped by default: a plain npm install still downloads all seven, same as\nit always has for sharp or pdf-parse. What optional buys you is the ability to opt out β€”\nnpm install --omit=optional, or an npm client falling back gracefully when one of them fails to\nbuild on an unsupported platform, drops them without breaking anything else. We verified that\ndirectly: a clean --omit=optional install still runs import('@juspay/neurolink'),\nneurolink --version, and the CLI entry point directly (node dist/cli/index.js --version)\ncorrectly, and asking for a provider whose SDK got skipped β€” Bedrock or SageMaker, say β€” fails\nwith a plain, actionable error at the moment you request that provider, not a crash at import\ntime.\n\nThat safety only holds because all seven are genuinely lazy β€” nothing reachable from\nimport '@juspay/neurolink' (or from the CLI's startup path) touches them at load time. Three of\nthem weren't, until the audits that found it: @google-cloud/vertexai was a top-of-file import in\nthe built-in websearchGrounding agent tool (src/lib/agent/directTools.ts);\n@google-cloud/text-to-speech was a top-of-file import in the TTS auto-registration that runs on\nvoice module load (src/lib/adapters/tts/googleTTSHandler.ts); and\n@aws-sdk/client-sagemaker-runtime's client was constructed synchronously in\nSageMakerRuntimeClient's constructor (src/lib/providers/sagemaker/client.ts), reachable\nstatically from the CLI entry point via cli/index.ts -> parser.ts -> commandFactory.ts ->\nsagemakerCommand","hierarchy":{"lvl0":"About","lvl1":"Why the NeuroLink Core Stays Thin","lvl2":"Where we're heavy today, honestly","lvl3":""}},
@@ -3198,11 +3199,21 @@
3198
3199
  {"objectID":"c0ba93406e03843f76fb0921606e334f356caed8d83559573c4b1d04989b1364","title":">>> neurolink-proxy (managed) >>>","url":"/docs/features/codex-proxy-support#-neurolink-proxy-managed-","content":"[model_providers.neurolink]\nname = \"NeuroLink Proxy\"\nbase_url = \"http://127.0.0.1:55669/backend-api/codex\"\nwire_api = \"responses\"\nrequiresopenaiauth = true","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":">>> neurolink-proxy (managed) >>>","lvl3":""}},
3199
3200
  {"objectID":"8f00ff46e651a81fa28b61fd20f66b0a93b72d2024b4da2f2e8fe661b32c4d9e","title":"<<< neurolink-proxy (managed) <<<","url":"/docs/features/codex-proxy-support#-neurolink-proxy-managed--1","content":"Your original model_provider value is snapshotted to ~/.neurolink/codex-proxy-snapshot.json and restored on shutdown, so the edit is fully reversible even if the proxy crashes. The managed block is delimited by markers and removed cleanly on clear. If ~/.codex/config.toml` doesn't exist, the step is skipped silently.\n\nRestart Codex after starting the proxy for it to pick up the new provider.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"<<< neurolink-proxy (managed) <<<","lvl3":""}},
3200
3201
  {"objectID":"404cd8f6d8542fc84651326cf0b252e7c3c3098678409fecaf8c2ff1559906fd","title":"4. Quota and routing","url":"/docs/features/codex-proxy-support#4-quota-and-routing","content":"Codex reports two rate-limit windows β€” primary (short) and secondary (weekly) β€” which map onto the shared AccountQuota model as the session and weekly fields respectively. That means Codex reuses the existing cooldown, persistence, and display code rather than duplicating it.\n\nAccount ordering is fill-first and quota-aware:\nAccounts on cooldown sort last.\nWithin the same cooldown state, accounts with a rejected unified quota sort after accounts without known rejection, even when their session usage is low or unknown.\nWithin each group, accounts with no session quota measurement sort first β€” they get probed so they become comparable, rather than being starved.\nOtherwise, least session utilization first. Rejected accounts remain eligible after preferred accounts so stale rejection evidence cannot permanently prevent a recovery probe.\n\nCooldown reasons map to the shared vocabulary: a rejected weekly window cools until its real reset (weekly), a rejected primary window until its reset (session), and a plain burst limit gets a bounded transient cooldown (60 s floor, 15 min ceiling).\n\nQuota and cooldown state are keyed by the full codex: account key, so a Codex account and an Anthropic account that share a bare label never collide.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"4. Quota and routing","lvl3":""}},
3201
- {"objectID":"acb592d28fcb90659fa175ac4f0d1e73cfb535d0aac2b63a101f0722e538eff7","title":"5. Response headers","url":"/docs/features/codex-proxy-support#5-response-headers","content":"Every pooled Codex response carries attribution headers:\n\n| Header | Meaning |\n| ------------------------------------ | --------------------------------------------------- |\n| x-neurolink-account | Which pooled account served the request |\n| x-neurolink-account-type | Always codex-oauth |\n| x-neurolink-served-by | Always codex |\n| x-neurolink-attempt | Which attempt succeeded (1 = first account tried) |\n| x-neurolink-quota-source | live when the backend reported quota, else none |\n| x-neurolink-quota-session-left-pct | Remaining primary-window headroom |\n| x-neurolink-quota-weekly-left-pct | Canonical remaining secondary-window headroom |\n| x-neurolink-weekly-left-pct | Compatibility alias for the canonical weekly header |","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"5. Response headers","lvl3":""}},
3202
+ {"objectID":"acb592d28fcb90659fa175ac4f0d1e73cfb535d0aac2b63a101f0722e538eff7","title":"5. Response headers","url":"/docs/features/codex-proxy-support#5-response-headers","content":"A response served by the Codex pool carries these attribution headers. A\nresponse served by the outbound fallback\ncarries only x-neurolink-served-by: the account, attempt and quota headers\ndescribe a Codex account, and none served that turn.\n\n| Header | Meaning |\n| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| x-neurolink-account | Which pooled account served the request |\n| x-neurolink-account-type | Always codex-oauth |\n| x-neurolink-served-by | codex, or codex-outbound-fallback:anthropic / codex-outbound-fallback:vertex when the outbound fallback served the turn |\n| x-neurolink-attempt | Which attempt succeeded (1 = first account tried) |\n| x-neurolink-quota-source | live when the backend reported quota, else none |\n| x-neurolink-quota-session-left-pct | Remaining primary-window headroom |\n| x-neurolink-quota-weekly-left-pct | Canonical remaining","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"5. Response headers","lvl3":""}},
3202
3203
  {"objectID":"2b17d62c4d9cb65067100d6a90003bee2dce2bc06ede425f25f0b761aade359a","title":"6. Error handling","url":"/docs/features/codex-proxy-support#6-error-handling","content":"| Condition | Behaviour |\n| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| No Codex accounts configured | 401 with a message pointing at neurolink auth login codex |\n| All accounts cooling | Terminal response.failed SSE event (200, retry-after computed from the soonest recovery) instead of a bare 429, so the CLI shows a real error instead of \"Reconnecting...\" |\n| 401 / 403 from upstream | One forced token refresh, then rotate; a failed refresh disables the account until re-login |\n| 429 | Cool the account per its reported window, then rotate |\n| 5xx / network | Rotate to the next account |\n\nAccess tokens are refreshed proactively when within 5 minutes of expiry, and the rotated refresh token is written back to the store.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"6. Error handling","lvl3":""}},
3203
3204
  {"objectID":"9b1627d0fcff6a9e292b7f794fb47e62eb3da6b9c633e308da2b1de08a83368d","title":"7. Implementation map","url":"/docs/features/codex-proxy-support#7-implementation-map","content":"| File | Role |\n| ------------------------------------------- | ----------------------------------------------------------------------------------------- |\n| src/lib/types/codex.ts | Codex auth-file, token, and rate-limit types |\n| src/lib/auth/codexOAuth.ts | Endpoints/constants, auth.json import, token refresh, JWT decode, account-id resolution |\n| src/lib/proxy/codexAccountUsage.ts | Account enumeration, usage fetch, quota normalisation, header parsing |\n| src/lib/server/routes/codexProxyRoutes.ts | The pool engine: load β†’ order β†’ forward β†’ rotate |\n| src/cli/commands/auth.ts | auth login codex, Codex rows in auth list --refresh |\n| src/cli/commands/proxy.ts | Route registration, /backend-api/* request tracking, ~/.codex/config.toml management |\n\nThe Anthropic engine in claudeProxyRoutes.ts is untouched. The Codex engine is deliberately leaner: it does pre-commit rotation but not the full transient-retry-budget or admission-lease machinery.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"7. Implementation map","lvl3":""}},
3204
3205
  {"objectID":"c90a5222434bc717385c13b9e9f9cb865d3ce71ed7a58c077a7b5241def7d086","title":"8. Caveats","url":"/docs/features/codex-proxy-support#8-caveats","content":"Model ids matter. The ChatGPT backend rejects models that aren't available to Codex-with-a-ChatGPT-account (e.g. gpt-5-codex returns a 400). Use the model your Codex config already uses.\nTerms of service. Pooling multiple personal ChatGPT subscriptions through one client fingerprint is the kind of pattern subscription anti-abuse systems are built to detect. The originator, installation_id, user-agent, and turn-metadata headers are all correlatable. Pooling your own accounts is materially different from sharing across people β€” weigh the account-ban risk accordingly.\nNative browser login is not implemented. The verified path is importing an existing codex login credential. The OAuth constants (authorize URL, PKCE, scopes) are present in codexOAuth.ts for a future native flow.\nQuota-aware ordering is unverified against the live backend. The usage endpoint and the rate-limit header shape were reconstructed from a capture, not confirmed end to end. If either is wrong, fetchCodexAccountUsage returns no quota, x-neurolink-quota-source reads none, and ordering degenerates to insertion order while every 429 falls back to the 15-minute transient cooldown. Rotation still works; it is simply not quota-aware. Verify with neurolink auth list --refresh β€” a codex usage … error line per account means the quota path is not live.\nSSE usage-limit signals are not acted on. Only an HTTP 429 triggers a cooldown and rotation. A 200 response whose SSE stream carries usage_limit_reached (or the workspace-credit variants) is relayed to the client untouched, so the account is neither cooled nor rotated away from. HTTP-level exhaustion is handled; in-stream exhaustion is not.\nClient fingerprint is forwarded verbatim. The proxy replaces the caller's credentials but does not regenerate originator, installation_id, session-id, or turn metadata per account, so every pooled account shares the client's fingerprint. This is what makes the terms-of-service point above concrete.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"8. Caveats","lvl3":""}},
3205
3206
  {"objectID":"607b92eb2c3b89a57438267a92b6212e616ad0dd106cb1c1a6bd0514c3446cd2","title":"Model discovery","url":"/docs/features/codex-proxy-support#model-discovery","content":"The Codex CLI refreshes its model list on every invocation:\n\nThe proxy relays that upstream to chatgpt.com/backend-api/codex/models using a\npooled account, forwarding the CLI's own query parameters.\n\nIt relays rather than synthesises, unlike the Claude and OpenAI /v1/models\nroutes, which build their lists locally from the model router. Which Codex\nmodels an account can reach is a property of that account β€” plan tier, rollout\nstate β€” not something this proxy knows, so a locally-built list would be a guess\nthat reads as authoritative.\n\nTwo details matter to anyone touching it:\nclient_version is required upstream. Omit it and ChatGPT answers 400\n with a pydantic Field required on ('query', 'client_version'). The query\n is rebuilt from ctx.query; ctx.path carries no query string, and reading it\n from there drops the parameter silently.\nDiscovery is side-effect free. No cooldown is recorded and no quota is\n consumed, so the once-per-invocation refresh cannot influence routing for real\n traffic. A cooling account is still allowed to answer it β€” being rate-limited\n for completions does not make an account unable to say which models exist.\n\nBefore this route existed the request 404'd, and the CLI printed\nfailed to refresh available models: unexpected status 404 Not Found on every\nrun before silently falling back to a default model β€” quietly ignoring the model\nthe user had configured.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"Model discovery","lvl3":""}},
3207
+ {"objectID":"6d2395f1bb843928fffc947203d5d331fb549bb7d9403adfa777e676578b4d18","title":"Status: implemented, default OFF, request-shape verified only β€” see \"Live validation\" below","url":"/docs/features/codex-proxy-support#status-implemented-default-off-request-shape-verified-only-see-live-validation-below","content":"Sections 1-8 above cover the inbound direction: a Codex CLI request served\nby the Codex/ChatGPT pool engine. This section covers the opposite direction β€”\na native Codex /backend-api/codex/responses request that, when every Codex\naccount is exhausted or otherwise ineligible, is translated into an\nAnthropic Messages request and served by either the Anthropic OAuth pool (via\nan in-process loopback, no extra network hop to this same proxy) or a\nVertex-Claude passthrough account, then translated back into the Codex\nResponses wire shape the CLI expects. It is a third direction alongside the\ntwo-engine matrix in the rest of this document, not a replacement for either.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"Status: implemented, default OFF, request-shape verified only β€” see \"Live validation\" below","lvl3":""}},
3208
+ {"objectID":"3c2f324de0830846bbdce959aabe14903475260a2adfabf478b669fef52ac726","title":"9.1 Enabling it","url":"/docs/features/codex-proxy-support#91-enabling-it","content":"The kill switch defaults to off. Nothing changes for an existing Codex\npool setup until you opt in:\n\n`yaml","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.1 Enabling it","lvl3":""}},
3209
+ {"objectID":"675353930bf4ce07f3edced6805246ec2af789cc4adf673f9e80554163b2e4f3","title":"proxy-config.yaml","url":"/docs/features/codex-proxy-support#proxy-configyaml","content":"routing:\n codex-outbound-fallback-enabled: true\n # Ordered list of engines to try once Codex itself is exhausted; the first\n # entry with a healthy pooled account wins. At most 8 entries.\n codex-outbound-fallback-targets:\nprovider: anthropic\n model: claude-sonnet-4-5\nprovider: vertex\n model: claude-sonnet-4-5\n # Optional: remap a Codex-side model id to a different target-side model id\n # per target provider (falls back to the target's own model above when a\n # request's model has no matching entry).\n codex-outbound-fallback-model-mappings:\nfrom: gpt-5-codex\n to: claude-opus-4-1\n provider: anthropic\nbash\nNEUROLINKPROXYCODEXOUTBOUNDFALLBACK=true\n\n\ncodex-outbound-fallback-targets is required (non-empty) for the fallback to\never fire β€” the kill switch alone is not enough. provider must be anthropic\nor vertex; up to 8 targets are accepted and validated at config-load time.\nWith the flag off, or with no targets configured, an exhausted Codex request\nbehaves exactly as it did before this feature existed: the same terminal\nresponse.failed SSE / 401 behaviour documented in section 6 above, byte-\nfor-byte.\n\n**The anthropic target needs the CLI proxy's in-process loopback.** Dispatch\nto an anthropic target goes through an in-process loopback\n(127.0.0.1: plus an unforgeable internalDispatch marker, see\nΒ§9.3) rather than a second network hop, and only neurolink proxy start (the\nCLI proxy command) wires that loopback through today. createAllRoutes /\nregisterAllRoutes β€” the generic SDK route surface in\nsrc/lib/server/routes/index.ts, used when embedding the proxy routes in your\nown server β€” does **not** thread loopbackPort/internalDispatch into\ncreateCodexProxyRoutes, so enabling this feature there with an anthropic\ntarget configured cannot actually dispatch: route creation logs one warning\nand every such request falls through to the normal Codex-exhaustion behaviour\nin section 6, indistinguishable on the wire from the feature being off. A\nvertex target","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"proxy-config.yaml","lvl3":""}},
3210
+ {"objectID":"d20a853143464660ecb4669832c8c0d5e24292564bc7ea9161354afda9a39223","title":"9.2 When it triggers","url":"/docs/features/codex-proxy-support#92-when-it-triggers","content":"attemptCodexOutboundFallback (module-private in codexProxyRoutes.ts) is\nconsulted only after every Codex account has been tried and failed, and only\nwhen all of the following hold, in this order β€” any one failing gate means\n\"not attempted\", and the request falls through to the normal Codex-exhaustion\nbehaviour in section 6:\nThe inbound request is not itself already a fallback leg (see loop\n prevention below).\nruntimeConfigProvider is configured (the proxy is running with the\n runtime-config feature enabled at all).\nclassifyCodexOutboundFailure judges the failure itself eligible β€” a total\n function, not a heuristic guess, over the terminal Codex failure class.\nThe kill switch (codexOutboundFallbackEnabled) is on.\ncodexOutboundFallbackTargets is non-empty.\nThe native Codex request body parses (parseCodexNativeRequest) β€” a\n request the translator cannot even parse is never a candidate.\n\nWhen the account loop runs out, the last attempt decides eligibility. A\n401/403 across the whole pool is a Codex credential failure and falls back, as\ndo 429 and 5xx. A content-policy or invalid-request error code\n(classifyProxyFailureCode), or any other 4xx, rejects the request itself and\nis returned unchanged.\n\nThe Anthropic loopback has a 5-minute timeout that covers the connection until\nits response headers arrive. After that the stream runs as long as the turn\ndoes; a client disconnect still ends it.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.2 When it triggers","lvl3":""}},
3211
+ {"objectID":"63d9622db4658e47ba8122e511162860b322d42d2b741a5c76611250fe670550","title":"9.3 Loop prevention","url":"/docs/features/codex-proxy-support#93-loop-prevention","content":"Two independent guards, so a defect in one does not remove the bound:\nMarker guard. The moment every gate above has passed and a real\n dispatch attempt is about to begin, codexProxyRoutes.ts sets\n ctx.metadata[\"neurolink.codexOutboundFallbackAttempted\"] = true on the\n request context. Gate 1 above checks this same key, so a request that has\n already crossed engines once can never be offered a second outbound-fallback\n attempt within the same client request. This is a single flag, not a\n per-target counter, because at most one crossing is ever allowed regardless\n of how many of the internal dispatch call sites reach this function.\nDepth counter (defense-in-depth). ctx.metadata[\"neurolink.fallbackHopDepth\"]\n is incremented on every engine crossing (never on a same-engine account\n rotation) and checked against MAX_ENGINE_CROSSINGS = 1, independent of the\n marker check above. The invariant this enforces: the ordered sequence of\n engines serving any one client request has length ≀ 2 with no repeat β€” only\n [codex], [anthropic], [codex,anthropic], [anthropic,codex] are\n possible. A leg that is itself already an inner fallback of the other engine\n can never itself fall outbound again.\n\nA request that reaches this point already carrying a forged\ncodexOutboundFallbackAttempted-shaped marker from outside is not trusted β€”\nthe key lives on the server-side request context (ctx.metadata), not on\nanything the client can set on the wire, so there is no client-controlled way\nto spoof past gate 1.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.3 Loop prevention","lvl3":""}},
3212
+ {"objectID":"21189de88d4ffef1c2a4bbd0420442de71fe8d9fd6b9f6278867e39602e075c6","title":"9.4 What gets translated","url":"/docs/features/codex-proxy-support#94-what-gets-translated","content":"Request translation (src/lib/proxy/codexOutboundFallback.ts):\nCodex additional_tools namespaces (functions, collaboration, …) are\n flattened into one Claude tools array, preserving declaration order β€”\n namespace order, then tool order within each namespace β€” never regrouped or\n resorted by tool kind.\nA tool's kind (function vs custom) is read from the tool declaration's\n own type field, never inferred from which namespace it was declared under.\nFunction-tool JSON Schemas are dereferenced with inlineJsonSchema: plain,\n resolvable $ref/$defs (draft-07 definitions and 2020-12 $defs) are\n flattened into the inline schema and the pointer keys stripped entirely.\n A genuinely circular $ref degrades to {type: \"object\"} and is counted\n under the circular_ref_flattened degrade reason (see metrics below); a\n plain non-circular $ref is not.\nGrammar/custom tools (Lark grammar, regex format) have no JSON Schema\n equivalent, so they are wrapped as a single {input: string} parameter, with\n the response mapped back to a custom_tool_call item. This is lossy and\n explicitly flagged in code, and is the shipped behavior β€” see \"Sign-off\n status\" below.\nReasoning effort (minimal/low/medium/high/xhigh/max on the Codex\n side) maps to an Anthropic thinking budget via mapCodexReasoningToThinking:\n lowβ†’4096, mediumβ†’8192, highβ†’16384, xhighβ†’24576, maxβ†’32768, clamped to\n [1024, max_tokens - 1024]. Thinking is left off when tool_choice is\n forced, or when the immediately preceding assistant turn called a tool.\nA non-object function_call_output/custom_tool_call_output argument falls\n back to {input: raw} in the translated history rather than failing the\n translation.\n\nResponse translation (codexResponsesFormat.ts / codexToAnthropicFallback.ts)\nconverts the Claude response β€” including SSE streaming β€” back into the Codex\nResponses wire shape the CLI expects, so a client cannot tell, from the wire\nalone, that its request was served by a different engine underneath.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.4 What gets translated","lvl3":""}},
3213
+ {"objectID":"4a8c83d10dbaa054203c48c374f7aed2688cb58670135943b0b2c6cd358cf129","title":"9.5 Accounting","url":"/docs/features/codex-proxy-support#95-accounting","content":"A translated request is billed and recorded exactly like any other proxied\nrequest on the serving engine β€” the same OTel usage record, cache/reasoning\nbuckets, and cost estimate described in Usage accounting\n(section 1) apply, attributed to the Anthropic or Vertex account that actually\nserved it, not to a Codex account. There is no separate \"fallback\" ledger; look\nat the served-by account's own usage.\n\nThe Codex route's final record names what served the turn: provider,\nmodel, account and accountType are the Claude target's, and\nrequestedModel stays the Codex model the client asked for. With the Anthropic\ntarget, the loopback's own /v1/messages record owns the usage, and the Codex\nrecord points to it through usageOwnerRequestId, so the call is counted once.\nWith Vertex there is no child record, so the Codex record carries the usage and\nis priced from the Vertex table.\n\nThe translated request marks its stable prefix with a 1-hour cache TTL. 1-hour\nwrites (usage.cache_creation.ephemeral_1h_input_tokens) are recorded as\ncacheCreation1hTokens, a subset of the cache-write total, and priced at 2x\nbase input, Anthropic's 1-hour rate on every Claude model. The rest of the\ncache writes keep the 5-minute rate (1.25x).\n\nThe token/cost metrics carry an origin attribute (ProxyRequestOrigin:\n\"native\" | \"codex-fallback\"), but only when it is not \"native\" β€” a\nplain Codex or Claude request emits the exact same label set it always has,\nwith no origin key at all, so existing dashboards and alerts over these\nmetrics are unaffected. Only a request actually served by this outbound-fallback\npath carries origin=\"codex-fallback\", set via\ntracer.setRequestOrigin(\"codex-fallback\") once a target has been dispatched\nand will serve the turn (both the Anthropic-loopback and Vertex targets); a\nrequest whose every target fails keeps \"native\". A Codex-outbound request served by\na Vertex target is attributed as account vertex/<model>, accountType\n\"vertex\" β€” the same shape the native Claude-to-Vertex ","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.5 Accounting","lvl3":""}},
3214
+ {"objectID":"efd21276881aaff463922d63ebd364e1b177ac659b6927d25c7858f7bb0a6190","title":"9.6 Metrics","url":"/docs/features/codex-proxy-support#96-metrics","content":"Two Codex-outbound-specific OTel counters, both emitted from\nsrc/lib/proxy/proxyTracer.ts:\n\n| Metric | Labels | Meaning |\n| ---------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| proxy_codex_outbound_schema_degraded_total | {reason, toolName} | A tool declaration's JSON Schema needed flattening or lost a feature on the way to a Claude tool. reason is one of circular_ref_flattened (a genuine cycle degraded to {type:\"object\"}) or strict_mode_flag_dropped (the Codex tool's strict flag has no Claude equivalent and was dropped). |\n| proxy_codex_outbound_unsupported_field_total | {field} | A Codex-outbound request field this repo cannot faithfully represent in the Claude request was dropped rather than failing the translation. |\n\nEvery other proxy metric in the existing dashboard (proxy_request_duration_ms,\nproxy_tokens_*, proxy_errors_total, proxy_fallback_attempts_total,\nproxy_fallback_success_total, proxy_fallback_failure_total, …) also fires\nfor a Codex-outbound-fallback request, on the serving engine, same as any other\nproxied call.\n\nproxy_cost_usd_total is a cumulative counter, not a gauge. Like every\nother *_total metric here, it is an OTel Counter β€” it on","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.6 Metrics","lvl3":""}},
3215
+ {"objectID":"43b245bbe599ce8328e90e4528f7d83e953cf3ddea0448aac3ac7834587419f7","title":"9.7 Sign-off status","url":"/docs/features/codex-proxy-support#97-sign-off-status","content":"The following were flagged in the original design as behavior-changing choices\nneeding product sign-off. All three were signed off 2026-09-28 and ship as\ndesigned, not as placeholders awaiting approval:\nThe lossy grammar/custom-tool β†’ single {input: string} parameter mapping\n (Β§9.4 above), with custom_tool_call on the way back.\nThe {input: raw} non-JSON-argument history fallback and its downstream\n effect on tool execution.\nThe reasoning-effort β†’ thinking-budget table in Β§9.4 above.\n\nSee docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design.md for\nthe full design record and rulings.","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.7 Sign-off status","lvl3":""}},
3216
+ {"objectID":"7d4e6e1166cdf67bb24a8c10d864e2a5e88df37bb004a60467cdf247ebd829ca","title":"9.8 Live validation (after the Codex quota resets 2026-10-03)","url":"/docs/features/codex-proxy-support#98-live-validation-after-the-codex-quota-resets-2026-10-03","content":"Everything above has been verified at the request/response shape level β€”\ntranslation correctness, config validation, metrics, loop-prevention logic β€”\nusing synthetic and characterization fixtures (including a redacted real Codex\nCLI wire capture). None of it has been exercised against live Codex, Vertex\nor Anthropic traffic end to end, because the Codex account available to this\nwork is quota-rejected until 2026-10-03. The validation plan's Part B live\nmatrix (stage-d-validation.md Β§4) is the run to do once quota is back; its\nscenarios, unchanged from the plan:\n[ ] B1 β€” Cold start, single turn (gpt-5.6-sol β†’ Anthropic): if the\n Section-0 tool policy permits dispatch, proxy_tokens_input rises by\n roughly the translated prefix token count and proxy_tokens_cache_creation\n rises above 0; if the policy is fail-closed, a Codex-shaped diagnostic\n with no dispatch attempted is the correct, expected outcome β€” not a bug.\n[ ] B2 β€” Warm second turn, same session/account (gpt-5.6-sol β†’\n Anthropic): confirm both turns were served by the same account (query\n proxy_cost_usd_total's account label) before drawing any cache\n conclusion, then confirm proxy_tokens_cache_read rises by β‰₯80% of the\n prior turn's cache_creation delta with the second turn's own\n cache_creation delta near zero β€” the live proof that A.6's byte-\n stability requirement (already fixture-verified offline) holds against\n a real cache hop.\n[ ] B3 β€” Functions-namespace tool call (gpt-6-astra β†’ Anthropic):\n read_file alongside the ever-present exec/request_review\n collaboration tools; the client-visible stream carries a well-formed\n function_call the Codex CLI can execute, proxy_fallback_success_total{provider='anthropic'}\n increments by exactly 1, and there is no Anthropic 400 on the\n accompanying custom-tool schema.\n[ ] B4 β€” Parallel tool calls (gpt-6-astra β†’ Anthropic): both\n function_call items carry distinct toolu_-derived call_i","hierarchy":{"lvl0":"Features","lvl1":"Codex (ChatGPT) Support for NeuroLink Proxy","lvl2":"9.8 Live validation (after the Codex quota resets 2026-10-03)","lvl3":""}},
3206
3217
  {"objectID":"89c0f088b6b4f91a1fc99b1db7acb741e912010bac6416ffe5d94730ff7e11a2","title":"Per-request context budget","url":"/docs/features/context-budget","content":"Per-request context budget\n\nContext compaction has always triggered at a fixed 80% of the model's window.\nThat number is now a default, not a constant: a per-request compactionThreshold\noption lowers it for requests that need less room, and the\nclassifier router's jev strategy can fill\nit in automatically by asking how much of the conversation the request actually\nneeds.\n\nThe degradation contract. Every input here is optional. With no\ncompactionThreshold passed and no classifier router configured, the effective\nthreshold is exactly 0.8, exactly as before β€” resolveHistoryBudget()'s\nscale factor is compactionThreshold / 0.8, so the default value reproduces\nthe previous budget calculation to the token.\n\nSetting it directly\n\nCLI: neurolink generate \"...\" --compaction-threshold 0.45 (existing flag,\ndescription: \"Fraction of the context window (0-1) at which history is\ncompacted. Default 0.8.\").\n\nLetting the classifier fill it in\n\nWhen classifierRouter.contextBudget is enabled (default: true whenever\nthe strategy resolves to a decision model; ignored by heuristic/llm), the\nsame jev request that classifies difficulty also asks a score question:\n\nHow much of the earlier conversation does answering this request actually\nrequire?\n\nagainst a four-level rubric:\n\n| Scope | Criterion | Threshold |\n| ------------------- | -------------------------------------------------------------------------------------------------- | --------- |\n| current-message | Self-contained. Earlier conversation would not change the answer. | 0.45 |\n| recent-turns | Needs the last few exchanges β€” a follow-up, a correction, a reference to something just discussed. | 0.60 |\n| full-conversation | Needs the whole conversation, including decisions and constraints established much earlier. | 0.75 |\n| everything | Needs the conversation and every document, file and tool result that has been gathered. | 0.80 |\n\nThis is a rubric, not a token count, and deliberately so: a decision model\nplaces a request on an ordered scale reliably and reads digit strings as text,\nnot quantities β€” asking \"how many tokens does this need\" would get an answer\nshaped like a guess at a number, not a calibrated judgement.\n\nThe reading is used only above MIN_CONTEXT_SCOPE_CONFIDENCE (0.5); below\nthat, contextScope is left undefined and the request falls back to the\n0.8 default exactly as if the question had not been asked. The scope judgement\nis independent of the difficulty tier β€” a trivial request can still need the\nwhole conversation, and an expert one can be entirely self-contained β€” so\nit is read and applied on its own, and survives every path on which the\ndifficulty verdict itself is discarded by its confidence bar.\n\napplyClassifierRouting() then applies the derived threshold only when the\ncaller left compactionThreshold unset:\n\nAn explicit per-call value always wins. The classifier is filling in a default\nyou didn't set, never overriding one you did.\n\nThe one-directional invariant\n\nThe mapping from scope to threshold can only ever lower the 0.8 default,\nnever raise it, and this is enforced independently at two layers:\ncontextScopeToThreshold() clamps its result with Math.min(0.8, ...)\n before returning.\nClassifierRouter.selectContextBudget() re-checks the result and discards\n it unless threshold < 0.8.\n\nThe reason this is a hard invariant rather than a tuning choice: shrinking a\nbudget merely compacts a little earlier than strictly necessary β€” the model\nstill answers correctly with slightly less history than it could have used.\nGrowing one lets a request through that the provider then rejects with a\ncontext-window error, and ModelPool treats that as a permanent (10-year)\ncooldown β€” one optimistic guess retires the model for the life of the\nprocess. Getting this wrong in one direction is recoverable; getting it wrong\nin the other is not, so the code refuses to let a bug make that mistake even\nonce.\n\nHow the threshold changes the actual budget\n\nresolveHistoryBudget() is what the compactor targets β€” the model's available\ninput space minus system prompt, current prompt, tool definitions and file\nattachments, all of which ride alongside history rather than being part of\nit. The per-request threshold scales that budget directly:\n\nWith the default 0.8, scale is exactly 1 and the budget is unchanged\nfrom before this option existed. A 0.4 threshold halves the history budget.\nThe 0.95 safety factor is unrelated to this feature β€” it exists because\ntoken estimation is character-based and approximate, so the compactor always\naims slightly under the true ceiling.\n\nWhat this is bad at\nIt is a suggestion, not a measurement. The rubric asks \"how much would\n a human say this needs,\" not \"how many tokens will this actually consume.\"\n A request correctly judged recent-turns can still occasionally need","hierarchy":{"lvl0":"Features","lvl1":"Per-request context budget","lvl2":"","lvl3":""}},
3207
3218
  {"objectID":"6e42051940f716e8463919220990087875ede2015f3d6d14951b113f55d268ad","title":"Per-request context budget","url":"/docs/features/context-budget#per-request-context-budget","content":"Context compaction has always triggered at a fixed 80% of the model's window.\nThat number is now a default, not a constant: a per-request compactionThreshold\noption lowers it for requests that need less room, and the\nclassifier router's jev strategy can fill\nit in automatically by asking how much of the conversation the request actually\nneeds.\n\nThe degradation contract. Every input here is optional. With no\ncompactionThreshold passed and no classifier router configured, the effective\nthreshold is exactly 0.8, exactly as before β€” resolveHistoryBudget()'s\nscale factor is compactionThreshold / 0.8, so the default value reproduces\nthe previous budget calculation to the token.","hierarchy":{"lvl0":"Features","lvl1":"Per-request context budget","lvl2":"Per-request context budget","lvl3":""}},
3208
3219
  {"objectID":"f0d23f694f874e0f664f348ae61fb3dcfa09869d8fa23d3708bef58bb1d8a3a5","title":"Setting it directly","url":"/docs/features/context-budget#setting-it-directly","content":"CLI: neurolink generate \"...\" --compaction-threshold 0.45 (existing flag,\ndescription: \"Fraction of the context window (0-1) at which history is\ncompacted. Default 0.8.\").","hierarchy":{"lvl0":"Features","lvl1":"Per-request context budget","lvl2":"Setting it directly","lvl3":""}},
@@ -11230,8 +11241,8 @@
11230
11241
  {"objectID":"25e1ac265c89596a09bce35c4ba7b8d82ccf92f016708d1f6f4826b290af00ae","title":"Delivery","url":"/docs/superpowers/specs/2026-09-23-proxy-account-routing-policies-design#delivery","content":"juspay/neurolink merges one commit per PR, so two PRs:\nRefactor, no behaviour change: extract the comparator into accountRanking.ts and count in-flight on the unlimited lease. Proven by the replay and the existing route tests.\nPolicies: headroom-first, prefer-primary, session affinity, spill, config, telemetry, /status, and the documentation correction below.","hierarchy":{"lvl0":"Superpowers","lvl1":"Proxy Account Routing Policies Design","lvl2":"Delivery","lvl3":""}},
11231
11242
  {"objectID":"06eb4e8ea79a4077f7cb445c1ec17985516046ab28d49d3c87b85ade0155f2c1","title":"Documentation","url":"/docs/superpowers/specs/2026-09-23-proxy-account-routing-policies-design#documentation","content":"docs/features/claude-proxy-config-reference.md gains the five keys. The primary-account text and source comments that still say the primary is \"tried first under fill-first\" are corrected: with quota routing the primary is the final tiebreaker unless prefer-primary is set.","hierarchy":{"lvl0":"Superpowers","lvl1":"Proxy Account Routing Policies Design","lvl2":"Documentation","lvl3":""}},
11232
11243
  {"objectID":"1cf022d6f43b961b6fd1c13d8a5677077f9109584a3a5e67d837c4d5f6e613da","title":"Out of scope","url":"/docs/superpowers/specs/2026-09-23-proxy-account-routing-policies-design#out-of-scope","content":"Per-model policies, weighted scoring, persisting affinity across restarts, an account reserve, and changes to cooldown classification, in-stream rate-limit handling or state persistence. Those last three are separate designs.","hierarchy":{"lvl0":"Superpowers","lvl1":"Proxy Account Routing Policies Design","lvl2":"Out of scope","lvl3":""}},
11233
- {"objectID":"f0109d9239beed7372578f5d0ad41b745b321c526407db0d3bbb32adced24e2e","title":"Codex Outbound Fallback — Design","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design","content":"Codex Outbound Fallback — Design\n\nStatus: design complete; implementation under way in build order. PR 1 (the IR\ntypes) merged as #1826; PR 2 (the request codec) is #1836; PRs 3 to 8 have not\nstarted, and nothing is wired into a route yet. Produced by 22 parallel agents\n(6 grounding, 8 design, 8 adversarial review) plus 8 resolution passes; every design\nclaim below was checked against the code by a reviewer whose brief was to refute it.\n\nGoal\n\nA native Codex client request that its own account pool cannot serve currently fails to the\nclient. This design gives the Codex engine an outbound fallback chain to the Anthropic OAuth\npool and to Vertex, so the request is served instead — with prompt caching preserved across\nthe hop, tool calls translated faithfully in both directions, and no client-visible protocol\nanomaly.\n\nEvidence\nsrc/lib/server/routes/codexProxyRoutes.ts has no outbound fallback. Its only fallback\n references are a string-default helper (line 138) and code recognising when the Codex engine\n is itself invoked as an inner Anthropic fallback (lines 666, 817, CODEX_FALLBACK_METADATA_KEY\n at 109). On pool exhaustion it returns a terminal error: buildCodexErrorResponse\n (1458-1462), or buildCodexQuotaExhaustedResponse (909-914) when every account is already\n cooling.\nThree directions exist and work: Claude Code → Anthropic pool; Claude/Anthropic → Codex\n (executeClaudeCodexFallback, claudeProxyRoutes.ts:5499, called at 6131, in-process — no\n HTTP hop); Claude/Anthropic → Vertex (vertexAnthropicFallback.ts:611, gated on\n isVertexAnthropicModel, 576).\nThree do not exist: Vertex → Anthropic, native Codex → Anthropic, native Codex →\n Vertex. PR #1782 fixed translation defects in the existing Claude → Vertex direction; it\n did not add a reverse path.\nCodex prompt caching is live but partial. proxy_tokens_cache_read by model:\n gpt-5.6-sol 566,657,536; gpt-6-astra 3,285,888; gpt-5.6-terra 490,752;\n gpt-5.6-luna 5,888. proxy_tokens_cache_creation is 0 for every Codex series, which is\n correct — OpenAI caching is automatic and has no explicit write.\nThe caching asymmetry is the central design constraint. Anthropic caching is explicit:\n up to 4 cache_control breakpoints over a tools → system → messages prefix, write at\n 1.25x base input (5-minute TTL) or 2x (1-hour), read at 0.1x, and each account holds its\n own cache. A Codex request translated to Anthropic arrives with no breakpoints at all,\n so it would be cached not at all unless the translator inserts them deliberately.\nSince Claude-on-Vertex speaks the Anthropic Messages wire format, one Codex→Anthropic\n translator covers both targets. There is no smaller Vertex-only variant of this work.\n\nBuild order\n\nDependency-ordered; each step is one reviewable PR under the one-commit-per-PR policy. Default\noff at every step, so behaviour is byte-for-byte unchanged until the final enable.\n\n| # | PR | Depends on | Gate |\n| --- | ------------------------------------------------------------------- | ---------- | -------------------------------------------- |\n| 1 | IR types + codec contract (src/lib/types/proxyIR.ts) | — | typecheck; exhaustiveness is a compile error |\n| 2 | Request codec Codex → Anthropic (codexOutboundFallback.ts) | 1 | offline shape suite |\n| 3 | Response/stream codec Anthropic → Codex | 1 | offline event-order suite |\n| 4 | Tool fidelity + id correlation | 2, 3 | offline round-trip suite |\n| 5 | Cache breakpoints + prefix byte-stability (codexOutboundCache.ts) | 2 | byte-stability assertion across two turns |\n| 6 | Trigger policy, commitment rule, loop prevention, accounting | 2-4 | totality + commitment suites |\n| 7 | Config keys, kill switch, metrics | 6 | config validation suite |\n| 8 | Enable + live model matrix | all | live pass criteria |\n\nOpen questions gating the build\n\nEvery one needs the same thing: real captured native Codex traffic. The repo has 4 synthetic\nfixtures, one real single-turn capture (~/.neurolink/reference/codex-cli-wire-sample.json,\ncaptured 2026-09-21), a real usage-frame capture (test/fixtures/codex-response-usage.sse,\ncaptured 2026-08-21), and one real controlled bisection experiment against the actual\ncodex_exec/0.155.1 binary (scripts/codex-replay-listener.ts, test/fixtures/sse-bisection-findings.md,\nrun 2026-09-27). Two questions are now settled; the rest are listed here because they can still\ninvalidate a whole section, so they should be settled before the PRs that depend on them are\nwritten.\n\n| Question ","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback — Design","lvl2":"","lvl3":""}},
11234
- {"objectID":"6034ef59cb5b3d2520fa92f2f181aca534a58aa28338f3411aa9d28d998e487e","title":"Codex Outbound Fallback β€” Design","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#codex-outbound-fallback-design","content":"Status: design complete; implementation under way in build order. PR 1 (the IR\ntypes) merged as #1826; PR 2 (the request codec) is #1836; PRs 3 to 8 have not\nstarted, and nothing is wired into a route yet. Produced by 22 parallel agents\n(6 grounding, 8 design, 8 adversarial review) plus 8 resolution passes; every design\nclaim below was checked against the code by a reviewer whose brief was to refute it.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Codex Outbound Fallback β€” Design","lvl3":""}},
11244
+ {"objectID":"f0109d9239beed7372578f5d0ad41b745b321c526407db0d3bbb32adced24e2e","title":"Codex Outbound Fallback — Design","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design","content":"Codex Outbound Fallback — Design\n\nStatus: design complete; implementation shipped on feat/codex-outbound-fallback-rollout\nin four staged local commits (build order PRs 1-8, landed as one branch rather than eight\nseparate PRs — see the branch's own commit history for the stage-by-stage breakdown: cache\nand request/response codecs, config + kill switch, trigger policy + route wiring + loop\nprevention + accounting, then this stage's test-matrix closure and docs). The feature ships\nOFF by default, gated end-to-end by routing.codex-outbound-fallback-enabled /\nNEUROLINK_PROXY_CODEX_OUTBOUND_FALLBACK; turning it on for the live proxy is a separate,\noperator-approved config change, not part of this work. All three items previously flagged\n\"needs product sign-off\" below (grammar/custom-tool wrapping, the {input: raw} history\nfallback, and the reasoning-effort-to-thinking-budget table) were signed off 2026-09-28 and\nare shipped as designed. Live validation against real Codex traffic is blocked on account\nquota until 2026-10-03 and is tracked separately (see the \"Live validation\" checklist in\ndocs/features/codex-proxy-support.md). Produced by 22 parallel agents\n(6 grounding, 8 design, 8 adversarial review) plus 8 resolution passes; every design\nclaim below was checked against the code by a reviewer whose brief was to refute it.\n\nGoal\n\nA native Codex client request that its own account pool cannot serve currently fails to the\nclient. This design gives the Codex engine an outbound fallback chain to the Anthropic OAuth\npool and to Vertex, so the request is served instead — with prompt caching preserved across\nthe hop, tool calls translated faithfully in both directions, and no client-visible protocol\nanomaly.\n\nEvidence\nsrc/lib/server/routes/codexProxyRoutes.ts has no outbound fallback. Its only fallback\n references are a string-default helper (line 138) and code recognising when the Codex engine\n is itself invoked as an inner Anthropic fallback (lines 666, 817, CODEX_FALLBACK_METADATA_KEY\n at 109). On pool exhaustion it returns a terminal error: buildCodexErrorResponse\n (1458-1462), or buildCodexQuotaExhaustedResponse (909-914) when every account is already\n cooling.\nThree directions exist and work: Claude Code → Anthropic pool; Claude/Anthropic → Codex\n (executeClaudeCodexFallback, claudeProxyRoutes.ts:5499, called at 6131, in-process — no\n HTTP hop); Claude/Anthropic → Vertex (vertexAnthropicFallback.ts:611, gated on\n isVertexAnthropicModel, 576).\nThree do not exist: Vertex → Anthropic, native Codex → Anthropic, native Codex →\n Vertex. PR #1782 fixed translation defects in the existing Claude → Vertex direction; it\n did not add a reverse path.\nCodex prompt caching is live but partial. proxy_tokens_cache_read by model:\n gpt-5.6-sol 566,657,536; gpt-6-astra 3,285,888; gpt-5.6-terra 490,752;\n gpt-5.6-luna 5,888. proxy_tokens_cache_creation is 0 for every Codex series, which is\n correct — OpenAI caching is automatic and has no explicit write.\nThe caching asymmetry is the central design constraint. Anthropic caching is explicit:\n up to 4 cache_control breakpoints over a tools → system → messages prefix, write at\n 1.25x base input (5-minute TTL) or 2x (1-hour), read at 0.1x, and each account holds its\n own cache. A Codex request translated to Anthropic arrives with no breakpoints at all,\n so it would be cached not at all unless the translator inserts them deliberately.\nSince Claude-on-Vertex speaks the Anthropic Messages wire format, one Codex→Anthropic\n translator covers both targets. There is no smaller Vertex-only variant of this work.\n\nBuild order\n\nDependency-ordered; each step is one reviewable PR under the one-commit-per-PR policy. Default\noff at every step, so behaviour is byte-for-byte unchanged until the final enable.\n\n| # | PR | Depends on | Gate |\n| --- | ------------------------------------------------------------------- | ---------- | -------------------------------------------- |\n| 1 | IR types + codec contract (src/lib/types/proxyIR.ts) | — | typecheck; exhaustiveness is a compile error |\n| 2 | Request codec Codex → Anthropic (codexOutboundFallback.ts) | 1 | offline shape suite |\n| 3 | Response/stream codec Anthropic → Codex | 1 | offline event-order suite |\n| 4 | Tool fidelity + id correlation | 2, 3 | offline round-trip suite |\n| 5 | Cache breakpoints + prefix byte-stability (codexOutboundCache.ts) | 2 | byte-stability assertion across two turns |\n| 6 | Trigger policy, commitment rule, loop prevention, accounting | 2-4 | totality + commitment suites |\n| 7 | Config keys, kill switch, metrics | 6 | config valida","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback — Design","lvl2":"","lvl3":""}},
11245
+ {"objectID":"6034ef59cb5b3d2520fa92f2f181aca534a58aa28338f3411aa9d28d998e487e","title":"Codex Outbound Fallback β€” Design","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#codex-outbound-fallback-design","content":"Status: design complete; implementation shipped on feat/codex-outbound-fallback-rollout\nin four staged local commits (build order PRs 1-8, landed as one branch rather than eight\nseparate PRs β€” see the branch's own commit history for the stage-by-stage breakdown: cache\nand request/response codecs, config + kill switch, trigger policy + route wiring + loop\nprevention + accounting, then this stage's test-matrix closure and docs). The feature ships\nOFF by default, gated end-to-end by routing.codex-outbound-fallback-enabled /\nNEUROLINK_PROXY_CODEX_OUTBOUND_FALLBACK; turning it on for the live proxy is a separate,\noperator-approved config change, not part of this work. All three items previously flagged\n\"needs product sign-off\" below (grammar/custom-tool wrapping, the {input: raw} history\nfallback, and the reasoning-effort-to-thinking-budget table) were signed off 2026-09-28 and\nare shipped as designed. Live validation against real Codex traffic is blocked on account\nquota until 2026-10-03 and is tracked separately (see the \"Live validation\" checklist in\ndocs/features/codex-proxy-support.md). Produced by 22 parallel agents\n(6 grounding, 8 design, 8 adversarial review) plus 8 resolution passes; every design\nclaim below was checked against the code by a reviewer whose brief was to refute it.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Codex Outbound Fallback β€” Design","lvl3":""}},
11235
11246
  {"objectID":"580c5a06163d1e603bb34f5f3b51c78e4efa36e12426cf22b63706680af0ea56","title":"Goal","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#goal","content":"A native Codex client request that its own account pool cannot serve currently fails to the\nclient. This design gives the Codex engine an outbound fallback chain to the Anthropic OAuth\npool and to Vertex, so the request is served instead β€” with prompt caching preserved across\nthe hop, tool calls translated faithfully in both directions, and no client-visible protocol\nanomaly.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Goal","lvl3":""}},
11236
11247
  {"objectID":"ed180fb4ca61ad0027c4359503ac6ea54b391281020d32b2a9b498cd6c83abeb","title":"Evidence","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#evidence","content":"src/lib/server/routes/codexProxyRoutes.ts has no outbound fallback. Its only fallback\n references are a string-default helper (line 138) and code recognising when the Codex engine\n is itself invoked as an inner Anthropic fallback (lines 666, 817, CODEX_FALLBACK_METADATA_KEY\n at 109). On pool exhaustion it returns a terminal error: buildCodexErrorResponse\n (1458-1462), or buildCodexQuotaExhaustedResponse (909-914) when every account is already\n cooling.\nThree directions exist and work: Claude Code β†’ Anthropic pool; Claude/Anthropic β†’ Codex\n (executeClaudeCodexFallback, claudeProxyRoutes.ts:5499, called at 6131, in-process β€” no\n HTTP hop); Claude/Anthropic β†’ Vertex (vertexAnthropicFallback.ts:611, gated on\n isVertexAnthropicModel, 576).\nThree do not exist: Vertex β†’ Anthropic, native Codex β†’ Anthropic, native Codex β†’\n Vertex. PR #1782 fixed translation defects in the existing Claude β†’ Vertex direction; it\n did not add a reverse path.\nCodex prompt caching is live but partial. proxy_tokens_cache_read by model:\n gpt-5.6-sol 566,657,536; gpt-6-astra 3,285,888; gpt-5.6-terra 490,752;\n gpt-5.6-luna 5,888. proxy_tokens_cache_creation is 0 for every Codex series, which is\n correct β€” OpenAI caching is automatic and has no explicit write.\nThe caching asymmetry is the central design constraint. Anthropic caching is explicit:\n up to 4 cache_control breakpoints over a tools β†’ system β†’ messages prefix, write at\n 1.25x base input (5-minute TTL) or 2x (1-hour), read at 0.1x, and each account holds its\n own cache. A Codex request translated to Anthropic arrives with no breakpoints at all,\n so it would be cached not at all unless the translator inserts them deliberately.\nSince Claude-on-Vertex speaks the Anthropic Messages wire format, one Codexβ†’Anthropic\n translator covers both targets. There is no smaller Vertex-only variant of this work.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Evidence","lvl3":""}},
11237
11248
  {"objectID":"a5aa19ce68fa1175c4bade50ae303cc0a954bf1128588e294af993c049e2f466","title":"Build order","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#build-order","content":"Dependency-ordered; each step is one reviewable PR under the one-commit-per-PR policy. Default\noff at every step, so behaviour is byte-for-byte unchanged until the final enable.\n\n| # | PR | Depends on | Gate |\n| --- | ------------------------------------------------------------------- | ---------- | -------------------------------------------- |\n| 1 | IR types + codec contract (src/lib/types/proxyIR.ts) | β€” | typecheck; exhaustiveness is a compile error |\n| 2 | Request codec Codex β†’ Anthropic (codexOutboundFallback.ts) | 1 | offline shape suite |\n| 3 | Response/stream codec Anthropic β†’ Codex | 1 | offline event-order suite |\n| 4 | Tool fidelity + id correlation | 2, 3 | offline round-trip suite |\n| 5 | Cache breakpoints + prefix byte-stability (codexOutboundCache.ts) | 2 | byte-stability assertion across two turns |\n| 6 | Trigger policy, commitment rule, loop prevention, accounting | 2-4 | totality + commitment suites |\n| 7 | Config keys, kill switch, metrics | 6 | config validation suite |\n| 8 | Enable + live model matrix | all | live pass criteria |","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Build order","lvl3":""}},
@@ -11252,7 +11263,7 @@
11252
11263
  {"objectID":"68bb4710fa66eaf0eb9924248640f292aa7e1a11e01fce9dd0ae985692eac0c7","title":"Files","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#files","content":"| Path | Action | Purpose |\n| ------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| src/lib/types/proxyIR.ts | create | Canonical wire-neutral request/response IR types + 4-capability codec contract |\n| src/lib/types/codex.ts | modify | Add CodexNative* types, additive only; existing types untouched |\n| src/lib/types/index.ts | modify | Add export * from \"./proxyIR.js\"; per barrel-only rule 10 |\n| src/lib/proxy/proxyIR.ts | create | assertProxyIRExhaustive, assertProxyIRRequestMappable, ProxyIRUnmappableRequestError,","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Files","lvl3":""}},
11253
11264
  {"objectID":"ed2d1dd1f2268b6ea569df738cb11af721dea28a0871c007992141dd05a480a4","title":"Tests","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#tests","content":"All assertions below live in one new file, test/continuous-test-suite-proxy-ir-codec.ts β€” a new determinism-exception suite (pure translation-table functions; header names the exception per the repo's own convention documented for the vector-store suites):\nGolden-fixture round-trip for all 4 test/fixtures/codex-request-*.json: codexNativeRequestCodec.parseRequest β†’ ProxyIRRequest β†’ anthropicMessagesIRCodec.buildRequest β†’ exactly one cache_control marker present on the last system-derived ClaudeTextBlock, and 1:1 tool/message order preservation (array length and per-index role/kind match input).\nA custom_grammar tool with a multi-token grammar (a synthetic fixture variant, not exec) must throw ProxyIRUnmappableRequestError before buildRequest/parseResponseStream is ever called β€” assert via a spy/counter that the downstream call was never invoked, not just that the throw happened.\nexec's function_call (arguments: \"ls -la\") from codex-request-tool-result-turn.json renders through anthropicMessagesIRCodec as ClaudeToolUseBlock.input = {[UNPARSED_TOOL_ARGUMENTS_KEY]: \"ls -la\"} β€” assert the exact key and value, never an empty object and never a thrown error.\nrequest_review (from any fixture's collaboration array) is classified as ProxyIRFunctionToolDeclaration, not ProxyIRCustomGrammarToolDeclaration β€” assert tool.kind === \"function\" and tool.parametersSchema matches its fixture parameters object verbatim.\nThe 4 contiguous developer messages in codex-request-exec-mode.json map to 4 distinct ClaudeTextBlock entries in ClaudeRequest.system (array length 4), never one joined string.\nThe 2 consecutive user messages in codex-request-tool-result-turn.json map to 2 separate ClaudeMessage entries (array length 2 at that position), not one merged message.\nExhaustiveness: a ProxyIRContentPart variant added to the union without a matching case in every consumer switch must fail tsc, verified by a // @ts-expect-error fixture asserting assertProxyIRExhaustive is the reachable default ","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Tests","lvl3":""}},
11254
11265
  {"objectID":"4a5660476c63fddef96520cc9eea37fdd9fb8bcad379ba2ddec1e7ff23716c67","title":"Dependencies on other sections (named, unresolved here by design)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#dependencies-on-other-sections-named-unresolved-here-by-design","content":"cache-anthropic / cache-codex-prefix (session-affinity): consumes originSessionId/originThreadId/promptCachePrefixKey; this section only guarantees those values survive parseRequest, and owns the cross-turn ProxyIRToolCallIdMap persistence question left UNRESOLVED above.\ncache-vertex: must confirm the same-wire-codec-as-Anthropic simplification holds; if Vertex ever needs a field Anthropic's wire lacks, that assumption breaks and Vertex needs its own thin adapter.\nFailure-taxonomy/commit-boundary design: owns exactly where ProxyIRUnmappableRequestError is checked relative to ProxyContextPreflightError/getProxyTokenBudgetError in proxyTranslationEngine.ts, and the pre-commit/post-commit split for the new response stream β€” parseResponseStream is a plain AsyncGenerator specifically so a caller can peek-then-commit like existing preflight stream buffering.\nproxy-waste (fallback telemetry): the new Codex-outbound leg needs its own attempt-recording call (zero call sites exist today in codexProxyRoutes.ts, confirmed via grep); this section only notes the codec boundary is the natural place to hang it.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Dependencies on other sections (named, unresolved here by design)","lvl3":""}},
11255
- {"objectID":"f3c9478912b34e48a4808d6325e810e74f96737c04909bcf2a0b8611caaa3bf3","title":"Risks (kept, one added)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#risks-kept-one-added","content":"Native Codex requests may not always place additional_tools as the single first input item β€” 4 synthetic, not live-captured, fixtures only.\nReasoning-effort-to-thinking-budget mapping has no existing precedent here; any bucketing table is new policy needing owner sign-off, not decided here.\nLeaving codexFallback.ts unrefactored means two independently maintained tool_choice/reasoning bijections exist until the named follow-up PR.\nTier-3 thinking-to-Codex rendering has no verified real wire target; shipping a guess risks an SSE frame no real Codex CLI understands β€” worse than withholding, so withholding stays the default.\nAdded: the collaboration-bucket/grammar-field distinction (this section's own code-check finding) is grounded in only 4 fixtures with only 2 collaboration tools (exec, request_review); a third collaboration tool with a different shape (parameters AND grammar both present, or a multi-token grammar) is unverified and needs isDegradableSingleArgGrammar to handle it explicitly rather than silently misclassifying it as a plain function.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Risks (kept, one added)","lvl3":""}},
11266
+ {"objectID":"f3c9478912b34e48a4808d6325e810e74f96737c04909bcf2a0b8611caaa3bf3","title":"Risks (kept, one added)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#risks-kept-one-added","content":"Native Codex requests may not always place additional_tools as the single first input item — 4 synthetic, not live-captured, fixtures only.\nReasoning-effort-to-thinking-budget mapping has no existing precedent here; any bucketing table was new policy needing owner sign-off. Settled 2026-09-28: low→4096, medium→8192, high→16384, xhigh→24576, max→32768, clamped to [1024, max_tokens-1024]; thinking is left off on a forced tool_choice or when the last assistant turn called a tool. Shipped in mapCodexReasoningToThinking (src/lib/proxy/codexOutboundFallback.ts).\nLeaving codexFallback.ts unrefactored means two independently maintained tool_choice/reasoning bijections exist until the named follow-up PR.\nTier-3 thinking-to-Codex rendering has no verified real wire target; shipping a guess risks an SSE frame no real Codex CLI understands — worse than withholding, so withholding stays the default.\nAdded: the collaboration-bucket/grammar-field distinction (this section's own code-check finding) is grounded in only 4 fixtures with only 2 collaboration tools (exec, request_review); a third collaboration tool with a different shape (parameters AND grammar both present) is unverified. Settled 2026-09-28 (ruling 7): tools are classified by their own type, never by bucket (mapCodexToolDeclarationToClaude), and every type:\"custom\" tool, whatever its grammar, is wrapped as a single {input: string} parameter; no isDegradableSingleArgGrammar guard ships (see the superseded note under \"Blocker resolved\" above).","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback — Design","lvl2":"Risks (kept, one added)","lvl3":""}},
11256
11267
  {"objectID":"b735a043f1f3af7383db3a2ba9533c4837eee9bb9a838d781a546da7e823afaf","title":"Request translation: Codex Responses request β†’ ClaudeRequest","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#request-translation-codex-responses-request-clauderequest","content":"Recovered section for \"Codex outbound fallback\" (native Codex client β†’ Anthropic/Vertex fallback). Grounded against /Users/sachinsharma/Developer/Official/fix/proxy-ci-gaps-and-dead-leg-alert (read-only).","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Request translation: Codex Responses request β†’ ClaudeRequest","lvl3":""}},
11257
11268
  {"objectID":"712eb4de989934c49bc5d08130e851855d1f6f76130b968f28e06e244cb3bc2c","title":"0. Boundary","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#0-boundary","content":"Owns one thing: untrusted inbound Codex Responses JSON β†’ a typed ClaudeRequest. Does not own: target/model selection (orchestration section supplies target: {provider, model}), response/stream translation back to Codex wire format (sibling section owns ClaudeThinkingBlock, ClaudeStreamSerializer), tool execution, or dispatch/HTTP. Vertex/Anthropic dispatch is named in Β§6 only to fix a wrong hand-off found in review; this section still does not own it.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"0. Boundary","lvl3":""}},
11258
11269
  {"objectID":"44d4e6a3c5e7beda3f95d4ab3e78954b9616be8ae4d49d5c947efde1b75af32b","title":"1. Ground truth (re-verified directly against test/fixtures/codex-request-{interactive-mode,tool-result-turn,resumed-session,exec-mode}.json, generated by scripts/generate-codex-fixtures.mjs)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#1-ground-truth-re-verified-directly-against-testfixturescodex-request-interactive-modetool-result-turnresumed-sessionexec-modejson-generated-by-scriptsgenerate-codex-fixturesmjs","content":"Top level: { model, stream, store, input } (+ session_id/thread_id only on codex-request-resumed-session.json). No top-level tools/instructions in any of the 4.\ninput is flat. Tool declarations ride inside one { type: \"additional_tools\", role: \"developer\", tools: { functions, collaboration } } item, never a top-level field.\n4 separate developer-role message items per fixture (persona / sandbox policy / tool-usage rule / output-format rule), all before the first user item in all 4 fixtures.\nEvery fixture has two consecutive user items (real prompt, then a synthetic <environment_context>...</environment_context> message) β€” the concrete proof that the flat stream violates Anthropic's strict user/assistant alternation unless coalesced.\nContent parts observed: input_text only, in all 4 fixtures β€” 0 occurrences of output_text or input_image, and 0 assistant-role message items. Correction to the first design pass, which claimed all three CodexContentPart variants were \"confirmed against the fixtures\": only input_text is fixture-confirmed; output_text/input_image handling is carried over from the pre-existing CodexContentPart type (src/lib/types/codex.ts:144-147) by structural assumption, not fixture evidence. Flagged in Β§10.\ntest/fixtures/codex-request-tool-result-turn.json (not exec-mode.json β€” corrected misread: exec-mode.json contains no function_call item at all, verified by listing its input array) is the one fixture with a function_call/function_call_output pair: {\"type\":\"function_call\",\"call_id\":\"call_synthetic_0001\",\"name\":\"exec\",\"arguments\":\"ls -la\"} β€” a raw shell string, not JSON.\nadditional_tools.tools.collaboration is a mixed-shape array, verified in codex-request-interactive-mode.json: entry 1 is {\"name\":\"exec\",\"type\":\"custom\",\"description\":...,\"grammar\":\"start: command\\n...\"} (no JSON schema); entry 2 is {\"name\":\"request_review\",\"description\":...,\"parameters\":{\"type\":\"object\",\"properties\":{\"summary\":{\"type\":\"string\"}},\"required\":[\"summary\"],\"additionalPropert","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"1. Ground truth (re-verified directly against test/fixtures/codex-request-{interactive-mode,tool-result-turn,resumed-session,exec-mode}.json, generated by scripts/generate-codex-fixtures.mjs)","lvl3":""}},
@@ -11271,7 +11282,7 @@
11271
11282
  {"objectID":"d90f2bab67865a3271d300b6ec12f3a2412ac5f481479abe2d9cb1259dc59ed3","title":"7. Determinism ledger (cache-prefix stability), with the corrected marker claim","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#7-determinism-ledger-cache-prefix-stability-with-the-corrected-marker-claim","content":"Tool array order: namespace order, then declaration order within each namespace, via nested .flatMap(), never through a name-keyed Map (corrected β€” Β§3.4).\nMessage/system order preserved 1:1 from input; coalescing merges only adjacent same-role items.\ntool_use.id/tool_result.tool_use_id = verbatim Codex call_id β€” never generateToolUseId() (src/lib/proxy/claudeFormat.ts:35-37, randomBytes-based, confirmed non-deterministic); this module imports nothing from it.\ngenerateMessageId() (claudeFormat.ts:28) is response-serializer-only, never imported here.\nsystem must be array-form, never a string β€” minor finding resolved on where the marker actually lands: applyClaudeRequestCacheBreakpoints (src/lib/utils/anthropicCacheBreakpoints.ts:217-271) marks the last system block whenever Array.isArray(out.system) && out.system.length > 0 (line 244), and only falls through to marking the last tool when system is absent/empty (line 252) β€” a branch that is unreachable for any translated request here, since every genuine Codex request carries β‰₯1 developer message and this design always emits array-form system. The first design pass's ledger framed tool-array order as the operative cache-key constraint in the common case; corrected: for real Codex-origin traffic the marker lands on the last system block, and tool-array order matters only for the (here, unreachable) empty-system path. Tool-array ordering (point 1) still matters for its own sake (a stable tools array is part of what gets hashed into the prefix even when the breakpoint itself sits on system).\nNo cache-key derivation on this leg β€” codexPromptCacheKey/codexCachePrefix exist only for the other (Claudeβ†’Codex, OpenAI-caching) direction; Anthropic's caching is marker-based and needs none invented here.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"7. Determinism ledger (cache-prefix stability), with the corrected marker claim","lvl3":""}},
11272
11283
  {"objectID":"b2e2c7923039d9a884b29933f4a378d010bab5bc9c58da7b27b2029a27b82b7f","title":"8. CI gating gap found by grounding (not in either design or review pass)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#8-ci-gating-gap-found-by-grounding-not-in-either-design-or-review-pass","content":"The design's file list creates test/continuous-test-suite-codex-outbound-translation.ts but never wires it to run. scripts/run-proxy-reliability.mjs's checks array is a hardcoded list β€” a new file in test/ runs in CI only if added there (or to another script+workflow step). Precedent: this exact wrapper's own comment explains it added test/continuous-test-suite-vertex-anthropic-fallback.ts for the identical reason (\"It had no npm script and no workflow reference, so its 51 cases never ran anywhere\"). This section's suite must be added the same way:\n\nscripts/run-proxy-reliability.mjs (modify): add [\"tsx\", \"test/continuous-test-suite-codex-outbound-translation.ts\"] to the checks array (same pattern as the vertex-anthropic-fallback entry, with a comment stating why it's not proxy-*-templated). This keeps it gated by test:proxy-reliability (package.json:170) β†’ .github/workflows/ci.yml:445 inside provider-safety-net-shards (matrix rest) β†’ required provider-safety-net job, satisfying the repo's \"npm script AND workflow runs it\" gating rule.\n\nscripts/generate-codex-fixtures.mjs (modify): none of the 4 existing fixtures exercise N-parallel tool calls. Generalize the two zero-arg helpers with defaults that reproduce today's output byte-for-byte (functionCallItem(callId = \"call_synthetic_0001\", name = \"exec\", args = \"ls -la\"), functionCallOutputItem(callId = \"call_synthetic_0001\", output = JSON.stringify({stdout:\"total 0\\ndrwxr-xr-x 2 codex staff 64 Jan 1 00:00 .\\n\",stderr:\"\",exit_code:0}))), then append one new fixtures[] entry, codex-request-parallel-tool-calls.json, with extraInputItems: [functionCallItem(\"call_synthetic_0001\",\"exec\",\"ls -la\"), functionCallItem(\"call_synthetic_0002\",\"read_file\", JSON.stringify({path:\"src/index.ts\"})), functionCallOutputItem(\"call_synthetic_0001\"), functionCallOutputItem(\"call_synthetic_0002\", JSON.stringify({content:\"export {};\\n\"}))] β€” 2 consecutive function_call then 2 consecutive function_call_output, one JSON args one non-JSON args","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"8. CI gating gap found by grounding (not in either design or review pass)","lvl3":""}},
11273
11284
  {"objectID":"0786d2230d6d50da3ac9c06fba3f2135868a43e69c7e525bf5ebfec680214899","title":"9. Tests","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#9-tests","content":"All in test/continuous-test-suite-codex-outbound-translation.ts (new; #!/usr/bin/env tsx, plain test()/asyncTest() + passed counter + console.log(\"Passed: N; Failed: 0; RESULT: PASS\") pattern, matching test/continuous-test-suite-vertex-anthropic-fallback.ts):\nparseCodexNativeRequest returns ok:false, code:\"MALFORMED_REQUEST\" for a body missing input, and ok:true for all 5 fixture bodies (4 existing + new parallel-tool-calls one).\nFeed all 5 fixtures through translateCodexRequestToClaude twice each; assert JSON.stringify equality both times (determinism).\nAssert .value.system is always ClaudeTextBlock[], never a string, for every fixture (cache-breakpoint array-gate dependency, Β§7 point 5).\ncodex-request-interactive-mode.json: assert its two consecutive user items collapse into one Claude message with exactly two content blocks (alternation invariant), and that its namespace's mixed-kind declarations map to two ClaudeTools where the type:\"custom\" entry's input_schema is the single-input-string fallback schema and the type:\"function\" entry's input_schema equals its original parameters object byte-for-byte (per-declaration-type split, Β§3.4). Depends on the fixture regeneration tool-fidelity Β§10 test #26 already tracks β€” today's test/fixtures/codex-request-*.json still use the flat, pre-correction {functions, collaboration} shape (confirmed \"still wrong relative to the real capture\"), so this assertion cannot pass until those fixtures are regenerated to the namespace-array shape; it is not a new gap introduced here.\nNew codex-request-parallel-tool-calls.json: assert the 2 consecutive function_call items become one assistant message with 2 tool_use blocks whose ids are call_synthetic_0001/call_synthetic_0002 verbatim, and the 2 function_call_output items become one user message with 2 tool_result blocks whose tool_use_ids match; assert call_synthetic_0001's parsed input is {input: \"ls -la\"} and call_synthetic_0002's is {path: \"src/index.ts\"} (both JSON and non-JSON argum","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"9. Tests","lvl3":""}},
11274
- {"objectID":"1c25d7cf90dac3c24fe3aacba825502c32e23103138a39f3c4715f7e40eb8c4c","title":"10. Explicitly missing facts","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#10-explicitly-missing-facts","content":"UNRESOLVED: whether additional_tools/developer items can appear anywhere other than before the first user item in real traffic — the one check that would settle it: capture one real Codex CLI multi-turn session with a mid-conversation system nudge (e.g. a compaction event) and inspect its input ordering.\nUNRESOLVED: whether a resumed-session request's input is genuinely full-history or incremental — the one check that would settle it: capture one real resumed-session Codex Responses request (the referenced ~/.neurolink/reference/codex-cli-wire-sample.json is not available in this environment) and compare its input.length against the same session's prior turn.\nUNRESOLVED: whether tool_choice or reasoning ever appear on genuine inbound requests at all (absent from all 4 fixtures) — the one check that would settle it: grep captured production Codex traffic logs (once available) for either key.\nUNRESOLVED: whether more than one additional_tools item can appear in one request — the one check that would settle it: same real-traffic capture as (1).\nProduct sign-off still needed on the lossy grammar→single-string-parameter mapping in §3.4 (behavior-changing, not just encoding) and on the {input: raw} non-JSON fallback's downstream effect on tool execution (owned by the tool-fidelity section).","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback — Design","lvl2":"10. Explicitly missing facts","lvl3":""}},
11285
+ {"objectID":"1c25d7cf90dac3c24fe3aacba825502c32e23103138a39f3c4715f7e40eb8c4c","title":"10. Explicitly missing facts","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#10-explicitly-missing-facts","content":"UNRESOLVED: whether additional_tools/developer items can appear anywhere other than before the first user item in real traffic — the one check that would settle it: capture one real Codex CLI multi-turn session with a mid-conversation system nudge (e.g. a compaction event) and inspect its input ordering.\nUNRESOLVED: whether a resumed-session request's input is genuinely full-history or incremental — the one check that would settle it: capture one real resumed-session Codex Responses request (the referenced ~/.neurolink/reference/codex-cli-wire-sample.json is not available in this environment) and compare its input.length against the same session's prior turn.\nUNRESOLVED: whether tool_choice or reasoning ever appear on genuine inbound requests at all (absent from all 4 fixtures) — the one check that would settle it: grep captured production Codex traffic logs (once available) for either key.\nUNRESOLVED: whether more than one additional_tools item can appear in one request — the one check that would settle it: same real-traffic capture as (1).\nSIGNED OFF 2026-09-28 on the lossy grammar→single-string-parameter mapping in §3.4 (behavior-changing, not just encoding) and on the {input: raw} non-JSON fallback's downstream effect on tool execution (owned by the tool-fidelity section). Both ship as designed; see ruling 7 in the stage rulings record.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback — Design","lvl2":"10. Explicitly missing facts","lvl3":""}},
11275
11286
  {"objectID":"bf904f3a68ba7f893922b7bfb2cbc7f96184272624bef1401a06de08af93bb14","title":"11. Files this section touches","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#11-files-this-section-touches","content":"src/lib/types/codex.ts (append types, per the tool-fidelity section's Β§8 β€” see the reconciliation note above), src/lib/proxy/codexOutboundFallback.ts (create), scripts/generate-codex-fixtures.mjs (generalize two helpers, add one fixture), test/fixtures/codex-request-parallel-tool-calls.json (generated, not hand-authored), test/continuous-test-suite-codex-outbound-translation.ts (create), scripts/run-proxy-reliability.mjs (add the new suite to checks), eslint.config.js (add the new suite to neurolink/e2e-tests-only allow). The config key lives in src/lib/proxy/proxyConfig.ts/runtimeConfig.ts per the rollout-and-config section's codex-outbound-fallback-{enabled,targets,model-mappings}, not src/lib/types/subscription.ts/codexFallbackChain? as this subsection originally proposed.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"11. Files this section touches","lvl3":""}},
11276
11287
  {"objectID":"9071e70cbb3446b69f045596793feee6859618efbc074d93e1a96029486e268c","title":"Response translation: Anthropic β†’ Codex wire format","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#response-translation-anthropic-codex-wire-format","content":"Wire-level translation from an Anthropic-shaped upstream result (ClaudeResponse JSON, or the\nAnthropic SSE vocabulary message_start/content_block_start/content_block_delta/content_block_stop/\nmessage_delta/message_stop/ping) into the Codex Responses shape a native Codex client expects\n(response.* SSE events, or one non-streaming Response JSON body). Out of scope: engine selection /\nfallback trigger, OAuth/account selection, retry/cooldown policy, analytics recording β€” owned by sibling\nsections proxy-waste, cache-anthropic, cache-vertex. Assumes an outer route already has an\nAnthropic-shaped Response (from the Claude Code OAuth pool or Vertex) to translate; this section does not\ntouch src/lib/server/routes/codexProxyRoutes.ts.","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"Response translation: Anthropic β†’ Codex wire format","lvl3":""}},
11277
11288
  {"objectID":"9d2410fda9c55c78bd2184a368781a456c353350ac9ae2154480c17eb42f00af","title":"1. Grounded facts (verified against /Users/sachinsharma/Developer/Official/fix/proxy-ci-gaps-and-dead-leg-alert)","url":"/docs/superpowers/specs/2026-09-27-codex-outbound-fallback-design#1-grounded-facts-verified-against-userssachinsharmadeveloperofficialfixproxy-ci-gaps-and-dead-leg-alert","content":"ClaudeUsage (src/lib/types/proxy.ts:177) = { input_tokens, output_tokens, cache_creation_input_tokens?, cache_read_input_tokens? }. ClaudeThinkingBlock (:118) = { type:\"thinking\"; thinking: string }, no signature. ClaudeContentBlock (:124) union has no refusal variant. ClaudeToolUseBlock.input (:103) is Record<string, unknown>, never undefined β€” so JSON.stringify(block.input ?? {}) is defensive-only, not load-bearing.\nCorrected citation (was Blocker, resolved): message_start carries input_tokens/cache_creation_input_tokens/cache_read_input_tokens; message_delta carries only the running output_tokens total. Verified directly in this repo's own reader for the identical Anthropic-shape wire, readUsageEvent (src/lib/proxy/vertexAnthropicFallback.ts:669-696): it sets input+cache fields only inside if (event.type === \"message_start\"), and unconditionally overwrites outputTokens from message_delta/message_start's usage.output_tokens. The original draft of this section called message_start a no-op and read cache fields from message_delta; that was backwards and would have zeroed cache accounting on every response. Fixed in Β§4/Β§5 below.\nSSEContentBlockDescriptor (proxy.ts:203-206) gives tool id/name at content_block_start, so the Codex function_call item can be opened immediately, not deferred to block close.\nSSEDeltaDescriptor (proxy.ts:208-212) = text_delta | thinking_delta | input_json_delta; input_json_delta.partial_json is a plain string, not individually valid JSON β€” confirmed against this repo's own reverse-direction parser, parseFunctionArguments (src/lib/proxy/codexFallback.ts:466-484), which requires a fully-accumulated string before JSON.parse. A serializer method that validates JSON per-chunk cannot forward these incrementally; Β§3 fixes this with a raw-chunk delta method (was Major finding).\nSSEPing (proxy.ts:266-268) is a real member of SSEEvent, and this repo's own emitter proves it is not rare: ClaudeStreamSerializer.ensureMessageStarted (src/lib/proxy/claudeF","hierarchy":{"lvl0":"Superpowers","lvl1":"Codex Outbound Fallback β€” Design","lvl2":"1. Grounded facts (verified against /Users/sachinsharma/Developer/Official/fix/proxy-ci-gaps-and-dead-leg-alert)","lvl3":""}},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.40.2",
3
+ "version": "12.41.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "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/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI Β· Anthropic Β· Gemini Β· Bedrock Β· Azure Β· Ollama Β· TypeSafe Jev and more.",
6
6
  "author": {
@@ -685,7 +685,7 @@
685
685
  "ajv@>=8.0.0 <8.18.0": ">=8.18.0",
686
686
  "@grpc/grpc-js@<1.14.4": ">=1.14.4",
687
687
  "@protobufjs/utf8@<1.1.1": ">=1.1.1",
688
- "fast-uri@<3.1.7": ">=3.1.7 <4",
688
+ "fast-uri@<3.1.8": ">=3.1.8 <4",
689
689
  "ip-address@<10.1.1": ">=10.1.1",
690
690
  "basic-ftp@<5.2.2": ">=5.2.2",
691
691
  "fast-xml-builder@<1.1.7": ">=1.1.7",
@@ -696,11 +696,11 @@
696
696
  "markdown-it@<14.2.0": ">=14.2.0",
697
697
  "@xmldom/xmldom@<0.9.12": ">=0.9.12",
698
698
  "protobufjs@<7.6.5": ">=7.6.5",
699
- "brace-expansion@>=5.0.0 <5.0.9": ">=5.0.9",
699
+ "brace-expansion@>=5.0.0 <5.0.12": ">=5.0.12",
700
700
  "@opentelemetry/core@>=2.0.0 <2.8.0": ">=2.8.0",
701
701
  "qs": "6.16.0",
702
702
  "fastify": "5.12.1",
703
- "fast-uri": "3.1.7",
703
+ "fast-uri": "3.1.8",
704
704
  "@xmldom/xmldom": "0.9.12"
705
705
  },
706
706
  "patchedDependencies": {