@juspay/neurolink 12.17.0 → 12.17.2

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 (71) hide show
  1. package/CHANGELOG.md +2 -7
  2. package/dist/browser/neurolink.min.js +422 -413
  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/providers/anthropic/client.js +15 -5
  7. package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
  8. package/dist/proxy/anthropicBudgetResponse.js +153 -0
  9. package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
  10. package/dist/proxy/bodyCaptureProcessing.js +23 -4
  11. package/dist/proxy/bodyCaptureWorker.js +20 -2
  12. package/dist/proxy/codexFallback.d.ts +10 -0
  13. package/dist/proxy/codexFallback.js +108 -24
  14. package/dist/proxy/codexUsage.js +2 -0
  15. package/dist/proxy/globalInstaller.d.ts +32 -1
  16. package/dist/proxy/globalInstaller.js +386 -3
  17. package/dist/proxy/otelLogSink.d.ts +3 -3
  18. package/dist/proxy/otelLogSink.js +137 -144
  19. package/dist/proxy/otlpLogTransport.d.ts +3 -0
  20. package/dist/proxy/otlpLogTransport.js +334 -0
  21. package/dist/proxy/proxyActivity.d.ts +22 -2
  22. package/dist/proxy/proxyActivity.js +114 -9
  23. package/dist/proxy/proxyAnalysis.js +82 -52
  24. package/dist/proxy/proxyClientCancellation.d.ts +7 -0
  25. package/dist/proxy/proxyClientCancellation.js +23 -0
  26. package/dist/proxy/proxyContextPreflight.d.ts +25 -0
  27. package/dist/proxy/proxyContextPreflight.js +337 -0
  28. package/dist/proxy/proxyFailureDetails.d.ts +19 -0
  29. package/dist/proxy/proxyFailureDetails.js +96 -0
  30. package/dist/proxy/proxyLifecycle.js +7 -0
  31. package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
  32. package/dist/proxy/proxyRouteBodyCapture.js +112 -0
  33. package/dist/proxy/proxyTokenBudget.d.ts +40 -0
  34. package/dist/proxy/proxyTokenBudget.js +523 -0
  35. package/dist/proxy/proxyTracer.d.ts +3 -2
  36. package/dist/proxy/proxyTracer.js +46 -20
  37. package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
  38. package/dist/proxy/proxyTranslationEngine.js +736 -376
  39. package/dist/proxy/requestLogger.d.ts +1 -0
  40. package/dist/proxy/requestLogger.js +107 -5
  41. package/dist/proxy/restartControl.js +9 -4
  42. package/dist/proxy/rollingWorkerProcess.js +65 -0
  43. package/dist/proxy/rollingWorkerSupervisor.js +7 -0
  44. package/dist/proxy/updateCoordinator.d.ts +14 -1
  45. package/dist/proxy/updateCoordinator.js +57 -2
  46. package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
  47. package/dist/server/routes/claudeProxyRoutes.js +809 -577
  48. package/dist/server/routes/codexProxyRoutes.js +191 -9
  49. package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
  50. package/dist/server/routes/openaiProxyRoutes.js +261 -69
  51. package/dist/types/cli.d.ts +3 -0
  52. package/dist/types/codex.d.ts +5 -1
  53. package/dist/types/index.d.ts +2 -0
  54. package/dist/types/index.js +2 -0
  55. package/dist/types/proxy.d.ts +148 -3
  56. package/dist/types/proxy.js +0 -15
  57. package/dist/types/proxyBudget.d.ts +74 -0
  58. package/dist/types/proxyBudget.js +1 -0
  59. package/dist/types/proxyContext.d.ts +35 -0
  60. package/dist/types/proxyContext.js +1 -0
  61. package/dist/utils/toolChoice.d.ts +19 -0
  62. package/dist/utils/toolChoice.js +39 -0
  63. package/docs/assets/dashboards/README.md +37 -0
  64. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
  65. package/docs-site/static/search-index.json +1 -1
  66. package/package.json +4 -1
  67. package/scripts/observability/NATIVE-DURABLE.md +63 -0
  68. package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
  69. package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
  70. package/scripts/observability/proxy-telemetry-check.mjs +396 -61
  71. package/scripts/observability/query-proxy-history.mjs +34 -5
@@ -1,3 +1,5 @@
1
+ import type { ProxyTokenBudgetSnapshot } from "./proxyBudget.js";
2
+ import type { ProxyContextEvidence, ProxyPreparedContext } from "./proxyContext.js";
1
3
  /**
2
4
  * Proxy type definitions for NeuroLink
3
5
  *
@@ -556,6 +558,27 @@ export type ProxyAccountSortMetrics = {
556
558
  scopedSaturated: boolean;
557
559
  };
558
560
  export type RequestLogEntry = {
561
+ /** Whether model identifies observed serving output rather than a routing target. */
562
+ servingModelStatus?: "observed" | "unavailable";
563
+ /** Whether retrying unchanged input can resolve this terminal failure. */
564
+ retryable?: boolean;
565
+ tokenBudget?: ProxyTokenBudgetSnapshot;
566
+ contextPreflight?: ProxyContextEvidence;
567
+ /** Internal adapter requests link to their one client-facing parent. */
568
+ parentRequestId?: string;
569
+ accountingScope?: "client" | "internal";
570
+ /** Exactly this request owns usage; bridge parents never duplicate it. */
571
+ usageOwnerRequestId?: string;
572
+ /** API price-table estimate, never a subscription charge or quota measure.
573
+ * Null when complete usage and an exact model rate are not both available. */
574
+ apiEquivalentCostUsd?: number | null;
575
+ /** API-equivalent cache-read savings against uncached input at the same rate. */
576
+ apiEquivalentCacheSavingsUsd?: number | null;
577
+ pricingStatus?: "exact" | "inferred" | "unavailable" | "usage_incomplete" | "owned_by_child";
578
+ pricingBasis?: "api_price_table";
579
+ pricingProvider?: string;
580
+ /** Credential identity is unavailable when the SDK does not expose it. */
581
+ accountIdentityStatus?: "observed" | "unavailable";
559
582
  /** First text, refusal or populated tool output, excluding SSE control frames. */
560
583
  firstUsefulOutputMs?: number;
561
584
  /** Why first-useful-output timing is absent; never substitute a zero. */
@@ -646,7 +669,10 @@ export type ProcessedProxyBodyCapture = {
646
669
  headers?: Record<string, string>;
647
670
  stored: StoredBodyArtifact;
648
671
  error?: string;
672
+ /** Admission plus worker queue time; retained for existing consumers. */
649
673
  queueWaitMs?: number;
674
+ admissionWaitMs?: number;
675
+ workerQueueWaitMs?: number;
650
676
  processingMs?: number;
651
677
  admission?: ProxyBodyCaptureAdmission;
652
678
  };
@@ -662,6 +688,11 @@ export type ProxyBodyCaptureAdmission = {
662
688
  maxPendingBytes: number;
663
689
  };
664
690
  export type ProxyBodyCaptureWorkerSnapshot = {
691
+ processing?: number;
692
+ publishing?: number;
693
+ oldestAdmissionWaitMs?: number;
694
+ oldestProcessingMs?: number;
695
+ oldestPublicationMs?: number;
665
696
  attempted: number;
666
697
  completed: number;
667
698
  rejected: number;
@@ -686,6 +717,13 @@ export type ProxyBodyDeliveryResult = {
686
717
  unconfirmedChunks: number;
687
718
  droppedChunks: number;
688
719
  notSubmittedChunks?: number;
720
+ /** Total publication elapsed time, including capacity and exporter waits. */
721
+ publicationMs?: number;
722
+ capacityWaitMs?: number;
723
+ /** Maximum per-chunk residence before an OTLP export starts. */
724
+ maxChunkQueueWaitMs?: number;
725
+ /** Maximum per-chunk transport duration; never sums overlapping exports. */
726
+ maxChunkExportMs?: number;
689
727
  reason?: string;
690
728
  };
691
729
  /** One bounded body publication, tracked across exporter callbacks. */
@@ -694,6 +732,8 @@ export type ProxyBodyPublicationProgress = {
694
732
  unconfirmed: number;
695
733
  dropped: number;
696
734
  emitted: number;
735
+ maxChunkQueueWaitMs?: number;
736
+ maxChunkExportMs?: number;
697
737
  notify?: () => void;
698
738
  };
699
739
  /** Bounded metadata-only evidence for an unconfirmed or locally rejected OTLP batch. */
@@ -714,6 +754,8 @@ export type ProxyTelemetryBackend = {
714
754
  baseUrl: string;
715
755
  organization: string;
716
756
  stream: string;
757
+ /** Optional separate backend storage for bulk body chunks. */
758
+ bodyStream?: string;
717
759
  authorization?: string;
718
760
  collectorMetricsUrl?: string;
719
761
  };
@@ -741,6 +783,10 @@ export type ProxyTelemetryDoctorOptions = {
741
783
  endTime: number;
742
784
  proxyUrl?: string;
743
785
  maxRows?: number;
786
+ /** Explicit age-search horizon; older admissions are outside this proof. */
787
+ admissionLookbackMs?: number;
788
+ requestTimeoutMs?: number;
789
+ ingestionGraceMs?: number;
744
790
  fetchImpl?: typeof fetch;
745
791
  };
746
792
  export type ProxyTelemetryCheck = {
@@ -766,6 +812,8 @@ export type ProxyTelemetryStoredRecord = Partial<RequestLogEntry> & {
766
812
  event?: string;
767
813
  telemetryStatus?: string;
768
814
  outcomeSource?: string;
815
+ requestTimeoutMs?: number;
816
+ redactionLossy?: boolean;
769
817
  };
770
818
  export type ProxyTelemetryFieldCoverage = {
771
819
  records: number;
@@ -786,6 +834,14 @@ export type ProxyRequestLoggerSnapshot = {
786
834
  debug: ProxyRequestLogSinkSnapshot;
787
835
  };
788
836
  export type RequestAttemptLogEntry = {
837
+ /** False for a local context/budget refusal before a provider call. */
838
+ upstreamDispatched?: boolean;
839
+ /** Client model name retained independently of the dispatched model. */
840
+ requestedModel?: string;
841
+ tokenBudget?: ProxyTokenBudgetSnapshot;
842
+ contextPreflight?: ProxyContextEvidence;
843
+ accountingScope?: "client" | "internal";
844
+ usageOwnerRequestId?: string;
789
845
  timestamp: string;
790
846
  requestId: string;
791
847
  /** Parent client request for an internal fallback invocation. */
@@ -825,10 +881,14 @@ export type RequestAttemptLogEntry = {
825
881
  quotaResetAt?: number;
826
882
  /** Unknown scope never implies an account-wide long-window rejection. */
827
883
  quotaScope?: "session" | "weekly" | "unknown";
884
+ /** True when input includes the cache breakdown (native Codex wire usage). */
885
+ inputIncludesCachedTokens?: boolean;
828
886
  inputTokens?: number;
829
887
  outputTokens?: number;
830
888
  cacheCreationTokens?: number;
831
889
  cacheReadTokens?: number;
890
+ /** Reasoning tokens are a subset of output, never additional usage. */
891
+ reasoningTokens?: number;
832
892
  /** Provider that received this upstream attempt. */
833
893
  provider?: string;
834
894
  /** OTel trace ID for correlation with distributed traces */
@@ -839,9 +899,9 @@ export type RequestAttemptLogEntry = {
839
899
  traceFlags?: number;
840
900
  };
841
901
  /** Additional fields recorded when a Codex response becomes client-final. */
842
- export type CodexFinalLogExtra = Partial<Pick<RequestLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "reasoningTokens" | "terminalOutcome" | "firstUsefulOutputMs" | "firstUsefulOutputStatus" | "firstUsefulOutputEvent">>;
902
+ export type CodexFinalLogExtra = Partial<Pick<RequestLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "reasoningTokens" | "terminalOutcome" | "firstUsefulOutputMs" | "firstUsefulOutputStatus" | "firstUsefulOutputEvent" | "retryable">>;
843
903
  /** Additional fields recorded for each upstream Codex account attempt. */
844
- export type CodexAttemptLogExtra = Partial<Pick<RequestAttemptLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "retryable" | "rateLimitKind" | "cooldownReason" | "quotaResetAt" | "quotaScope">>;
904
+ export type CodexAttemptLogExtra = Partial<Pick<RequestAttemptLogEntry, "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "reasoningTokens" | "inputIncludesCachedTokens" | "errorType" | "errorMessage" | "errorCode" | "transportScope" | "retryable" | "rateLimitKind" | "cooldownReason" | "quotaResetAt" | "quotaScope">>;
845
905
  /** Minimal persistence contract needed by Codex rotating-token refreshes. */
846
906
  export type CodexRefreshTokenStore = {
847
907
  peekTokens(provider: string): Promise<StoredOAuthTokens | null>;
@@ -876,6 +936,7 @@ export type ClaudeFinalRequestLogger = (status: number, accountLabel: string, ac
876
936
  cacheReadTokens?: number;
877
937
  reasoningTokens?: number;
878
938
  errorCode?: string;
939
+ retryable?: boolean;
879
940
  transportScope?: ProxyTransportScope;
880
941
  }) => void;
881
942
  export type ClaudeLoggedErrorBuilder = (status: number, message: string, errorType?: string, extra?: {
@@ -883,6 +944,7 @@ export type ClaudeLoggedErrorBuilder = (status: number, message: string, errorTy
883
944
  accountType?: string;
884
945
  attempt?: number;
885
946
  errorCode?: string;
947
+ retryable?: boolean;
886
948
  transportScope?: ProxyTransportScope;
887
949
  }) => ClaudeErrorResponse;
888
950
  export type ClaudeRequestRuntimeContext = {
@@ -935,6 +997,7 @@ export type AnthropicLoopState = {
935
997
  export type AnthropicUpstreamBody = {
936
998
  bodyStr: string;
937
999
  sessionId?: string;
1000
+ preparedContext: ProxyPreparedContext<ClaudeRequest>;
938
1001
  };
939
1002
  export type AnthropicUpstreamBodyBuilder = (token: string) => AnthropicUpstreamBody;
940
1003
  export type LoadedClaudeAccountContext = {
@@ -1044,6 +1107,7 @@ export type PreparedAnthropicAccountAttempt = {
1044
1107
  headers?: Record<string, string>;
1045
1108
  buildUpstreamBody?: AnthropicUpstreamBodyBuilder;
1046
1109
  finalBodyStr?: string;
1110
+ preparedContext?: ProxyPreparedContext<ClaudeRequest>;
1047
1111
  fetchStartMs?: number;
1048
1112
  upstreamSpan?: Span;
1049
1113
  };
@@ -1681,8 +1745,12 @@ export type ProxyRequestContext = {
1681
1745
  * every non-Anthropic model prices to $0.
1682
1746
  */
1683
1747
  provider?: string;
1684
- /** An internal fallback is an attempt; its parent owns request/token metrics. */
1748
+ /** Whether this span owns client request, latency, and error metrics. */
1685
1749
  recordRequestMetrics?: boolean;
1750
+ /** Provider usage ownership. Defaults to recordRequestMetrics for backwards
1751
+ * compatibility: internal Codex fallback spans suppress both, while bridge
1752
+ * children explicitly retain usage and their parent owns client outcomes. */
1753
+ recordUsageMetrics?: boolean;
1686
1754
  };
1687
1755
  /** Response-side details parsed from the upstream reply (model, finish, tools). */
1688
1756
  export type ResponseInfoContext = {
@@ -1722,6 +1790,11 @@ export type UsageContext = {
1722
1790
  rateLimitAfter5h?: number;
1723
1791
  rateLimitAfter7d?: number;
1724
1792
  };
1793
+ /** Keep trace usage when a child request owns its token and cost metrics. */
1794
+ export type ProxyUsageAttributionOptions = {
1795
+ /** Defaults to the tracer's configured ownership when omitted. */
1796
+ recordMetrics?: boolean;
1797
+ };
1725
1798
  /** Where a proxy env file path was sourced from. */
1726
1799
  export type ProxyEnvSource = "cli" | "environment" | "default" | "none";
1727
1800
  /** Result of resolving which proxy env file to load. */
@@ -1765,6 +1838,11 @@ export type ProxyActivitySnapshot = {
1765
1838
  export type ProxyRuntimeActivity = {
1766
1839
  activeRequests: number;
1767
1840
  lastActivityAt: string | null;
1841
+ /** Supervisor-wide resources that must settle before process replacement. */
1842
+ drainingWorkers?: number;
1843
+ queuedSockets?: number;
1844
+ pendingTransfers?: number;
1845
+ candidateWorkers?: number;
1768
1846
  };
1769
1847
  /** Terminal state observed while the HTTP adapter relays a response body. */
1770
1848
  export type ProxyResponseTerminalOutcome = "completed" | "bodyless" | "client_cancelled" | "stream_error";
@@ -1790,6 +1868,13 @@ export type ProxyLifecycleEventName = "runtime_sample" | "supervisor_event" | "r
1790
1868
  export type ProxyLifecycleTerminalOutcome = ProxyResponseTerminalOutcome | "handler_error" | "unknown";
1791
1869
  /** Content-free lifecycle event accepted by the bounded metadata logger. */
1792
1870
  export type ProxyLifecycleEventInput = {
1871
+ /** Hard admission-to-terminal deadline used by reconciliation. */
1872
+ requestTimeoutMs?: number;
1873
+ /** Internal adapter requests link to their one client-facing parent. */
1874
+ parentRequestId?: string;
1875
+ accountingScope?: "client" | "internal";
1876
+ /** Exactly this request owns usage; bridge parents never duplicate it. */
1877
+ usageOwnerRequestId?: string;
1793
1878
  event: ProxyLifecycleEventName;
1794
1879
  requestId: string;
1795
1880
  method: string;
@@ -1976,6 +2061,9 @@ export type ProxyAnalysisReport = {
1976
2061
  providerFinalRecorded: boolean;
1977
2062
  }>;
1978
2063
  accepted: number;
2064
+ internalAccepted?: number;
2065
+ internalTerminal?: number;
2066
+ internalUnsettled?: number;
1979
2067
  /** Accepted metadata requests that do not require model final records. */
1980
2068
  auxiliaryRequests: number;
1981
2069
  headers: number;
@@ -1988,6 +2076,8 @@ export type ProxyAnalysisReport = {
1988
2076
  };
1989
2077
  requests: {
1990
2078
  completed: number;
2079
+ /** Linked internal adapter finals excluded from client request totals. */
2080
+ internalCompleted?: number;
1991
2081
  success: number;
1992
2082
  errors: number;
1993
2083
  finalRateLimits: number;
@@ -2075,6 +2165,9 @@ export type ProxyAnalysisAttemptRecord = {
2075
2165
  };
2076
2166
  /** Final request fields retained while joining offline proxy log records. */
2077
2167
  export type ProxyAnalysisFinalRequestRecord = {
2168
+ accountingScope?: "client" | "internal";
2169
+ parentRequestId?: string;
2170
+ usageOwnerRequestId?: string;
2078
2171
  firstUsefulOutputMs: number | null;
2079
2172
  timestamp: string;
2080
2173
  status: number;
@@ -2114,6 +2207,9 @@ export type ProxyCancellableTransformer<I, O> = Transformer<I, O> & {
2114
2207
  * a null result as "not observed", never as "zero tokens".
2115
2208
  */
2116
2209
  export type CodexStreamUsage = {
2210
+ /** Distinguish missing provider usage from a reported zero. */
2211
+ inputTokensObserved?: boolean;
2212
+ outputTokensObserved?: boolean;
2117
2213
  inputTokens: number;
2118
2214
  outputTokens: number;
2119
2215
  cacheReadTokens: number;
@@ -2146,6 +2242,12 @@ export type ProxyAnalysisRoutingRecord = {
2146
2242
  };
2147
2243
  /** Request metadata retained by the HTTP adapter for terminal error logging. */
2148
2244
  export type RuntimeRequestMetadata = {
2245
+ /** Internal adapter requests link to their one client-facing parent. */
2246
+ parentRequestId?: string;
2247
+ accountingScope?: "client" | "internal";
2248
+ /** Exactly this request owns usage; bridge parents never duplicate it. */
2249
+ usageOwnerRequestId?: string;
2250
+ abortController?: AbortController;
2149
2251
  /** Last dispatched attempt, retained until this HTTP request terminates. */
2150
2252
  lastUpstreamAttempt?: RequestAttemptLogEntry;
2151
2253
  requestId: string;
@@ -2331,6 +2433,14 @@ export type CompareProxyReplayOptions = {
2331
2433
  };
2332
2434
  /** Persisted artifact produced when a body is stored to disk. */
2333
2435
  export type StoredBodyArtifact = {
2436
+ /** Bytes supplied to redaction, after any source observer limit. */
2437
+ inputRetainedBytes?: number;
2438
+ inputEncoding?: "utf8_text" | "structured_object";
2439
+ sourceTruncated?: boolean;
2440
+ processingTruncated?: boolean;
2441
+ /** Invalid structured frames were removed for secret safety. */
2442
+ redactionLossy?: boolean;
2443
+ unparseableRedactedFrames?: number;
2334
2444
  bodyPath?: string;
2335
2445
  bodySha256?: string;
2336
2446
  redactedBodyBytes?: number;
@@ -4029,4 +4139,39 @@ type ProxyBodyCaptureWaitingSnapshot = {
4029
4139
  admissionWaits: number;
4030
4140
  admissionTimeouts: number;
4031
4141
  };
4142
+ /** Immutable package selection used by the proxy launcher, never by global npm. */
4143
+ export type ProxyPackageSelection = {
4144
+ version: string;
4145
+ entryScript: string;
4146
+ nodePath: string;
4147
+ };
4148
+ /** Atomically published active and rollback package selections. */
4149
+ export type ProxyPackageSelectionState = {
4150
+ schemaVersion: 1;
4151
+ active: ProxyPackageSelection | null;
4152
+ previous: ProxyPackageSelection | null;
4153
+ };
4154
+ /** Bounded asynchronous package installation with injectable subprocesses. */
4155
+ export type ProxyStagedInstallOptions = {
4156
+ version: string;
4157
+ packagesDir: string;
4158
+ installer: Pick<GlobalInstallerProbe, "kind" | "bin">;
4159
+ idleTimeoutMs?: number;
4160
+ maxDurationMs?: number;
4161
+ killGraceMs?: number;
4162
+ spawn?: typeof import("node:child_process").spawn;
4163
+ execFileSync?: GlobalInstallerExecFile;
4164
+ onProgress?: (progress: {
4165
+ elapsedMs: number;
4166
+ outputBytes: number;
4167
+ }) => void;
4168
+ };
4169
+ /** Saved launchd settings retained when installing the service again. */
4170
+ export type ProxyServiceInstallSettings = {
4171
+ envFile?: string;
4172
+ configFile?: string;
4173
+ host?: string;
4174
+ port?: number;
4175
+ environment: Record<string, string>;
4176
+ };
4032
4177
  export {};
@@ -1,16 +1 @@
1
- /**
2
- * Proxy type definitions for NeuroLink
3
- *
4
- * Consolidates all proxy-related types from:
5
- * - src/lib/proxy/claudeFormat.ts (Claude API types, SSE types, internal result)
6
- * - src/lib/proxy/cloaking/types.ts (cloaking pipeline types)
7
- * - src/lib/proxy/cloaking/plugins/ (plugin option types)
8
- * - src/lib/proxy/modelRouter.ts (routing types)
9
- * - src/lib/proxy/proxyConfig.ts (config file types)
10
- * - src/lib/proxy/requestLogger.ts (log entry type)
11
- * - src/lib/proxy/usageStats.ts (stats types)
12
- * - src/lib/proxy/tokenRefresh.ts (refresh types)
13
- * - src/lib/proxy/accountQuota.ts (quota type)
14
- * - src/lib/server/routes/claudeProxyRoutes.ts (runtime state, deps)
15
- */
16
1
  export {};
@@ -0,0 +1,74 @@
1
+ /** Operator-configured proxy token caps. All caps are optional and disabled by default. */
2
+ export type ProxyTokenBudgetPolicy = {
3
+ /** Per-provider/account estimated outstanding input plus maximum output. */
4
+ maxInFlightTokens?: number;
5
+ /** Charged tokens in the account's fixed window, including outstanding reservations. */
6
+ accountWindowTokens?: number;
7
+ /** Charged tokens across providers/accounts sharing the same client session. */
8
+ sessionWindowTokens?: number;
9
+ windowMs?: number;
10
+ };
11
+ export type ProxyTokenBudgetReservation = {
12
+ provider: string;
13
+ accountKey: string;
14
+ sessionKey: string;
15
+ requestId: string;
16
+ reservationTokens: number;
17
+ policy?: ProxyTokenBudgetPolicy;
18
+ estimateProvenance?: string;
19
+ };
20
+ export type ProxyTokenBudgetSnapshot = {
21
+ scope: "disabled" | "process" | "supervisor";
22
+ reservationTokens: number;
23
+ accountChargedTokens: number;
24
+ accountInFlightTokens: number;
25
+ sessionChargedTokens: number;
26
+ sessionInFlightTokens: number;
27
+ windowMs: number;
28
+ estimateProvenance: string;
29
+ settlement?: "provider_reported" | "estimate_retained" | "cancelled_before_dispatch" | "unconfirmed";
30
+ };
31
+ export type ProxyTokenBudgetLease = {
32
+ leaseId: string;
33
+ snapshot: ProxyTokenBudgetSnapshot;
34
+ settle: (actualTotalTokens?: number) => Promise<void>;
35
+ cancelBeforeDispatch: () => Promise<void>;
36
+ };
37
+ export type ProxyTokenBudgetBucket = {
38
+ chargedTokens: number;
39
+ inFlightTokens: number;
40
+ startedAt: number;
41
+ windowMs: number;
42
+ lastTouched: number;
43
+ };
44
+ export type ProxyTokenBudgetRecord = {
45
+ owner: string;
46
+ accountKey: string;
47
+ sessionKey: string;
48
+ estimate: number;
49
+ snapshot: ProxyTokenBudgetSnapshot;
50
+ };
51
+ export type ProxyTokenBudgetRpcRequest = {
52
+ type: "proxy-budget:request";
53
+ rpcId: string;
54
+ pid: number;
55
+ generation: number;
56
+ action: "reserve" | "settle" | "cancel";
57
+ reservation?: ProxyTokenBudgetReservation;
58
+ leaseId?: string;
59
+ actualTotalTokens?: number;
60
+ };
61
+ export type ProxyTokenBudgetRpcResponse = {
62
+ type: "proxy-budget:response";
63
+ rpcId: string;
64
+ ok: boolean;
65
+ leaseId?: string;
66
+ snapshot?: ProxyTokenBudgetSnapshot;
67
+ errorCode?: string;
68
+ error?: string;
69
+ };
70
+ export type ProxyTokenBudgetRpcPending = {
71
+ resolve: (result: ProxyTokenBudgetRpcResponse) => void;
72
+ reject: (error: Error) => void;
73
+ timer: NodeJS.Timeout;
74
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ /** Explicit proxy context controls; all token quantities remain estimates. */
2
+ export type ProxyContextPolicy = {
3
+ maxInputTokens?: number;
4
+ outputReserveTokens?: number;
5
+ enforceDiscoveredLimits?: boolean;
6
+ toolAllowlist?: string[];
7
+ models?: Record<string, {
8
+ contextWindow: number;
9
+ maxOutputTokens?: number;
10
+ }>;
11
+ };
12
+ export type ProxyContextEvidence = {
13
+ provider: string;
14
+ model: string;
15
+ inputTokensEstimate: number;
16
+ toolsTokensEstimate: number;
17
+ instructionsTokensEstimate: number;
18
+ schemaTokensEstimate: number;
19
+ outputTokensReserve: number;
20
+ reasoningIncludedInOutputReserve: true;
21
+ contextWindow?: number;
22
+ contextLimitSource: "configured" | "discovered" | "unknown";
23
+ tokenCountSource: "estimated";
24
+ multimodalEstimate: boolean;
25
+ originalToolCount: number;
26
+ retainedToolCount: number;
27
+ historyModified: false;
28
+ };
29
+ export type ProxyPreparedContext<T> = {
30
+ body: T;
31
+ inputTokensEstimate: number;
32
+ outputTokensReserve: number;
33
+ totalTokensReservation: number;
34
+ evidence: ProxyContextEvidence;
35
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -2,3 +2,22 @@ import type { Tool, ToolChoice } from "../types/index.js";
2
2
  export declare function resolveToolChoice(options: {
3
3
  toolChoice?: ToolChoice<Record<string, Tool>>;
4
4
  }, tools: Record<string, Tool> | undefined, shouldUseTools: boolean): ToolChoice<Record<string, Tool>> | "none";
5
+ /**
6
+ * Normalise an already-resolved tool choice into the shape
7
+ * `toolChoiceToAnthropic` (and its peers) accept.
8
+ *
9
+ * A provider's model handle sees a value that has *already* been through
10
+ * `resolveToolChoice` upstream, so it must translate rather than re-resolve —
11
+ * re-resolving there would apply the `auto`/`none` defaulting twice, and the
12
+ * `tools`/`shouldUseTools` inputs it needs are not in scope at that layer
13
+ * anyway.
14
+ *
15
+ * What does vary is the shape. The value arrives either as a bare string
16
+ * (`"auto"` / `"none"` / `"required"`), as `{ type: "tool", toolName }`, or —
17
+ * on the native loop's tool-free re-ask — as an injected `{ type: "none" }`
18
+ * object. Reading `.type` off the string forms yields `undefined`, which is
19
+ * how string choices were silently dropped from the Anthropic generate
20
+ * request. This exists so that normalisation lives in one place instead of
21
+ * being re-derived at each call site.
22
+ */
23
+ export declare function normalizeResolvedToolChoice(value: unknown): ToolChoice<Record<string, Tool>> | undefined;
@@ -4,3 +4,42 @@ export function resolveToolChoice(options, tools, shouldUseTools) {
4
4
  }
5
5
  return options.toolChoice ?? "auto";
6
6
  }
7
+ /**
8
+ * Normalise an already-resolved tool choice into the shape
9
+ * `toolChoiceToAnthropic` (and its peers) accept.
10
+ *
11
+ * A provider's model handle sees a value that has *already* been through
12
+ * `resolveToolChoice` upstream, so it must translate rather than re-resolve —
13
+ * re-resolving there would apply the `auto`/`none` defaulting twice, and the
14
+ * `tools`/`shouldUseTools` inputs it needs are not in scope at that layer
15
+ * anyway.
16
+ *
17
+ * What does vary is the shape. The value arrives either as a bare string
18
+ * (`"auto"` / `"none"` / `"required"`), as `{ type: "tool", toolName }`, or —
19
+ * on the native loop's tool-free re-ask — as an injected `{ type: "none" }`
20
+ * object. Reading `.type` off the string forms yields `undefined`, which is
21
+ * how string choices were silently dropped from the Anthropic generate
22
+ * request. This exists so that normalisation lives in one place instead of
23
+ * being re-derived at each call site.
24
+ */
25
+ export function normalizeResolvedToolChoice(value) {
26
+ if (typeof value === "string") {
27
+ return value;
28
+ }
29
+ if (value && typeof value === "object") {
30
+ const candidate = value;
31
+ if (candidate.type === "tool") {
32
+ // A tool choice that names no tool cannot be honoured. Fall through to
33
+ // the provider default rather than emitting the bare string "tool",
34
+ // which is not one of the valid auto/none/required forms and would be
35
+ // rejected downstream.
36
+ return candidate.toolName
37
+ ? { type: "tool", toolName: candidate.toolName }
38
+ : undefined;
39
+ }
40
+ if (candidate.type) {
41
+ return candidate.type;
42
+ }
43
+ }
44
+ return undefined;
45
+ }
@@ -0,0 +1,37 @@
1
+ # Proxy dashboard accounting
2
+
3
+ Import `neurolink-proxy-observability-dashboard.json` into OpenObserve after
4
+ the matching proxy release emits the indexed accounting and pricing fields.
5
+ The template is an artifact; changing it does not update an installed dashboard
6
+ or activate a proxy release. Request panels query the metadata log stream.
7
+ Bulk body captures can use a separate stream.
8
+
9
+ Client traffic, errors, and latency belong to the outer request. An internal
10
+ bridge request owns provider usage when `proxy_usage_owner_request_id` names
11
+ that child; the parent contributes no second token charge. Cached input is
12
+ included once according to `ai_input_includes_cached_tokens`.
13
+
14
+ Log queries first collapse exact producer retries, ignoring ingestion-time
15
+ changes. The stable identity combines service instance and producer event ID;
16
+ older records fall back to request ID where available. Contradictory payloads
17
+ under one identity are excluded from aggregate totals. The Traffic & Health
18
+ conflict counter reports those exclusions. A nonzero counter means totals
19
+ cover only the verified subset; use telemetry doctor for the conflicting raw
20
+ payloads. Rows without any stable identity cannot establish retry deduplication.
21
+
22
+ Cost panels sum `ai_cost_api_equivalent_usd` only when
23
+ `ai_pricing_status='exact'`. These are estimates from this release's API price
24
+ table and complete provider usage, **not subscription charges or quota usage**.
25
+ Prefix-inferred prices, missing rates, partial token usage, and a bridge parent's
26
+ delegated usage do not receive an invented dollar amount. Pricing-gap panels
27
+ show the excluded usage owners; a blank known-cost total is not zero spending.
28
+ Cache savings compare cache reads with the same table's uncached input rate.
29
+ The offline analyzer's historical estimates separately retain their existing
30
+ `requestsPricedByPrefix` and `modelsPricedByPrefix` disclosures.
31
+
32
+ The deterministic capture-pipeline suite executes the shipped request, token,
33
+ cost, and conflict SQL on an in-memory database and records exact price-table,
34
+ partial-usage, and unknown-model OTLP fixtures. SQLite validates the standard SQL
35
+ logic; verify field availability and query compatibility in the target
36
+ OpenObserve version when installing the dashboard. Metric panels retain their
37
+ scrape/window semantics and are not a substitute for reconciled request logs.