@juspay/neurolink 10.11.2 → 10.12.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/adapters/audioFormatSupport.d.ts +53 -0
  3. package/dist/adapters/audioFormatSupport.js +200 -0
  4. package/dist/browser/neurolink.min.js +399 -398
  5. package/dist/cli/commands/auth.d.ts +8 -1
  6. package/dist/cli/commands/auth.js +185 -6
  7. package/dist/cli/factories/authCommandFactory.js +7 -1
  8. package/dist/lib/adapters/audioFormatSupport.d.ts +53 -0
  9. package/dist/lib/adapters/audioFormatSupport.js +201 -0
  10. package/dist/lib/processors/archive/ArchiveProcessor.d.ts +37 -0
  11. package/dist/lib/processors/archive/ArchiveProcessor.js +347 -32
  12. package/dist/lib/providers/googleAiStudio/client.d.ts +17 -0
  13. package/dist/lib/providers/googleAiStudio/client.js +45 -19
  14. package/dist/lib/providers/googleNativeGemini3/utils.d.ts +22 -1
  15. package/dist/lib/providers/googleNativeGemini3/utils.js +54 -0
  16. package/dist/lib/providers/googleVertex/client.js +3 -0
  17. package/dist/lib/proxy/accountQuota.d.ts +6 -0
  18. package/dist/lib/proxy/accountQuota.js +19 -2
  19. package/dist/lib/proxy/accountUsage.d.ts +45 -0
  20. package/dist/lib/proxy/accountUsage.js +289 -0
  21. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +15 -1
  22. package/dist/lib/server/routes/claudeProxyRoutes.js +166 -0
  23. package/dist/lib/types/cli.d.ts +12 -0
  24. package/dist/lib/types/file.d.ts +41 -0
  25. package/dist/lib/types/generate.d.ts +12 -1
  26. package/dist/lib/types/processor.d.ts +20 -1
  27. package/dist/lib/types/providers.d.ts +7 -0
  28. package/dist/lib/types/proxy.d.ts +101 -0
  29. package/dist/lib/utils/fileDetector.d.ts +27 -0
  30. package/dist/lib/utils/fileDetector.js +130 -7
  31. package/dist/lib/utils/imageProcessor.js +31 -0
  32. package/dist/lib/utils/messageBuilder.d.ts +0 -9
  33. package/dist/lib/utils/messageBuilder.js +380 -56
  34. package/dist/processors/archive/ArchiveProcessor.d.ts +37 -0
  35. package/dist/processors/archive/ArchiveProcessor.js +347 -32
  36. package/dist/providers/googleAiStudio/client.d.ts +17 -0
  37. package/dist/providers/googleAiStudio/client.js +45 -19
  38. package/dist/providers/googleNativeGemini3/utils.d.ts +22 -1
  39. package/dist/providers/googleNativeGemini3/utils.js +54 -0
  40. package/dist/providers/googleVertex/client.js +3 -0
  41. package/dist/proxy/accountQuota.d.ts +6 -0
  42. package/dist/proxy/accountQuota.js +19 -2
  43. package/dist/proxy/accountUsage.d.ts +45 -0
  44. package/dist/proxy/accountUsage.js +288 -0
  45. package/dist/server/routes/claudeProxyRoutes.d.ts +15 -1
  46. package/dist/server/routes/claudeProxyRoutes.js +166 -0
  47. package/dist/types/cli.d.ts +12 -0
  48. package/dist/types/file.d.ts +41 -0
  49. package/dist/types/generate.d.ts +12 -1
  50. package/dist/types/processor.d.ts +20 -1
  51. package/dist/types/providers.d.ts +7 -0
  52. package/dist/types/proxy.d.ts +101 -0
  53. package/dist/utils/fileDetector.d.ts +27 -0
  54. package/dist/utils/fileDetector.js +130 -7
  55. package/dist/utils/imageProcessor.js +31 -0
  56. package/dist/utils/messageBuilder.d.ts +0 -9
  57. package/dist/utils/messageBuilder.js +380 -56
  58. package/package.json +3 -2
@@ -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 { fetchAccountUsage, listAnthropicAccountsForUsage, usageToQuota, } from "../../proxy/accountUsage.js";
20
21
  import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quotaHeaders.js";
21
22
  import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
22
23
  import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
@@ -639,6 +640,127 @@ async function seedRuntimeQuotasFromDisk(accounts) {
639
640
  // Non-fatal: seeding is best-effort; ordering falls back to probe-first.
640
641
  }
641
642
  }
643
+ // ---------------------------------------------------------------------------
644
+ // Manual limits refresh (GET /limits)
645
+ // ---------------------------------------------------------------------------
646
+ /** Minimum spacing between usage-endpoint fetches for one account. Bounds
647
+ * abuse of the ungated endpoint; inside the window the last reading is
648
+ * returned as "throttled" (still fresher than any passive snapshot). */
649
+ const MIN_USAGE_REFETCH_INTERVAL_MS = 15_000;
650
+ const lastUsageFetchAt = new Map();
651
+ let limitsRefreshInFlight = null;
652
+ const USAGE_REFRESH_CONCURRENCY = 4;
653
+ /**
654
+ * Fetch fresh limits from Anthropic's usage endpoint for every eligible OAuth
655
+ * account and write them through the exact same chain the passive header
656
+ * capture uses (runtime state → cooldown reconciliation → debounced disk
657
+ * snapshot), so routing and `auth list` see the refreshed windows and the
658
+ * automatic path keeps working unchanged on top of them.
659
+ */
660
+ async function refreshAccountLimits(options = {}) {
661
+ const fetchedAt = Date.now();
662
+ const allAccounts = await listAnthropicAccountsForUsage(options.accountAllowlist);
663
+ const accounts = options.accountFilter
664
+ ? allAccounts.filter((account) => account.label === options.accountFilter ||
665
+ account.key === options.accountFilter)
666
+ : allAccounts;
667
+ const persisted = await loadAccountQuotas().catch(() => ({}));
668
+ const buildResult = (account, status, quota, error) => {
669
+ const state = accountRuntimeState.get(account.key);
670
+ const result = {
671
+ account: account.label,
672
+ key: account.key,
673
+ type: account.type,
674
+ status,
675
+ quota: quota ?? state?.quota ?? persisted[account.label] ?? null,
676
+ };
677
+ if (error !== undefined) {
678
+ result.error = error;
679
+ }
680
+ if (state?.coolingUntil && state.coolingUntil > Date.now()) {
681
+ result.coolingUntil = state.coolingUntil;
682
+ if (state.coolingReason) {
683
+ result.coolingReason = state.coolingReason;
684
+ }
685
+ }
686
+ return result;
687
+ };
688
+ if (options.snapshotOnly) {
689
+ return {
690
+ fetchedAt,
691
+ snapshot: true,
692
+ results: accounts.map((account) => buildResult(account, "snapshot", null)),
693
+ };
694
+ }
695
+ const results = new Array(accounts.length);
696
+ let nextIndex = 0;
697
+ const worker = async () => {
698
+ for (;;) {
699
+ const index = nextIndex++;
700
+ if (index >= accounts.length) {
701
+ return;
702
+ }
703
+ const account = accounts[index];
704
+ if (account.type !== "oauth") {
705
+ results[index] = buildResult(account, "skipped_api_key", null);
706
+ continue;
707
+ }
708
+ const lastFetch = lastUsageFetchAt.get(account.key) ?? 0;
709
+ if (Date.now() - lastFetch < MIN_USAGE_REFETCH_INTERVAL_MS) {
710
+ results[index] = buildResult(account, "throttled", null);
711
+ continue;
712
+ }
713
+ lastUsageFetchAt.set(account.key, Date.now());
714
+ // Isolate failures per account: an unexpected rejection must not abort
715
+ // the Promise.all sweep and turn the whole /limits response into a 502.
716
+ try {
717
+ const fetchResult = await fetchAccountUsage(account);
718
+ // `=== false` (not `!ok`) — the react-hooks sub-build compiles this
719
+ // file without strictNullChecks, where negated boolean-discriminant
720
+ // narrowing does not apply.
721
+ if (fetchResult.ok === false) {
722
+ results[index] = buildResult(account, "error", null, fetchResult.error);
723
+ continue;
724
+ }
725
+ const state = getOrCreateRuntimeState(account.key);
726
+ const capturedAt = Date.now();
727
+ const quota = usageToQuota(fetchResult.usage, {
728
+ now: capturedAt,
729
+ prior: state.quota ?? persisted[account.label] ?? null,
730
+ });
731
+ if (!quota) {
732
+ results[index] = buildResult(account, "error", null, "usage payload had no recognizable limit windows");
733
+ continue;
734
+ }
735
+ // Guard against a passive header capture that landed mid-fetch: never
736
+ // replace a fresher runtime snapshot with an older reading.
737
+ if (quota.lastUpdated >= (state.quota?.lastUpdated ?? 0)) {
738
+ state.quota = quota;
739
+ }
740
+ const cooldownUpdate = reconcileCooldownFromQuota(state, quota, capturedAt);
741
+ if (cooldownUpdate?.kind === "cooled") {
742
+ await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
743
+ // Non-fatal: cooldown is already active in memory.
744
+ });
745
+ }
746
+ else if (cooldownUpdate?.kind === "cleared") {
747
+ await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
748
+ // Non-fatal: the next successful response will reconcile again.
749
+ });
750
+ }
751
+ await saveAccountQuota(account.label, quota).catch(() => {
752
+ // Non-fatal: quota persistence is best-effort
753
+ });
754
+ results[index] = buildResult(account, "refreshed", quota);
755
+ }
756
+ catch (err) {
757
+ results[index] = buildResult(account, "error", null, err instanceof Error ? err.message : String(err));
758
+ }
759
+ }
760
+ };
761
+ await Promise.all(Array.from({ length: Math.min(USAGE_REFRESH_CONCURRENCY, accounts.length || 1) }, () => worker()));
762
+ return { fetchedAt, snapshot: false, results };
763
+ }
642
764
  /** Quota-aware selection is on by default; disable with
643
765
  * NEUROLINK_PROXY_QUOTA_ROUTING=off|false|0. Only affects the fill-first
644
766
  * strategy (round-robin keeps strict rotation). */
@@ -5037,6 +5159,45 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
5037
5159
  description: "Count tokens for a messages request",
5038
5160
  tags: ["claude-proxy", "tokens"],
5039
5161
  },
5162
+ // =====================================================================
5163
+ // GET /limits -- Fresh account limits from Anthropic's usage endpoint
5164
+ // =====================================================================
5165
+ {
5166
+ method: "GET",
5167
+ path: `${basePath}/limits`,
5168
+ handler: async (ctx) => {
5169
+ const effectiveAllowlist = runtimeConfigProvider
5170
+ ? runtimeConfigProvider().accountAllowlist
5171
+ : accountAllowlist;
5172
+ const snapshotOnly = ctx.query?.snapshot === "true" || ctx.query?.snapshot === "1";
5173
+ const accountFilter = ctx.query?.account;
5174
+ return withSpan({
5175
+ name: "neurolink.http.claudeProxy.limits",
5176
+ tracer: tracers.http,
5177
+ attributes: { "http.route": `${basePath}/limits` },
5178
+ }, async () => {
5179
+ // Single-flight: concurrent full refreshes share one sweep.
5180
+ if (!snapshotOnly && !accountFilter) {
5181
+ if (!limitsRefreshInFlight) {
5182
+ limitsRefreshInFlight = refreshAccountLimits({
5183
+ accountAllowlist: effectiveAllowlist,
5184
+ }).finally(() => {
5185
+ limitsRefreshInFlight = null;
5186
+ });
5187
+ }
5188
+ return limitsRefreshInFlight;
5189
+ }
5190
+ return refreshAccountLimits({
5191
+ accountAllowlist: effectiveAllowlist,
5192
+ accountFilter,
5193
+ snapshotOnly,
5194
+ });
5195
+ });
5196
+ },
5197
+ description: "Fetch fresh per-account limits from Anthropic (usage API). " +
5198
+ "?account=<label> for one account, ?snapshot=true for stored state",
5199
+ tags: ["claude-proxy", "limits"],
5200
+ },
5040
5201
  ],
5041
5202
  };
5042
5203
  }
@@ -5340,6 +5501,11 @@ export const __testHooks = {
5340
5501
  maybeResetPrimaryToHome,
5341
5502
  planCooldownFor429,
5342
5503
  reconcileCooldownFromQuota,
5504
+ refreshAccountLimits,
5505
+ clearLimitsRefreshStateForTests: () => {
5506
+ lastUsageFetchAt.clear();
5507
+ limitsRefreshInFlight = null;
5508
+ },
5343
5509
  isRetryableNetworkError,
5344
5510
  isPermanentRefreshFailure,
5345
5511
  getStreamFailureDetails,
@@ -11,6 +11,7 @@ import type { PPTGenerationResult } from "./ppt.js";
11
11
  import type { AvatarResult } from "./avatar.js";
12
12
  import type { MusicResult } from "./music.js";
13
13
  import type { OAuthTokens } from "./auth.js";
14
+ import type { AccountQuota } from "./proxy.js";
14
15
  import type { ClaudeSubscriptionTier } from "./subscription.js";
15
16
  import type { ServerFramework } from "./server.js";
16
17
  import type { AuthProviderType } from "./auth.js";
@@ -947,6 +948,8 @@ export type AuthCommandArgs = BaseCommandArgs & {
947
948
  label?: string;
948
949
  account?: string;
949
950
  force?: boolean;
951
+ /** `auth list --refresh`: fetch fresh limits from Anthropic before listing */
952
+ refresh?: boolean;
950
953
  /** Path to the proxy config YAML, used by set-/get-/clear-primary */
951
954
  config?: string;
952
955
  /** Email passed to `auth set-primary <email>` */
@@ -954,6 +957,15 @@ export type AuthCommandArgs = BaseCommandArgs & {
954
957
  /** Yargs positional arguments */
955
958
  _?: (string | number)[];
956
959
  };
960
+ /** Outcome of the `auth list --refresh` fresh-limit fetch. */
961
+ export type AuthListRefreshOutcome = {
962
+ /** How the fresh limits were obtained ("none" when every path failed). */
963
+ via: "proxy" | "direct" | "none";
964
+ /** Freshly fetched quotas keyed by account label; null when none fetched. */
965
+ quotas: Record<string, AccountQuota> | null;
966
+ /** Per-account and transport errors, already formatted for display. */
967
+ errors: string[];
968
+ };
957
969
  /** Telemetry command arguments */
958
970
  export type TelemetryCommandArgs = {
959
971
  format?: "text" | "json" | "table";
@@ -22,6 +22,36 @@ export type VisionImageConversion = {
22
22
  /** True when the bytes were re-encoded; false when they were left alone. */
23
23
  readonly converted: boolean;
24
24
  };
25
+ /**
26
+ * Outcome of an audio-compatibility pass over one file.
27
+ *
28
+ * See `adapters/audioFormatSupport.ts`. As with images, `converted` is false
29
+ * both when the container was already acceptable and when nothing could
30
+ * re-encode it, so it is not a success flag — the caller decides what to do
31
+ * from the resulting `mimeType`.
32
+ */
33
+ export type AudioConversionResult = {
34
+ readonly buffer: Buffer;
35
+ readonly mimeType: string;
36
+ /** True when the bytes were re-encoded; false when they were left alone. */
37
+ readonly converted: boolean;
38
+ };
39
+ /**
40
+ * One audio file destined for native delivery to a provider.
41
+ *
42
+ * Carries the bytes rather than a path because the decision to send audio is
43
+ * made per provider, after detection has already read the file — re-reading it
44
+ * from disk at dispatch time would be a second read of something already in
45
+ * memory.
46
+ */
47
+ export type MultimodalAudioEntry = {
48
+ /** Raw audio bytes, as detected. */
49
+ buffer: Buffer;
50
+ /** Display name; may be a full path, so log only its basename. */
51
+ filename: string;
52
+ /** Detected MIME type of `buffer`. */
53
+ mimeType: string;
54
+ };
25
55
  /**
26
56
  * Broad category a file format belongs to, as a human would name it.
27
57
  *
@@ -445,6 +475,17 @@ export type FileDetectorOptions = {
445
475
  * hint (the lazy FileReferenceRegistry path has its own hint-handling).
446
476
  */
447
477
  mimetypeHint?: string;
478
+ /**
479
+ * Caller-provided filename hint, the companion to {@link mimetypeHint}.
480
+ *
481
+ * The unified file path unwraps a `FileWithMetadata` to its `buffer` before
482
+ * detection runs, so the object's `filename` is gone by the time extension
483
+ * resolution looks for one — and TAR in particular cannot be identified any
484
+ * other way, because its "ustar" marker sits at byte 257 rather than at
485
+ * offset 0. Passing the name alongside the bytes keeps `.odp`, `.rtf` and
486
+ * `.tar` routed to the processors that can actually read them.
487
+ */
488
+ filenameHint?: string;
448
489
  };
449
490
  /**
450
491
  * Google AI Studio Files API types
@@ -17,7 +17,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
17
17
  import type { MusicOptions, MusicResult } from "./music.js";
18
18
  import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
19
19
  import type { NeurolinkCredentials } from "./providers.js";
20
- import type { CSVProcessorOptions, FileWithMetadata } from "./file.js";
20
+ import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry } from "./file.js";
21
21
  import type { WorkflowConfig } from "./workflow.js";
22
22
  import type { Schema, Tool, ToolChoice } from "./tools.js";
23
23
  import type { StepResult, LanguageModel } from "./providers.js";
@@ -56,6 +56,17 @@ export type GenerateOptions = {
56
56
  csvFiles?: Array<Buffer | string>;
57
57
  pdfFiles?: Array<Buffer | string>;
58
58
  audioFiles?: Array<Buffer | string>;
59
+ /**
60
+ * Audio whose bytes should be delivered to the provider, populated during
61
+ * detection rather than by callers.
62
+ *
63
+ * Separate from `audioFiles` above, which is the caller-facing input that
64
+ * yields a metadata summary. This one carries the decoded bytes forward so
65
+ * a provider that can actually listen receives the audio instead of a
66
+ * description of it; providers that cannot fall back to the summary and
67
+ * this is ignored.
68
+ */
69
+ nativeAudioFiles?: MultimodalAudioEntry[];
59
70
  videoFiles?: Array<Buffer | string>;
60
71
  files?: Array<Buffer | string | FileWithMetadata>;
61
72
  content?: Content[];
@@ -741,7 +741,26 @@ export type ProcessedVideo = ProcessedFileBase & {
741
741
  /**
742
742
  * Supported archive format identifiers.
743
743
  */
744
- export type ArchiveFormat = "zip" | "tar" | "tar.gz" | "tar.bz2" | "gz" | "rar" | "7z";
744
+ export type ArchiveFormat = "zip" | "tar" | "tar.gz" | "tar.bz2" | "gz" | "bz2" | "xz" | "zst" | "rar" | "7z";
745
+ /**
746
+ * Outcome of decompressing a single-stream archive (.bz2, .xz, .zst).
747
+ *
748
+ * A plain `Buffer | null` collapsed two very different failures into one: a
749
+ * machine that has no `xz` installed and a `.xz` file that is corrupt both
750
+ * returned null, and the caller reported both as "the command is unavailable on
751
+ * this machine" — actively misleading for the second. The reason is carried so
752
+ * the message can match the fact.
753
+ */
754
+ export type ArchiveDecompressionResult = {
755
+ readonly status: "ok";
756
+ readonly buffer: Buffer;
757
+ } | {
758
+ readonly status: "tool-unavailable";
759
+ } | {
760
+ readonly status: "too-large";
761
+ } | {
762
+ readonly status: "failed";
763
+ };
745
764
  /**
746
765
  * Metadata about an individual entry within an archive.
747
766
  */
@@ -6,6 +6,7 @@ import type { NeuroLink } from "../neurolink.js";
6
6
  import { AIProviderName, AnthropicModels, BedrockModels, DeepSeekModels, GoogleAIModels, LlamaCppModels, LMStudioModels, NvidiaNimModels, OpenAIModels, VertexModels } from "../constants/enums.js";
7
7
  import type { ValidationSchema } from "./aliases.js";
8
8
  import type { EnhancedGenerateResult, GenerateResult, TextGenerationOptions } from "./generate.js";
9
+ import type { MultimodalAudioEntry } from "./file.js";
9
10
  import type { StreamOptions, StreamResult } from "./stream.js";
10
11
  import type { ExternalMCPToolInfo } from "./externalMcp.js";
11
12
  import type { ClaudeSubscriptionTier, AnthropicAuthMethod, AnthropicAuthConfig, SubscriptionInfo, OAuthToken } from "./subscription.js";
@@ -1821,6 +1822,12 @@ export type GeminiMultimodalInput = {
1821
1822
  data: Buffer | string;
1822
1823
  altText?: string;
1823
1824
  }>;
1825
+ /**
1826
+ * Audio collected during file detection, carried through to the native
1827
+ * request as `inlineData`. Distinct from the user-facing `audioFiles`: these
1828
+ * are already-materialised bytes with a resolved mime type.
1829
+ */
1830
+ nativeAudioFiles?: MultimodalAudioEntry[];
1824
1831
  };
1825
1832
  /**
1826
1833
  * Internal helpers used by the conversation-history builder in
@@ -943,6 +943,107 @@ export type AccountQuota = {
943
943
  overageStatus: string;
944
944
  /** Epoch ms when we last captured this data */
945
945
  lastUpdated: number;
946
+ /** Dynamic per-plan limit buckets from the usage API `limits[]` array
947
+ * (session / weekly_all / model-scoped weeklies such as Fable / future
948
+ * kinds). Absent on purely header-sourced snapshots. */
949
+ windows?: AccountQuotaWindow[];
950
+ /** Epoch ms when `windows` was last refreshed from the usage API. */
951
+ windowsUpdatedAt?: number;
952
+ /** Provenance of this snapshot's numbers. */
953
+ source?: AccountQuotaSource;
954
+ };
955
+ /** Where an AccountQuota snapshot came from.
956
+ * - "headers" : passive capture of anthropic-ratelimit-unified-* response
957
+ * headers on a routed request (the automatic path).
958
+ * - "usage-api" : an explicit refresh against Anthropic's OAuth usage
959
+ * endpoint (manual refetch path). */
960
+ export type AccountQuotaSource = "headers" | "usage-api";
961
+ /** One dynamic limit bucket from the usage API. Provider vocabulary (`kind`,
962
+ * `group`, `severity`) is preserved verbatim so buckets Anthropic adds later
963
+ * survive storage and display without a code change. */
964
+ export type AccountQuotaWindow = {
965
+ /** Provider kind, verbatim ("session", "weekly_all", "weekly_scoped", ...). */
966
+ kind: string;
967
+ /** Provider group, verbatim ("session" | "weekly" | future values). */
968
+ group?: string;
969
+ /** 0.0-1.0 utilization (provider percent / 100). */
970
+ used: number;
971
+ /** Provider severity, verbatim ("normal", ...). */
972
+ severity?: string;
973
+ /** Derived "allowed" | "rejected" (see usageToQuota status mapping). */
974
+ status: string;
975
+ /** Unix timestamp (seconds) when this window resets; 0 when unparseable. */
976
+ resetsAt: number;
977
+ isActive?: boolean;
978
+ /** Model display name for model-scoped windows (e.g. "Fable"). */
979
+ scopeModel?: string;
980
+ /** Surface scope when the provider reports one. */
981
+ scopeSurface?: string;
982
+ };
983
+ /** One utilization window from the OAuth usage endpoint (wire shape, loose). */
984
+ export type AnthropicUsageWindow = {
985
+ /** 0-100 percent (note: NOT the 0-1 fraction used by headers). */
986
+ utilization?: number | null;
987
+ /** ISO-8601 timestamp. */
988
+ resets_at?: string | null;
989
+ };
990
+ /** One entry of the usage endpoint's generic `limits[]` array (wire shape). */
991
+ export type AnthropicUsageLimit = {
992
+ kind?: string;
993
+ group?: string;
994
+ /** 0-100 percent. */
995
+ percent?: number | null;
996
+ severity?: string | null;
997
+ resets_at?: string | null;
998
+ scope?: {
999
+ model?: {
1000
+ id?: string | null;
1001
+ display_name?: string | null;
1002
+ } | null;
1003
+ surface?: string | null;
1004
+ } | null;
1005
+ is_active?: boolean | null;
1006
+ };
1007
+ /** Response body of GET https://api.anthropic.com/api/oauth/usage (loose —
1008
+ * unknown keys are ignored, known keys may be absent or null). */
1009
+ export type AnthropicUsageResponse = {
1010
+ five_hour?: AnthropicUsageWindow | null;
1011
+ seven_day?: AnthropicUsageWindow | null;
1012
+ limits?: AnthropicUsageLimit[] | null;
1013
+ extra_usage?: {
1014
+ is_enabled?: boolean | null;
1015
+ } | null;
1016
+ };
1017
+ /** Outcome of one account's usage-endpoint fetch. Return-not-throw. */
1018
+ export type AccountUsageFetchResult = {
1019
+ ok: true;
1020
+ usage: AnthropicUsageResponse;
1021
+ } | {
1022
+ ok: false;
1023
+ reason: "not_oauth" | "auth" | "http" | "network" | "parse";
1024
+ error: string;
1025
+ status?: number;
1026
+ };
1027
+ /** Per-account result inside a GET /limits response. */
1028
+ export type ProxyLimitsAccountResult = {
1029
+ /** Account label (quota-store key). */
1030
+ account: string;
1031
+ /** Token-store key ("anthropic:<label>"). */
1032
+ key: string;
1033
+ type: ProxyAccountType;
1034
+ status: "refreshed" | "throttled" | "skipped_api_key" | "snapshot" | "error";
1035
+ /** Fresh quota on "refreshed"; last known snapshot otherwise (may be null). */
1036
+ quota: AccountQuota | null;
1037
+ error?: string;
1038
+ coolingUntil?: number;
1039
+ coolingReason?: AccountCoolingReason;
1040
+ };
1041
+ /** Response body of the proxy's GET /limits endpoint. */
1042
+ export type ProxyLimitsRefreshResponse = {
1043
+ fetchedAt: number;
1044
+ /** True when served from stored state without contacting Anthropic. */
1045
+ snapshot: boolean;
1046
+ results: ProxyLimitsAccountResult[];
946
1047
  };
947
1048
  /**
948
1049
  * Provenance of the quota numbers attached to a single proxy response.
@@ -17,6 +17,13 @@ import type { FileDetectorOptions, FileInput, FileProcessingResult } from "../ty
17
17
  export declare class FileDetector {
18
18
  static readonly DEFAULT_NETWORK_TIMEOUT = 30000;
19
19
  static readonly DEFAULT_HEAD_TIMEOUT = 5000;
20
+ /**
21
+ * Ceiling on an in-process document parse (unzip + XML walk). Generous
22
+ * relative to the work, because the cost of firing early on a large but
23
+ * legitimate file is a lost extraction, while the cost of never firing is a
24
+ * held request.
25
+ */
26
+ static readonly DEFAULT_DOCUMENT_TIMEOUT = 30000;
20
27
  /**
21
28
  * Auto-detect file type and process in one call
22
29
  *
@@ -80,6 +87,26 @@ export declare class FileDetector {
80
87
  * Stops at first strategy with confidence >= threshold (default: 80%)
81
88
  */
82
89
  private static detect;
90
+ /**
91
+ * Fill in `extension` from the input's name when detection did not set it.
92
+ *
93
+ * Content-based strategies identify a type from magic bytes and legitimately
94
+ * have no extension to report, so they return null. That is fine for the type
95
+ * itself but not for routing: several processors are chosen by extension
96
+ * *after* detection has settled the type, because one routing type covers
97
+ * several formats — `docx` covers .docx, .odt and .rtf.
98
+ *
99
+ * With a null extension those branches were unreachable. An .rtf scored high
100
+ * on its `{\\rtf1` signature, arrived as type "docx" with no extension, and
101
+ * fell through to the Word processor, which cannot read RTF — so a file whose
102
+ * dedicated processor extracts it perfectly reported "Could not extract
103
+ * content". The extension was known the whole time; it was simply dropped on
104
+ * the way through.
105
+ *
106
+ * Only fills a gap — a strategy that did determine an extension keeps it, so
107
+ * content still wins over a lying filename.
108
+ */
109
+ private static withResolvedExtension;
83
110
  /**
84
111
  * Load file content from various sources
85
112
  */