@askalf/dario 3.22.0 → 3.24.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.
- package/README.md +13 -12
- package/dist/cli.js +41 -1
- package/dist/doctor.js +20 -0
- package/dist/pacing.d.ts +62 -0
- package/dist/pacing.js +78 -0
- package/dist/proxy.d.ts +3 -0
- package/dist/proxy.js +42 -8
- package/dist/runtime-fingerprint.d.ts +77 -0
- package/dist/runtime-fingerprint.js +117 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ One command, one local URL, every provider behind it. Point `ANTHROPIC_BASE_URL`
|
|
|
26
26
|
- `llama-3.3-70b`, `deepseek-v3`, anything else → **Groq**, **OpenRouter**, **local LiteLLM**, **vLLM**, **Ollama**, whichever OpenAI-compat backend you wired up
|
|
27
27
|
- Force a backend explicitly with a prefix: `openai:gpt-4o`, `groq:llama-3.3-70b`, `local:qwen-coder`, `claude:opus`
|
|
28
28
|
|
|
29
|
-
Switching providers is a **model-name change** in your tool. Not a reconfigure. Not new base URLs. Not new API keys. Not a new SDK import. **Zero runtime dependencies. ~
|
|
29
|
+
Switching providers is a **model-name change** in your tool. Not a reconfigure. Not new base URLs. Not new API keys. Not a new SDK import. **Zero runtime dependencies. ~8,100 lines of TypeScript across ~15 files. ~840 assertions across 24 test suites. [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) on every release. Nothing phones home, ever.**
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
@@ -88,7 +88,7 @@ Something broken? `dario doctor` prints a single aggregated health report — da
|
|
|
88
88
|
|
|
89
89
|
**You hit rate limits on long agent runs.** Add a second / third Claude subscription with `dario accounts add work` and pool mode routes each request to whichever account has the most headroom. **Session stickiness** (v3.13.0) pins a multi-turn conversation to one account so the Anthropic prompt cache survives the run. **In-flight 429 failover** retries the same request against a different account before your client sees an error. See [Multi-account pool mode](#multi-account-pool-mode).
|
|
90
90
|
|
|
91
|
-
**You run a coding agent that isn't Claude Code.** Cline, Roo Code, Cursor, Windsurf, Continue.dev, GitHub Copilot, OpenHands, OpenClaw, Hermes — they each ship their own tool schemas and their own validators. Dario's universal `TOOL_MAP` (
|
|
91
|
+
**You run a coding agent that isn't Claude Code.** Cline, Roo Code, Cursor, Windsurf, Continue.dev, GitHub Copilot, OpenHands, OpenClaw, Hermes — they each ship their own tool schemas and their own validators. Dario's universal `TOOL_MAP` (**~66 schema-verified entries**) pre-maps every major coding agent's tool names to Claude Code's native set on the outbound path and rebuilds to your agent's exact expected shape on the inbound path. No `--preserve-tools`, no fingerprint loss, no validator errors. See [Agent compatibility](#agent-compatibility).
|
|
92
92
|
|
|
93
93
|
**You want the proxy layer off the wire entirely.** **Shim mode** (v3.12, hardened in v3.13) is an in-process `globalThis.fetch` patch injected via `NODE_OPTIONS=--require`. No HTTP hop, no port to bind, no `BASE_URL` to set. `dario shim -- claude --print "hi"` and CC thinks it's talking directly to `api.anthropic.com`. See [Shim mode](#shim-mode).
|
|
94
94
|
|
|
@@ -156,11 +156,11 @@ Force a backend with a **provider prefix** on the model field (`openai:gpt-4o`,
|
|
|
156
156
|
|
|
157
157
|
OAuth-backed Claude Max / Pro, billed against your plan instead of the API. Activated by `dario login`.
|
|
158
158
|
|
|
159
|
-
**What it does.** Every outbound Claude request is rebuilt to look exactly like a request Claude Code itself would make — system prompt, tool definitions, fingerprint headers, billing tag, beta flags, **even the exact header insertion order** — using a live-extracted template from your actually-installed CC binary that self-heals on every Anthropic release. Anthropic's classifier sees a CC session because, from the wire up, it *is* one. That's what keeps your usage on subscription billing instead of API overage.
|
|
159
|
+
**What it does.** Every outbound Claude request is rebuilt to look exactly like a request Claude Code itself would make — system prompt, tool definitions, fingerprint headers, billing tag, beta flags, **even the exact header insertion order and request-body key order** — using a live-extracted template from your actually-installed CC binary that self-heals on every Anthropic release. Anthropic's classifier sees a CC session because, from the wire up, it *is* one. That's what keeps your usage on subscription billing instead of API overage.
|
|
160
160
|
|
|
161
161
|
**Key mechanisms:**
|
|
162
162
|
|
|
163
|
-
- **Live fingerprint extraction** (v3.11). Dario spawns your installed `claude` binary against a loopback MITM endpoint on startup, captures its outbound request, and extracts the live template (system prompt, tools, user-agent, beta flags, **header insertion order** as of v3.13
|
|
163
|
+
- **Live fingerprint extraction** (v3.11). Dario spawns your installed `claude` binary against a loopback MITM endpoint on startup, captures its outbound request, and extracts the live template (system prompt, tools, user-agent, beta flags, **header insertion order** as of v3.13 replayed by the shim since v3.13 and the proxy since v3.16, **static header values** + **`anthropic-beta` flags** as of v3.19, and **top-level request-body key order** as of v3.22). Eliminates the "Anthropic ships a new CC, dario is stale for 48 hours" window. Cached at `~/.dario/cc-template.live.json` with a 24h TTL. Falls back to the bundled snapshot if CC isn't installed; the bundled snapshot is scrubbed of host-identifying paths at bake time (v3.21).
|
|
164
164
|
- **Drift detection** (v3.17). On startup dario probes the installed `claude` binary and compares against the captured template. Mismatch triggers a forced refresh and prints a one-line warning. Users never silently sit on a stale template again.
|
|
165
165
|
- **Compat matrix** (v3.17). `SUPPORTED_CC_RANGE = { min: "1.0.0", maxTested: "2.1.104" }` is encoded in code. Installed CC outside that band prints a warn (untested above) or fail (below min) — zero-dep dotted-numeric comparator, no `semver` import per the dep policy.
|
|
166
166
|
- **Billing tag** reconstructed using CC's own algorithm: `x-anthropic-billing-header: cc_version=<version>.<build_tag>; cc_entrypoint=cli; cch=<5-char-hex>;` where `build_tag = SHA-256(seed + chars[4,7,20] of user message + version).slice(0,3)`.
|
|
@@ -272,7 +272,7 @@ Under the hood: `dario shim` spawns the child with `NODE_OPTIONS=--require <dari
|
|
|
272
272
|
|
|
273
273
|
## Agent compatibility
|
|
274
274
|
|
|
275
|
-
As of **v3.
|
|
275
|
+
As of **v3.22**, dario's built-in `TOOL_MAP` carries **~66 schema-verified entries** covering the tool schemas of every major coding agent. On the Claude backend, tool calls translate to CC's native `Bash / Read / Write / Edit / Glob / Grep / WebSearch / WebFetch` on the outbound path (keeping the subscription fingerprint intact) and rebuild to your agent's exact expected shape on the inbound path (so your validator is happy). No flag required.
|
|
276
276
|
|
|
277
277
|
| Agent | Covered tool names (subset) |
|
|
278
278
|
|---|---|
|
|
@@ -515,11 +515,11 @@ Dario handles your OAuth tokens and API keys locally. Here's why you can trust i
|
|
|
515
515
|
|
|
516
516
|
| Signal | Status |
|
|
517
517
|
|---|---|
|
|
518
|
-
| **Source code** | ~
|
|
518
|
+
| **Source code** | ~8,100 lines of TypeScript across ~15 files — small enough to audit in a weekend |
|
|
519
519
|
| **Dependencies** | 0 runtime dependencies. Verify: `npm ls --production` |
|
|
520
520
|
| **npm provenance** | Every release is [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions with sigstore provenance attached to the transparency log |
|
|
521
521
|
| **Security scanning** | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) runs on every push and weekly |
|
|
522
|
-
| **Test footprint** | ~
|
|
522
|
+
| **Test footprint** | ~840 assertions across 24 files. Full `npm test` green on every release |
|
|
523
523
|
| **Credential handling** | Tokens and API keys never logged, redacted from errors, stored with `0600` permissions |
|
|
524
524
|
| **OAuth flow** | PKCE (Proof Key for Code Exchange), no client secret |
|
|
525
525
|
| **Network scope** | Binds to `127.0.0.1` by default. `--host` allows LAN/mesh with `DARIO_API_KEY` gating. Upstream traffic goes only to the configured backend target URLs over HTTPS |
|
|
@@ -568,7 +568,7 @@ Yes — anything that speaks the OpenAI Chat Completions API. Groq, OpenRouter,
|
|
|
568
568
|
Dario auto-detects OAuth config from the installed Claude Code binary. When CC ships a new version with rotated values, dario picks them up on the next run. Cache at `~/.dario/cc-oauth-cache-v4.json`, keyed by the CC binary fingerprint.
|
|
569
569
|
|
|
570
570
|
**What happens when Anthropic changes the CC request template?**
|
|
571
|
-
Dario extracts the live request template from your installed Claude Code binary on startup — the system prompt, tool schemas, user-agent, beta flags,
|
|
571
|
+
Dario extracts the live request template from your installed Claude Code binary on startup — the system prompt, tool schemas, user-agent, beta flags, header insertion order, static header values, and top-level request-body key order — and uses those to replay requests instead of a version pinned into dario itself. When CC ships a new version with a tweaked template, the next `dario proxy` run picks it up automatically. Drift detection (v3.17) forces a refresh when the installed CC version changes under dario, and the nightly `cc-drift-watch` workflow catches upstream rotations (client_id, URLs, tool set, version) the day they ship on npm.
|
|
572
572
|
|
|
573
573
|
**First time setup on a fresh Claude account.**
|
|
574
574
|
If dario is the first thing you run against a brand-new Claude account, prime the account with a few real Claude Code commands first:
|
|
@@ -617,15 +617,16 @@ Longer-form writing on how dario works and why it works that way:
|
|
|
617
617
|
|
|
618
618
|
## Contributing
|
|
619
619
|
|
|
620
|
-
PRs welcome. The codebase is small TypeScript — ~
|
|
620
|
+
PRs welcome. The codebase is small TypeScript — ~8,100 lines across ~15 files:
|
|
621
621
|
|
|
622
622
|
| File | Purpose |
|
|
623
623
|
|---|---|
|
|
624
624
|
| `src/proxy.ts` | HTTP proxy server, request handler, rate governor, Claude backend dispatch, OpenAI-compat routing, pool failover |
|
|
625
|
-
| `src/cc-template.ts` | CC request template engine, universal `TOOL_MAP` (~
|
|
626
|
-
| `src/cc-template-data.json` | Bundled fallback CC request template (used when live-fingerprint extraction isn't possible) |
|
|
625
|
+
| `src/cc-template.ts` | CC request template engine, universal `TOOL_MAP` (~66 schema-verified entries), orchestration and framework scrubbing, header-order + body-field-order replay |
|
|
626
|
+
| `src/cc-template-data.json` | Bundled fallback CC request template (used when live-fingerprint extraction isn't possible). Scrubbed of host-identifying paths at bake time. |
|
|
627
|
+
| `src/scrub-template.ts` | Host-context scrubber for the baked fallback template — strips per-session sections, replaces user-dir paths with a placeholder, drops `mcp__*` tools (v3.21) |
|
|
627
628
|
| `src/cc-oauth-detect.ts` | OAuth config auto-detection from the installed CC binary |
|
|
628
|
-
| `src/live-fingerprint.ts` | Live extraction of the CC request template (system prompt, tools, user-agent, beta flags, header order) from the installed Claude Code binary, drift detection, compat matrix, atomic cache writes, corruption recovery |
|
|
629
|
+
| `src/live-fingerprint.ts` | Live extraction of the CC request template (system prompt, tools, user-agent, beta flags, header order, static header values, body field order) from the installed Claude Code binary, drift detection, compat matrix, atomic cache writes, corruption recovery |
|
|
629
630
|
| `src/doctor.ts` | `dario doctor` health report aggregator — dario/Node/CC/template/drift/OAuth/pool/backends |
|
|
630
631
|
| `src/oauth.ts` | Single-account token storage, PKCE flow, auto-refresh |
|
|
631
632
|
| `src/accounts.ts` | Multi-account credential storage, independent OAuth lifecycle, refresh single-flight |
|
package/dist/cli.js
CHANGED
|
@@ -200,9 +200,33 @@ async function proxy() {
|
|
|
200
200
|
// when Cline/Kilo/Roo is detected can pass --no-auto-detect; they keep
|
|
201
201
|
// explicit control with --preserve-tools per session. dario#40 (ringge).
|
|
202
202
|
const noAutoDetect = args.includes('--no-auto-detect') || args.includes('--no-auto-preserve');
|
|
203
|
+
// --strict-tls refuses to start proxy mode when the process's TLS stack
|
|
204
|
+
// doesn't match Claude Code's (i.e. we're on Node without Bun). Opt-in
|
|
205
|
+
// hard guardrail for operators who want certainty that the JA3 the
|
|
206
|
+
// proxy presents to Anthropic is Bun's BoringSSL ClientHello, not
|
|
207
|
+
// Node's OpenSSL one. v3.23 (direction #3).
|
|
208
|
+
const strictTls = args.includes('--strict-tls');
|
|
203
209
|
const modelArg = args.find(a => a.startsWith('--model='));
|
|
204
210
|
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
205
|
-
|
|
211
|
+
// --pace-min=MS / --pace-jitter=MS (v3.24, direction #6 — behavioral
|
|
212
|
+
// smoothing). Inter-request gap floor + optional uniform-random jitter.
|
|
213
|
+
// Defaults preserve v3.23 behavior (500ms floor, no jitter). The pure
|
|
214
|
+
// calc lives in src/pacing.ts; the flags just feed it.
|
|
215
|
+
const pacingMinMs = parsePositiveIntFlag('--pace-min=');
|
|
216
|
+
const pacingJitterMs = parsePositiveIntFlag('--pace-jitter=');
|
|
217
|
+
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, noAutoDetect, strictTls, pacingMinMs, pacingJitterMs });
|
|
218
|
+
}
|
|
219
|
+
function parsePositiveIntFlag(prefix) {
|
|
220
|
+
const found = args.find(a => a.startsWith(prefix));
|
|
221
|
+
if (!found)
|
|
222
|
+
return undefined;
|
|
223
|
+
const raw = found.slice(prefix.length);
|
|
224
|
+
const n = parseInt(raw, 10);
|
|
225
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
226
|
+
console.error(`[dario] Invalid ${prefix.replace(/=$/, '')} value: ${JSON.stringify(raw)}. Must be a non-negative integer (ms).`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
return n;
|
|
206
230
|
}
|
|
207
231
|
async function accounts() {
|
|
208
232
|
const sub = args[1];
|
|
@@ -446,6 +470,22 @@ async function help() {
|
|
|
446
470
|
intact even when a text-tool client is
|
|
447
471
|
detected; use --preserve-tools per session
|
|
448
472
|
when edits are needed. (dario#40)
|
|
473
|
+
--strict-tls Refuse to start proxy mode if this process
|
|
474
|
+
isn't running under Bun. Bun is what Claude
|
|
475
|
+
Code uses; matching its TLS stack keeps the
|
|
476
|
+
proxy's JA3/JA4 ClientHello indistinguishable
|
|
477
|
+
from a stock CC request. Install Bun
|
|
478
|
+
(https://bun.sh) so dario auto-relaunches
|
|
479
|
+
under it, or use shim mode. (v3.23)
|
|
480
|
+
--pace-min=MS Minimum ms between upstream requests
|
|
481
|
+
(default: 500). Prevents request floods
|
|
482
|
+
that are distinguishable from human-paced
|
|
483
|
+
CC traffic.
|
|
484
|
+
--pace-jitter=MS Max additional uniform-random jitter (ms)
|
|
485
|
+
added on top of --pace-min per request.
|
|
486
|
+
Default: 0 (off). Set to e.g. 300 to hide
|
|
487
|
+
the floor from long-run inter-arrival
|
|
488
|
+
statistics. (v3.24)
|
|
449
489
|
--port=PORT Port to listen on (default: 3456)
|
|
450
490
|
--host=ADDRESS Address to bind to (default: 127.0.0.1)
|
|
451
491
|
Use 0.0.0.0 for LAN; see README for DARIO_API_KEY
|
package/dist/doctor.js
CHANGED
|
@@ -72,6 +72,26 @@ export async function runChecks() {
|
|
|
72
72
|
label: 'Platform',
|
|
73
73
|
detail: `${platform()} ${arch()} (${release()})`,
|
|
74
74
|
});
|
|
75
|
+
// ---- Runtime TLS fingerprint (v3.23, direction #3)
|
|
76
|
+
// Proxy mode terminates TLS in this process, so Bun-vs-Node is a
|
|
77
|
+
// fingerprint axis Anthropic can read directly off the wire.
|
|
78
|
+
try {
|
|
79
|
+
const { detectRuntimeFingerprint } = await import('./runtime-fingerprint.js');
|
|
80
|
+
const rt = detectRuntimeFingerprint();
|
|
81
|
+
const status = rt.status === 'bun-match' ? 'ok' : 'warn';
|
|
82
|
+
checks.push({
|
|
83
|
+
status,
|
|
84
|
+
label: 'Runtime / TLS',
|
|
85
|
+
detail: rt.hint ? `${rt.detail}. ${rt.hint}` : rt.detail,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
checks.push({
|
|
90
|
+
status: 'warn',
|
|
91
|
+
label: 'Runtime / TLS',
|
|
92
|
+
detail: `check failed: ${err.message}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
75
95
|
// ---- CC binary
|
|
76
96
|
const cc = safely(() => findInstalledCC(), { path: null, version: null });
|
|
77
97
|
if (cc.path && cc.version) {
|
package/dist/pacing.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-request pacing (v3.24, direction #6 — behavioral smoothing).
|
|
3
|
+
*
|
|
4
|
+
* Real CC traffic has human-paced gaps between requests — sub-second when
|
|
5
|
+
* the model is streaming tool-loop output, multi-second when the user is
|
|
6
|
+
* typing the next message. A proxy that fires requests at machine speed
|
|
7
|
+
* with perfectly uniform spacing stands out against that rhythm.
|
|
8
|
+
*
|
|
9
|
+
* This module supplies the pure gap-calculation function the proxy's
|
|
10
|
+
* rate governor calls before every outbound fetch. Two knobs:
|
|
11
|
+
*
|
|
12
|
+
* minGapMs — lower bound on the wall-clock distance between requests.
|
|
13
|
+
* Was a hardcoded 500ms through v3.23; keep 500 as default
|
|
14
|
+
* so back-compat is exact when both knobs stay at defaults.
|
|
15
|
+
*
|
|
16
|
+
* jitterMs — uniform random addition on top of minGap. The *effective*
|
|
17
|
+
* gap for a given request is minGap + U(0, jitter). Adds
|
|
18
|
+
* non-uniformity so an observer can't infer the floor from
|
|
19
|
+
* the long-run minimum of inter-arrival times.
|
|
20
|
+
*
|
|
21
|
+
* Pure over (now, lastRequestTime, minGap, jitter, rng) so the tests can
|
|
22
|
+
* exercise every edge without spawning timers. The proxy passes
|
|
23
|
+
* `Math.random` as the rng at runtime; tests pass a deterministic stub.
|
|
24
|
+
*
|
|
25
|
+
* The first request in a session (lastRequestTime === 0) is never paced —
|
|
26
|
+
* the purpose is smoothing the *gap between* requests, not delaying the
|
|
27
|
+
* first one from whenever the consumer happens to connect.
|
|
28
|
+
*/
|
|
29
|
+
export interface PacingConfig {
|
|
30
|
+
/** Minimum wall-clock milliseconds between the completion of one request and the start of the next. */
|
|
31
|
+
minGapMs: number;
|
|
32
|
+
/** Max additional uniform-random jitter (ms) added on top of minGap. Pass 0 to disable. */
|
|
33
|
+
jitterMs: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How many milliseconds to sleep before the next upstream fetch.
|
|
37
|
+
*
|
|
38
|
+
* Returns 0 when no delay is required — either because this is the first
|
|
39
|
+
* request of the session, or enough wall-clock time has already elapsed
|
|
40
|
+
* since `lastRequestTime`.
|
|
41
|
+
*
|
|
42
|
+
* `rng` defaults to Math.random; tests inject a deterministic stub.
|
|
43
|
+
* Negative configuration values are clamped to 0 (lenient, not an error).
|
|
44
|
+
*/
|
|
45
|
+
export declare function computePacingDelay(now: number, lastRequestTime: number, cfg: PacingConfig, rng?: () => number): number;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a PacingConfig from explicit options, env vars, and defaults.
|
|
48
|
+
*
|
|
49
|
+
* Precedence (highest first):
|
|
50
|
+
* 1. Explicit argument (typically from CLI flag)
|
|
51
|
+
* 2. DARIO_PACE_MIN_MS / DARIO_PACE_JITTER_MS env vars
|
|
52
|
+
* 3. Legacy DARIO_MIN_INTERVAL_MS env var (minGap only — matches v3.23
|
|
53
|
+
* behavior so existing setups don't regress silently)
|
|
54
|
+
* 4. Defaults: minGap=500, jitter=0
|
|
55
|
+
*
|
|
56
|
+
* Invalid strings (non-numeric, negative) are ignored and fall through to
|
|
57
|
+
* the next source — a typoed env var shouldn't fail-loud at startup.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolvePacingConfig(explicit?: {
|
|
60
|
+
minGapMs?: number;
|
|
61
|
+
jitterMs?: number;
|
|
62
|
+
}, env?: NodeJS.ProcessEnv): PacingConfig;
|
package/dist/pacing.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-request pacing (v3.24, direction #6 — behavioral smoothing).
|
|
3
|
+
*
|
|
4
|
+
* Real CC traffic has human-paced gaps between requests — sub-second when
|
|
5
|
+
* the model is streaming tool-loop output, multi-second when the user is
|
|
6
|
+
* typing the next message. A proxy that fires requests at machine speed
|
|
7
|
+
* with perfectly uniform spacing stands out against that rhythm.
|
|
8
|
+
*
|
|
9
|
+
* This module supplies the pure gap-calculation function the proxy's
|
|
10
|
+
* rate governor calls before every outbound fetch. Two knobs:
|
|
11
|
+
*
|
|
12
|
+
* minGapMs — lower bound on the wall-clock distance between requests.
|
|
13
|
+
* Was a hardcoded 500ms through v3.23; keep 500 as default
|
|
14
|
+
* so back-compat is exact when both knobs stay at defaults.
|
|
15
|
+
*
|
|
16
|
+
* jitterMs — uniform random addition on top of minGap. The *effective*
|
|
17
|
+
* gap for a given request is minGap + U(0, jitter). Adds
|
|
18
|
+
* non-uniformity so an observer can't infer the floor from
|
|
19
|
+
* the long-run minimum of inter-arrival times.
|
|
20
|
+
*
|
|
21
|
+
* Pure over (now, lastRequestTime, minGap, jitter, rng) so the tests can
|
|
22
|
+
* exercise every edge without spawning timers. The proxy passes
|
|
23
|
+
* `Math.random` as the rng at runtime; tests pass a deterministic stub.
|
|
24
|
+
*
|
|
25
|
+
* The first request in a session (lastRequestTime === 0) is never paced —
|
|
26
|
+
* the purpose is smoothing the *gap between* requests, not delaying the
|
|
27
|
+
* first one from whenever the consumer happens to connect.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* How many milliseconds to sleep before the next upstream fetch.
|
|
31
|
+
*
|
|
32
|
+
* Returns 0 when no delay is required — either because this is the first
|
|
33
|
+
* request of the session, or enough wall-clock time has already elapsed
|
|
34
|
+
* since `lastRequestTime`.
|
|
35
|
+
*
|
|
36
|
+
* `rng` defaults to Math.random; tests inject a deterministic stub.
|
|
37
|
+
* Negative configuration values are clamped to 0 (lenient, not an error).
|
|
38
|
+
*/
|
|
39
|
+
export function computePacingDelay(now, lastRequestTime, cfg, rng = Math.random) {
|
|
40
|
+
if (lastRequestTime <= 0)
|
|
41
|
+
return 0;
|
|
42
|
+
const minGap = Math.max(0, cfg.minGapMs);
|
|
43
|
+
const jitter = Math.max(0, cfg.jitterMs);
|
|
44
|
+
const jitterAdd = jitter > 0 ? Math.floor(rng() * jitter) : 0;
|
|
45
|
+
const effectiveGap = minGap + jitterAdd;
|
|
46
|
+
const elapsed = now - lastRequestTime;
|
|
47
|
+
if (elapsed >= effectiveGap)
|
|
48
|
+
return 0;
|
|
49
|
+
return effectiveGap - elapsed;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a PacingConfig from explicit options, env vars, and defaults.
|
|
53
|
+
*
|
|
54
|
+
* Precedence (highest first):
|
|
55
|
+
* 1. Explicit argument (typically from CLI flag)
|
|
56
|
+
* 2. DARIO_PACE_MIN_MS / DARIO_PACE_JITTER_MS env vars
|
|
57
|
+
* 3. Legacy DARIO_MIN_INTERVAL_MS env var (minGap only — matches v3.23
|
|
58
|
+
* behavior so existing setups don't regress silently)
|
|
59
|
+
* 4. Defaults: minGap=500, jitter=0
|
|
60
|
+
*
|
|
61
|
+
* Invalid strings (non-numeric, negative) are ignored and fall through to
|
|
62
|
+
* the next source — a typoed env var shouldn't fail-loud at startup.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePacingConfig(explicit = {}, env = process.env) {
|
|
65
|
+
const minGap = pickNonNegativeInt(explicit.minGapMs, env.DARIO_PACE_MIN_MS, env.DARIO_MIN_INTERVAL_MS) ?? 500;
|
|
66
|
+
const jitter = pickNonNegativeInt(explicit.jitterMs, env.DARIO_PACE_JITTER_MS) ?? 0;
|
|
67
|
+
return { minGapMs: minGap, jitterMs: jitter };
|
|
68
|
+
}
|
|
69
|
+
function pickNonNegativeInt(...candidates) {
|
|
70
|
+
for (const c of candidates) {
|
|
71
|
+
if (c === undefined || c === null || c === '')
|
|
72
|
+
continue;
|
|
73
|
+
const n = typeof c === 'number' ? c : parseInt(c, 10);
|
|
74
|
+
if (Number.isFinite(n) && n >= 0)
|
|
75
|
+
return Math.floor(n);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ interface ProxyOptions {
|
|
|
12
12
|
preserveTools?: boolean;
|
|
13
13
|
hybridTools?: boolean;
|
|
14
14
|
noAutoDetect?: boolean;
|
|
15
|
+
strictTls?: boolean;
|
|
16
|
+
pacingMinMs?: number;
|
|
17
|
+
pacingJitterMs?: number;
|
|
15
18
|
}
|
|
16
19
|
export declare function sanitizeError(err: unknown): string;
|
|
17
20
|
export declare function startProxy(opts?: ProxyOptions): Promise<void>;
|
package/dist/proxy.js
CHANGED
|
@@ -363,6 +363,22 @@ export async function startProxy(opts = {}) {
|
|
|
363
363
|
const host = opts.host ?? process.env.DARIO_HOST ?? DEFAULT_HOST;
|
|
364
364
|
const verbose = opts.verbose ?? false;
|
|
365
365
|
const passthrough = opts.passthrough ?? false;
|
|
366
|
+
// TLS-fingerprint axis (v3.23, direction #3). Proxy mode terminates TLS
|
|
367
|
+
// to api.anthropic.com from this process; if we're not on Bun, the
|
|
368
|
+
// ClientHello that reaches Anthropic is Node's OpenSSL shape, not CC's
|
|
369
|
+
// Bun/BoringSSL shape. `--strict-tls` turns this silent divergence into
|
|
370
|
+
// a startup refusal. Doctor + the always-on banner below surface the
|
|
371
|
+
// same information without aborting, for users who know they're fine
|
|
372
|
+
// (API-key billing, single-call invocations, shim-mode-elsewhere, etc.).
|
|
373
|
+
const { detectRuntimeFingerprint } = await import('./runtime-fingerprint.js');
|
|
374
|
+
const runtimeFp = detectRuntimeFingerprint();
|
|
375
|
+
if (opts.strictTls && runtimeFp.status !== 'bun-match') {
|
|
376
|
+
console.error(`[dario] --strict-tls: ${runtimeFp.detail}`);
|
|
377
|
+
if (runtimeFp.hint)
|
|
378
|
+
console.error(`[dario] → ${runtimeFp.hint}`);
|
|
379
|
+
console.error('[dario] refusing to start proxy mode. Omit --strict-tls to run anyway.');
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
366
382
|
// Text-tool-protocol client families that have already logged a
|
|
367
383
|
// "detected → auto-enabling preserve-tools" banner this session.
|
|
368
384
|
// Set once on first sighting per family so the startup log stays
|
|
@@ -555,10 +571,19 @@ export async function startProxy(opts = {}) {
|
|
|
555
571
|
betaBase = betaBase ? `${betaBase},oauth-2025-04-20` : 'oauth-2025-04-20';
|
|
556
572
|
}
|
|
557
573
|
const betaWithoutContext1m = betaBase.split(',').filter((t) => t !== 'context-1m-2025-08-07').join(',');
|
|
558
|
-
// Rate governor —
|
|
559
|
-
//
|
|
574
|
+
// Rate governor — floor + optional jitter between requests. A hardcoded
|
|
575
|
+
// 500ms floor keeps the default behavior identical to v3.23; `--pace-min`
|
|
576
|
+
// and `--pace-jitter` let callers tune the distribution. Pure calc lives
|
|
577
|
+
// in src/pacing.ts so the edge cases are unit-tested without timers.
|
|
578
|
+
const { computePacingDelay, resolvePacingConfig } = await import('./pacing.js');
|
|
560
579
|
let lastRequestTime = 0;
|
|
561
|
-
const
|
|
580
|
+
const pacingCfg = resolvePacingConfig({
|
|
581
|
+
minGapMs: opts.pacingMinMs,
|
|
582
|
+
jitterMs: opts.pacingJitterMs,
|
|
583
|
+
});
|
|
584
|
+
if (verbose) {
|
|
585
|
+
console.log(`[dario] pacing: min=${pacingCfg.minGapMs}ms jitter=${pacingCfg.jitterMs}ms`);
|
|
586
|
+
}
|
|
562
587
|
// Optional proxy authentication — pre-encode key buffer for performance
|
|
563
588
|
const apiKey = process.env.DARIO_API_KEY;
|
|
564
589
|
const apiKeyBuf = apiKey ? Buffer.from(apiKey) : null;
|
|
@@ -1060,11 +1085,11 @@ export async function startProxy(opts = {}) {
|
|
|
1060
1085
|
beta = beta.split(',').filter((t) => t.length > 0 && !rejectedSet.has(t)).join(',');
|
|
1061
1086
|
}
|
|
1062
1087
|
}
|
|
1063
|
-
// Rate governor — prevent inhuman request cadence
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
if (
|
|
1067
|
-
await new Promise(r => setTimeout(r,
|
|
1088
|
+
// Rate governor — prevent inhuman request cadence. See src/pacing.ts
|
|
1089
|
+
// for the pure delay calculator (floor + uniform jitter).
|
|
1090
|
+
const pacingDelay = computePacingDelay(Date.now(), lastRequestTime, pacingCfg);
|
|
1091
|
+
if (pacingDelay > 0) {
|
|
1092
|
+
await new Promise(r => setTimeout(r, pacingDelay));
|
|
1068
1093
|
}
|
|
1069
1094
|
lastRequestTime = Date.now();
|
|
1070
1095
|
// Session ID: pool mode uses the per-account identity.sessionId (stable
|
|
@@ -1635,6 +1660,15 @@ export async function startProxy(opts = {}) {
|
|
|
1635
1660
|
if (compat.status === 'below-min' || compat.status === 'untested-above') {
|
|
1636
1661
|
console.log(`[dario] ⚠ CC compat: ${compat.message}`);
|
|
1637
1662
|
}
|
|
1663
|
+
// TLS-fingerprint banner (v3.23). Proxy mode terminates TLS from this
|
|
1664
|
+
// process, so the Bun-vs-Node runtime choice is actually on the wire.
|
|
1665
|
+
// Silence via DARIO_QUIET_TLS=1 for known-fine environments.
|
|
1666
|
+
if (runtimeFp.status !== 'bun-match' && process.env.DARIO_QUIET_TLS !== '1') {
|
|
1667
|
+
console.log(`[dario] ⚠ TLS fingerprint: ${runtimeFp.detail}`);
|
|
1668
|
+
if (runtimeFp.hint)
|
|
1669
|
+
console.log(`[dario] → ${runtimeFp.hint}`);
|
|
1670
|
+
console.log('[dario] (silence with DARIO_QUIET_TLS=1, or use --strict-tls to hard-fail)');
|
|
1671
|
+
}
|
|
1638
1672
|
// Kick off a live fingerprint refresh in the background. Re-captures the
|
|
1639
1673
|
// user's own CC binary request shape and updates ~/.dario/cc-template.live.json
|
|
1640
1674
|
// for the next startup. No-op if CC isn't installed or the cache is fresh.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime TLS-fingerprint detector (direction #3 from the v3.22 roadmap).
|
|
3
|
+
*
|
|
4
|
+
* The Claude Code binary is a Bun-compiled standalone executable, so every
|
|
5
|
+
* HTTPS request it makes goes out through Bun's BoringSSL-derived TLS stack.
|
|
6
|
+
* That ClientHello (JA3/JA4 hash) is what Anthropic's TLS-layer classifier
|
|
7
|
+
* actually sees on the wire.
|
|
8
|
+
*
|
|
9
|
+
* Dario has two transports with different exposure to this axis:
|
|
10
|
+
*
|
|
11
|
+
* - **Shim mode** runs inside CC's own process (NODE_OPTIONS=--require),
|
|
12
|
+
* so its outbound fetch rides on CC's TLS stack by construction.
|
|
13
|
+
* Nothing to reconcile — the shim is always TLS-matched to CC.
|
|
14
|
+
*
|
|
15
|
+
* - **Proxy mode** is a separate process holding its own TLS sessions
|
|
16
|
+
* to api.anthropic.com. Anthropic sees the proxy's TLS fingerprint,
|
|
17
|
+
* not the consumer client's. If the proxy runs under Node, the
|
|
18
|
+
* ClientHello is OpenSSL-shaped — distinct from Bun's BoringSSL shape.
|
|
19
|
+
* That's the JA3 gap this module flags.
|
|
20
|
+
*
|
|
21
|
+
* Mitigation today: dario auto-relaunches under Bun when Bun is on PATH
|
|
22
|
+
* (see top of `src/cli.ts`). When Bun isn't available the auto-relaunch
|
|
23
|
+
* is a silent no-op, so proxy mode silently runs on Node's TLS stack
|
|
24
|
+
* with no indication to the operator. This module makes the runtime
|
|
25
|
+
* status a first-class check: `dario doctor` reports it, proxy startup
|
|
26
|
+
* warns when the axis is mismatched, and `--strict-tls` hard-fails
|
|
27
|
+
* instead of silently running with a divergent fingerprint.
|
|
28
|
+
*
|
|
29
|
+
* Pure-function: every input is passed in explicitly so tests can
|
|
30
|
+
* exercise each runtime combination without spawning processes.
|
|
31
|
+
*/
|
|
32
|
+
/** Canonical buckets the caller pivots on. */
|
|
33
|
+
export type RuntimeFingerprintStatus =
|
|
34
|
+
/** Running under Bun — TLS stack matches CC. */
|
|
35
|
+
'bun-match'
|
|
36
|
+
/** Running under Node, Bun available on PATH but auto-relaunch was bypassed. */
|
|
37
|
+
| 'bun-bypassed'
|
|
38
|
+
/** Running under Node, Bun not installed. */
|
|
39
|
+
| 'node-only';
|
|
40
|
+
export interface RuntimeFingerprint {
|
|
41
|
+
status: RuntimeFingerprintStatus;
|
|
42
|
+
/** 'bun' or 'node' — which runtime this process is actually on. */
|
|
43
|
+
runtime: 'bun' | 'node';
|
|
44
|
+
/** Version string from the runtime (e.g. "1.1.30" or "v20.11.1"). */
|
|
45
|
+
runtimeVersion: string;
|
|
46
|
+
/** Bun version discovered on PATH, if any. undefined when runtime==='bun' or bun-not-found. */
|
|
47
|
+
availableBunVersion?: string;
|
|
48
|
+
/** Why auto-relaunch didn't fire when `status === 'bun-bypassed'`. */
|
|
49
|
+
bypassReason?: 'DARIO_NO_BUN' | 'unknown';
|
|
50
|
+
/** Human-readable one-line explanation for the check label. */
|
|
51
|
+
detail: string;
|
|
52
|
+
/** Actionable hint when status !== 'bun-match'. undefined otherwise. */
|
|
53
|
+
hint?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Probe the Bun binary on PATH without relaunching. Returns undefined
|
|
57
|
+
* when bun isn't installed or the version probe fails for any reason
|
|
58
|
+
* (timeout, non-zero exit, etc.). Kept synchronous to match cli.ts's
|
|
59
|
+
* pre-import flow; doctor.ts is the only other caller and is fine with
|
|
60
|
+
* the (~sub-100ms) cost when Bun is installed.
|
|
61
|
+
*/
|
|
62
|
+
export declare function probeBunVersion(): string | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Synthesize the TLS-fingerprint status from three inputs. All three are
|
|
65
|
+
* passed explicitly so tests can cover every combination without touching
|
|
66
|
+
* the real environment. Production callers pass
|
|
67
|
+
* `classifyRuntimeFingerprint(typeof Bun !== 'undefined', probeBunVersion(), process.env)`.
|
|
68
|
+
*
|
|
69
|
+
* The `env` parameter is read-only — this function never mutates it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function classifyRuntimeFingerprint(runningUnderBun: boolean, availableBunVersion: string | undefined, env: Record<string, string | undefined>, nodeVersion?: string): RuntimeFingerprint;
|
|
72
|
+
/**
|
|
73
|
+
* Convenience wrapper that reads the current process state. doctor.ts
|
|
74
|
+
* calls this once; tests do not — they exercise classifyRuntimeFingerprint
|
|
75
|
+
* directly with synthetic inputs.
|
|
76
|
+
*/
|
|
77
|
+
export declare function detectRuntimeFingerprint(): RuntimeFingerprint;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime TLS-fingerprint detector (direction #3 from the v3.22 roadmap).
|
|
3
|
+
*
|
|
4
|
+
* The Claude Code binary is a Bun-compiled standalone executable, so every
|
|
5
|
+
* HTTPS request it makes goes out through Bun's BoringSSL-derived TLS stack.
|
|
6
|
+
* That ClientHello (JA3/JA4 hash) is what Anthropic's TLS-layer classifier
|
|
7
|
+
* actually sees on the wire.
|
|
8
|
+
*
|
|
9
|
+
* Dario has two transports with different exposure to this axis:
|
|
10
|
+
*
|
|
11
|
+
* - **Shim mode** runs inside CC's own process (NODE_OPTIONS=--require),
|
|
12
|
+
* so its outbound fetch rides on CC's TLS stack by construction.
|
|
13
|
+
* Nothing to reconcile — the shim is always TLS-matched to CC.
|
|
14
|
+
*
|
|
15
|
+
* - **Proxy mode** is a separate process holding its own TLS sessions
|
|
16
|
+
* to api.anthropic.com. Anthropic sees the proxy's TLS fingerprint,
|
|
17
|
+
* not the consumer client's. If the proxy runs under Node, the
|
|
18
|
+
* ClientHello is OpenSSL-shaped — distinct from Bun's BoringSSL shape.
|
|
19
|
+
* That's the JA3 gap this module flags.
|
|
20
|
+
*
|
|
21
|
+
* Mitigation today: dario auto-relaunches under Bun when Bun is on PATH
|
|
22
|
+
* (see top of `src/cli.ts`). When Bun isn't available the auto-relaunch
|
|
23
|
+
* is a silent no-op, so proxy mode silently runs on Node's TLS stack
|
|
24
|
+
* with no indication to the operator. This module makes the runtime
|
|
25
|
+
* status a first-class check: `dario doctor` reports it, proxy startup
|
|
26
|
+
* warns when the axis is mismatched, and `--strict-tls` hard-fails
|
|
27
|
+
* instead of silently running with a divergent fingerprint.
|
|
28
|
+
*
|
|
29
|
+
* Pure-function: every input is passed in explicitly so tests can
|
|
30
|
+
* exercise each runtime combination without spawning processes.
|
|
31
|
+
*/
|
|
32
|
+
import { execFileSync } from 'node:child_process';
|
|
33
|
+
/**
|
|
34
|
+
* Probe the Bun binary on PATH without relaunching. Returns undefined
|
|
35
|
+
* when bun isn't installed or the version probe fails for any reason
|
|
36
|
+
* (timeout, non-zero exit, etc.). Kept synchronous to match cli.ts's
|
|
37
|
+
* pre-import flow; doctor.ts is the only other caller and is fine with
|
|
38
|
+
* the (~sub-100ms) cost when Bun is installed.
|
|
39
|
+
*/
|
|
40
|
+
export function probeBunVersion() {
|
|
41
|
+
try {
|
|
42
|
+
const out = execFileSync('bun', ['--version'], {
|
|
43
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
44
|
+
timeout: 3000,
|
|
45
|
+
encoding: 'utf-8',
|
|
46
|
+
});
|
|
47
|
+
const trimmed = out.trim();
|
|
48
|
+
// `bun --version` prints just the version like "1.1.30". Reject anything
|
|
49
|
+
// longer than a sanity threshold so an unrelated `bun` binary can't
|
|
50
|
+
// poison the detection.
|
|
51
|
+
if (trimmed.length > 0 && trimmed.length < 32 && /^[0-9]/.test(trimmed)) {
|
|
52
|
+
return trimmed;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Synthesize the TLS-fingerprint status from three inputs. All three are
|
|
62
|
+
* passed explicitly so tests can cover every combination without touching
|
|
63
|
+
* the real environment. Production callers pass
|
|
64
|
+
* `classifyRuntimeFingerprint(typeof Bun !== 'undefined', probeBunVersion(), process.env)`.
|
|
65
|
+
*
|
|
66
|
+
* The `env` parameter is read-only — this function never mutates it.
|
|
67
|
+
*/
|
|
68
|
+
export function classifyRuntimeFingerprint(runningUnderBun, availableBunVersion, env, nodeVersion = process.version) {
|
|
69
|
+
if (runningUnderBun) {
|
|
70
|
+
// When we're under Bun, we expose the Bun version if globalThis.Bun.version
|
|
71
|
+
// is readable; we don't require a separate probe. The caller passes the
|
|
72
|
+
// resolved version string as `availableBunVersion` in the bun case.
|
|
73
|
+
const bunVer = availableBunVersion ?? 'unknown';
|
|
74
|
+
return {
|
|
75
|
+
status: 'bun-match',
|
|
76
|
+
runtime: 'bun',
|
|
77
|
+
runtimeVersion: bunVer,
|
|
78
|
+
detail: `Bun v${bunVer} — TLS fingerprint matches Claude Code`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (availableBunVersion !== undefined) {
|
|
82
|
+
const reason = env.DARIO_NO_BUN ? 'DARIO_NO_BUN' : 'unknown';
|
|
83
|
+
return {
|
|
84
|
+
status: 'bun-bypassed',
|
|
85
|
+
runtime: 'node',
|
|
86
|
+
runtimeVersion: nodeVersion,
|
|
87
|
+
availableBunVersion,
|
|
88
|
+
bypassReason: reason,
|
|
89
|
+
detail: `Node ${nodeVersion} — Bun v${availableBunVersion} on PATH but auto-relaunch bypassed (${reason})`,
|
|
90
|
+
hint: reason === 'DARIO_NO_BUN'
|
|
91
|
+
? 'Unset DARIO_NO_BUN to auto-relaunch under Bun on the next invocation.'
|
|
92
|
+
: 'Run dario fresh (no inherited DARIO_NO_BUN) so auto-relaunch can fire.',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
status: 'node-only',
|
|
97
|
+
runtime: 'node',
|
|
98
|
+
runtimeVersion: nodeVersion,
|
|
99
|
+
detail: `Node ${nodeVersion} — Bun not installed; proxy-mode TLS fingerprint diverges from Claude Code`,
|
|
100
|
+
hint: 'Install Bun (https://bun.sh) so dario can auto-relaunch under it, or use shim mode ' +
|
|
101
|
+
'(`dario shim -- claude …`) which runs inside CC\'s own process and inherits its TLS stack.',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Convenience wrapper that reads the current process state. doctor.ts
|
|
106
|
+
* calls this once; tests do not — they exercise classifyRuntimeFingerprint
|
|
107
|
+
* directly with synthetic inputs.
|
|
108
|
+
*/
|
|
109
|
+
export function detectRuntimeFingerprint() {
|
|
110
|
+
const bunGlobal = globalThis.Bun;
|
|
111
|
+
const runningUnderBun = typeof bunGlobal?.version === 'string';
|
|
112
|
+
if (runningUnderBun) {
|
|
113
|
+
return classifyRuntimeFingerprint(true, bunGlobal?.version, process.env);
|
|
114
|
+
}
|
|
115
|
+
const probed = probeBunVersion();
|
|
116
|
+
return classifyRuntimeFingerprint(false, probed, process.env);
|
|
117
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
4
4
|
"description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && cp src/cc-template-data.json dist/ && node -e \"require('fs').mkdirSync('dist/shim',{recursive:true})\" && cp src/shim/runtime.cjs dist/shim/",
|
|
24
|
-
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs",
|
|
24
|
+
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs",
|
|
25
25
|
"audit": "npm audit --production --audit-level=high",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"start": "node dist/cli.js",
|