@juspay/neurolink 10.9.1 → 10.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,7 @@ import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_
17
17
  import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
18
18
  import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
19
19
  import { getUnifiedRateLimitStatus, isQuotaOverageAvailable, loadAccountQuotas, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
20
+ import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quotaHeaders.js";
20
21
  import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
21
22
  import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
22
23
  import { tracers } from "../../telemetry/tracers.js";
@@ -426,6 +427,56 @@ function resetEpochToMs(resetEpoch, now) {
426
427
  const ms = resetEpoch > 4_102_444_800 ? resetEpoch : resetEpoch * 1000;
427
428
  return ms > now ? ms : undefined;
428
429
  }
430
+ /**
431
+ * Publish limit/quota headers for this response onto the request context.
432
+ *
433
+ * Every response path funnels through here so the header contract is defined
434
+ * once. The proxy runtime copies `ctx.responseHeaders` onto JSON and error
435
+ * responses, and merges them into streaming Responses for keys those don't
436
+ * already set — so a single call covers both shapes.
437
+ */
438
+ function publishLimitHeaders(ctx, args) {
439
+ try {
440
+ const pool = args.poolAccounts
441
+ ? summarizePoolHeadroom(args.poolAccounts.map((account) => {
442
+ const state = accountRuntimeState.get(account.key);
443
+ return {
444
+ ...(state?.coolingUntil !== undefined
445
+ ? { coolingUntil: state.coolingUntil }
446
+ : {}),
447
+ ...(state?.quota ? { quota: state.quota } : {}),
448
+ };
449
+ }))
450
+ : undefined;
451
+ const headers = buildProxyLimitHeaders({
452
+ ...(args.upstreamHeaders
453
+ ? { upstreamHeaders: args.upstreamHeaders }
454
+ : {}),
455
+ context: {
456
+ quota: args.quota ?? null,
457
+ source: args.source,
458
+ ...(args.account ? { accountLabel: args.account.label } : {}),
459
+ ...((args.accountType ?? args.account?.type)
460
+ ? { accountType: args.accountType ?? args.account?.type }
461
+ : {}),
462
+ ...(args.servedBy ? { servedBy: args.servedBy } : {}),
463
+ ...(args.attempt !== undefined ? { attempt: args.attempt } : {}),
464
+ ...(args.accountState?.coolingUntil !== undefined
465
+ ? { coolingUntil: args.accountState.coolingUntil }
466
+ : {}),
467
+ ...(args.accountState?.coolingReason
468
+ ? { coolingReason: args.accountState.coolingReason }
469
+ : {}),
470
+ ...(pool ? { pool } : {}),
471
+ },
472
+ });
473
+ ctx.responseHeaders = { ...(ctx.responseHeaders ?? {}), ...headers };
474
+ }
475
+ catch (error) {
476
+ // Diagnostics must never break a response that is otherwise fine.
477
+ logger.debug(`[proxy] failed to publish limit headers: ${error instanceof Error ? error.message : String(error)}`);
478
+ }
479
+ }
429
480
  /** Clamp a cooldown target epoch-ms into [now+MIN, now+MAX]. */
430
481
  function clampCooldownUntil(untilMs, now) {
431
482
  return Math.min(Math.max(untilMs, now + MIN_COOLDOWN_MS), now + MAX_COOLDOWN_MS);
@@ -1466,6 +1517,20 @@ async function handleClaudePassthroughRequest(args) {
1466
1517
  upstreamResponseHeaders[key] = value;
1467
1518
  });
1468
1519
  tracer?.logUpstreamResponseHeaders(upstreamResponseHeaders);
1520
+ // Passthrough uses the caller's own credentials, so there is no account pool
1521
+ // to report — but the upstream quota headers are still the caller's real
1522
+ // limits. Published before the ok/non-ok split so a 429 carries them too.
1523
+ {
1524
+ const passthroughQuota = parseQuotaHeaders(response.headers);
1525
+ publishLimitHeaders(ctx, {
1526
+ upstreamHeaders: response.headers,
1527
+ quota: passthroughQuota,
1528
+ source: passthroughQuota ? "live" : "none",
1529
+ accountType: "passthrough",
1530
+ servedBy: "anthropic",
1531
+ attempt: 1,
1532
+ });
1533
+ }
1469
1534
  if (!response.ok) {
1470
1535
  const errorText = await response.text();
1471
1536
  recordAttemptError("passthrough", "passthrough", response.status, response.status === 429 ? "quota" : undefined);
@@ -1572,7 +1637,7 @@ function trackUpstreamReadableStream(source) {
1572
1637
  return { stream, outcome: tracker.outcome };
1573
1638
  }
1574
1639
  async function handleClaudePassthroughStreamResponse(args) {
1575
- const { bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
1640
+ const { ctx, bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
1576
1641
  const responseHeaders = { ...upstreamResponseHeaders };
1577
1642
  const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
1578
1643
  const responseBody = response.body;
@@ -1754,7 +1819,13 @@ async function handleClaudePassthroughStreamResponse(args) {
1754
1819
  const clientStream = streamSource.pipeThrough(clientCaptureStream);
1755
1820
  return new Response(clientStream, {
1756
1821
  status: response.status,
1757
- headers: responseHeaders,
1822
+ // Upstream headers first, then the proxy's own — passthrough already
1823
+ // forwarded everything Anthropic sent, so this only adds the x-neurolink-*
1824
+ // fields the client cannot derive on its own.
1825
+ headers: {
1826
+ ...responseHeaders,
1827
+ ...(ctx.responseHeaders ?? {}),
1828
+ },
1758
1829
  });
1759
1830
  }
1760
1831
  async function handleClaudePassthroughJsonResponse(args) {
@@ -2244,6 +2315,14 @@ async function tryConfiguredClaudeFallbackChain(args) {
2244
2315
  attemptCount: fallbackPlan.attempts.slice(1).length,
2245
2316
  reason: "fallback_success",
2246
2317
  });
2318
+ // A different provider produced this response — say so, and report no
2319
+ // quota. Emitting the last Anthropic snapshot here would attribute one
2320
+ // provider's capacity to another's output.
2321
+ publishLimitHeaders(ctx, {
2322
+ quota: null,
2323
+ source: "none",
2324
+ servedBy: fallback.provider,
2325
+ });
2247
2326
  return { response };
2248
2327
  }
2249
2328
  catch (fallbackErr) {
@@ -2320,6 +2399,13 @@ async function tryAutoClaudeFallback(args) {
2320
2399
  attemptCount: 1,
2321
2400
  reason: "fallback_success",
2322
2401
  });
2402
+ // See the configured-chain path: never attribute Anthropic quota to a
2403
+ // response another provider produced.
2404
+ publishLimitHeaders(ctx, {
2405
+ quota: null,
2406
+ source: "none",
2407
+ servedBy: "auto-provider",
2408
+ });
2323
2409
  return { response };
2324
2410
  }
2325
2411
  catch (fallbackErr) {
@@ -2441,7 +2527,7 @@ function buildClaudeAnthropicFailureResponse(args) {
2441
2527
  });
2442
2528
  }
2443
2529
  async function handleAnthropicSuccessfulResponse(args) {
2444
- const { ctx, body, account, accountState, response, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, logFinalRequest, } = args;
2530
+ const { ctx, body, account, accountState, response, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, poolAccounts, logFinalRequest, } = args;
2445
2531
  accountState.consecutiveRefreshFailures = 0;
2446
2532
  logger.always(`[proxy] ← ${response.status} account=${account.label}`);
2447
2533
  const quota = parseQuotaHeaders(response.headers);
@@ -2470,6 +2556,20 @@ async function handleAnthropicSuccessfulResponse(args) {
2470
2556
  responseHeaders[key] = value;
2471
2557
  });
2472
2558
  tracer?.logUpstreamResponseHeaders(responseHeaders);
2559
+ // Surface limits to the client. `quota` is non-null only when this upstream
2560
+ // response actually carried the unified headers; otherwise fall back to the
2561
+ // account's last snapshot and label it as such, so a consumer never mistakes
2562
+ // a carried-over reading for a fresh one.
2563
+ publishLimitHeaders(ctx, {
2564
+ upstreamHeaders: response.headers,
2565
+ quota: quota ?? accountState.quota ?? null,
2566
+ source: quota ? "live" : accountState.quota ? "snapshot" : "none",
2567
+ account,
2568
+ accountState,
2569
+ servedBy: "anthropic",
2570
+ attempt: attemptNumber,
2571
+ ...(poolAccounts ? { poolAccounts } : {}),
2572
+ });
2473
2573
  if (body.stream) {
2474
2574
  return handleAnthropicStreamingSuccessResponse({
2475
2575
  ctx,
@@ -2506,7 +2606,7 @@ async function handleAnthropicSuccessfulResponse(args) {
2506
2606
  });
2507
2607
  }
2508
2608
  async function handleAnthropicStreamingSuccessResponse(args) {
2509
- const { account, accountState, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, logFinalRequest, } = args;
2609
+ const { ctx, account, accountState, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, logFinalRequest, } = args;
2510
2610
  if (!response.body) {
2511
2611
  recordAttemptError(account.label, account.type, 502);
2512
2612
  logAttempt(502, "stream_error", "No response body from upstream");
@@ -2703,6 +2803,7 @@ async function handleAnthropicStreamingSuccessResponse(args) {
2703
2803
  },
2704
2804
  });
2705
2805
  const { response: result, telemetryDone } = attachAnthropicSuccessStreamTelemetry({
2806
+ ctx,
2706
2807
  account,
2707
2808
  response,
2708
2809
  responseHeaders,
@@ -2742,7 +2843,7 @@ function recordCommittedAnthropicStreamAttemptFailure(outcome, account) {
2742
2843
  }
2743
2844
  }
2744
2845
  function attachAnthropicSuccessStreamTelemetry(args) {
2745
- const { account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logProxyBody, logFinalRequest, } = args;
2846
+ const { ctx, account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logProxyBody, logFinalRequest, } = args;
2746
2847
  const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
2747
2848
  let streamSource = remainingStream;
2748
2849
  let telemetryDone;
@@ -2952,23 +3053,18 @@ function attachAnthropicSuccessStreamTelemetry(args) {
2952
3053
  }
2953
3054
  }
2954
3055
  const clientStream = streamSource.pipeThrough(clientCaptureStream);
3056
+ // Limit headers published on the context are applied here rather than left
3057
+ // to the runtime wrapper: this Response goes straight to the client on every
3058
+ // mount (proxy runtime and the generic server adapters alike), so the
3059
+ // streaming path has to carry them itself. The previous five-name allowlist
3060
+ // forwarded only the legacy counters and dropped the unified subscription
3061
+ // windows entirely.
2955
3062
  const clientResponseHeaders = {
3063
+ ...(ctx.responseHeaders ?? {}),
2956
3064
  "content-type": "text/event-stream",
2957
3065
  "cache-control": "no-cache",
2958
3066
  connection: "keep-alive",
2959
3067
  };
2960
- for (const headerName of [
2961
- "retry-after",
2962
- "anthropic-ratelimit-requests-remaining",
2963
- "anthropic-ratelimit-requests-limit",
2964
- "anthropic-ratelimit-tokens-remaining",
2965
- "anthropic-ratelimit-tokens-limit",
2966
- ]) {
2967
- const value = response.headers.get(headerName);
2968
- if (value) {
2969
- clientResponseHeaders[headerName] = value;
2970
- }
2971
- }
2972
3068
  return {
2973
3069
  response: new Response(clientStream, {
2974
3070
  status: response.status,
@@ -4612,6 +4708,7 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4612
4708
  logProxyBody,
4613
4709
  logFinalRequest,
4614
4710
  onStreamTerminal: admissionLease.release,
4711
+ poolAccounts: enabledAccounts,
4615
4712
  });
4616
4713
  if ("retryNextAccount" in successResult) {
4617
4714
  if (successResult.failure) {
@@ -4674,6 +4771,24 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4674
4771
  }
4675
4772
  loopState.fallbackFailureMessage = fallbackFailureMessage;
4676
4773
  }
4774
+ // Terminal failure — usually "every account is rate-limited". This is the
4775
+ // response a caller most needs limit data on, and historically the one that
4776
+ // carried none. No single account served it, so report the account we would
4777
+ // have tried first plus pool headroom, explicitly marked as a snapshot.
4778
+ {
4779
+ const primary = orderedAccounts[0];
4780
+ const primaryState = primary
4781
+ ? accountRuntimeState.get(primary.key)
4782
+ : undefined;
4783
+ publishLimitHeaders(ctx, {
4784
+ quota: primaryState?.quota ?? null,
4785
+ source: primaryState?.quota ? "snapshot" : "none",
4786
+ ...(primary ? { account: primary } : {}),
4787
+ ...(primaryState ? { accountState: primaryState } : {}),
4788
+ attempt: loopState.attemptNumber,
4789
+ poolAccounts: enabledAccounts,
4790
+ });
4791
+ }
4677
4792
  return buildClaudeAnthropicFailureResponse({
4678
4793
  tracer,
4679
4794
  requestStartTime,
@@ -3,6 +3,7 @@
3
3
  * Comprehensive usage tracking, performance metrics, and cost analysis types
4
4
  */
5
5
  import type { JsonValue, UnknownRecord } from "./common.js";
6
+ import type { ClaudeLimitSnapshot } from "./subscription.js";
6
7
  /**
7
8
  * Token usage information (consolidated from multiple sources)
8
9
  */
@@ -45,6 +46,13 @@ export type AnalyticsData = {
45
46
  elapsedMs?: number;
46
47
  /** Verbatim provider finish/stop reason for the terminal model call. */
47
48
  rawFinishReason?: string;
49
+ /**
50
+ * Account limit state observed on this request — subscription window
51
+ * headroom, reset times, and (via the NeuroLink Claude proxy) which account
52
+ * served it and how much the pool has left. Present for Anthropic traffic
53
+ * whose response carried rate-limit headers.
54
+ */
55
+ limits?: ClaudeLimitSnapshot;
48
56
  };
49
57
  /**
50
58
  * Stream Analytics Data - Enhanced for performance tracking
@@ -3,6 +3,7 @@ import type { RAGConfig } from "./rag.js";
3
3
  import type { KnowledgeGroundingMetadata, KnowledgeRequestScope } from "./knowledge.js";
4
4
  import type { SkillsCallOptions } from "./skills.js";
5
5
  import type { AnalyticsData, TokenUsage } from "./analytics.js";
6
+ import type { ClaudeLimitSnapshot } from "./subscription.js";
6
7
  import type { JsonValue } from "./common.js";
7
8
  import type { Content, ImageWithAltText } from "./content.js";
8
9
  import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
@@ -963,6 +964,17 @@ export type GenerateResult = {
963
964
  message: string;
964
965
  }>;
965
966
  };
967
+ /**
968
+ * Account limit state for this request, parsed from Anthropic's
969
+ * `anthropic-ratelimit-*` response headers (plus the NeuroLink Claude
970
+ * proxy's `x-neurolink-*` additions when routed through it).
971
+ *
972
+ * Subscription windows report utilization, so headroom is a percentage
973
+ * (`sessionLeftPct`) rather than an absolute count — Anthropic publishes no
974
+ * remaining message or token figure for them. API-key accounts do carry
975
+ * absolute `requestsRemaining` / `tokensRemaining`.
976
+ */
977
+ limits?: ClaudeLimitSnapshot;
966
978
  };
967
979
  /**
968
980
  * Unified options for both generation and streaming
@@ -944,6 +944,49 @@ export type AccountQuota = {
944
944
  /** Epoch ms when we last captured this data */
945
945
  lastUpdated: number;
946
946
  };
947
+ /**
948
+ * Provenance of the quota numbers attached to a single proxy response.
949
+ * - "live" : parsed from THIS upstream response's headers.
950
+ * - "snapshot" : the last known snapshot for the serving account; the upstream
951
+ * response carried no quota headers.
952
+ * - "none" : no Anthropic account served this request (fallback provider,
953
+ * or a failure before any account was reached).
954
+ *
955
+ * Consumers must not treat "snapshot" as current. Without this distinction a
956
+ * stale reading is indistinguishable from a fresh one.
957
+ */
958
+ export type ProxyQuotaSource = "live" | "snapshot" | "none";
959
+ /** Aggregate account-pool headroom at the moment a response was produced. */
960
+ export type ProxyPoolHeadroom = {
961
+ /** Accounts eligible to serve a request right now (not cooling/disabled). */
962
+ available: number;
963
+ /** Accounts currently in a cooldown window. */
964
+ cooling: number;
965
+ /** Best session headroom across available accounts, as a percentage 0-100.
966
+ * Undefined when no available account has a quota snapshot. */
967
+ bestSessionLeftPct?: number;
968
+ };
969
+ /**
970
+ * Everything the proxy knows about limits and routing for one response.
971
+ * Consumed by `buildQuotaResponseHeaders` — kept as data (not headers) so the
972
+ * assembly stays pure and testable.
973
+ */
974
+ export type ProxyQuotaHeaderContext = {
975
+ quota: AccountQuota | null;
976
+ source: ProxyQuotaSource;
977
+ /** Account label that served the request; omitted for fallback/no-account. */
978
+ accountLabel?: string;
979
+ accountType?: string;
980
+ /** Which upstream actually produced the response ("anthropic" or a fallback
981
+ * provider name). Lets a consumer avoid attributing quota to the wrong one. */
982
+ servedBy?: string;
983
+ /** 1-based attempt index within the routing loop. */
984
+ attempt?: number;
985
+ /** Epoch ms until which the serving account is cooling, when applicable. */
986
+ coolingUntil?: number;
987
+ coolingReason?: AccountCoolingReason;
988
+ pool?: ProxyPoolHeadroom;
989
+ };
947
990
  /** Why an account is currently cooling. Drives cooldown duration and logging.
948
991
  * - "weekly" : 7d unified limit rejected — cool until the weekly reset.
949
992
  * - "session" : 5h unified limit rejected — cool until the session reset.
@@ -87,6 +87,83 @@ export type AnthropicRateLimitInfo = {
87
87
  * Retry-After header value in seconds (present on 429 responses)
88
88
  */
89
89
  retryAfter?: number;
90
+ /**
91
+ * Subscription (OAuth) window utilization, 0.0-1.0 of capacity USED, from
92
+ * `anthropic-ratelimit-unified-5h-utilization`.
93
+ *
94
+ * Anthropic publishes utilization for subscription windows, never an
95
+ * absolute remaining count — there is no message or token figure to report.
96
+ * `sessionLeftPct` below is the derived "how much is left".
97
+ */
98
+ sessionUtilization?: number;
99
+ /** "allowed" | "throttled" | "rejected" for the 5h window. */
100
+ sessionStatus?: string;
101
+ /** Unix epoch seconds at which the 5h window resets. */
102
+ sessionResetAt?: number;
103
+ /** Whole-percent capacity remaining in the 5h window (100 - utilization). */
104
+ sessionLeftPct?: number;
105
+ /** 7d window utilization, 0.0-1.0 of capacity USED. */
106
+ weeklyUtilization?: number;
107
+ weeklyStatus?: string;
108
+ weeklyResetAt?: number;
109
+ weeklyLeftPct?: number;
110
+ /** Authoritative top-level unified status; can be "rejected" even while both
111
+ * sub-windows still report "allowed". */
112
+ unifiedStatus?: string;
113
+ /** Whether overage is permitted once a window is exhausted. */
114
+ overageStatus?: string;
115
+ };
116
+ /**
117
+ * Per-request limit snapshot as observed by the Anthropic provider.
118
+ *
119
+ * Assembled from response headers on every request — whether the provider is
120
+ * talking directly to Anthropic (both auth methods) or through the NeuroLink
121
+ * Claude proxy. The `account`/`pool`/`servedBy` fields are populated only by
122
+ * the proxy, which is the only party that knows them.
123
+ */
124
+ export type ClaudeLimitSnapshot = {
125
+ /** Rate-limit figures parsed from `anthropic-ratelimit-*` headers. */
126
+ rateLimit: AnthropicRateLimitInfo;
127
+ /**
128
+ * Provenance of the quota numbers. "snapshot" means the proxy reported a
129
+ * previously captured reading rather than one from this response; "none"
130
+ * means no Anthropic account served the request (e.g. a fallback provider).
131
+ * Absent when talking directly to Anthropic, where any figures are live.
132
+ */
133
+ quotaSource?: "live" | "snapshot" | "none";
134
+ /** Proxy account label that served the request. */
135
+ account?: string;
136
+ /** "oauth" | "api_key" | "passthrough". */
137
+ accountType?: string;
138
+ /** Upstream that produced the response — "anthropic" or a fallback provider. */
139
+ servedBy?: string;
140
+ /** Epoch ms until which the serving account is cooling. */
141
+ accountCoolingUntil?: number;
142
+ accountCoolingReason?: string;
143
+ /** Proxy account-pool headroom at response time. */
144
+ pool?: {
145
+ available?: number;
146
+ cooling?: number;
147
+ bestSessionLeftPct?: number;
148
+ };
149
+ /** Anthropic request id, for correlating with provider-side logs. */
150
+ requestId?: string;
151
+ /** HTTP status of the response the snapshot came from. */
152
+ status?: number;
153
+ /** Epoch ms when this snapshot was captured. */
154
+ capturedAt: number;
155
+ };
156
+ /**
157
+ * Per-request capture slot the Anthropic fetch wrapper writes into.
158
+ *
159
+ * Held in AsyncLocalStorage for the duration of a generate/stream call, so
160
+ * concurrent calls on one provider instance cannot see each other's limits.
161
+ * `headers` keeps the raw response header bag so the AI-SDK model adapter can
162
+ * report real response headers.
163
+ */
164
+ export type ClaudeLimitCaptureSlot = {
165
+ snapshot?: ClaudeLimitSnapshot;
166
+ headers?: Record<string, string>;
90
167
  };
91
168
  /**
92
169
  * Response metadata including rate limit information
@@ -1,6 +1,6 @@
1
1
  import { type AIProviderName } from "../../constants/enums.js";
2
2
  import { BaseProvider } from "../../core/baseProvider.js";
3
- import type { AnthropicProviderConfig, StreamOptions, StreamResult, ValidationSchema, EnhancedGenerateResult, TextGenerationOptions, AnthropicAuthMethod, AnthropicResponseMetadata, ClaudeSubscriptionTier, ClaudeUsageInfo } from "../../types/index.js";
3
+ import type { AnthropicProviderConfig, StreamOptions, StreamResult, ValidationSchema, EnhancedGenerateResult, TextGenerationOptions, AnthropicAuthMethod, AnthropicRateLimitInfo, AnthropicResponseMetadata, ClaudeSubscriptionTier, ClaudeUsageInfo } from "../../types/index.js";
4
4
  import type { LanguageModel } from "../../types/index.js";
5
5
  /**
6
6
  * Anthropic Provider v2 - BaseProvider Implementation
@@ -103,12 +103,18 @@ export declare class AnthropicProvider extends BaseProvider {
103
103
  */
104
104
  getLastResponseMetadata(): AnthropicResponseMetadata | null;
105
105
  /**
106
- * Update response metadata from API response headers.
107
- * This should be called after each API request to track rate limits.
108
- * @param headers - Response headers from the API
109
- * @param requestId - Optional request ID
106
+ * Update response metadata from a captured limit snapshot.
107
+ *
108
+ * Takes already-parsed rate-limit info rather than raw headers: parsing now
109
+ * lives in `rateLimitCapture`, which is the only layer that sees the raw
110
+ * response and understands both header families (unified subscription
111
+ * windows and legacy per-tier counters).
112
+ *
113
+ * @param rateLimit - Parsed rate-limit figures
114
+ * @param requestId - Optional Anthropic request ID
115
+ * @param usageUpdate - Optional token counts to fold into usage tracking
110
116
  */
111
- protected updateResponseMetadata(headers: Headers | Record<string, string>, requestId?: string, usageUpdate?: {
117
+ protected updateResponseMetadata(rateLimit: AnthropicRateLimitInfo, requestId?: string, usageUpdate?: {
112
118
  inputTokens?: number;
113
119
  outputTokens?: number;
114
120
  }): void;
@@ -127,7 +133,16 @@ export declare class AnthropicProvider extends BaseProvider {
127
133
  * BaseProvider so that expired tokens are renewed automatically.
128
134
  */
129
135
  generate(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
130
- protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
136
+ /**
137
+ * Fold a captured snapshot into the provider's usage bookkeeping and log it.
138
+ *
139
+ * `updateResponseMetadata` had no callers before this — the metadata it
140
+ * maintains, and the public `getLastResponseMetadata()` / `getUsageInfo()`
141
+ * that read it, were never populated by anything.
142
+ */
143
+ private recordLimitSnapshot;
144
+ protected executeStream(options: StreamOptions, analysisSchema?: ValidationSchema): Promise<StreamResult>;
145
+ private executeStreamInCaptureScope;
131
146
  isAvailable(): Promise<boolean>;
132
147
  getModel(): LanguageModel;
133
148
  }