@juspay/neurolink 13.8.17 → 13.8.19
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.
- package/CHANGELOG.md +5 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/providers/openaiChatCompletionsClient.js +12 -3
- package/dist/proxy/accountLedger.js +6 -8
- package/dist/proxy/geminiFormat.d.ts +3 -11
- package/dist/proxy/geminiFormat.js +11 -2
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/openaiFormat.d.ts +3 -9
- package/dist/proxy/openaiFormat.js +21 -15
- package/dist/proxy/proxyTranslationEngine.d.ts +2 -6
- package/dist/proxy/proxyTranslationEngine.js +25 -14
- package/dist/proxy/routingPolicy.d.ts +3 -1
- package/dist/proxy/routingPolicy.js +7 -8
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +78 -6
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/openaiCompatible.d.ts +1 -1
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +71 -16
- package/dist/types/proxyClient.d.ts +2 -0
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +206 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/retryAfter.d.ts +32 -0
- package/dist/utils/retryAfter.js +135 -11
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/dist/workflow/workflows/adaptiveWorkflow.d.ts +2 -2
- package/dist/workflow/workflows/adaptiveWorkflow.js +10 -10
- package/dist/workflow/workflows/consensusWorkflow.d.ts +4 -4
- package/dist/workflow/workflows/consensusWorkflow.js +12 -12
- package/dist/workflow/workflows/fallbackWorkflow.d.ts +3 -3
- package/dist/workflow/workflows/fallbackWorkflow.js +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.d.ts +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.js +31 -31
- package/docs-site/static/search-index.json +74 -52
- package/package.json +173 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -45,6 +45,7 @@ import { evaluateResidentAccount, getResidentGrantForAccount, recordResidentSpen
|
|
|
45
45
|
import { buildProvisionClaim, isLeaseRefusal, issueLease, } from "../../proxy/shareLease.js";
|
|
46
46
|
import { forwardToPeer } from "../../proxy/peerTransport.js";
|
|
47
47
|
import { getShareContext } from "../../proxy/shareContext.js";
|
|
48
|
+
import { authorizeInboundRequest, inboundAuthRefusalResponse, isInboundAuthExemptPath, resolveInboundToken, resolveMaxAccountsPerRequest, secretsMatch, warnIfProxyRoutesUnauthenticated, } from "../../proxy/inboundAuth.js";
|
|
48
49
|
import { ProxyImageInputError } from "../../proxy/proxyImageInput.js";
|
|
49
50
|
import { buildShareRefusal, extractShareToken } from "../../proxy/shareGate.js";
|
|
50
51
|
import { debitShareGrantCoins, getNodePublicUrl, getNoteSecret, getShareGrant, resolveShareToken, setShareGrantState, } from "../../proxy/shareGrants.js";
|
|
@@ -69,15 +70,75 @@ import { handleCodexResponsesRequest } from "./codexProxyRoutes.js";
|
|
|
69
70
|
// ---------------------------------------------------------------------------
|
|
70
71
|
// Helpers
|
|
71
72
|
// ---------------------------------------------------------------------------
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Protocol headers a client may influence upstream. Everything else is dropped
|
|
75
|
+
* rather than forwarded: proxy-internal `x-neurolink-*` (a borrower's share
|
|
76
|
+
* token among them), trace context, CDN and forwarding identity, cookies and
|
|
77
|
+
* hop-by-hop headers. An allow-list because the blocklist it replaced leaked
|
|
78
|
+
* every header nobody had thought to name.
|
|
79
|
+
*/
|
|
80
|
+
const FORWARDABLE_UPSTREAM_HEADERS = new Set([
|
|
81
|
+
"accept",
|
|
82
|
+
"accept-encoding",
|
|
83
|
+
"accept-language",
|
|
84
|
+
"anthropic-beta",
|
|
85
|
+
"anthropic-dangerous-direct-browser-access",
|
|
86
|
+
"anthropic-version",
|
|
87
|
+
"content-type",
|
|
88
|
+
"sec-fetch-mode",
|
|
89
|
+
"user-agent",
|
|
90
|
+
"x-app",
|
|
91
|
+
"x-claude-code-session-id",
|
|
92
|
+
"x-subscription-tier",
|
|
80
93
|
]);
|
|
94
|
+
const FORWARDABLE_UPSTREAM_HEADER_PREFIXES = ["x-stainless-"];
|
|
95
|
+
/** Set by the proxy on the pool path, forwarded only in passthrough mode. */
|
|
96
|
+
const CLIENT_CREDENTIAL_HEADERS = new Set(["authorization", "x-api-key"]);
|
|
97
|
+
function isForwardableUpstreamHeader(lowerName) {
|
|
98
|
+
return (FORWARDABLE_UPSTREAM_HEADERS.has(lowerName) ||
|
|
99
|
+
FORWARDABLE_UPSTREAM_HEADER_PREFIXES.some((prefix) => lowerName.startsWith(prefix)));
|
|
100
|
+
}
|
|
101
|
+
/** The subset of client headers that may travel upstream, lower-cased. */
|
|
102
|
+
function filterForwardableUpstreamHeaders(headers, options) {
|
|
103
|
+
const forwarded = {};
|
|
104
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
105
|
+
if (typeof value !== "string" || value === "") {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const lower = name.toLowerCase();
|
|
109
|
+
if (isForwardableUpstreamHeader(lower) ||
|
|
110
|
+
(options.includeClientCredentials && CLIENT_CREDENTIAL_HEADERS.has(lower))) {
|
|
111
|
+
forwarded[lower] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return forwarded;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A request from Claude Code itself: both the session header and the CLI
|
|
118
|
+
* user-agent. The looser `isLikelyClaudeClient` still decides how a request's
|
|
119
|
+
* own `system` blocks are shaped, but nothing is persisted and no client-chosen
|
|
120
|
+
* identity is trusted on anything weaker than this.
|
|
121
|
+
*/
|
|
122
|
+
function isGenuineClaudeCodeClient(headers) {
|
|
123
|
+
return (typeof headers["x-claude-code-session-id"] === "string" &&
|
|
124
|
+
headers["x-claude-code-session-id"] !== "" &&
|
|
125
|
+
(headers["user-agent"]?.startsWith("claude-cli/") ?? false));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The only headers the Codex fallback leg inherits from a `/v1/messages`
|
|
129
|
+
* client: the ones the token-budget session key reads. Nothing else crosses
|
|
130
|
+
* engines, so a Claude client's fingerprint never reaches chatgpt.com.
|
|
131
|
+
*/
|
|
132
|
+
function pickClaudeFallbackHeaders(headers) {
|
|
133
|
+
const picked = {};
|
|
134
|
+
for (const name of ["x-neurolink-session-id", "x-claude-code-session-id"]) {
|
|
135
|
+
const value = headers[name];
|
|
136
|
+
if (typeof value === "string" && value !== "") {
|
|
137
|
+
picked[name] = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return picked;
|
|
141
|
+
}
|
|
81
142
|
const PROXY_INTERNAL_ACCOUNT_LABEL = "proxy/internal";
|
|
82
143
|
const PROXY_INTERNAL_ACCOUNT_TYPE = "internal";
|
|
83
144
|
function resolveRequestLogAccountIdentity(accountLabel, accountType) {
|
|
@@ -2147,9 +2208,7 @@ function applySnapshotHeaders(headers, snapshot) {
|
|
|
2147
2208
|
const lower = sk.toLowerCase();
|
|
2148
2209
|
if (typeof sv === "string" &&
|
|
2149
2210
|
!headers[lower] &&
|
|
2150
|
-
|
|
2151
|
-
lower !== "authorization" &&
|
|
2152
|
-
lower !== "x-api-key" &&
|
|
2211
|
+
isForwardableUpstreamHeader(lower) &&
|
|
2153
2212
|
lower !== "x-claude-code-session-id") {
|
|
2154
2213
|
headers[lower] = sv;
|
|
2155
2214
|
}
|
|
@@ -2173,6 +2232,18 @@ async function loadClaudeSnapshot(accountLabel) {
|
|
|
2173
2232
|
if (!snapshot || typeof snapshot !== "object") {
|
|
2174
2233
|
return null;
|
|
2175
2234
|
}
|
|
2235
|
+
// Only a capture this code made — from a verified Claude Code client —
|
|
2236
|
+
// may contribute an identity tuple. Anything older was written by a
|
|
2237
|
+
// heuristic any caller could satisfy, so its body is dropped and the next
|
|
2238
|
+
// genuine Claude Code request recaptures it.
|
|
2239
|
+
const verifiedCapture = "capturedFrom" in snapshot &&
|
|
2240
|
+
snapshot.capturedFrom === "genuine-claude-code";
|
|
2241
|
+
const { body: sanitizedBody, changed: bodyChanged } = verifiedCapture
|
|
2242
|
+
? sanitizeSnapshotBody(snapshot.body)
|
|
2243
|
+
: {
|
|
2244
|
+
body: undefined,
|
|
2245
|
+
changed: Object.keys(snapshot.body ?? {}).length > 0,
|
|
2246
|
+
};
|
|
2176
2247
|
const normalized = {
|
|
2177
2248
|
accountKey: "accountKey" in snapshot && typeof snapshot.accountKey === "string"
|
|
2178
2249
|
? snapshot.accountKey
|
|
@@ -2181,8 +2252,13 @@ async function loadClaudeSnapshot(accountLabel) {
|
|
|
2181
2252
|
? snapshot.capturedAt
|
|
2182
2253
|
: new Date(0).toISOString(),
|
|
2183
2254
|
source: "claude-code",
|
|
2184
|
-
|
|
2185
|
-
|
|
2255
|
+
...(verifiedCapture ? { capturedFrom: "genuine-claude-code" } : {}),
|
|
2256
|
+
headers: "headers" in snapshot && snapshot.headers
|
|
2257
|
+
? filterForwardableUpstreamHeaders(snapshot.headers, {
|
|
2258
|
+
includeClientCredentials: false,
|
|
2259
|
+
})
|
|
2260
|
+
: {},
|
|
2261
|
+
...(sanitizedBody ? { body: sanitizedBody } : {}),
|
|
2186
2262
|
};
|
|
2187
2263
|
if (Object.keys(normalized.headers).length === 0 &&
|
|
2188
2264
|
Object.keys(normalized.body ?? {}).length === 0) {
|
|
@@ -2192,6 +2268,14 @@ async function loadClaudeSnapshot(accountLabel) {
|
|
|
2192
2268
|
snapshot: normalized,
|
|
2193
2269
|
loadedAt: Date.now(),
|
|
2194
2270
|
});
|
|
2271
|
+
if (bodyChanged) {
|
|
2272
|
+
// A file written by an earlier release may carry prompt text captured
|
|
2273
|
+
// from an arbitrary client. Rewrite it without, so the poison is gone
|
|
2274
|
+
// for the next process as well as this one.
|
|
2275
|
+
void persistClaudeSnapshot(accountLabel, normalized).catch(() => {
|
|
2276
|
+
// Best effort; the in-memory copy is already clean.
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2195
2279
|
return normalized;
|
|
2196
2280
|
}
|
|
2197
2281
|
catch {
|
|
@@ -2204,9 +2288,7 @@ function buildSnapshotHeaders(headers, existingHeaders) {
|
|
|
2204
2288
|
const lower = key.toLowerCase();
|
|
2205
2289
|
if (typeof value === "string" &&
|
|
2206
2290
|
SNAPSHOT_STABLE_HEADERS.has(lower) &&
|
|
2207
|
-
|
|
2208
|
-
lower !== "authorization" &&
|
|
2209
|
-
lower !== "x-api-key" &&
|
|
2291
|
+
isForwardableUpstreamHeader(lower) &&
|
|
2210
2292
|
lower !== "x-claude-code-session-id") {
|
|
2211
2293
|
merged[lower] = value;
|
|
2212
2294
|
}
|
|
@@ -2224,18 +2306,56 @@ function extractSnapshotBody(body) {
|
|
|
2224
2306
|
: typeof parsed.system === "string"
|
|
2225
2307
|
? [{ type: "text", text: parsed.system }]
|
|
2226
2308
|
: [];
|
|
2227
|
-
const
|
|
2309
|
+
const rawBillingHeader = systemBlocks.find((block) => typeof block?.text === "string" &&
|
|
2228
2310
|
block.text.includes("x-anthropic-billing-header"))?.text;
|
|
2229
|
-
const
|
|
2230
|
-
block.text.includes("Claude Agent SDK"))
|
|
2231
|
-
if (!identity && !
|
|
2311
|
+
const hasAgentBlock = systemBlocks.some((block) => typeof block?.text === "string" &&
|
|
2312
|
+
block.text.includes("Claude Agent SDK"));
|
|
2313
|
+
if (!identity && !rawBillingHeader && !hasAgentBlock) {
|
|
2232
2314
|
return undefined;
|
|
2233
2315
|
}
|
|
2316
|
+
// Only the identity tuple, and the billing header reduced to the two
|
|
2317
|
+
// fields the proxy re-emits. No prompt text from the body is ever kept.
|
|
2234
2318
|
return {
|
|
2235
2319
|
...(identity ? { metadataUserId: identity.metadataUserId } : {}),
|
|
2236
2320
|
...(identity ? { sessionId: identity.sessionId } : {}),
|
|
2237
|
-
...(
|
|
2238
|
-
|
|
2321
|
+
...(rawBillingHeader
|
|
2322
|
+
? { billingHeader: buildStableClaudeCodeBillingHeader(rawBillingHeader) }
|
|
2323
|
+
: {}),
|
|
2324
|
+
...(hasAgentBlock ? { hasAgentBlock: true } : {}),
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Drop anything a persisted snapshot body must no longer carry (an earlier
|
|
2329
|
+
* release stored the client's agent-block prompt text) and normalise the rest.
|
|
2330
|
+
*/
|
|
2331
|
+
function sanitizeSnapshotBody(body) {
|
|
2332
|
+
if (!body || typeof body !== "object") {
|
|
2333
|
+
return { body: undefined, changed: false };
|
|
2334
|
+
}
|
|
2335
|
+
const normalisedBilling = typeof body.billingHeader === "string"
|
|
2336
|
+
? buildStableClaudeCodeBillingHeader(body.billingHeader)
|
|
2337
|
+
: undefined;
|
|
2338
|
+
const sanitized = {
|
|
2339
|
+
...(typeof body.metadataUserId === "string"
|
|
2340
|
+
? { metadataUserId: body.metadataUserId }
|
|
2341
|
+
: {}),
|
|
2342
|
+
...(normalisedBilling ? { billingHeader: normalisedBilling } : {}),
|
|
2343
|
+
...(typeof body.sessionId === "string"
|
|
2344
|
+
? { sessionId: body.sessionId }
|
|
2345
|
+
: {}),
|
|
2346
|
+
};
|
|
2347
|
+
const changed = "agentBlock" in body ||
|
|
2348
|
+
(typeof body.billingHeader === "string" &&
|
|
2349
|
+
body.billingHeader !== normalisedBilling) ||
|
|
2350
|
+
Object.keys(body).some((key) => ![
|
|
2351
|
+
"metadataUserId",
|
|
2352
|
+
"billingHeader",
|
|
2353
|
+
"sessionId",
|
|
2354
|
+
"agentBlock",
|
|
2355
|
+
].includes(key));
|
|
2356
|
+
return {
|
|
2357
|
+
body: Object.keys(sanitized).length > 0 ? sanitized : undefined,
|
|
2358
|
+
changed,
|
|
2239
2359
|
};
|
|
2240
2360
|
}
|
|
2241
2361
|
function isLikelyClaudeClient(headers, snapshotBody) {
|
|
@@ -2243,7 +2363,7 @@ function isLikelyClaudeClient(headers, snapshotBody) {
|
|
|
2243
2363
|
headers["user-agent"]?.startsWith("claude-cli/") ||
|
|
2244
2364
|
!!snapshotBody?.metadataUserId ||
|
|
2245
2365
|
!!snapshotBody?.billingHeader ||
|
|
2246
|
-
!!snapshotBody?.
|
|
2366
|
+
!!snapshotBody?.hasAgentBlock);
|
|
2247
2367
|
}
|
|
2248
2368
|
function snapshotsMatch(existing, next) {
|
|
2249
2369
|
if (!existing) {
|
|
@@ -2266,18 +2386,23 @@ async function maybeRefreshClaudeSnapshot(accountLabel, accountKey, headers, bod
|
|
|
2266
2386
|
catch {
|
|
2267
2387
|
return existing;
|
|
2268
2388
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2389
|
+
// Capture only from Claude Code itself, and never from a borrowed request:
|
|
2390
|
+
// whatever lands here is replayed on every later request for this account,
|
|
2391
|
+
// so a body-shaped heuristic (a `system` block mentioning the Agent SDK, a
|
|
2392
|
+
// well-formed `metadata.user_id`) let any caller write it.
|
|
2393
|
+
if (getShareContext() || !isGenuineClaudeCodeClient(headers)) {
|
|
2271
2394
|
return existing;
|
|
2272
2395
|
}
|
|
2396
|
+
const { hasAgentBlock: _hasAgentBlock, ...body } = extractSnapshotBody(parsedBody) ?? {};
|
|
2273
2397
|
const next = {
|
|
2274
2398
|
accountKey,
|
|
2275
2399
|
capturedAt: new Date().toISOString(),
|
|
2276
2400
|
source: "claude-code",
|
|
2401
|
+
capturedFrom: "genuine-claude-code",
|
|
2277
2402
|
headers: buildSnapshotHeaders(headers, existing?.headers),
|
|
2278
2403
|
body: {
|
|
2279
2404
|
...(existing?.body ?? {}),
|
|
2280
|
-
...
|
|
2405
|
+
...body,
|
|
2281
2406
|
...(typeof headers["x-claude-code-session-id"] === "string"
|
|
2282
2407
|
? { sessionId: headers["x-claude-code-session-id"] }
|
|
2283
2408
|
: {}),
|
|
@@ -2370,15 +2495,15 @@ function responseInfoFromStream(data) {
|
|
|
2370
2495
|
* into the body. Non-CC clients (Curator, custom apps) don't send these —
|
|
2371
2496
|
* Anthropic rejects without them.
|
|
2372
2497
|
*/
|
|
2373
|
-
|
|
2498
|
+
/** The agent identity block the OAuth path sends for non-Claude-Code clients.
|
|
2499
|
+
* A compiled-in constant: it is never taken from a request or a snapshot. */
|
|
2500
|
+
const CLAUDE_AGENT_SDK_SYSTEM_TEXT = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
2501
|
+
function polyfillOAuthBody(bodyStr, accountToken, snapshot, isClaudeClientRequest, preferredSessionId, trustClientIdentity = isClaudeClientRequest) {
|
|
2374
2502
|
try {
|
|
2375
2503
|
const parsed = JSON.parse(bodyStr);
|
|
2376
|
-
// Billing header block synthesized for clients that do not already provide
|
|
2377
|
-
// the genuine Claude Code identity shape.
|
|
2378
2504
|
const agentBlock = {
|
|
2379
2505
|
type: "text",
|
|
2380
|
-
text:
|
|
2381
|
-
"You are a Claude agent, built on Anthropic's Claude Agent SDK.",
|
|
2506
|
+
text: CLAUDE_AGENT_SDK_SYSTEM_TEXT,
|
|
2382
2507
|
};
|
|
2383
2508
|
// Normalise system to an array, then route by client type.
|
|
2384
2509
|
//
|
|
@@ -2436,8 +2561,14 @@ function polyfillOAuthBody(bodyStr, accountToken, snapshot, isClaudeClientReques
|
|
|
2436
2561
|
}
|
|
2437
2562
|
// Inject Claude-Code-shaped metadata.user_id (required for OAuth).
|
|
2438
2563
|
const tokenPrefix = accountToken.substring(0, Math.min(20, accountToken.length));
|
|
2564
|
+
// A client-chosen `metadata.user_id` is the device identity this account
|
|
2565
|
+
// presents to Anthropic. Only Claude Code itself gets to pick it; anyone
|
|
2566
|
+
// else inherits the identity derived from the token (or captured from a
|
|
2567
|
+
// genuine Claude Code session on this account).
|
|
2439
2568
|
const identity = getOrCreateClaudeCodeIdentity(tokenPrefix, {
|
|
2440
|
-
existingUserId:
|
|
2569
|
+
existingUserId: trustClientIdentity
|
|
2570
|
+
? (parsed.metadata?.user_id ?? snapshot?.body?.metadataUserId)
|
|
2571
|
+
: snapshot?.body?.metadataUserId,
|
|
2441
2572
|
preferredSessionId: preferredSessionId ?? snapshot?.body?.sessionId,
|
|
2442
2573
|
});
|
|
2443
2574
|
parsed.metadata = {
|
|
@@ -2585,12 +2716,11 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
2585
2716
|
tracer?.setMode("passthrough-cli");
|
|
2586
2717
|
const { bodyStr, preparedContext } = prepareAnthropicWire(ctx, clientRequestBody);
|
|
2587
2718
|
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
}
|
|
2719
|
+
// Passthrough relays the caller's own credential, so authorization and
|
|
2720
|
+
// x-api-key travel here; the protocol allow-list bounds everything else.
|
|
2721
|
+
const upstreamHeaders = filterForwardableUpstreamHeaders(ctx.headers, {
|
|
2722
|
+
includeClientCredentials: true,
|
|
2723
|
+
});
|
|
2594
2724
|
if (!upstreamHeaders["content-type"]) {
|
|
2595
2725
|
upstreamHeaders["content-type"] = "application/json";
|
|
2596
2726
|
}
|
|
@@ -3455,22 +3585,6 @@ async function auditCompleteShareHeartbeat(grant, reportedCoins) {
|
|
|
3455
3585
|
async function recordReportedResidentSpend(grant, coins) {
|
|
3456
3586
|
await debitShareGrantCoins(grant.id, coins);
|
|
3457
3587
|
}
|
|
3458
|
-
/**
|
|
3459
|
-
* Compare a presented secret against the stored one without leaking where they
|
|
3460
|
-
* diverge. Hand-rolled rather than `crypto.timingSafeEqual` for the same reason
|
|
3461
|
-
* as `shareGrants.digestsMatch`: this module is reachable from a build whose
|
|
3462
|
-
* `node:crypto` stub does not carry it.
|
|
3463
|
-
*/
|
|
3464
|
-
function secretsMatch(expected, presented) {
|
|
3465
|
-
if (expected.length !== presented.length || expected.length === 0) {
|
|
3466
|
-
return false;
|
|
3467
|
-
}
|
|
3468
|
-
let difference = 0;
|
|
3469
|
-
for (let index = 0; index < expected.length; index += 1) {
|
|
3470
|
-
difference |= expected.charCodeAt(index) ^ presented.charCodeAt(index);
|
|
3471
|
-
}
|
|
3472
|
-
return difference === 0;
|
|
3473
|
-
}
|
|
3474
3588
|
/**
|
|
3475
3589
|
* Peer wire protocol version. Bumped when a borrower would misread an older
|
|
3476
3590
|
* node's answers — not when a field is added, which every version tolerates.
|
|
@@ -3999,7 +4113,7 @@ async function executeClaudeCodexFallback(args) {
|
|
|
3999
4113
|
method: "POST",
|
|
4000
4114
|
path: "/backend-api/codex/responses",
|
|
4001
4115
|
headers: {
|
|
4002
|
-
...ctx.headers,
|
|
4116
|
+
...pickClaudeFallbackHeaders(ctx.headers),
|
|
4003
4117
|
"content-type": "application/json",
|
|
4004
4118
|
accept: "text/event-stream",
|
|
4005
4119
|
},
|
|
@@ -4742,8 +4856,14 @@ function joinAccountLabels(labels, max = 5) {
|
|
|
4742
4856
|
function buildEntitlementErrorMessage(failure) {
|
|
4743
4857
|
const count = new Set(failure.accounts).size;
|
|
4744
4858
|
const code = failure.errorCode ? ` (${failure.errorCode})` : "";
|
|
4859
|
+
// A borrower is told how many accounts, never which: the labels are the
|
|
4860
|
+
// lender's email addresses.
|
|
4861
|
+
const accounts = getShareContext()
|
|
4862
|
+
? `${count} pooled account${count === 1 ? "" : "s"} (identity withheld for borrowed requests)`
|
|
4863
|
+
: joinAccountLabels(failure.accounts);
|
|
4745
4864
|
return (`All ${count} Anthropic account${count === 1 ? "" : "s"} are blocked by an ` +
|
|
4746
|
-
`organization entitlement policy${code}. Accounts: ${
|
|
4865
|
+
`organization entitlement policy${code}. Accounts: ${accounts}. ` +
|
|
4866
|
+
`Upstream: "${failure.message}". Ask an organization admin to re-enable ` +
|
|
4747
4867
|
`Claude Code OAuth access, then run: neurolink auth enable anthropic:<account>.`);
|
|
4748
4868
|
}
|
|
4749
4869
|
/** Name the subscription window a set of cooldowns is waiting on. */
|
|
@@ -6113,7 +6233,7 @@ function finalizeAnthropicTerminalTransportError(args) {
|
|
|
6113
6233
|
return clientError;
|
|
6114
6234
|
}
|
|
6115
6235
|
async function handleAnthropicNonOkResponse(args) {
|
|
6116
|
-
const { response, account, accountState, enabledAccounts, orderedAccounts, tracer, requestStartTime, fetchStartMs, attemptNumber, logAttempt, logProxyBody, logFinalRequest, lastError, authFailureMessage, sawTransientFailure, invalidRequestFailure, entitlementFailure, allowConfiguredModelFallback = false, } = args;
|
|
6236
|
+
const { response, account, accountState, enabledAccounts, orderedAccounts, clientSuppliedBetaForwarded = false, tracer, requestStartTime, fetchStartMs, attemptNumber, logAttempt, logProxyBody, logFinalRequest, lastError, authFailureMessage, sawTransientFailure, invalidRequestFailure, entitlementFailure, allowConfiguredModelFallback = false, } = args;
|
|
6117
6237
|
let currentLastError = lastError;
|
|
6118
6238
|
let currentAuthFailureMessage = authFailureMessage;
|
|
6119
6239
|
let currentSawTransientFailure = sawTransientFailure;
|
|
@@ -6159,8 +6279,12 @@ async function handleAnthropicNonOkResponse(args) {
|
|
|
6159
6279
|
tracer?.recordRetry(account.label, "beta_unavailable");
|
|
6160
6280
|
// A tier's lack of a beta is stable, not transient — advance the
|
|
6161
6281
|
// fill-first primary pointer (like the auth/rate-limit rotation paths) so
|
|
6162
|
-
// this account stops being retried first on every future request.
|
|
6163
|
-
|
|
6282
|
+
// this account stops being retried first on every future request. Not
|
|
6283
|
+
// when the rejected beta was the client's own addition: one caller must
|
|
6284
|
+
// not be able to re-rank the pool for everyone by naming a beta.
|
|
6285
|
+
if (!clientSuppliedBetaForwarded) {
|
|
6286
|
+
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
|
|
6287
|
+
}
|
|
6164
6288
|
currentLastError = summarizeErrorMessage(errBody);
|
|
6165
6289
|
return {
|
|
6166
6290
|
continueLoop: true,
|
|
@@ -6776,20 +6900,18 @@ async function prepareAnthropicAccountAttempt(args) {
|
|
|
6776
6900
|
const filteredHeaders = {};
|
|
6777
6901
|
for (const [k, v] of Object.entries(clientHeaders)) {
|
|
6778
6902
|
if (typeof v === "string") {
|
|
6779
|
-
filteredHeaders[k] = v;
|
|
6903
|
+
filteredHeaders[k.toLowerCase()] = v;
|
|
6780
6904
|
}
|
|
6781
6905
|
}
|
|
6906
|
+
const genuineClaudeCode = isGenuineClaudeCodeClient(filteredHeaders);
|
|
6782
6907
|
const snapshot = isOAuth
|
|
6783
6908
|
? await maybeRefreshClaudeSnapshot(account.label, account.key, filteredHeaders, bodyStr)
|
|
6784
6909
|
: null;
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
headers[lower] = headerValue;
|
|
6791
|
-
}
|
|
6792
|
-
}
|
|
6910
|
+
// Pool credentials replace the client's, so its authorization/x-api-key
|
|
6911
|
+
// never travel; the allow-list bounds everything else.
|
|
6912
|
+
const headers = filterForwardableUpstreamHeaders(clientHeaders, {
|
|
6913
|
+
includeClientCredentials: false,
|
|
6914
|
+
});
|
|
6793
6915
|
headers["content-type"] = "application/json";
|
|
6794
6916
|
if (isOAuth) {
|
|
6795
6917
|
headers.authorization = `Bearer ${account.token}`;
|
|
@@ -6815,17 +6937,27 @@ async function prepareAnthropicAccountAttempt(args) {
|
|
|
6815
6937
|
if (!headers.accept) {
|
|
6816
6938
|
headers.accept = "application/json";
|
|
6817
6939
|
}
|
|
6940
|
+
let clientSuppliedBetaForwarded = false;
|
|
6818
6941
|
if (isOAuth) {
|
|
6819
|
-
const
|
|
6820
|
-
?
|
|
6821
|
-
:
|
|
6822
|
-
const
|
|
6942
|
+
const proxyBetas = isClaudeClientRequest
|
|
6943
|
+
? CLAUDE_CODE_OAUTH_BETAS
|
|
6944
|
+
: NON_CLAUDE_OAUTH_BETAS;
|
|
6945
|
+
const knownBetas = new Set([
|
|
6946
|
+
...CLAUDE_CODE_OAUTH_BETAS,
|
|
6947
|
+
...NON_CLAUDE_OAUTH_BETAS,
|
|
6948
|
+
]);
|
|
6949
|
+
const clientBetas = (headers["anthropic-beta"] ?? "")
|
|
6823
6950
|
.split(",")
|
|
6824
6951
|
.map((value) => value.trim())
|
|
6825
|
-
.filter(Boolean)
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6952
|
+
.filter(Boolean);
|
|
6953
|
+
// A beta the pool's tier lacks turns one request into one upstream request
|
|
6954
|
+
// per account, so only Claude Code itself may add betas the proxy would
|
|
6955
|
+
// not send; everyone else is held to the proxy's own set.
|
|
6956
|
+
const existing = new Set(genuineClaudeCode
|
|
6957
|
+
? clientBetas
|
|
6958
|
+
: clientBetas.filter((beta) => knownBetas.has(beta)));
|
|
6959
|
+
clientSuppliedBetaForwarded = [...existing].some((beta) => !proxyBetas.includes(beta));
|
|
6960
|
+
for (const beta of proxyBetas) {
|
|
6829
6961
|
existing.add(beta);
|
|
6830
6962
|
}
|
|
6831
6963
|
headers["anthropic-beta"] = [...existing].join(",");
|
|
@@ -6845,7 +6977,7 @@ async function prepareAnthropicAccountAttempt(args) {
|
|
|
6845
6977
|
}
|
|
6846
6978
|
const buildUpstreamBody = (token) => {
|
|
6847
6979
|
const built = isOAuth
|
|
6848
|
-
? polyfillOAuthBody(bodyStr, token, snapshot, isClaudeClientRequest, headers["x-claude-code-session-id"])
|
|
6980
|
+
? polyfillOAuthBody(bodyStr, token, snapshot, isClaudeClientRequest, headers["x-claude-code-session-id"], genuineClaudeCode)
|
|
6849
6981
|
: { bodyStr };
|
|
6850
6982
|
return { ...built, ...prepareAnthropicWire(ctx, built.bodyStr) };
|
|
6851
6983
|
};
|
|
@@ -6874,6 +7006,7 @@ async function prepareAnthropicAccountAttempt(args) {
|
|
|
6874
7006
|
lastError,
|
|
6875
7007
|
authFailureMessage,
|
|
6876
7008
|
headers,
|
|
7009
|
+
clientSuppliedBetaForwarded,
|
|
6877
7010
|
buildUpstreamBody,
|
|
6878
7011
|
finalBodyStr,
|
|
6879
7012
|
preparedContext: polyfilledBody.preparedContext,
|
|
@@ -7310,6 +7443,13 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
7310
7443
|
effectiveAccounts.every((account) => !isAccountAdmissionAvailable(account.key, accountAdmissionCapacity))) {
|
|
7311
7444
|
queuedAccountAdmission = await acquireFirstAvailableAccountAdmission(effectiveAccounts.map((account) => account.key), accountAdmissionCapacity, ctx.abortSignal, MAX_TRANSIENT_QUEUE_WAIT_MS, configGeneration);
|
|
7312
7445
|
}
|
|
7446
|
+
// One inbound request may fan out to at most this many accounts. Without
|
|
7447
|
+
// the cap any rotation trigger multiplied one request by the pool size.
|
|
7448
|
+
const maxAccountsPerRequest = resolveMaxAccountsPerRequest();
|
|
7449
|
+
if (effectiveAccounts.length > maxAccountsPerRequest) {
|
|
7450
|
+
logger.debug(`[proxy] capping account fan-out at ${maxAccountsPerRequest} of ${effectiveAccounts.length} eligible accounts`);
|
|
7451
|
+
effectiveAccounts = effectiveAccounts.slice(0, maxAccountsPerRequest);
|
|
7452
|
+
}
|
|
7313
7453
|
accountLoop: for (const [accountIndex, account,] of effectiveAccounts.entries()) {
|
|
7314
7454
|
const hasNextAccount = accountIndex < effectiveAccounts.length - 1;
|
|
7315
7455
|
const accountState = getOrCreateRuntimeState(account.key);
|
|
@@ -7616,6 +7756,7 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
7616
7756
|
invalidRequestFailure: loopState.invalidRequestFailure,
|
|
7617
7757
|
entitlementFailure: loopState.entitlementFailure,
|
|
7618
7758
|
allowConfiguredModelFallback: hasConfiguredFallback,
|
|
7759
|
+
clientSuppliedBetaForwarded: preparedAttempt.clientSuppliedBetaForwarded ?? false,
|
|
7619
7760
|
});
|
|
7620
7761
|
loopState.lastError = nonOkResult.lastError;
|
|
7621
7762
|
loopState.authFailureMessage = nonOkResult.authFailureMessage;
|
|
@@ -7848,9 +7989,37 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
7848
7989
|
// Route factory
|
|
7849
7990
|
// ---------------------------------------------------------------------------
|
|
7850
7991
|
function isClaudeProxyRouteRuntimeOptions(value) {
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7992
|
+
// The allowlist is a Set; the options bag is a plain object that may carry
|
|
7993
|
+
// only `inboundAuth`, so presence of the provider is no longer the tell.
|
|
7994
|
+
return (value !== undefined && typeof value === "object" && !(value instanceof Set));
|
|
7995
|
+
}
|
|
7996
|
+
/**
|
|
7997
|
+
* Require the inbound token on every route of a group that is not
|
|
7998
|
+
* self-authenticating. The refusal is a `Response` so every adapter — the CLI
|
|
7999
|
+
* listener and the SDK server adapters alike — passes it through unchanged.
|
|
8000
|
+
*/
|
|
8001
|
+
function guardRouteGroupWithInboundAuth(group, basePath, token) {
|
|
8002
|
+
if (!token) {
|
|
8003
|
+
return group;
|
|
8004
|
+
}
|
|
8005
|
+
return {
|
|
8006
|
+
...group,
|
|
8007
|
+
routes: group.routes.map((route) => {
|
|
8008
|
+
if (isInboundAuthExemptPath(route.path.slice(basePath.length))) {
|
|
8009
|
+
return route;
|
|
8010
|
+
}
|
|
8011
|
+
return {
|
|
8012
|
+
...route,
|
|
8013
|
+
handler: async (ctx) => {
|
|
8014
|
+
const verdict = authorizeInboundRequest(ctx.headers, token);
|
|
8015
|
+
if (!verdict.ok) {
|
|
8016
|
+
return inboundAuthRefusalResponse();
|
|
8017
|
+
}
|
|
8018
|
+
return route.handler(ctx);
|
|
8019
|
+
},
|
|
8020
|
+
};
|
|
8021
|
+
}),
|
|
8022
|
+
};
|
|
7854
8023
|
}
|
|
7855
8024
|
function buildEarlyClaudeRequestError(args) {
|
|
7856
8025
|
const { ctx, body, status, message, errorType } = args;
|
|
@@ -7945,7 +8114,12 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
7945
8114
|
const runtimeConfigProvider = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
|
|
7946
8115
|
? accountAllowlistOrRuntimeOptions.runtimeConfigProvider
|
|
7947
8116
|
: undefined;
|
|
7948
|
-
|
|
8117
|
+
const inboundAuth = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
|
|
8118
|
+
? accountAllowlistOrRuntimeOptions.inboundAuth
|
|
8119
|
+
: undefined;
|
|
8120
|
+
const inboundToken = resolveInboundToken(inboundAuth?.token);
|
|
8121
|
+
warnIfProxyRoutesUnauthenticated("Claude", inboundAuth, inboundToken);
|
|
8122
|
+
const group = {
|
|
7949
8123
|
prefix: `${basePath}/v1`,
|
|
7950
8124
|
routes: [
|
|
7951
8125
|
// =====================================================================
|
|
@@ -8612,6 +8786,16 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
8612
8786
|
method: "GET",
|
|
8613
8787
|
path: `${basePath}/accounts`,
|
|
8614
8788
|
handler: async (ctx) => {
|
|
8789
|
+
// Operator-only, exactly as `/limits`: every row names an account —
|
|
8790
|
+
// an email for an OAuth account — with its quota and spend, and a
|
|
8791
|
+
// live refresh drives usage-API calls under the lender's tokens.
|
|
8792
|
+
if (getShareContext()) {
|
|
8793
|
+
return buildShareRefusal("no_capacity", {
|
|
8794
|
+
status: 403,
|
|
8795
|
+
message: "This proxy does not expose its account inventory to peers. " +
|
|
8796
|
+
"Use GET /peer/limits for what your grant may still do.",
|
|
8797
|
+
}).body;
|
|
8798
|
+
}
|
|
8615
8799
|
const routing = runtimeConfigProvider?.();
|
|
8616
8800
|
const effectiveAllowlist = routing?.accountAllowlist ?? accountAllowlist;
|
|
8617
8801
|
// Hot-reloaded routing wins over the value baked in at construction,
|
|
@@ -8845,6 +9029,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
8845
9029
|
},
|
|
8846
9030
|
],
|
|
8847
9031
|
};
|
|
9032
|
+
return guardRouteGroupWithInboundAuth(group, basePath, inboundToken);
|
|
8848
9033
|
}
|
|
8849
9034
|
// ---------------------------------------------------------------------------
|
|
8850
9035
|
// Helpers
|
|
@@ -8940,8 +9125,12 @@ async function clearAuthCooldownAfterRefresh(account, state) {
|
|
|
8940
9125
|
});
|
|
8941
9126
|
}
|
|
8942
9127
|
function formatReauthMessage(labels) {
|
|
8943
|
-
const
|
|
8944
|
-
|
|
9128
|
+
const list = Array.isArray(labels) ? labels : [labels];
|
|
9129
|
+
if (getShareContext()) {
|
|
9130
|
+
const count = new Set(list).size;
|
|
9131
|
+
return `Account(s) require re-authentication: ${count} pooled account${count === 1 ? "" : "s"} (identity withheld for borrowed requests). Ask the lender to run: neurolink auth login anthropic --method oauth`;
|
|
9132
|
+
}
|
|
9133
|
+
return `Account(s) require re-authentication: ${list.join(", ")}. Run: neurolink auth login anthropic --method oauth`;
|
|
8945
9134
|
}
|
|
8946
9135
|
function summarizeErrorMessage(message, maxLength = 180) {
|
|
8947
9136
|
const compact = message.replace(/\s+/g, " ").trim();
|
|
@@ -9331,7 +9520,7 @@ export const __testHooks = {
|
|
|
9331
9520
|
primaryAccountIndex = 0;
|
|
9332
9521
|
lastKnownAccountCount = 0;
|
|
9333
9522
|
},
|
|
9334
|
-
polyfillOAuthBody: (bodyStr, isClaudeClientRequest) => polyfillOAuthBody(bodyStr, "test-account-token", null, isClaudeClientRequest),
|
|
9523
|
+
polyfillOAuthBody: (bodyStr, isClaudeClientRequest) => polyfillOAuthBody(bodyStr, "test-account-token", null, isClaudeClientRequest, undefined, isClaudeClientRequest),
|
|
9335
9524
|
// Pins the sessionId path loadClaudeProxyAccounts resolves ahead of
|
|
9336
9525
|
// account selection and feeds into selectClaudeProxyAccountOrder for
|
|
9337
9526
|
// session affinity.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* never collides with anthropic entries) and does pre-commit rotation only, not
|
|
18
18
|
* the full transient-budget / admission machinery.
|
|
19
19
|
*/
|
|
20
|
-
import type { AccountQuota, CodexRefreshTokenStore, CodexResponseEnvelope, CodexResponseStream, CodexRuntimeAccount, CodexTokenRefresher, RateLimitCoolingReason, RouteGroup, ServerContext, ProxyRuntimeConfigProvider } from "../../types/index.js";
|
|
20
|
+
import type { AccountQuota, CodexRefreshTokenStore, CodexResponseEnvelope, CodexResponseStream, CodexRuntimeAccount, CodexTokenRefresher, RateLimitCoolingReason, RouteGroup, ServerContext, ProxyInboundAuthOptions, ProxyRuntimeConfigProvider } from "../../types/index.js";
|
|
21
21
|
declare function refreshCodexTokenOnceWithDependencies(key: string, refreshToken: string, store: CodexRefreshTokenStore, refresh: CodexTokenRefresher): Promise<{
|
|
22
22
|
accessToken: string;
|
|
23
23
|
refreshToken: string;
|
|
@@ -89,7 +89,7 @@ export declare function handleCodexResponsesRequest(ctx: ServerContext, runtimeC
|
|
|
89
89
|
* `loopbackPort` when omitted.
|
|
90
90
|
* @returns RouteGroup with the Codex backend Responses endpoint.
|
|
91
91
|
*/
|
|
92
|
-
export declare function createCodexProxyRoutes(basePath?: string, runtimeConfigProvider?: ProxyRuntimeConfigProvider, loopbackPort?: number, internalDispatch?: (request: Request) => Response | Promise<Response
|
|
92
|
+
export declare function createCodexProxyRoutes(basePath?: string, runtimeConfigProvider?: ProxyRuntimeConfigProvider, loopbackPort?: number, internalDispatch?: (request: Request) => Response | Promise<Response>, inboundAuth?: ProxyInboundAuthOptions): RouteGroup;
|
|
93
93
|
export declare const __testHooks: {
|
|
94
94
|
loadCodexProxyAccounts: typeof loadCodexProxyAccounts;
|
|
95
95
|
orderCodexAccounts: typeof orderCodexAccounts;
|