@juspay/neurolink 11.13.2 → 11.14.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.
@@ -5759,6 +5759,59 @@ function buildEarlyClaudeRequestError(args) {
5759
5759
  * @param basePath - Base path prefix (default: "" since Claude API uses /v1/...).
5760
5760
  * @returns RouteGroup with Claude-compatible endpoints.
5761
5761
  */
5762
+ /**
5763
+ * Quota timestamps arrive in two units. `sessionResetAt`, `weeklyResetAt` and
5764
+ * each window's `resetsAt` are unix SECONDS; `lastUpdated`, `updatedAt` and
5765
+ * `coolingUntil` are already MILLISECONDS. Blanket-multiplying would push the
5766
+ * millisecond fields tens of thousands of years into the future, so only the
5767
+ * seconds fields are converted, and 0 becomes null rather than epoch zero.
5768
+ */
5769
+ /**
5770
+ * Account types that represent a real credential rather than proxy plumbing.
5771
+ * Mirrors the Anthropic pool's own account types.
5772
+ */
5773
+ const REAL_ACCOUNT_TYPES = new Set(["oauth", "api_key"]);
5774
+ function toMillis(value) {
5775
+ return typeof value === "number" && Number.isFinite(value) && value > 0
5776
+ ? Math.round(value * 1000)
5777
+ : null;
5778
+ }
5779
+ /**
5780
+ * Normalise one account's quota for a dashboard consumer.
5781
+ *
5782
+ * `severity` and `isActive` are absent on header-sourced windows — a
5783
+ * structural property of how those rows are parsed, not a transient gap — so
5784
+ * every consumer would otherwise need the same fallback branch.
5785
+ */
5786
+ function normalizeQuotaForAccounts(quota) {
5787
+ if (!quota || typeof quota !== "object") {
5788
+ return null;
5789
+ }
5790
+ const q = { ...quota };
5791
+ q.sessionResetAtMs = toMillis(q.sessionResetAt);
5792
+ q.weeklyResetAtMs = toMillis(q.weeklyResetAt);
5793
+ if (Array.isArray(q.windows)) {
5794
+ q.windows = q.windows.map((raw) => {
5795
+ const w = { ...raw };
5796
+ w.resetsAtMs = toMillis(w.resetsAt);
5797
+ w.severity =
5798
+ w.severity ?? (w.status === "rejected" ? "critical" : "normal");
5799
+ w.isActive = w.isActive ?? false;
5800
+ return w;
5801
+ });
5802
+ }
5803
+ return q;
5804
+ }
5805
+ /** "rejected" and "throttled" both mean degraded; unknown strings stay unknown. */
5806
+ function quotaHealth(status) {
5807
+ if (status === "allowed") {
5808
+ return "ok";
5809
+ }
5810
+ if (status === "rejected" || status === "throttled") {
5811
+ return "degraded";
5812
+ }
5813
+ return "unknown";
5814
+ }
5762
5815
  export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrategy = "fill-first", passthroughMode = false, primaryAccountKey, accountAllowlistOrRuntimeOptions) {
5763
5816
  const accountAllowlist = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
5764
5817
  ? accountAllowlistOrRuntimeOptions.accountAllowlist
@@ -5991,6 +6044,199 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
5991
6044
  "?account=<label> for one account, ?snapshot=true for stored state",
5992
6045
  tags: ["claude-proxy", "limits"],
5993
6046
  },
6047
+ // =====================================================================
6048
+ // GET /accounts -- One dashboard-shaped row per account
6049
+ // =====================================================================
6050
+ // Joins what previously took two calls with incompatible schemas plus a
6051
+ // client-side merge: request counters from the usage snapshot, quota from
6052
+ // the limits snapshot, and today's tokens and cost from the request log.
6053
+ {
6054
+ method: "GET",
6055
+ path: `${basePath}/accounts`,
6056
+ handler: async (ctx) => {
6057
+ const routing = runtimeConfigProvider?.();
6058
+ const effectiveAllowlist = routing?.accountAllowlist ?? accountAllowlist;
6059
+ // Cached by default. This endpoint is built to be polled, and a live
6060
+ // refresh spends the user's own OAuth credentials against Anthropic's
6061
+ // usage API — one dashboard on a short interval would hammer it.
6062
+ const live = ctx.query?.refresh === "true";
6063
+ // A live refresh reconciles cooldowns from the fetched quota, so it
6064
+ // makes the same overage judgement the request path does and needs
6065
+ // the same operator policy in scope — exactly as /limits sets it.
6066
+ if (live) {
6067
+ setOveragePolicy(routing?.useOverage);
6068
+ }
6069
+ // Every other source in this handler degrades to a partial row on
6070
+ // failure; quota must too, or one upstream hiccup 500s a dashboard.
6071
+ let limits = {
6072
+ fetchedAt: Date.now(),
6073
+ snapshot: !live,
6074
+ results: [],
6075
+ };
6076
+ let quotaError = null;
6077
+ try {
6078
+ limits = await refreshAccountLimits({
6079
+ accountAllowlist: effectiveAllowlist,
6080
+ snapshotOnly: !live,
6081
+ });
6082
+ }
6083
+ catch (error) {
6084
+ quotaError = error instanceof Error ? error.message : String(error);
6085
+ }
6086
+ const { getUsageSnapshot } = await import("../../proxy/usageStats.js");
6087
+ const statsAccounts = getUsageSnapshot().stats.accounts;
6088
+ // `cooling` is the field an operator actually acts on, and one small
6089
+ // file read answers it. `allowed`/`expired` additionally need the
6090
+ // token store, which /status guards behind its own timeouts — this
6091
+ // route reports them as null rather than taking that latency.
6092
+ const now = Date.now();
6093
+ // Typed from the loader, NOT re-asserted into a local shape. The
6094
+ // first version cast this to `{ until?: number }` and read
6095
+ // `.until` — a field PersistedAccountCooldown does not have, so
6096
+ // every row reported cooling: false and the assertion is precisely
6097
+ // what stopped the compiler from saying so.
6098
+ let cooldowns = {};
6099
+ try {
6100
+ cooldowns = await loadAccountCooldowns();
6101
+ }
6102
+ catch {
6103
+ // Non-fatal: every row simply reports cooling: false.
6104
+ }
6105
+ const isCooling = (key) => {
6106
+ if (!key) {
6107
+ return false;
6108
+ }
6109
+ const until = cooldowns[key]?.coolingUntil;
6110
+ return typeof until === "number" && until > now;
6111
+ };
6112
+ let usageByAccount = new Map();
6113
+ let usageError = null;
6114
+ let usageDate = "";
6115
+ try {
6116
+ const { readAccountUsage, currentUsageDate } = await import("../../proxy/accountLedger.js");
6117
+ usageDate = currentUsageDate();
6118
+ usageByAccount = await readAccountUsage(usageDate);
6119
+ }
6120
+ catch (error) {
6121
+ // Usage is the optional half; quota and status must still render.
6122
+ usageError = error instanceof Error ? error.message : String(error);
6123
+ }
6124
+ const statsByLabel = new Map();
6125
+ for (const entry of Object.values(statsAccounts)) {
6126
+ statsByLabel.set(entry.label, entry);
6127
+ }
6128
+ const rows = [];
6129
+ const claimed = new Set();
6130
+ // Real logins drive the row set. Building it from the log instead
6131
+ // would silently drop any account that happened to serve no traffic
6132
+ // today, which is exactly when an operator most wants to see it.
6133
+ for (const result of limits.results) {
6134
+ const label = result.account;
6135
+ claimed.add(label);
6136
+ const stat = statsByLabel.get(label);
6137
+ const quota = normalizeQuotaForAccounts(result.quota);
6138
+ const cooling = isCooling(result.key ?? null);
6139
+ rows.push({
6140
+ label,
6141
+ key: result.key ?? null,
6142
+ kind: "account",
6143
+ type: result.type ?? stat?.type ?? "oauth",
6144
+ // result.status describes how the quota was obtained
6145
+ // ("snapshot"/"fetched"), not the account's health, so it must
6146
+ // not leak into a field consumers read as health.
6147
+ // BOTH quota windows, not just the weekly one. A session
6148
+ // window that is rejected or throttled stops the account
6149
+ // serving right now — this same file gates routing on
6150
+ // `sessionStatus` (see the overage checks above) — so an
6151
+ // account with a healthy weekly window and a degraded session
6152
+ // window would otherwise be reported "active" while being
6153
+ // unable to take work.
6154
+ status: cooling
6155
+ ? "cooling"
6156
+ : quotaHealth(quota?.weeklyStatus) === "degraded" ||
6157
+ quotaHealth(quota?.sessionStatus) === "degraded"
6158
+ ? "exhausted"
6159
+ : "active",
6160
+ cooling,
6161
+ allowed: null,
6162
+ expired: null,
6163
+ isPrimary: false,
6164
+ requests: stat ? stat.successCount + stat.errorCount : null,
6165
+ errors: stat?.errorCount ?? null,
6166
+ rateLimits: stat?.rateLimitCount ?? null,
6167
+ quotaRateLimits: stat?.quotaRateLimitCount ?? null,
6168
+ quota: quota
6169
+ ? {
6170
+ ...quota,
6171
+ sessionHealth: quotaHealth(quota.sessionStatus),
6172
+ weeklyHealth: quotaHealth(quota.weeklyStatus),
6173
+ }
6174
+ : null,
6175
+ usage: usageByAccount.get(label) ?? null,
6176
+ });
6177
+ }
6178
+ // Plumbing rows are still reported, but tagged, so a consumer can
6179
+ // show or hide them rather than rendering them as credentials.
6180
+ //
6181
+ // A real login can land here too: listAnthropicAccountsForUsage
6182
+ // skips accounts the token store has disabled or the allowlist
6183
+ // excludes, so an account with real usage history but no current
6184
+ // route is absent from limits.results. Tagging that as plumbing hid
6185
+ // the one account an operator is looking for when they ask why
6186
+ // traffic stopped — the docs tell consumers to filter internal rows
6187
+ // out. It stays kind "account", with a status saying why it has no
6188
+ // quota block.
6189
+ for (const entry of Object.values(statsAccounts)) {
6190
+ if (claimed.has(entry.label)) {
6191
+ continue;
6192
+ }
6193
+ const isRealAccount = REAL_ACCOUNT_TYPES.has(entry.type);
6194
+ rows.push({
6195
+ label: entry.label,
6196
+ key: null,
6197
+ kind: isRealAccount
6198
+ ? "account"
6199
+ : entry.type === "translation"
6200
+ ? "translation"
6201
+ : "internal",
6202
+ type: entry.type,
6203
+ status: isRealAccount ? "unrouted" : null,
6204
+ cooling: false,
6205
+ allowed: null,
6206
+ expired: null,
6207
+ isPrimary: false,
6208
+ requests: entry.successCount + entry.errorCount,
6209
+ errors: entry.errorCount,
6210
+ rateLimits: entry.rateLimitCount,
6211
+ quotaRateLimits: entry.quotaRateLimitCount,
6212
+ quota: null,
6213
+ // Looked up the same way the routed rows do. These accounts are
6214
+ // built FROM today's usage stats, so an account that served
6215
+ // requests and was then disabled or excluded still has tokens
6216
+ // and cost in the ledger — hardcoding null here discarded
6217
+ // exactly the usage an operator is looking for when they ask
6218
+ // why traffic stopped.
6219
+ usage: usageByAccount.get(entry.label) ?? null,
6220
+ });
6221
+ }
6222
+ const response = {
6223
+ generatedAt: Date.now(),
6224
+ usageDate,
6225
+ quotaFromSnapshot: !live,
6226
+ usageError,
6227
+ quotaError,
6228
+ // Pooled accounts bill by subscription. This is what the recorded
6229
+ // tokens would have cost at published rates — a value estimate, not
6230
+ // an invoice — and consumers must label it that way.
6231
+ costBasis: "api-equivalent",
6232
+ accounts: rows,
6233
+ };
6234
+ return response;
6235
+ },
6236
+ description: "One row per account: status, quota, and today's tokens and " +
6237
+ "API-equivalent cost. ?refresh=true forces a live quota fetch",
6238
+ tags: ["claude-proxy", "accounts"],
6239
+ },
5994
6240
  ],
5995
6241
  };
5996
6242
  }
@@ -6354,6 +6600,7 @@ export function redactProviderErrorMessage(message) {
6354
6600
  // spinning up a full proxy. Keep this surface small.
6355
6601
  // ---------------------------------------------------------------------------
6356
6602
  export const __testHooks = {
6603
+ normalizeQuotaForAccounts,
6357
6604
  resolveHomeIndex,
6358
6605
  maybeResetPrimaryToHome,
6359
6606
  planCooldownFor429,
@@ -1,3 +1,4 @@
1
+ import type { JsonObject } from "./common.js";
1
2
  /**
2
3
  * One AI coding CLI the proxy can point at itself.
3
4
  *
@@ -52,3 +53,83 @@ export type CliProxyClientRestoreResult = {
52
53
  * key the user has set, including ones this repo does not know about.
53
54
  */
54
55
  export type CliQwenSettings = Record<string, unknown>;
56
+ /**
57
+ * Per-account token and cost totals derived from the proxy's own request log.
58
+ *
59
+ * `costUsd` is an **API-equivalent** figure: what the recorded tokens would
60
+ * have cost at published per-token rates. Pooled OAuth accounts are billed by
61
+ * subscription, so this is a value estimate, never an invoice. Consumers must
62
+ * label it as such.
63
+ */
64
+ export type CliAccountUsageTotals = {
65
+ requests: number;
66
+ inputTokens: number;
67
+ outputTokens: number;
68
+ cacheReadTokens: number;
69
+ cacheCreationTokens: number;
70
+ costUsd: number;
71
+ /** Requests whose model carried no pricing row, so contributed no cost. */
72
+ unpricedRequests: number;
73
+ /** Distinct models with no pricing row, so an operator can chase them. */
74
+ unpricedModels: string[];
75
+ };
76
+ /** One row of GET /accounts. */
77
+ export type CliAccountsRow = {
78
+ /** Bare label, e.g. "someone@example.com". The join key across all sources. */
79
+ label: string;
80
+ /** Full pool key, e.g. "anthropic:someone@example.com". */
81
+ key: string | null;
82
+ /**
83
+ * What this row actually is. Only "account" rows are real logins; the proxy
84
+ * also tracks internal and translation pseudo-accounts, which have no quota
85
+ * and should not be rendered as credentials.
86
+ */
87
+ kind: "account" | "internal" | "translation";
88
+ type: string;
89
+ status: string | null;
90
+ cooling: boolean;
91
+ allowed: boolean | null;
92
+ expired: boolean | null;
93
+ isPrimary: boolean;
94
+ requests: number | null;
95
+ errors: number | null;
96
+ rateLimits: number | null;
97
+ quotaRateLimits: number | null;
98
+ /** Quota block from the limits snapshot, timestamps normalised to ms. */
99
+ quota: JsonObject | null;
100
+ /** Today's usage from the request log, or null when the log is unreadable. */
101
+ usage: CliAccountUsageTotals | null;
102
+ };
103
+ /** Response body of GET /accounts. */
104
+ export type CliAccountsResponse = {
105
+ generatedAt: number;
106
+ /** UTC date whose request log the usage totals cover. */
107
+ usageDate: string;
108
+ /** True when quota came from the stored snapshot rather than a live fetch. */
109
+ quotaFromSnapshot: boolean;
110
+ /** Set when the usage totals could not be read at all. */
111
+ usageError: string | null;
112
+ /** Set when the quota snapshot could not be read; rows still carry status. */
113
+ quotaError: string | null;
114
+ costBasis: "api-equivalent";
115
+ accounts: CliAccountsRow[];
116
+ };
117
+ /** One request as recorded in the proxy request log, reduced to what costing needs. */
118
+ export type ProxyLedgerEntry = {
119
+ account: string;
120
+ accountType: string;
121
+ model: string;
122
+ provider?: string;
123
+ inputTokens: number;
124
+ outputTokens: number;
125
+ cacheReadTokens: number;
126
+ cacheCreationTokens: number;
127
+ };
128
+ /** Incremental read position and accumulated entries for one request-log file. */
129
+ export type ProxyLedgerFileCursor = {
130
+ /** Byte offset just past the last complete line consumed. */
131
+ offset: number;
132
+ size: number;
133
+ /** requestId -> latest known entry, so a re-logged request cannot double count. */
134
+ entries: Map<string, ProxyLedgerEntry>;
135
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.13.2",
3
+ "version": "11.14.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -32,8 +32,6 @@
32
32
  "build:browser": "node scripts/build-browser.mjs",
33
33
  "build:browser:dev": "node scripts/build-browser.mjs --dev",
34
34
  "build:cli": "echo 'Building CLI...' && svelte-kit sync && tsc --project tsconfig.cli.json && node scripts/collapse-cli-lib-duplicate.mjs",
35
- "build:cli:bundle": "node scripts/bundle-cli.mjs",
36
- "build:cli:bundle:minify": "node scripts/bundle-cli.mjs --minify",
37
35
  "build:action": "ncc build src/action/index.ts -o action-dist --source-map",
38
36
  "build:cli:link": "pnpm run build:cli && pnpm link --global",
39
37
  "check:deps": "tsx scripts/check-banned-deps.ts",
@@ -154,9 +152,6 @@
154
152
  "// Documentation Automation (Legacy MkDocs)": "",
155
153
  "docs:api": "typedoc",
156
154
  "docs:sync": "bash scripts/sync-readme.sh",
157
- "docs:build:mkdocs": "pnpm run docs:api && bash scripts/sync-readme.sh && mkdocs build --strict --clean",
158
- "docs:serve:mkdocs": "bash scripts/sync-readme.sh && mkdocs serve",
159
- "docs:gh-deploy:mkdocs": "bash scripts/sync-readme.sh && mkdocs gh-deploy --force",
160
155
  "docs:validate": "tsx tools/content/documentationSync.ts --validate",
161
156
  "docs:generate": "pnpm run docs:validate",
162
157
  "// Documentation (Docusaurus)": "",
@@ -200,7 +195,6 @@
200
195
  "validate:commit": "tsx scripts/commit-validation.ts",
201
196
  "quality:metrics": "tsx scripts/quality-metrics.ts",
202
197
  "quality:report": "pnpm run quality:metrics && echo 'Quality metrics saved to quality-metrics.json'",
203
- "pre-commit": "lint-staged",
204
198
  "pre-push": "pnpm run check:deps && pnpm run build && pnpm run test:providers-mocked && pnpm run test:provider-structure && pnpm run test:model-manifests",
205
199
  "check:all": "pnpm run lint && pnpm run format --check && pnpm run validate && pnpm run validate:commit",
206
200
  "test:file-formats": "npx tsx test/continuous-test-suite-file-formats.ts",
@@ -383,9 +377,7 @@
383
377
  "js-yaml": "^4.3.1",
384
378
  "json-schema-to-zod": "^2.7.0",
385
379
  "jsonrepair": "^3.14.0",
386
- "minisearch": "^7.2.0",
387
380
  "nanoid": "^5.1.16",
388
- "open": "^11.0.0",
389
381
  "ora": "^9.3.0",
390
382
  "p-limit": "^7.3.0",
391
383
  "redis": "^5.11.0",
@@ -424,9 +416,7 @@
424
416
  "@fastify/cors": "^11.2.0",
425
417
  "@fastify/rate-limit": "^10.3.0",
426
418
  "@google-cloud/text-to-speech": "^6.4.0",
427
- "@google-cloud/vertexai": "^1.10.0",
428
419
  "@hono/node-server": "^1.19.15",
429
- "@huggingface/inference": "^4.13.14",
430
420
  "@koa/cors": "^5.0.0",
431
421
  "@koa/router": "^15.3.1",
432
422
  "@langfuse/otel": "^5.0.1",
@@ -463,7 +453,6 @@
463
453
  "@actions/core": "^3.0.0",
464
454
  "@actions/exec": "^3.0.0",
465
455
  "@actions/github": "^9.0.0",
466
- "@biomejs/biome": "^2.4.4",
467
456
  "@changesets/changelog-github": "^0.6.0",
468
457
  "@changesets/cli": "^2.29.8",
469
458
  "@electric-sql/pglite": "^0.4.6",
@@ -474,7 +463,6 @@
474
463
  "@opentelemetry/sdk-trace-node": "^2.6.0",
475
464
  "@semantic-release/changelog": "^6.0.3",
476
465
  "@semantic-release/commit-analyzer": "^13.0.1",
477
- "@semantic-release/git": "^10.0.1",
478
466
  "@semantic-release/github": "^12.0.6",
479
467
  "@semantic-release/npm": "^13.1.4",
480
468
  "@semantic-release/release-notes-generator": "^14.1.0",
@@ -483,11 +471,9 @@
483
471
  "@sveltejs/kit": "^2.70.3",
484
472
  "@sveltejs/package": "^2.5.7",
485
473
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
486
- "@types/adm-zip": "^0.5.7",
487
474
  "@types/cors": "^2.8.19",
488
475
  "@types/express": "^5.0.6",
489
476
  "@types/fluent-ffmpeg": "^2.1.28",
490
- "@types/inquirer": "^9.0.9",
491
477
  "@types/js-yaml": "^4.0.9",
492
478
  "@types/koa": "^3.0.1",
493
479
  "@types/koa-bodyparser": "^4.3.13",
@@ -500,32 +486,25 @@
500
486
  "@typescript-eslint/eslint-plugin": "^8.57.2",
501
487
  "@typescript-eslint/parser": "^8.57.2",
502
488
  "@vercel/ncc": "^0.38.4",
503
- "@vitest/coverage-v8": "^4.1.0",
504
489
  "concurrently": "^9.2.1",
505
490
  "conventional-changelog-conventionalcommits": "^9.1.0",
506
491
  "esbuild": "^0.28.1",
507
492
  "eslint": "^10.0.2",
508
493
  "husky": "^9.1.7",
509
- "lint-staged": "^16.3.0",
510
494
  "playwright": "^1.58.2",
511
495
  "prettier": "^3.8.1",
512
496
  "publint": "^0.3.17",
513
- "puppeteer": "^24.37.5",
514
497
  "react": "^19.2.4",
515
498
  "react-dom": "^19.2.4",
516
499
  "semantic-release": "^25.0.3",
517
- "shell-quote": "^1.9.0",
518
500
  "svelte": "^5.55.7",
519
501
  "svelte-check": "^4.4.4",
520
- "ts-morph": "^24.0.0",
521
- "tslib": "^2.8.1",
522
502
  "tsx": "^4.21.0",
523
503
  "typedoc": "^0.28.17",
524
504
  "typedoc-plugin-markdown": "^4.10.0",
525
505
  "typescript": "^5.9.3",
526
506
  "vite": "^8.2.2",
527
- "vitest": "^4.1.0",
528
- "why-is-node-running": "^3.2.2"
507
+ "vitest": "^4.1.0"
529
508
  },
530
509
  "keywords": [
531
510
  "ai",
@@ -650,18 +629,5 @@
650
629
  "tabWidth": 2,
651
630
  "useTabs": false,
652
631
  "proseWrap": "preserve"
653
- },
654
- "lint-staged": {
655
- "src/**/*.{ts,tsx}": [
656
- "eslint --fix --max-warnings=50",
657
- "prettier --write"
658
- ],
659
- "test/**/*.{ts,tsx}": [
660
- "eslint --fix --max-warnings=0",
661
- "prettier --write"
662
- ],
663
- "*.{json,md}": [
664
- "prettier --write"
665
- ]
666
632
  }
667
633
  }