@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.
- package/CHANGELOG.md +6 -2
- package/dist/browser/neurolink.min.js +430 -421
- package/dist/cli/commands/proxy.js +455 -259
- package/dist/constants/contextWindows.d.ts +2 -0
- package/dist/constants/contextWindows.js +4 -0
- package/dist/neurolink.js +8 -3
- package/dist/providers/openaiChatCompletionsBase.js +60 -51
- package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
- package/dist/proxy/anthropicBudgetResponse.js +153 -0
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
- package/dist/proxy/bodyCaptureProcessing.js +23 -4
- package/dist/proxy/bodyCaptureWorker.js +20 -2
- package/dist/proxy/codexFallback.d.ts +10 -0
- package/dist/proxy/codexFallback.js +108 -24
- package/dist/proxy/codexUsage.js +2 -0
- package/dist/proxy/globalInstaller.d.ts +32 -1
- package/dist/proxy/globalInstaller.js +386 -3
- package/dist/proxy/otelLogSink.d.ts +3 -3
- package/dist/proxy/otelLogSink.js +137 -144
- package/dist/proxy/otlpLogTransport.d.ts +3 -0
- package/dist/proxy/otlpLogTransport.js +334 -0
- package/dist/proxy/proxyActivity.d.ts +22 -2
- package/dist/proxy/proxyActivity.js +114 -9
- package/dist/proxy/proxyAnalysis.js +82 -52
- package/dist/proxy/proxyClientCancellation.d.ts +7 -0
- package/dist/proxy/proxyClientCancellation.js +23 -0
- package/dist/proxy/proxyContextPreflight.d.ts +25 -0
- package/dist/proxy/proxyContextPreflight.js +337 -0
- package/dist/proxy/proxyFailureDetails.d.ts +19 -0
- package/dist/proxy/proxyFailureDetails.js +96 -0
- package/dist/proxy/proxyLifecycle.js +7 -0
- package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
- package/dist/proxy/proxyRouteBodyCapture.js +112 -0
- package/dist/proxy/proxyTokenBudget.d.ts +40 -0
- package/dist/proxy/proxyTokenBudget.js +523 -0
- package/dist/proxy/proxyTracer.d.ts +3 -2
- package/dist/proxy/proxyTracer.js +46 -20
- package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
- package/dist/proxy/proxyTranslationEngine.js +736 -376
- package/dist/proxy/requestLogger.d.ts +1 -0
- package/dist/proxy/requestLogger.js +107 -5
- package/dist/proxy/restartControl.js +9 -4
- package/dist/proxy/rollingWorkerProcess.js +65 -0
- package/dist/proxy/rollingWorkerSupervisor.js +7 -0
- package/dist/proxy/updateCoordinator.d.ts +14 -1
- package/dist/proxy/updateCoordinator.js +57 -2
- package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
- package/dist/server/routes/claudeProxyRoutes.js +809 -577
- package/dist/server/routes/codexProxyRoutes.js +191 -9
- package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
- package/dist/server/routes/openaiProxyRoutes.js +261 -69
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/codex.d.ts +5 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/proxy.d.ts +148 -3
- package/dist/types/proxy.js +0 -15
- package/dist/types/proxyBudget.d.ts +74 -0
- package/dist/types/proxyBudget.js +1 -0
- package/dist/types/proxyContext.d.ts +35 -0
- package/dist/types/proxyContext.js +1 -0
- package/dist/utils/fileDetector.js +28 -7
- package/dist/utils/messageBuilder.js +51 -26
- package/dist/utils/pdfProcessor.js +26 -4
- package/docs/assets/dashboards/README.md +37 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
- package/docs-site/static/search-index.json +1 -1
- package/package.json +7 -2
- package/scripts/observability/NATIVE-DURABLE.md +63 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
- package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
- package/scripts/observability/proxy-telemetry-check.mjs +396 -61
- package/scripts/observability/query-proxy-history.mjs +34 -5
package/dist/types/proxy.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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 {};
|
package/dist/types/proxy.js
CHANGED
|
@@ -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 {};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Uses multi-strategy approach for reliable type identification
|
|
5
5
|
*/
|
|
6
6
|
import { open, readFile, realpath } from "fs/promises";
|
|
7
|
+
import { STATUS_CODES } from "node:http";
|
|
7
8
|
import { basename, isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
8
9
|
import { request } from "undici";
|
|
9
10
|
import { redirectFollowingDispatcher } from "./redirectDispatcher.js";
|
|
@@ -675,21 +676,35 @@ export class FileDetector {
|
|
|
675
676
|
new ContentHeuristicStrategy(),
|
|
676
677
|
];
|
|
677
678
|
let best = null;
|
|
679
|
+
// #397: track which strategy produced `best` so the eventual log line
|
|
680
|
+
// can name it — the strategy instances themselves are gone by the time
|
|
681
|
+
// logging happens below, only their results survive.
|
|
682
|
+
let bestStrategyName = "none";
|
|
678
683
|
for (const strategy of strategies) {
|
|
684
|
+
const strategyName = strategy.constructor.name;
|
|
679
685
|
const result = await strategy.detect(strategy instanceof ExtensionStrategy && extensionInput
|
|
680
686
|
? extensionInput
|
|
681
687
|
: input);
|
|
682
688
|
if (!best || result.metadata.confidence > best.metadata.confidence) {
|
|
683
689
|
best = result;
|
|
690
|
+
bestStrategyName = strategyName;
|
|
684
691
|
}
|
|
685
692
|
if (result.metadata.confidence >= confidenceThreshold) {
|
|
686
|
-
logger.info(`[FileDetector] Type: ${result.type} (${result.metadata.confidence}
|
|
693
|
+
logger.info(`[FileDetector] Type: ${result.type} (${result.metadata.confidence}%, strategy: ${strategyName})`);
|
|
687
694
|
return FileDetector.withResolvedExtension(result, input, options);
|
|
688
695
|
}
|
|
689
696
|
}
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
|
|
697
|
+
// A genuinely unidentifiable input — every strategy failed to produce
|
|
698
|
+
// even a type — is a real failure, not a best-effort guess, so it is
|
|
699
|
+
// surfaced at error level instead of being buried in debug output.
|
|
700
|
+
if (!best || best.type === "unknown") {
|
|
701
|
+
logger.error(`[FileDetector] Detection failed: no strategy identified a type (best: ${bestStrategyName}, ${best?.metadata.confidence ?? 0}%, threshold ${confidenceThreshold}%)`);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
// Below-threshold detection is the common case for any file under the
|
|
705
|
+
// ContentHeuristic ceiling — a debug detail, not a warning-worthy anomaly.
|
|
706
|
+
logger.debug(`[FileDetector] Best-effort type below threshold: ${best.type} (${best.metadata.confidence}%, threshold ${confidenceThreshold}%, strategy: ${bestStrategyName})`);
|
|
707
|
+
}
|
|
693
708
|
return FileDetector.withResolvedExtension(best, input, options);
|
|
694
709
|
}
|
|
695
710
|
/**
|
|
@@ -1543,9 +1558,15 @@ export class FileDetector {
|
|
|
1543
1558
|
bodyTimeout: timeout,
|
|
1544
1559
|
});
|
|
1545
1560
|
if (response.statusCode !== 200) {
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
|
|
1561
|
+
// #360: pair the code with its standard reason phrase (e.g. 404 →
|
|
1562
|
+
// "Not Found") so the message is readable rather than a bare
|
|
1563
|
+
// number. Sourced from Node's own `http.STATUS_CODES` table
|
|
1564
|
+
// rather than a hand-written map that could drift from it, and
|
|
1565
|
+
// falls back gracefully for a non-standard code that table
|
|
1566
|
+
// doesn't recognize. Query string / fragment stays stripped — a
|
|
1567
|
+
// presigned URL's token must not be echoed into a thrown error.
|
|
1568
|
+
const statusText = STATUS_CODES[response.statusCode];
|
|
1569
|
+
throw new Error(`HTTP ${response.statusCode}${statusText ? ` ${statusText}` : ""} fetching ${redactUrlForError(url)}`);
|
|
1549
1570
|
}
|
|
1550
1571
|
const contentType = response.headers["content-type"] || "";
|
|
1551
1572
|
const declaredLength = Number(response.headers["content-length"]);
|
|
@@ -1877,10 +1877,14 @@ async function processImageToBase64(image, index) {
|
|
|
1877
1877
|
const match = image.match(/^data:([^;]+);base64,(.+)$/);
|
|
1878
1878
|
if (match) {
|
|
1879
1879
|
const declaredMime = match[1];
|
|
1880
|
-
// #348:
|
|
1881
|
-
//
|
|
1882
|
-
|
|
1883
|
-
|
|
1880
|
+
// #348: validate against the actual supported-image allowlist, not
|
|
1881
|
+
// just the "image/*" prefix — "image/invalid-format" started with
|
|
1882
|
+
// "image/" and passed here, only to fail later at the provider.
|
|
1883
|
+
// Reuses ImageProcessor's allowlist (itself sourced from
|
|
1884
|
+
// imageFormatSupport's SUPPORTED_INPUT_IMAGE_MIME_TYPES) so intake
|
|
1885
|
+
// validation can never drift from the set NeuroLink actually accepts.
|
|
1886
|
+
if (!ImageProcessor.validateImageFormat(declaredMime)) {
|
|
1887
|
+
throw new Error(`Unsupported data URI MIME type for image input at index ${index}: "${declaredMime}" (not a supported image format)`);
|
|
1884
1888
|
}
|
|
1885
1889
|
mimeType = declaredMime;
|
|
1886
1890
|
imageData = match[2]; // Raw base64 only — NOT the full data: URI
|
|
@@ -2323,7 +2327,16 @@ pdfFiles, provider, model, audioFiles = []) {
|
|
|
2323
2327
|
for (const pdf of pdfFiles) {
|
|
2324
2328
|
try {
|
|
2325
2329
|
const effectiveMaxPages = pdf.maxPages ?? PDF_LIMITS.DEFAULT_MAX_PAGES;
|
|
2326
|
-
|
|
2330
|
+
// #302: stream pages one at a time instead of materialising every
|
|
2331
|
+
// rendered page before any of them reaches `content` — this is the
|
|
2332
|
+
// path generate()/stream() callers actually exercise, so it must
|
|
2333
|
+
// carry the memory characteristic convertToImagesStream was built
|
|
2334
|
+
// for. Per-page ordering, error isolation (#294) and the resulting
|
|
2335
|
+
// message content are kept identical to the batch path below;
|
|
2336
|
+
// only how the pages are produced changes.
|
|
2337
|
+
const pageErrors = [];
|
|
2338
|
+
let convertedCount = 0;
|
|
2339
|
+
for await (const page of PDFImageConverter.convertToImagesStream(pdf.buffer, {
|
|
2327
2340
|
// #297: this is the only PDF→image call the product actually makes,
|
|
2328
2341
|
// and it used to hardcode scale 2.0 — silently overriding the
|
|
2329
2342
|
// lowered PDF_LIMITS.DEFAULT_SCALE and keeping the memory cost the
|
|
@@ -2337,7 +2350,34 @@ pdfFiles, provider, model, audioFiles = []) {
|
|
|
2337
2350
|
...(pdf.maxCanvasPixels
|
|
2338
2351
|
? { maxCanvasPixels: pdf.maxCanvasPixels }
|
|
2339
2352
|
: {}), // #260
|
|
2340
|
-
})
|
|
2353
|
+
})) {
|
|
2354
|
+
if (page.error) {
|
|
2355
|
+
// #294: per-page isolation — a failed page is recorded and
|
|
2356
|
+
// skipped, not thrown, so the rest of the document still
|
|
2357
|
+
// reaches the model.
|
|
2358
|
+
pageErrors.push({ page: page.pageIndex, error: page.error });
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
convertedCount++;
|
|
2362
|
+
// Add each page as an ImagePart (raw base64, not data: URI — see SSRF note above)
|
|
2363
|
+
content.push({
|
|
2364
|
+
type: "image",
|
|
2365
|
+
image: page.image,
|
|
2366
|
+
mimeType: "image/png",
|
|
2367
|
+
});
|
|
2368
|
+
logger.debug(`[PDF→Image] Added page ${page.pageIndex} of ${pdf.filename}`);
|
|
2369
|
+
}
|
|
2370
|
+
// Batch's convertToImages throws when nothing converted (either a
|
|
2371
|
+
// zero-page PDF or every page failed) instead of returning an empty
|
|
2372
|
+
// result; convertToImagesStream has no such guard (it simply yields
|
|
2373
|
+
// nothing / all-error pages), so replicate that contract explicitly
|
|
2374
|
+
// to keep the two paths' failure behavior identical.
|
|
2375
|
+
if (convertedCount === 0) {
|
|
2376
|
+
if (pageErrors.length > 0) {
|
|
2377
|
+
throw new Error(`All ${pageErrors.length} page(s) failed to render. First error: ${pageErrors[0].error}`);
|
|
2378
|
+
}
|
|
2379
|
+
throw new Error("PDF has 0 pages. Cannot convert empty PDF to images.");
|
|
2380
|
+
}
|
|
2341
2381
|
// The renderer stops at maxPages, so a longer document is silently
|
|
2342
2382
|
// truncated — say so rather than letting the model answer from a
|
|
2343
2383
|
// partial document as though it had the whole thing.
|
|
@@ -2348,31 +2388,16 @@ pdfFiles, provider, model, audioFiles = []) {
|
|
|
2348
2388
|
// there. Reaching the cap is also unambiguous — a short count caused by
|
|
2349
2389
|
// per-page render failures (#294 isolates those into `errors`) would
|
|
2350
2390
|
// otherwise be misreported as a maxPages truncation.
|
|
2351
|
-
if (
|
|
2391
|
+
if (convertedCount >= effectiveMaxPages) {
|
|
2352
2392
|
logger.warn(`[PDF→Image] ${safeBasename(pdf.filename)} hit the ${effectiveMaxPages}-page ` +
|
|
2353
2393
|
`conversion limit. Any pages beyond that were not sent — the model may be ` +
|
|
2354
2394
|
`answering from a partial document. Raise pdfOptions.maxPages or split the file.`);
|
|
2355
2395
|
}
|
|
2356
|
-
if (
|
|
2357
|
-
logger.warn(`[PDF→Image] ${safeBasename(pdf.filename)}: ${
|
|
2358
|
-
`failed to render and were omitted (page ${
|
|
2359
|
-
}
|
|
2360
|
-
logger.info(`[PDF→Image] ✅ Converted ${pdf.filename}: ${conversionResult.pageCount} page(s) → images`);
|
|
2361
|
-
// Add each page as an ImagePart (raw base64, not data: URI — see SSRF note above)
|
|
2362
|
-
conversionResult.images.forEach((base64Image, pageIndex) => {
|
|
2363
|
-
content.push({
|
|
2364
|
-
type: "image",
|
|
2365
|
-
image: base64Image,
|
|
2366
|
-
mimeType: "image/png",
|
|
2367
|
-
});
|
|
2368
|
-
logger.debug(`[PDF→Image] Added page ${pageIndex + 1}/${conversionResult.pageCount} of ${pdf.filename}`);
|
|
2369
|
-
});
|
|
2370
|
-
// Log any warnings from conversion
|
|
2371
|
-
if (conversionResult.warnings) {
|
|
2372
|
-
conversionResult.warnings.forEach((warning) => {
|
|
2373
|
-
logger.warn(`[PDF→Image] ${warning}`);
|
|
2374
|
-
});
|
|
2396
|
+
if (pageErrors.length > 0) {
|
|
2397
|
+
logger.warn(`[PDF→Image] ${safeBasename(pdf.filename)}: ${pageErrors.length} page(s) ` +
|
|
2398
|
+
`failed to render and were omitted (page ${pageErrors.map((e) => e.page).join(", ")}).`);
|
|
2375
2399
|
}
|
|
2400
|
+
logger.info(`[PDF→Image] ✅ Converted ${pdf.filename}: ${convertedCount} page(s) → images`);
|
|
2376
2401
|
}
|
|
2377
2402
|
catch (error) {
|
|
2378
2403
|
const errorMessage = error instanceof Error ? error.message : String(error);
|