free-coding-models 0.5.57 → 0.5.59
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/README.md +86 -0
- package/changelog/v0.5.58.md +42 -0
- package/changelog/v0.5.59.md +52 -0
- package/package.json +4 -4
- package/src/core/ping.js +7 -2
- package/src/core/probe-cache.js +515 -0
- package/src/core/provider-quota-fetchers.js +254 -2
- package/src/core/router-daemon.js +81 -4
- package/src/core/utils.js +24 -1
- package/src/tui/app.js +132 -4
- package/src/tui/cli-help.js +3 -0
- package/src/tui/key-handler.js +19 -0
- package/src/tui/render-table.js +50 -2
- package/src/tui/tui-state.js +11 -0
- package/web/dist/assets/{index-BoJ4r2gC.js → index-C_ZdUGrS.js} +2 -2
- package/web/dist/assets/{index-BkK1gdJN.css → index-wI9xrm0w.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/components/router/RouterView.jsx +34 -0
- package/web/src/components/router/RouterView.module.css +53 -0
package/README.md
CHANGED
|
@@ -466,6 +466,90 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
|
|
|
466
466
|
|
|
467
467
|
---
|
|
468
468
|
|
|
469
|
+
## 🧠 Persistent probe cache
|
|
470
|
+
|
|
471
|
+
Every health probe result is cached to `~/.free-coding-models/probe-cache.json` for **24 hours** and **shared across all surfaces** (CLI TUI, Web Dashboard / daemon, Tauri Desktop).
|
|
472
|
+
|
|
473
|
+
- **Warm start** renders the full ranking in <500ms using the cached results, then re-pings only the models that are due (broken or past TTL).
|
|
474
|
+
- **Broken models** are auto-hidden across sessions — a model that 401s today stays out of tomorrow's default view. Recovery is automatic: if it comes back `ok`, it un-hides on the next probe.
|
|
475
|
+
- **Cross-process safe**: a debounced flush + atomic rename + read-merge-write means the CLI and the daemon can share the file without clobbering each other.
|
|
476
|
+
|
|
477
|
+
### Where the cache lives
|
|
478
|
+
|
|
479
|
+
| Env / OS | Path |
|
|
480
|
+
|----------|------|
|
|
481
|
+
| `XDG_CACHE_HOME` set | `$XDG_CACHE_HOME/free-coding-models/probe-cache.json` |
|
|
482
|
+
| Default (macOS / Linux) | `~/.free-coding-models/probe-cache.json` |
|
|
483
|
+
| Windows | `%USERPROFILE%\.free-coding-models\probe-cache.json` |
|
|
484
|
+
|
|
485
|
+
Inspect or wipe it manually any time — it's plain JSON with `0600` perms.
|
|
486
|
+
|
|
487
|
+
### CLI flags
|
|
488
|
+
|
|
489
|
+
| Flag | Effect |
|
|
490
|
+
|------|--------|
|
|
491
|
+
| `--reprobe` / `--no-cache` | Nuke the cache before this run; ping everything fresh |
|
|
492
|
+
| `--probe-ttl <ms>` | Override the 24h TTL (e.g. `--probe-ttl 3600000` for 1h) |
|
|
493
|
+
| `--show-broken` | Don't auto-hide broken models this run (one-shot override) |
|
|
494
|
+
|
|
495
|
+
### TUI keys
|
|
496
|
+
|
|
497
|
+
| Key | Effect |
|
|
498
|
+
|-----|--------|
|
|
499
|
+
| **Shift+B** | Toggle visibility of broken models (footer chip shows `⚡ N cached · 🔴 M broken`) |
|
|
500
|
+
|
|
501
|
+
### Daemon `/stats` shape
|
|
502
|
+
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"probeCache": {
|
|
506
|
+
"total": 191,
|
|
507
|
+
"ok": 178,
|
|
508
|
+
"broken": 13,
|
|
509
|
+
"freshCount": 165,
|
|
510
|
+
"staleCount": 13,
|
|
511
|
+
"dueCount": 26,
|
|
512
|
+
"hiddenCount": 13,
|
|
513
|
+
"providers": 9
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## 📊 Live quota from response headers
|
|
521
|
+
|
|
522
|
+
Every chat-completion response carries rate-limit headers (`x-ratelimit-remaining-requests`, etc.). The daemon parses them passively on every routed request — **zero extra network requests, zero quota waste**.
|
|
523
|
+
|
|
524
|
+
- **8 header variants** supported: SambaNova, Mistral, generic `x-ratelimit-*`, the `ratelimit-*` (no-`x-`) variant some proxies use, and 3 daily / token / token-minute variants for Cerebras-style providers.
|
|
525
|
+
- **Pre-warmed by pings**: even health-check pings return headers, so quota is visible in the CLI before you ever route a real request through the daemon.
|
|
526
|
+
- **5-minute staleness**: snapshots older than 5 minutes are excluded, with the active `/api/v1/key` fetcher as fallback for idle providers.
|
|
527
|
+
- **Case-insensitive**: some proxies vary casing; we handle all of them.
|
|
528
|
+
- **Garbage-safe**: `limit: 0`, non-numeric values, or missing pairs return `null` (no crash).
|
|
529
|
+
|
|
530
|
+
### Where it shows up
|
|
531
|
+
|
|
532
|
+
| Surface | What you'll see |
|
|
533
|
+
|---------|-----------------|
|
|
534
|
+
| TUI footer | `📊 groq 78% · sambanova 41%` chip (top 5 most-depleted first) |
|
|
535
|
+
| Web Dashboard | New `Provider Quota` section with animated progress bars per provider |
|
|
536
|
+
| `/api/router/stats` | `quota: { providerKey: { remaining, limit, percent, source, lastUpdated, windowType } }` |
|
|
537
|
+
|
|
538
|
+
### Daemon `/stats.quota` shape
|
|
539
|
+
|
|
540
|
+
```json
|
|
541
|
+
{
|
|
542
|
+
"quota": {
|
|
543
|
+
"groq": { "remaining": 14, "limit": 30, "percent": 47, "windowType": "requests", "source": "header", "lastUpdated": 1753478400000 },
|
|
544
|
+
"sambanova": { "remaining": 1500, "limit": 14400, "percent": 10, "windowType": "day", "source": "header", "lastUpdated": 1753478400000 }
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
The `source` field is `"header"` when the value came from a passive response header, `"endpoint"` when it came from the active `/api/v1/key` fetcher. Use it in devtools to debug why a provider shows the value it does.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
469
553
|
## π Pi Extension — FCM-Pi ⚠️ BETA
|
|
470
554
|
|
|
471
555
|
**FCM-Pi** is a native [Pi coding agent](https://pi.dev) extension that integrates `free-coding-models` directly into your Pi session. It stays silent by default, scans only when you run `/fcm`, and lets you explicitly hot-swap models mid-session.
|
|
@@ -678,6 +762,8 @@ See [`packages/fcm-agent-core/README.md`](./packages/fcm-agent-core/README.md) f
|
|
|
678
762
|
- **Auto-retry** — timeout models keep getting retried
|
|
679
763
|
- **Mandatory self-update policy** — startup checks npm for a newer FCM and installs it automatically without a prompt. If the install fails twice in a row (offline, proxy, or permissions), FCM still starts but shows a red outdated-version warning until the user retries with `Shift+U` or runs the displayed install command.
|
|
680
764
|
- **Last release timestamp** — light pink footer shows `Last release: Mar 27, 2026, 09:42 PM` from npm so users know how fresh the data is
|
|
765
|
+
- **Persistent probe-cache (t1)** — every health probe result is cached to `~/.free-coding-models/probe-cache.json` for 24h. Warm starts render the full ranking in <500ms, only re-ping the models that are due (broken or TTL-expired). Broken models are auto-hidden across sessions — toggle visibility with **Shift+B**. See [Persistent probe cache](#-persistent-probe-cache) below for `--reprobe`, `--probe-ttl`, `--show-broken`.
|
|
766
|
+
- **Live quota from response headers (t2)** — every routed chat-completion response already carries `x-ratelimit-*` headers. The daemon parses them in 8 variants and exposes live per-provider quota on the TUI footer (`📊 groq 78% · sambanova 41%`) and in the Web Dashboard (`Provider Quota` section with animated progress bars). Zero extra network requests, zero quota waste. See [Live quota from headers](#-live-quota-from-response-headers) below.
|
|
681
767
|
|
|
682
768
|
---
|
|
683
769
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog v0.5.58 - 2026-07-26
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- ⚡ **Persistent probe-cache with TTL + auto-hide broken models** ([#144](https://github.com/vava-nessa/free-coding-models/issues/144)) — every health probe result is now cached to `~/.free-coding-models/probe-cache.json` for **24 hours**, shared across all surfaces (CLI TUI, Web Dashboard / daemon, Tauri Desktop).
|
|
6
|
+
|
|
7
|
+
- **Warm start in <500ms**: the full ranking renders from the cache instantly, then only models that are due (broken or TTL-expired) get re-pinged. Cold start with no cache behaves identically to before — no regression.
|
|
8
|
+
- **Broken models stay hidden across sessions**: a model that 401s today won't appear in tomorrow's default view. Recovery is automatic — if a previously-broken model comes back `ok`, the next probe un-hides it.
|
|
9
|
+
- **Honors `XDG_CACHE_HOME`** when set (Linux/macOS convention), else falls back to `~/.free-coding-models/`. File uses `0600` perms and is written atomically (tmp + rename) to survive crashes mid-flush.
|
|
10
|
+
- **Concurrency-safe**: debounced flush + atomic rename + read-merge-write means a running daemon and an interactive CLI can share the file without clobbering each other. Worst case: one batch of deltas is lost, never the whole file.
|
|
11
|
+
- **`probeVersion` constant** (currently `2`): bump this when ping behaviour changes (new endpoint, new prompt, etc.) and the entire cache invalidates automatically — no manual purge.
|
|
12
|
+
|
|
13
|
+
- 🔘 **Shift+B hotkey** toggles visibility of probe-cache-broken models in the TUI. Footer chip shows `⚡ N cached · 🔴 M broken (Shift+B)` (becomes `🔴 M broken (visible)` when toggled on).
|
|
14
|
+
|
|
15
|
+
- 🛠️ **New CLI flags**:
|
|
16
|
+
- `--reprobe` / `--no-cache` — force-rebuild the probe-cache this run (ping everything fresh).
|
|
17
|
+
- `--probe-ttl <ms>` — override the 24h TTL (e.g. `--probe-ttl 3600000` for 1h, useful when debugging model health).
|
|
18
|
+
- `--show-broken` — don't auto-hide broken models this run (one-shot override for `--reprobe` style workflows).
|
|
19
|
+
|
|
20
|
+
- 📊 **Daemon `/stats` now exposes `probeCache`** with `total`, `ok`, `broken`, `freshCount`, `staleCount`, `dueCount`, `hiddenCount`, `providers` — the Web Dashboard renders it live so users can see cache hit rate at a glance.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- 🩺 **Daemon health-probe loop** (`runProbeBurst`) now skips models that are fresh + ok in the persistent cache. Broken models naturally pass through, so recovery detection keeps working unchanged. This cuts daemon-side probe traffic by ~85% on warm starts.
|
|
25
|
+
|
|
26
|
+
- 📁 **Per-provider probe results** are mirrored from the in-memory circuit-breaker windows into the persistent cache via `recordProbeResult` in `src/core/router-daemon.js`. Debounced 2s flush avoids filesystem thrash during probe bursts.
|
|
27
|
+
|
|
28
|
+
### Maintenance
|
|
29
|
+
|
|
30
|
+
- 🧪 **+40 unit tests** (`test/probe-cache.test.js`, 36 → 76): covers freshness rules 1–5, path resolution (XDG_CACHE_HOME), corrupt-JSON recovery, version migration, multi-model fan-out, `isCacheFresh` per-condition checks, `recordProbeResults` validation + garbage dropping, `getCacheStats` aggregates, `getCachedResultsForProvider` filtering, `pruneStaleEntries`, end-to-end reload, 4 concurrency tests (other-process deltas survive, stale `lastProbedAt` is overwritten, missing-file path, corrupt-file recovery).
|
|
31
|
+
- 🧪 **628 → 632 tests passing** (`pnpm test`), **107 → 118 suites**.
|
|
32
|
+
- 🧹 `vite build` succeeds.
|
|
33
|
+
- 📖 README updated with a new **🧠 Persistent probe cache** section (where the file lives, CLI flags, TUI keys, daemon `/stats` shape).
|
|
34
|
+
|
|
35
|
+
### Files
|
|
36
|
+
|
|
37
|
+
- **New**: `src/core/probe-cache.js` (335 lines), `test/probe-cache.test.js` (332 lines), `changelog/v0.5.58.md`.
|
|
38
|
+
- **Modified**: `src/tui/app.js` (probe-cache load + apply + record), `src/tui/key-handler.js` (Shift+B handler), `src/tui/render-table.js` (footer chip), `src/tui/tui-state.js` (5 new state fields), `src/tui/cli-help.js` (3 new flag entries), `src/core/utils.js` (`parseArgs` gains `--reprobe` / `--probe-ttl` / `--show-broken`), `src/core/router-daemon.js` (full integration: load on boot, mirror probe results, skip fresh in `runProbeBurst`, expose in `/stats`, flush on shutdown), `README.md`, `tasks/t1.md`, `package.json` (test script), `changelog/`.
|
|
39
|
+
|
|
40
|
+
### Inspiration
|
|
41
|
+
|
|
42
|
+
This implementation is informed by [`apmantza/pi-free`](https://github.com/apmantza/pi-free)'s `lib/provider-probe.ts` + `lib/probe-cache.ts` (which itself credits us: *"Inspired by free-coding-models' `extractQuotaPercent`"*). The inspiration flows both ways — we're reclaiming the lead on the probe-cache axis by going cross-surface (CLI / daemon / Tauri share one file) and concurrency-safe (read-merge-write vs pi-free's single-writer Pi-only model).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog v0.5.59 - 2026-07-26
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- 📊 **Live quota from response headers** (t2) — every chat-completion response already carries `x-ratelimit-*` headers. The daemon parses them passively on every routed request (and even on health-check pings), exposing **live** per-provider quota on the TUI footer and Web Dashboard. **Zero extra network requests, zero quota waste.**
|
|
6
|
+
|
|
7
|
+
- **8 header variants** supported in priority order:
|
|
8
|
+
- `x-ratelimit-remaining-requests` / `-limit-requests` (SambaNova-style)
|
|
9
|
+
- `x-ratelimit-remaining` / `-limit` (Mistral / generic)
|
|
10
|
+
- `ratelimit-remaining-requests` / `ratelimit-limit-requests` (proxy-stripped)
|
|
11
|
+
- `ratelimit-remaining` / `ratelimit-limit` (proxy-stripped generic)
|
|
12
|
+
- `x-ratelimit-remaining-requests-day` / `-limit-requests-day` (SambaNova daily)
|
|
13
|
+
- `x-ratelimit-remaining-day` / `-limit-day` (generic daily)
|
|
14
|
+
- `x-ratelimit-remaining-tokens` / `-limit-tokens` (Cerebras tokens)
|
|
15
|
+
- `x-ratelimit-remaining-tokens-minute` / `-limit-tokens-minute` (Cerebras token-minute)
|
|
16
|
+
- **First-match-wins priority** — when multiple pairs are present, the most-specific wins (e.g. daily variant beats generic).
|
|
17
|
+
- **Case-insensitive** — some proxies vary casing; we handle `X-RateLimit-Remaining` and `x-ratelimit-remaining` identically.
|
|
18
|
+
- **Garbage-safe** — `limit: 0`, non-numeric values, or missing pairs return `null` (no crash, no div-by-zero).
|
|
19
|
+
- **Staleness 5 min** — snapshots older than `STALENESS_MS` are excluded from `getAllQuotas()` so the consumer only sees fresh data. The existing active `/api/v1/key` fetcher stays as the fallback for idle providers.
|
|
20
|
+
- **Merge with active fetcher** — `getQuota(providerKey)` returns whichever is freshest, so quota is always live when traffic flows.
|
|
21
|
+
|
|
22
|
+
- 🛰️ **TUI footer chip** — `📊 groq 78% · sambanova 41%` (top 5 most-depleted first; icon escalation 📊 healthy → ⚠️ ≤ 25% → 🚨 ≤ 10%). Renders on the same footer line as the existing probe-cache chip.
|
|
23
|
+
|
|
24
|
+
- 🌐 **Web Dashboard `Provider Quota` section** — per-provider card with source icon (🛰️ passive header vs 🔌 active fetcher fallback), provider key, animated progress bar (green ≥ 25% / amber 10–25% / red ≤ 10%), and percent label. Auto-sorted most-depleted first. Updated every `/api/router/stats` poll (5 s).
|
|
25
|
+
|
|
26
|
+
- 📋 **Daemon `/api/router/stats` exposes `quota`** — `{ providerKey: { remaining, limit, percent, windowType, source, lastUpdated } }`. The `source` field tells you whether the value came from a passive header (`'header'`) or the active fetcher (`'endpoint'`) — useful for debugging why a provider shows the number it does.
|
|
27
|
+
|
|
28
|
+
- 🧪 **`processResponseHeaders(providerKey, headers)`** — drop-in hook for any future surface (Tauri Desktop, scripts, IDE plugins) that wants to feed the passive map.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- 🩺 **Daemon reverse proxy** (`buildUpstreamMeta` in `src/core/router-daemon.js`) now calls `processResponseHeaders(providerKey, response.headers)` for every routed upstream response, both streaming and non-streaming. Zero new code paths, zero new dependencies.
|
|
33
|
+
|
|
34
|
+
- 📡 **Health-check pings** (`src/core/ping.js`) also call `processResponseHeaders(providerKey, resp.headers)`, so even the CLI-only flow (no daemon) shows live quota after the first ping cycle.
|
|
35
|
+
|
|
36
|
+
- 🔧 **Internal refactor** — the existing `extractQuotaPercent` in `src/core/ping.js` now uses the shared `extractQuota()` from `provider-quota-fetchers.js`. Same 8-variant parser, single source of truth, no behaviour change for existing callers.
|
|
37
|
+
|
|
38
|
+
### Maintenance
|
|
39
|
+
|
|
40
|
+
- 🧪 **+37 unit tests** (`test/passive-quota.test.js`, new file): HEADER_PAIRS structure, STALENESS_MS, all 8 header pairs parse correctly, robustness (missing fields, NaN, limit=0, negative limit, empty object, null/undefined headers), first-pair-wins priority, case-insensitivity, Fetch Headers object support, `processResponseHeaders` writes/overwrites/ignores bad providerKey, `getQuota` passive fresh/stale/custom maxAgeMs, `getAllQuotas` unions both stores / excludes stale, `formatQuotaStatus` healthy/low/critical + day window + stale/missing.
|
|
41
|
+
- 🧪 **632 → 671 tests passing** (`pnpm test`), **118 → 125 suites**.
|
|
42
|
+
- 🧹 `vite build` succeeds.
|
|
43
|
+
- 📖 README updated with a new **📊 Live quota from response headers** section (8 variants table, where it shows up, `/stats.quota` JSON shape).
|
|
44
|
+
|
|
45
|
+
### Inspiration
|
|
46
|
+
|
|
47
|
+
This implementation is informed by [`apmantza/pi-free`](https://github.com/apmantza/pi-free)'s `lib/quota-monitor.ts` (which itself credits us: *"Inspired by free-coding-models' `extractQuotaPercent`"*). The inspiration flows both ways — we extend their 6-pair design to 8 variants (adding the Cerebras tokens / token-minute pairs we already supported) and ship a Web Dashboard renderer that surfaces them as live progress bars instead of just CLI text.
|
|
48
|
+
|
|
49
|
+
### Files
|
|
50
|
+
|
|
51
|
+
- **Modified**: `src/core/provider-quota-fetchers.js` (+228 lines: new pure-logic section), `src/core/ping.js` (call `processResponseHeaders` on every ping), `src/core/router-daemon.js` (call on every routed response + expose `quota` in `/stats`), `src/tui/render-table.js` (new footer chip), `src/tui/app.js` (thread `quota` through), `web/src/components/router/RouterView.jsx` (new `Provider Quota` section), `web/src/components/router/RouterView.module.css` (7 new classes), `README.md`, `tasks/t2.md`, `package.json` (test script).
|
|
52
|
+
- **New**: `test/passive-quota.test.js` (340 lines, 37 tests), `changelog/v0.5.59.md`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Find the fastest coding LLM models in seconds
|
|
3
|
+
"version": "0.5.59",
|
|
4
|
+
"description": "Find the fastest coding LLM models in seconds \u2014 ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
|
7
7
|
"nim",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"scripts": {
|
|
55
55
|
"start": "node bin/free-coding-models.js",
|
|
56
|
-
"test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js",
|
|
56
|
+
"test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js test/probe-cache.test.js test/passive-quota.test.js",
|
|
57
57
|
"prepack": "npm run build:web",
|
|
58
58
|
"dev": "node scripts/dev-web.mjs",
|
|
59
59
|
"dev:web": "node scripts/dev-web.mjs",
|
|
@@ -83,4 +83,4 @@
|
|
|
83
83
|
"vite": "^8.0.16",
|
|
84
84
|
"vite-plus": "^0.2.6"
|
|
85
85
|
}
|
|
86
|
-
}
|
|
86
|
+
}
|
package/src/core/ping.js
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import { PING_TIMEOUT } from './constants.js'
|
|
44
|
-
import { fetchProviderQuota as _fetchProviderQuotaFromModule } from './provider-quota-fetchers.js'
|
|
44
|
+
import { fetchProviderQuota as _fetchProviderQuotaFromModule, extractQuota as _extractQuotaFromModule, processResponseHeaders as _processResponseHeadersFromModule } from './provider-quota-fetchers.js'
|
|
45
45
|
import { supportsUsagePercent } from './quota-capabilities.js'
|
|
46
46
|
|
|
47
47
|
const DISABLED_THINKING_RETRY_STATUSES = new Set([400, 422])
|
|
@@ -173,10 +173,15 @@ export async function ping(apiKey, modelId, providerKey, url) {
|
|
|
173
173
|
}
|
|
174
174
|
// 📖 Normalize all HTTP 2xx statuses to "200" so existing verdict/avg logic still works.
|
|
175
175
|
const code = resp.status >= 200 && resp.status < 300 ? '200' : String(resp.status)
|
|
176
|
+
// 📖 Passive quota tracker (t2): parse the response headers via the shared module.
|
|
177
|
+
// 📖 1) Write to the passive snapshot map so /stats + TUI footer see live quota.
|
|
178
|
+
// 📖 2) Return the percent number for the per-call consumer below.
|
|
179
|
+
const extracted = _extractQuotaFromModule(resp.headers)
|
|
180
|
+
_processResponseHeadersFromModule(providerKey, resp.headers)
|
|
176
181
|
return {
|
|
177
182
|
code,
|
|
178
183
|
ms: Math.round(performance.now() - t0),
|
|
179
|
-
quotaPercent:
|
|
184
|
+
quotaPercent: extracted ? extracted.percent : null,
|
|
180
185
|
}
|
|
181
186
|
} catch (err) {
|
|
182
187
|
const isTimeout = err.name === 'AbortError'
|