@juspay/neurolink 12.17.1 → 12.17.3

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 (73) hide show
  1. package/CHANGELOG.md +6 -2
  2. package/dist/browser/neurolink.min.js +430 -421
  3. package/dist/cli/commands/proxy.js +455 -259
  4. package/dist/constants/contextWindows.d.ts +2 -0
  5. package/dist/constants/contextWindows.js +4 -0
  6. package/dist/neurolink.js +8 -3
  7. package/dist/providers/openaiChatCompletionsBase.js +60 -51
  8. package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
  9. package/dist/proxy/anthropicBudgetResponse.js +153 -0
  10. package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
  11. package/dist/proxy/bodyCaptureProcessing.js +23 -4
  12. package/dist/proxy/bodyCaptureWorker.js +20 -2
  13. package/dist/proxy/codexFallback.d.ts +10 -0
  14. package/dist/proxy/codexFallback.js +108 -24
  15. package/dist/proxy/codexUsage.js +2 -0
  16. package/dist/proxy/globalInstaller.d.ts +32 -1
  17. package/dist/proxy/globalInstaller.js +386 -3
  18. package/dist/proxy/otelLogSink.d.ts +3 -3
  19. package/dist/proxy/otelLogSink.js +137 -144
  20. package/dist/proxy/otlpLogTransport.d.ts +3 -0
  21. package/dist/proxy/otlpLogTransport.js +334 -0
  22. package/dist/proxy/proxyActivity.d.ts +22 -2
  23. package/dist/proxy/proxyActivity.js +114 -9
  24. package/dist/proxy/proxyAnalysis.js +82 -52
  25. package/dist/proxy/proxyClientCancellation.d.ts +7 -0
  26. package/dist/proxy/proxyClientCancellation.js +23 -0
  27. package/dist/proxy/proxyContextPreflight.d.ts +25 -0
  28. package/dist/proxy/proxyContextPreflight.js +337 -0
  29. package/dist/proxy/proxyFailureDetails.d.ts +19 -0
  30. package/dist/proxy/proxyFailureDetails.js +96 -0
  31. package/dist/proxy/proxyLifecycle.js +7 -0
  32. package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
  33. package/dist/proxy/proxyRouteBodyCapture.js +112 -0
  34. package/dist/proxy/proxyTokenBudget.d.ts +40 -0
  35. package/dist/proxy/proxyTokenBudget.js +523 -0
  36. package/dist/proxy/proxyTracer.d.ts +3 -2
  37. package/dist/proxy/proxyTracer.js +46 -20
  38. package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
  39. package/dist/proxy/proxyTranslationEngine.js +736 -376
  40. package/dist/proxy/requestLogger.d.ts +1 -0
  41. package/dist/proxy/requestLogger.js +107 -5
  42. package/dist/proxy/restartControl.js +9 -4
  43. package/dist/proxy/rollingWorkerProcess.js +65 -0
  44. package/dist/proxy/rollingWorkerSupervisor.js +7 -0
  45. package/dist/proxy/updateCoordinator.d.ts +14 -1
  46. package/dist/proxy/updateCoordinator.js +57 -2
  47. package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
  48. package/dist/server/routes/claudeProxyRoutes.js +809 -577
  49. package/dist/server/routes/codexProxyRoutes.js +191 -9
  50. package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
  51. package/dist/server/routes/openaiProxyRoutes.js +261 -69
  52. package/dist/types/cli.d.ts +3 -0
  53. package/dist/types/codex.d.ts +5 -1
  54. package/dist/types/index.d.ts +2 -0
  55. package/dist/types/index.js +2 -0
  56. package/dist/types/proxy.d.ts +148 -3
  57. package/dist/types/proxy.js +0 -15
  58. package/dist/types/proxyBudget.d.ts +74 -0
  59. package/dist/types/proxyBudget.js +1 -0
  60. package/dist/types/proxyContext.d.ts +35 -0
  61. package/dist/types/proxyContext.js +1 -0
  62. package/dist/utils/fileDetector.js +28 -7
  63. package/dist/utils/messageBuilder.js +51 -26
  64. package/dist/utils/pdfProcessor.js +26 -4
  65. package/docs/assets/dashboards/README.md +37 -0
  66. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
  67. package/docs-site/static/search-index.json +1 -1
  68. package/package.json +7 -2
  69. package/scripts/observability/NATIVE-DURABLE.md +63 -0
  70. package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
  71. package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
  72. package/scripts/observability/proxy-telemetry-check.mjs +396 -61
  73. package/scripts/observability/query-proxy-history.mjs +34 -5
@@ -0,0 +1,523 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ const DEFAULT_WINDOW_MS = 60 * 60_000;
3
+ const MAX_KEYS = 10_000;
4
+ const MAX_LEASES = 5_000;
5
+ const RPC_TIMEOUT_MS = 2_000;
6
+ /** Shared private session scope across every protocol and provider fallback. */
7
+ export function getProxyTokenBudgetSessionKey(headers) {
8
+ const value = headers.get("x-neurolink-session-id") ??
9
+ headers.get("x-claude-code-session-id") ??
10
+ headers.get("session_id") ??
11
+ headers.get("session-id") ??
12
+ "unattributed";
13
+ return createHash("sha256").update(value).digest("hex");
14
+ }
15
+ function failure(code, message) {
16
+ const status = code === "PROXY_TOKEN_BUDGET_EXCEEDED"
17
+ ? 429
18
+ : code === "PROXY_TOKEN_BUDGET_INPUT"
19
+ ? 400
20
+ : 503;
21
+ return Object.assign(new Error(message), {
22
+ code,
23
+ status,
24
+ statusCode: status,
25
+ retryable: false,
26
+ });
27
+ }
28
+ /** Local admission failures must not be hidden by automatic provider fallback. */
29
+ export function getProxyTokenBudgetError(error) {
30
+ if (!error ||
31
+ typeof error !== "object" ||
32
+ !("code" in error) ||
33
+ typeof error.code !== "string" ||
34
+ !error.code.startsWith("PROXY_TOKEN_BUDGET_")) {
35
+ return null;
36
+ }
37
+ return {
38
+ code: error.code,
39
+ status: error.code === "PROXY_TOKEN_BUDGET_EXCEEDED"
40
+ ? 429
41
+ : error.code === "PROXY_TOKEN_BUDGET_INPUT"
42
+ ? 400
43
+ : 503,
44
+ retryable: false,
45
+ message: error instanceof Error
46
+ ? error.message
47
+ : "Proxy token budget admission failed",
48
+ };
49
+ }
50
+ /** Invalid configured limits fail closed instead of silently disabling protection. */
51
+ export function parseProxyTokenBudgetPolicy(value) {
52
+ if (typeof value === "string") {
53
+ try {
54
+ value = JSON.parse(value);
55
+ }
56
+ catch {
57
+ throw failure("PROXY_TOKEN_BUDGET_CONFIG", "Token budget configuration is not valid JSON");
58
+ }
59
+ }
60
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
61
+ throw failure("PROXY_TOKEN_BUDGET_CONFIG", "Token budget configuration must be an object");
62
+ }
63
+ const result = {};
64
+ for (const [key, limit] of Object.entries(value)) {
65
+ if (![
66
+ "maxInFlightTokens",
67
+ "accountWindowTokens",
68
+ "sessionWindowTokens",
69
+ "windowMs",
70
+ ].includes(key) ||
71
+ typeof limit !== "number" ||
72
+ !Number.isSafeInteger(limit) ||
73
+ limit <= 0) {
74
+ throw failure("PROXY_TOKEN_BUDGET_CONFIG", "Token budget limits must be positive safe integers with supported names");
75
+ }
76
+ if (key === "maxInFlightTokens") {
77
+ result.maxInFlightTokens = limit;
78
+ }
79
+ if (key === "accountWindowTokens") {
80
+ result.accountWindowTokens = limit;
81
+ }
82
+ if (key === "sessionWindowTokens") {
83
+ result.sessionWindowTokens = limit;
84
+ }
85
+ if (key === "windowMs") {
86
+ result.windowMs = limit;
87
+ }
88
+ }
89
+ return result;
90
+ }
91
+ export function getProxyTokenBudgetPolicy() {
92
+ const configured = process.env.NEUROLINK_PROXY_TOKEN_BUDGET;
93
+ return configured === undefined || configured.trim() === ""
94
+ ? undefined
95
+ : parseProxyTokenBudgetPolicy(configured);
96
+ }
97
+ function effectivePolicy(requested) {
98
+ const configured = getProxyTokenBudgetPolicy();
99
+ const local = requested === undefined
100
+ ? undefined
101
+ : parseProxyTokenBudgetPolicy(requested);
102
+ if (!configured) {
103
+ return local;
104
+ }
105
+ if (!local) {
106
+ return configured;
107
+ }
108
+ const result = {
109
+ windowMs: Math.max(configured.windowMs ?? DEFAULT_WINDOW_MS, local.windowMs ?? DEFAULT_WINDOW_MS),
110
+ };
111
+ for (const key of [
112
+ "maxInFlightTokens",
113
+ "accountWindowTokens",
114
+ "sessionWindowTokens",
115
+ ]) {
116
+ const left = configured[key];
117
+ const right = local[key];
118
+ if (left !== undefined || right !== undefined) {
119
+ result[key] = Math.min(left ?? Number.MAX_SAFE_INTEGER, right ?? Number.MAX_SAFE_INTEGER);
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+ function enabled(policy) {
125
+ return !!(policy?.maxInFlightTokens ||
126
+ policy?.accountWindowTokens ||
127
+ policy?.sessionWindowTokens);
128
+ }
129
+ function validateReservation(value) {
130
+ for (const key of [
131
+ "provider",
132
+ "accountKey",
133
+ "sessionKey",
134
+ "requestId",
135
+ ]) {
136
+ if (typeof value[key] !== "string" ||
137
+ value[key].length === 0 ||
138
+ value[key].length > 512) {
139
+ throw failure("PROXY_TOKEN_BUDGET_INPUT", "Token budget requires bounded provider, account, session and request identities");
140
+ }
141
+ }
142
+ if (!Number.isSafeInteger(value.reservationTokens) ||
143
+ value.reservationTokens <= 0) {
144
+ throw failure("PROXY_TOKEN_BUDGET_INPUT", "Token reservation must be a positive safe integer");
145
+ }
146
+ if (value.estimateProvenance !== undefined &&
147
+ (typeof value.estimateProvenance !== "string" ||
148
+ value.estimateProvenance.length > 128)) {
149
+ throw failure("PROXY_TOKEN_BUDGET_INPUT", "Token estimate provenance must be bounded text");
150
+ }
151
+ }
152
+ /** One atomic coordinator per supervisor; requests never write budget state to disk. */
153
+ export class ProxyTokenBudgetCoordinator {
154
+ scope;
155
+ now;
156
+ maxKeys;
157
+ maxLeases;
158
+ buckets = new Map();
159
+ leases = new Map();
160
+ constructor(scope = "supervisor", now = Date.now, maxKeys = MAX_KEYS, maxLeases = MAX_LEASES) {
161
+ this.scope = scope;
162
+ this.now = now;
163
+ this.maxKeys = maxKeys;
164
+ this.maxLeases = maxLeases;
165
+ }
166
+ bucket(key, windowMs) {
167
+ const now = this.now();
168
+ let bucket = this.buckets.get(key);
169
+ if (!bucket) {
170
+ if (this.buckets.size >= this.maxKeys) {
171
+ for (const [candidateKey, candidate] of this.buckets) {
172
+ if (candidate.inFlightTokens === 0 &&
173
+ now - candidate.lastTouched >= candidate.windowMs) {
174
+ this.buckets.delete(candidateKey);
175
+ }
176
+ }
177
+ }
178
+ if (this.buckets.size >= this.maxKeys) {
179
+ throw failure("PROXY_TOKEN_BUDGET_CAPACITY", "Token budget scope capacity is full; retry after its window expires");
180
+ }
181
+ bucket = {
182
+ chargedTokens: 0,
183
+ inFlightTokens: 0,
184
+ startedAt: now,
185
+ windowMs,
186
+ lastTouched: now,
187
+ };
188
+ this.buckets.set(key, bucket);
189
+ }
190
+ // Changing configuration cannot shorten an existing accounting window.
191
+ bucket.windowMs = Math.max(bucket.windowMs, windowMs);
192
+ if (now - bucket.startedAt >= bucket.windowMs) {
193
+ bucket.startedAt = now;
194
+ bucket.chargedTokens = bucket.inFlightTokens;
195
+ bucket.windowMs = windowMs;
196
+ }
197
+ bucket.lastTouched = now;
198
+ return bucket;
199
+ }
200
+ reserve(input, owner = "local") {
201
+ validateReservation(input);
202
+ const policy = parseProxyTokenBudgetPolicy(input.policy ?? {});
203
+ if (this.leases.size >= this.maxLeases) {
204
+ throw failure("PROXY_TOKEN_BUDGET_CAPACITY", "Token budget outstanding reservation capacity is full");
205
+ }
206
+ const windowMs = policy.windowMs ?? DEFAULT_WINDOW_MS;
207
+ const accountKey = `account:${JSON.stringify([input.provider, input.accountKey])}`;
208
+ const sessionKey = `session:${JSON.stringify(input.sessionKey)}`;
209
+ const account = this.bucket(accountKey, windowMs);
210
+ const session = this.bucket(sessionKey, windowMs);
211
+ const estimate = input.reservationTokens;
212
+ if ((policy.maxInFlightTokens !== undefined &&
213
+ estimate > policy.maxInFlightTokens - account.inFlightTokens) ||
214
+ (policy.accountWindowTokens !== undefined &&
215
+ estimate > policy.accountWindowTokens - account.chargedTokens) ||
216
+ (policy.sessionWindowTokens !== undefined &&
217
+ estimate > policy.sessionWindowTokens - session.chargedTokens)) {
218
+ throw failure("PROXY_TOKEN_BUDGET_EXCEEDED", "Token budget would be exceeded before upstream dispatch");
219
+ }
220
+ if (estimate >
221
+ Number.MAX_SAFE_INTEGER -
222
+ Math.max(account.chargedTokens, session.chargedTokens)) {
223
+ throw failure("PROXY_TOKEN_BUDGET_CAPACITY", "Token accounting integer capacity is full");
224
+ }
225
+ account.chargedTokens += estimate;
226
+ account.inFlightTokens += estimate;
227
+ session.chargedTokens += estimate;
228
+ session.inFlightTokens += estimate;
229
+ const snapshot = {
230
+ scope: this.scope,
231
+ reservationTokens: estimate,
232
+ accountChargedTokens: account.chargedTokens,
233
+ accountInFlightTokens: account.inFlightTokens,
234
+ sessionChargedTokens: session.chargedTokens,
235
+ sessionInFlightTokens: session.inFlightTokens,
236
+ windowMs,
237
+ estimateProvenance: input.estimateProvenance ?? "caller_estimate_input_plus_max_output",
238
+ };
239
+ const leaseId = randomUUID();
240
+ this.leases.set(leaseId, {
241
+ owner,
242
+ accountKey,
243
+ sessionKey,
244
+ estimate,
245
+ snapshot,
246
+ });
247
+ return { leaseId, snapshot: { ...snapshot } };
248
+ }
249
+ settle(leaseId, owner, actualTotalTokens, cancelled = false) {
250
+ const lease = this.leases.get(leaseId);
251
+ if (!lease) {
252
+ return undefined;
253
+ } // repeated completion never charges twice
254
+ if (lease.owner !== owner) {
255
+ throw failure("PROXY_TOKEN_BUDGET_OWNER", "Token budget lease belongs to another worker");
256
+ }
257
+ if (actualTotalTokens !== undefined &&
258
+ (!Number.isSafeInteger(actualTotalTokens) || actualTotalTokens < 0)) {
259
+ throw failure("PROXY_TOKEN_BUDGET_USAGE", "Reported token usage must be a nonnegative safe integer");
260
+ }
261
+ const actual = cancelled ? 0 : (actualTotalTokens ?? lease.estimate);
262
+ const account = this.bucket(lease.accountKey, lease.snapshot.windowMs);
263
+ const session = this.bucket(lease.sessionKey, lease.snapshot.windowMs);
264
+ for (const bucket of [account, session]) {
265
+ bucket.chargedTokens = Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, bucket.chargedTokens + actual - lease.estimate));
266
+ bucket.inFlightTokens = Math.max(0, bucket.inFlightTokens - lease.estimate);
267
+ }
268
+ this.leases.delete(leaseId);
269
+ return {
270
+ ...lease.snapshot,
271
+ accountChargedTokens: account.chargedTokens,
272
+ accountInFlightTokens: account.inFlightTokens,
273
+ sessionChargedTokens: session.chargedTokens,
274
+ sessionInFlightTokens: session.inFlightTokens,
275
+ settlement: cancelled
276
+ ? "cancelled_before_dispatch"
277
+ : actualTotalTokens === undefined
278
+ ? "estimate_retained"
279
+ : "provider_reported",
280
+ };
281
+ }
282
+ ownerExited(owner) {
283
+ for (const [leaseId, lease] of this.leases) {
284
+ if (lease.owner === owner) {
285
+ this.settle(leaseId, owner);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ const coordinator = new ProxyTokenBudgetCoordinator(process.env.NEUROLINK_PROXY_SOCKET_WORKER === "1" ? "process" : "supervisor");
291
+ /** Parent-side IPC hook is shared by every active and draining generation. */
292
+ export function handleProxyTokenBudgetMessage(value, owner, reply) {
293
+ if (!value ||
294
+ typeof value !== "object" ||
295
+ value.type !== "proxy-budget:request") {
296
+ return false;
297
+ }
298
+ const request = value;
299
+ if (request.pid !== owner.pid ||
300
+ request.generation !== owner.generation ||
301
+ typeof request.rpcId !== "string" ||
302
+ request.rpcId.length > 128) {
303
+ return true;
304
+ }
305
+ const ownerKey = `${owner.generation}:${owner.pid}`;
306
+ try {
307
+ if (request.action === "reserve" &&
308
+ request.reservation &&
309
+ typeof request.reservation === "object") {
310
+ // The supervisor's configured caps remain authoritative across generations.
311
+ const policy = effectivePolicy(request.reservation.policy);
312
+ const result = coordinator.reserve({ ...request.reservation, policy }, ownerKey);
313
+ reply({
314
+ type: "proxy-budget:response",
315
+ rpcId: request.rpcId,
316
+ ok: true,
317
+ ...result,
318
+ });
319
+ }
320
+ else if ((request.action === "settle" || request.action === "cancel") &&
321
+ typeof request.leaseId === "string" &&
322
+ request.leaseId.length <= 128) {
323
+ const snapshot = coordinator.settle(request.leaseId, ownerKey, request.actualTotalTokens, request.action === "cancel");
324
+ reply({
325
+ type: "proxy-budget:response",
326
+ rpcId: request.rpcId,
327
+ ok: true,
328
+ snapshot,
329
+ });
330
+ }
331
+ else {
332
+ throw failure("PROXY_TOKEN_BUDGET_INPUT", "Malformed token budget IPC request");
333
+ }
334
+ }
335
+ catch (error) {
336
+ reply({
337
+ type: "proxy-budget:response",
338
+ rpcId: request.rpcId,
339
+ ok: false,
340
+ errorCode: error && typeof error === "object" && "code" in error
341
+ ? String(error.code)
342
+ : "PROXY_TOKEN_BUDGET_UNAVAILABLE",
343
+ error: error instanceof Error ? error.message : "Token budget failed",
344
+ });
345
+ }
346
+ return true;
347
+ }
348
+ export function releaseProxyTokenBudgetOwner(pid, generation) {
349
+ coordinator.ownerExited(`${generation}:${pid}`);
350
+ }
351
+ const pending = new Map();
352
+ const expiredReservations = new Set();
353
+ function retainExpiredReservation(rpcId) {
354
+ if (expiredReservations.size >= MAX_LEASES) {
355
+ const oldest = expiredReservations.values().next().value;
356
+ if (oldest) {
357
+ expiredReservations.delete(oldest);
358
+ }
359
+ }
360
+ expiredReservations.add(rpcId);
361
+ }
362
+ let listening = false;
363
+ function startRpcListener() {
364
+ if (listening) {
365
+ return;
366
+ }
367
+ listening = true;
368
+ process.on("message", (value) => {
369
+ if (!value ||
370
+ typeof value !== "object" ||
371
+ value.type !== "proxy-budget:response") {
372
+ return;
373
+ }
374
+ const response = value;
375
+ const waiter = pending.get(response.rpcId);
376
+ if (!waiter) {
377
+ // A late admission response cannot dispatch upstream. Refund that known
378
+ // unused lease without creating a local fallback accounting scope.
379
+ if (expiredReservations.delete(response.rpcId) &&
380
+ response.ok &&
381
+ typeof response.leaseId === "string" &&
382
+ response.leaseId.length <= 128 &&
383
+ process.connected &&
384
+ process.send) {
385
+ process.send({
386
+ type: "proxy-budget:request",
387
+ rpcId: randomUUID(),
388
+ pid: process.pid,
389
+ generation: Number(process.env.NEUROLINK_PROXY_WORKER_GENERATION),
390
+ action: "cancel",
391
+ leaseId: response.leaseId,
392
+ }, () => undefined);
393
+ }
394
+ return;
395
+ }
396
+ pending.delete(response.rpcId);
397
+ clearTimeout(waiter.timer);
398
+ if (response.ok) {
399
+ waiter.resolve(response);
400
+ }
401
+ else {
402
+ waiter.reject(failure(response.errorCode ?? "PROXY_TOKEN_BUDGET_UNAVAILABLE", response.error ?? "Token budget coordinator refused the request"));
403
+ }
404
+ });
405
+ process.on("disconnect", () => {
406
+ for (const waiter of pending.values()) {
407
+ clearTimeout(waiter.timer);
408
+ waiter.reject(failure("PROXY_TOKEN_BUDGET_UNAVAILABLE", "Token budget supervisor disconnected"));
409
+ }
410
+ pending.clear();
411
+ });
412
+ }
413
+ function rpc(request) {
414
+ startRpcListener();
415
+ return new Promise((resolveRpc, rejectRpc) => {
416
+ if (!process.connected || !process.send || pending.size >= MAX_LEASES) {
417
+ rejectRpc(failure("PROXY_TOKEN_BUDGET_UNAVAILABLE", "Token budget supervisor IPC is unavailable"));
418
+ return;
419
+ }
420
+ const rpcId = randomUUID();
421
+ const timer = setTimeout(() => {
422
+ pending.delete(rpcId);
423
+ if (request.action === "reserve") {
424
+ retainExpiredReservation(rpcId);
425
+ }
426
+ rejectRpc(failure("PROXY_TOKEN_BUDGET_UNAVAILABLE", "Token budget supervisor response timed out; dispatch refused"));
427
+ }, RPC_TIMEOUT_MS);
428
+ pending.set(rpcId, { resolve: resolveRpc, reject: rejectRpc, timer });
429
+ const fail = (error) => {
430
+ if (!error || !pending.has(rpcId)) {
431
+ return;
432
+ }
433
+ clearTimeout(timer);
434
+ pending.delete(rpcId);
435
+ if (request.action === "reserve") {
436
+ retainExpiredReservation(rpcId);
437
+ }
438
+ rejectRpc(failure("PROXY_TOKEN_BUDGET_UNAVAILABLE", "Token budget IPC send failed"));
439
+ };
440
+ try {
441
+ process.send({
442
+ ...request,
443
+ type: "proxy-budget:request",
444
+ rpcId,
445
+ pid: process.pid,
446
+ generation: Number(process.env.NEUROLINK_PROXY_WORKER_GENERATION),
447
+ }, fail);
448
+ }
449
+ catch (error) {
450
+ fail(error instanceof Error ? error : new Error("IPC failed"));
451
+ }
452
+ });
453
+ }
454
+ /** Reserve before each upstream attempt; unknown provider usage retains its estimate. */
455
+ export async function reserveProxyTokenBudget(input) {
456
+ const policy = effectivePolicy(input.policy);
457
+ if (!enabled(policy)) {
458
+ return {
459
+ leaseId: "disabled",
460
+ snapshot: {
461
+ scope: "disabled",
462
+ reservationTokens: 0,
463
+ accountChargedTokens: 0,
464
+ accountInFlightTokens: 0,
465
+ sessionChargedTokens: 0,
466
+ sessionInFlightTokens: 0,
467
+ windowMs: policy?.windowMs ?? DEFAULT_WINDOW_MS,
468
+ estimateProvenance: "disabled",
469
+ },
470
+ settle: async () => undefined,
471
+ cancelBeforeDispatch: async () => undefined,
472
+ };
473
+ }
474
+ validateReservation(input);
475
+ const worker = process.env.NEUROLINK_PROXY_SOCKET_WORKER === "1";
476
+ const reservation = { ...input, policy };
477
+ const result = worker
478
+ ? await rpc({ action: "reserve", reservation })
479
+ : coordinator.reserve(reservation, "local");
480
+ if (!result.leaseId || !result.snapshot) {
481
+ throw failure("PROXY_TOKEN_BUDGET_UNAVAILABLE", "Token budget supervisor returned incomplete reservation proof");
482
+ }
483
+ const leaseId = result.leaseId;
484
+ let settled = false;
485
+ const lease = {
486
+ leaseId,
487
+ snapshot: { ...result.snapshot, scope: worker ? "supervisor" : "process" },
488
+ settle: async (actualTotalTokens) => {
489
+ if (settled) {
490
+ return;
491
+ }
492
+ const next = worker
493
+ ? (await rpc({ action: "settle", leaseId, actualTotalTokens })).snapshot
494
+ : coordinator.settle(leaseId, "local", actualTotalTokens);
495
+ settled = true;
496
+ if (next) {
497
+ lease.snapshot = { ...next, scope: worker ? "supervisor" : "process" };
498
+ }
499
+ },
500
+ cancelBeforeDispatch: async () => {
501
+ if (settled) {
502
+ return;
503
+ }
504
+ const next = worker
505
+ ? (await rpc({ action: "cancel", leaseId })).snapshot
506
+ : coordinator.settle(leaseId, "local", undefined, true);
507
+ settled = true;
508
+ if (next) {
509
+ lease.snapshot = { ...next, scope: worker ? "supervisor" : "process" };
510
+ }
511
+ },
512
+ };
513
+ return lease;
514
+ }
515
+ /** Accounting acknowledgement loss must not turn delivered provider success into a stream failure. */
516
+ export async function settleProxyTokenBudget(lease, actualTotalTokens) {
517
+ try {
518
+ await lease.settle(actualTotalTokens);
519
+ }
520
+ catch {
521
+ lease.snapshot = { ...lease.snapshot, settlement: "unconfirmed" };
522
+ }
523
+ }
@@ -14,7 +14,7 @@
14
14
  * - TelemetryService for metrics recording
15
15
  */
16
16
  import { type Span } from "@opentelemetry/api";
17
- import type { AccountSelectionContext, ProxyRequestContext, ResponseInfoContext, UpstreamAttemptContext, UsageContext, RuntimeRequestMetadata, ProxyLogTraceContext } from "../types/index.js";
17
+ import type { AccountSelectionContext, ProxyRequestContext, ResponseInfoContext, UpstreamAttemptContext, UsageContext, ProxyUsageAttributionOptions, RuntimeRequestMetadata, ProxyLogTraceContext } from "../types/index.js";
18
18
  declare class ProxyTracer {
19
19
  private readonly rootSpan;
20
20
  private readonly proxyTracer;
@@ -36,6 +36,7 @@ declare class ProxyTracer {
36
36
  private readonly isStream;
37
37
  private ended;
38
38
  private recordRequestMetrics;
39
+ private recordUsageMetrics;
39
40
  private accountEmail?;
40
41
  private usage?;
41
42
  private mode;
@@ -60,7 +61,7 @@ declare class ProxyTracer {
60
61
  /** Attribute fallback metrics to the account that actually served them. */
61
62
  setServedAccount(account: string, accountType: string): void;
62
63
  /** Record token usage and cost on the root span. */
63
- setUsage(ctx: UsageContext): void;
64
+ setUsage(ctx: UsageContext, options?: ProxyUsageAttributionOptions): void;
64
65
  /**
65
66
  * Record response-side details parsed from the upstream reply: the model
66
67
  * that actually answered, the finish reason, and which tools the model
@@ -17,7 +17,7 @@ import { SpanStatusCode, SpanKind, ROOT_CONTEXT, isSpanContextValid, propagation
17
17
  import { getTracer, setLangfuseContext, } from "../services/server/ai/observability/instrumentation.js";
18
18
  import { OtelBridge } from "../observability/otelBridge.js";
19
19
  import { proxyTokenUsage } from "./proxyTokenUsage.js";
20
- import { calculateCost } from "../utils/pricing.js";
20
+ import { calculateCost, hasPricing, isExactPricingMatch, } from "../utils/pricing.js";
21
21
  import { TelemetryService } from "../telemetry/telemetryService.js";
22
22
  import { logger } from "../utils/logger.js";
23
23
  import { registerProxyRequestTraceContext, releaseProxyRequestTraceContext, } from "./proxyTraceContext.js";
@@ -209,6 +209,7 @@ class ProxyTracer {
209
209
  isStream;
210
210
  ended = false;
211
211
  recordRequestMetrics = true;
212
+ recordUsageMetrics = true;
212
213
  accountEmail;
213
214
  usage;
214
215
  mode = "full";
@@ -275,6 +276,8 @@ class ProxyTracer {
275
276
  }
276
277
  const instance = new ProxyTracer(rootSpan, ctx.requestId, ctx.model, ctx.stream, ctx.provider ?? "anthropic");
277
278
  instance.recordRequestMetrics = ctx.recordRequestMetrics !== false;
279
+ instance.recordUsageMetrics =
280
+ ctx.recordUsageMetrics ?? instance.recordRequestMetrics;
278
281
  // Set Langfuse context (fire-and-forget — non-blocking)
279
282
  // Prefer NeuroLink session/user from calling SDK over Claude Code session
280
283
  setLangfuseContext({
@@ -370,7 +373,10 @@ class ProxyTracer {
370
373
  });
371
374
  }
372
375
  /** Record token usage and cost on the root span. */
373
- setUsage(ctx) {
376
+ setUsage(ctx, options = {}) {
377
+ if (options.recordMetrics !== undefined) {
378
+ this.recordUsageMetrics = options.recordMetrics;
379
+ }
374
380
  this.usage = ctx;
375
381
  const billingUsage = proxyTokenUsage(ctx);
376
382
  const totalTokens = billingUsage.total;
@@ -391,9 +397,19 @@ class ProxyTracer {
391
397
  "gen_ai.usage.output_tokens": ctx.outputTokens,
392
398
  "gen_ai.usage.total_tokens": totalTokens,
393
399
  });
394
- // Cost calculation via pricing.ts
395
- const cost = calculateCost(this.billingProvider, this.model, billingUsage);
396
- if (cost > 0) {
400
+ // Prefix-inferred prices are not an observed model rate. Keep provenance,
401
+ // but emit an amount only when this model has an exact price-table entry.
402
+ const pricingStatus = !hasPricing(this.billingProvider, this.model)
403
+ ? "unavailable"
404
+ : isExactPricingMatch(this.billingProvider, this.model)
405
+ ? "exact"
406
+ : "inferred";
407
+ this.rootSpan.setAttributes({
408
+ "ai.cost.basis": "api-equivalent",
409
+ "ai.cost.pricing_status": pricingStatus,
410
+ });
411
+ if (pricingStatus === "exact") {
412
+ const cost = calculateCost(this.billingProvider, this.model, billingUsage);
397
413
  this.rootSpan.setAttributes({
398
414
  "ai.cost.total": cost,
399
415
  "ai.cost.currency": "USD",
@@ -466,11 +482,13 @@ class ProxyTracer {
466
482
  "gen_ai.response.model": actualModel,
467
483
  ...(actualProvider ? { "proxy.actual_provider": actualProvider } : {}),
468
484
  });
469
- const m = getProxyMetrics();
470
- m.modelSubstitutionTotal.add(1, {
471
- requested_model: requestedModel,
472
- actual_model: actualModel,
473
- });
485
+ if (this.recordRequestMetrics) {
486
+ const m = getProxyMetrics();
487
+ m.modelSubstitutionTotal.add(1, {
488
+ requested_model: requestedModel,
489
+ actual_model: actualModel,
490
+ });
491
+ }
474
492
  }
475
493
  setFallbackInfo(info) {
476
494
  if (!this.rootSpan) {
@@ -645,7 +663,7 @@ class ProxyTracer {
645
663
  this.rootSpan.setStatus({ code: SpanStatusCode.OK });
646
664
  }
647
665
  this.rootSpan.end();
648
- if (!this.recordRequestMetrics) {
666
+ if (!this.recordRequestMetrics && !this.recordUsageMetrics) {
649
667
  return;
650
668
  }
651
669
  // ---- Emit OTEL metrics (lazy-init instruments) ----
@@ -657,10 +675,12 @@ class ProxyTracer {
657
675
  stream: String(this.isStream),
658
676
  mode: this.mode,
659
677
  };
660
- m.requestsTotal.add(1, labels);
661
- m.requestDuration.record(durationMs, labels);
678
+ if (this.recordRequestMetrics) {
679
+ m.requestsTotal.add(1, labels);
680
+ m.requestDuration.record(durationMs, labels);
681
+ }
662
682
  // Token metrics (only if usage was captured)
663
- if (this.usage) {
683
+ if (this.recordUsageMetrics && this.usage) {
664
684
  const tokenLabels = {
665
685
  model: this.model,
666
686
  account: this.accountEmail ?? "unknown",
@@ -674,13 +694,16 @@ class ProxyTracer {
674
694
  }
675
695
  // Cost
676
696
  const billingUsage = proxyTokenUsage(this.usage);
677
- const cost = calculateCost(this.billingProvider, this.model, billingUsage);
678
- if (cost > 0) {
697
+ const cost = isExactPricingMatch(this.billingProvider, this.model) &&
698
+ hasPricing(this.billingProvider, this.model)
699
+ ? calculateCost(this.billingProvider, this.model, billingUsage)
700
+ : undefined;
701
+ if (cost !== undefined) {
679
702
  m.costTotal.add(cost, tokenLabels);
680
703
  }
681
704
  }
682
705
  // Error metrics
683
- if (responseStatus >= 400) {
706
+ if (this.recordRequestMetrics && responseStatus >= 400) {
684
707
  const errorType = responseStatus === 429
685
708
  ? "rate_limit"
686
709
  : responseStatus === 401
@@ -698,14 +721,17 @@ class ProxyTracer {
698
721
  }
699
722
  /** Record metrics via TelemetryService (call after setUsage). */
700
723
  recordMetrics() {
701
- if (!this.usage) {
724
+ if (!this.recordUsageMetrics || !this.usage) {
702
725
  return;
703
726
  }
704
727
  const billingUsage = proxyTokenUsage(this.usage);
705
728
  const totalTokens = billingUsage.total;
706
729
  const durationMs = Date.now() - this.startTime;
707
- const cost = calculateCost(this.billingProvider, this.model, billingUsage);
708
- TelemetryService.getInstance().recordAIRequest(this.billingProvider, this.model, totalTokens, durationMs, cost > 0 ? cost : undefined);
730
+ const cost = isExactPricingMatch(this.billingProvider, this.model) &&
731
+ hasPricing(this.billingProvider, this.model)
732
+ ? calculateCost(this.billingProvider, this.model, billingUsage)
733
+ : undefined;
734
+ TelemetryService.getInstance().recordAIRequest(this.billingProvider, this.model, totalTokens, durationMs, cost);
709
735
  }
710
736
  // -------------------------------------------------------------------------
711
737
  // Context propagation