@askalf/dario 5.4.17 → 5.4.19
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/dist/cc-template.d.ts +11 -0
- package/dist/cc-template.js +68 -0
- package/dist/live-fingerprint.d.ts +0 -6
- package/dist/live-fingerprint.js +0 -8
- package/dist/proxy.js +26 -3
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -4
- package/docs/wire-fidelity.md +3 -1
- package/package.json +2 -2
package/dist/cc-template.d.ts
CHANGED
|
@@ -170,6 +170,17 @@ export declare const CC_AGENT_IDENTITY: string;
|
|
|
170
170
|
*/
|
|
171
171
|
export declare const CLIENT_SYSTEM_PREFACE: string;
|
|
172
172
|
export declare function resolveSystemPrompt(arg: string | undefined, model?: string): string;
|
|
173
|
+
/**
|
|
174
|
+
* Pick the CC-identity headers out of an inbound request so the passthrough
|
|
175
|
+
* path can forward them unchanged.
|
|
176
|
+
*
|
|
177
|
+
* Pure and exported so the allow/deny behaviour is unit-testable without
|
|
178
|
+
* standing up the proxy, matching `orderHeadersForOutbound` and
|
|
179
|
+
* `overlayTemplateHeaderValues`. Array-valued headers (Node allows repeats)
|
|
180
|
+
* take the first value; empty strings are skipped so a client sending a blank
|
|
181
|
+
* header cannot blank out a value dario would otherwise supply.
|
|
182
|
+
*/
|
|
183
|
+
export declare function forwardClientCCIdentityHeaders(reqHeaders: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
173
184
|
/**
|
|
174
185
|
* Overlay a captured template's `header_values` onto the outbound header record,
|
|
175
186
|
* skipping the keys that must never be replayed.
|
package/dist/cc-template.js
CHANGED
|
@@ -283,6 +283,74 @@ function stripBehavioralConstraints(input, level) {
|
|
|
283
283
|
* so new captures are clean. This skip list is what makes every ALREADY-baked
|
|
284
284
|
* template and warm cache self-heal without waiting for a re-bake.
|
|
285
285
|
*/
|
|
286
|
+
/**
|
|
287
|
+
* Headers a genuine Claude Code client sends to identify itself, which the
|
|
288
|
+
* passthrough path forwards verbatim instead of substituting template values.
|
|
289
|
+
*
|
|
290
|
+
* The template exists to SYNTHESISE CC's shape for clients that are not CC. On
|
|
291
|
+
* the passthrough path `isGenuineCCClient` has already established that the
|
|
292
|
+
* caller really is Claude Code, so its own headers are the authentic article and
|
|
293
|
+
* a capture is at best a good imitation of them. Measured before this changed:
|
|
294
|
+
* of 13 CC-identity headers a real client sent, 12 were replaced with template
|
|
295
|
+
* values and 1 was dropped — 0 forwarded (dario#885).
|
|
296
|
+
*
|
|
297
|
+
* Deliberately NOT in this list, because dario must own them:
|
|
298
|
+
*
|
|
299
|
+
* authorization / x-api-key must become the pool account's credential
|
|
300
|
+
* x-claude-code-session-id session rotation is a feature, not an accident
|
|
301
|
+
* anthropic-beta merged with operator pins + the per-account
|
|
302
|
+
* rejection cache; the client's set is not final
|
|
303
|
+
* anthropic-version already read from the client at the call site
|
|
304
|
+
* accept / content-type body framing, owned by the proxy
|
|
305
|
+
* host / connection / transport, owned by the HTTP stack; forwarding
|
|
306
|
+
* content-length / them corrupts the request
|
|
307
|
+
* transfer-encoding /
|
|
308
|
+
* accept-encoding / keep-alive
|
|
309
|
+
*
|
|
310
|
+
* Anything a future CC adds under the `x-stainless-*` or `x-claude-code-*`
|
|
311
|
+
* prefixes is forwarded by the prefix rules in
|
|
312
|
+
* `forwardClientCCIdentityHeaders` rather than needing to be enumerated here —
|
|
313
|
+
* the capture cannot see first-party-conditional headers at all (dario#885), so
|
|
314
|
+
* an allowlist of exact names would silently miss them.
|
|
315
|
+
*/
|
|
316
|
+
const CC_IDENTITY_HEADERS_TO_FORWARD = new Set([
|
|
317
|
+
'user-agent',
|
|
318
|
+
'x-app',
|
|
319
|
+
'anthropic-dangerous-direct-browser-access',
|
|
320
|
+
]);
|
|
321
|
+
/** Prefixes whose every member is CC self-identification, forwarded wholesale. */
|
|
322
|
+
const CC_IDENTITY_HEADER_PREFIXES = ['x-stainless-', 'x-claude-code-', 'x-client-'];
|
|
323
|
+
/** Never forwarded from the client even when it matches a prefix above. */
|
|
324
|
+
const NEVER_FORWARD_FROM_CLIENT = new Set([
|
|
325
|
+
'x-claude-code-session-id', // dario rotates sessions deliberately
|
|
326
|
+
]);
|
|
327
|
+
/**
|
|
328
|
+
* Pick the CC-identity headers out of an inbound request so the passthrough
|
|
329
|
+
* path can forward them unchanged.
|
|
330
|
+
*
|
|
331
|
+
* Pure and exported so the allow/deny behaviour is unit-testable without
|
|
332
|
+
* standing up the proxy, matching `orderHeadersForOutbound` and
|
|
333
|
+
* `overlayTemplateHeaderValues`. Array-valued headers (Node allows repeats)
|
|
334
|
+
* take the first value; empty strings are skipped so a client sending a blank
|
|
335
|
+
* header cannot blank out a value dario would otherwise supply.
|
|
336
|
+
*/
|
|
337
|
+
export function forwardClientCCIdentityHeaders(reqHeaders) {
|
|
338
|
+
const out = {};
|
|
339
|
+
for (const [rawName, rawValue] of Object.entries(reqHeaders)) {
|
|
340
|
+
const name = rawName.toLowerCase();
|
|
341
|
+
if (NEVER_FORWARD_FROM_CLIENT.has(name))
|
|
342
|
+
continue;
|
|
343
|
+
const allowed = CC_IDENTITY_HEADERS_TO_FORWARD.has(name)
|
|
344
|
+
|| CC_IDENTITY_HEADER_PREFIXES.some((p) => name.startsWith(p));
|
|
345
|
+
if (!allowed)
|
|
346
|
+
continue;
|
|
347
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
348
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
349
|
+
continue;
|
|
350
|
+
out[name] = value;
|
|
351
|
+
}
|
|
352
|
+
return out;
|
|
353
|
+
}
|
|
286
354
|
const NEVER_REPLAY_HEADER_VALUES = new Set([
|
|
287
355
|
'x-api-key',
|
|
288
356
|
'x-stainless-os',
|
|
@@ -336,12 +336,6 @@ export interface DriftResult {
|
|
|
336
336
|
* callers pass nothing and the real binary probe runs.
|
|
337
337
|
*/
|
|
338
338
|
export declare function detectDrift(t: TemplateData, installedOverride?: string | null): DriftResult;
|
|
339
|
-
/**
|
|
340
|
-
* Reset the memoized `claude --version` probe. Test-only — production
|
|
341
|
-
* code should never need to clear the cache since the installed binary
|
|
342
|
-
* doesn't change mid-process.
|
|
343
|
-
*/
|
|
344
|
-
export declare function _resetInstalledVersionProbeForTest(): void;
|
|
345
339
|
/**
|
|
346
340
|
* The CC version range the current dario release has been exercised
|
|
347
341
|
* against. Update `maxTested` every time we validate against a new CC
|
package/dist/live-fingerprint.js
CHANGED
|
@@ -919,14 +919,6 @@ export function detectDrift(t, installedOverride) {
|
|
|
919
919
|
message: `cache is from CC v${cachedVersion} but installed CC is v${installed} — background refresh will re-capture`,
|
|
920
920
|
};
|
|
921
921
|
}
|
|
922
|
-
/**
|
|
923
|
-
* Reset the memoized `claude --version` probe. Test-only — production
|
|
924
|
-
* code should never need to clear the cache since the installed binary
|
|
925
|
-
* doesn't change mid-process.
|
|
926
|
-
*/
|
|
927
|
-
export function _resetInstalledVersionProbeForTest() {
|
|
928
|
-
_installedVersionProbe = { value: null, cached: false };
|
|
929
|
-
}
|
|
930
922
|
// ============================================================
|
|
931
923
|
// CC version compat matrix (v3.17)
|
|
932
924
|
// ============================================================
|
package/dist/proxy.js
CHANGED
|
@@ -9,7 +9,7 @@ import { arch, platform } from 'node:process';
|
|
|
9
9
|
import { getAccessToken, getStatus } from './oauth.js';
|
|
10
10
|
import { buildHealthResponse, derivePoolStatus, shouldDiscloseHealthInternals } from './health-response.js';
|
|
11
11
|
import { darioVersion } from './version.js';
|
|
12
|
-
import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
12
|
+
import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
13
13
|
import { stampCch, hasCchSeed } from './cch.js';
|
|
14
14
|
import { describeTemplate, detectDrift, checkCCCompat } from './live-fingerprint.js';
|
|
15
15
|
import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, reconcilePoolAccounts, resolvePoolStrategy } from './pool.js';
|
|
@@ -2064,6 +2064,12 @@ export async function startProxy(opts = {}) {
|
|
|
2064
2064
|
let requestModel = '';
|
|
2065
2065
|
let detectedClientForLog;
|
|
2066
2066
|
let preserveToolsEffective = Boolean(opts.preserveTools);
|
|
2067
|
+
// Per-request: did isGenuineCCClient recognise the caller as real Claude
|
|
2068
|
+
// Code? Hoisted because the header build below needs it and `genuineCC` is
|
|
2069
|
+
// block-scoped where buildCCRequest destructures it. NOT the same thing as
|
|
2070
|
+
// `passthrough`, which is a startup CLI flag — conflating the two is why
|
|
2071
|
+
// the first version of this forwarded nothing (dario#885).
|
|
2072
|
+
let genuineCCRequest = false;
|
|
2067
2073
|
try {
|
|
2068
2074
|
// Select an account by headroom (v5.0: the pool is the one credential
|
|
2069
2075
|
// model, so every OAuth request selects from it — a plain `dario login`
|
|
@@ -2442,6 +2448,7 @@ export async function startProxy(opts = {}) {
|
|
|
2442
2448
|
preserveToolsEffective = Boolean(opts.preserveTools)
|
|
2443
2449
|
|| Boolean(genuineCC)
|
|
2444
2450
|
|| (Boolean(detectedClient) && !opts.hybridTools && !opts.mergeTools);
|
|
2451
|
+
genuineCCRequest = Boolean(genuineCC);
|
|
2445
2452
|
if (genuineCC && !ccPassthroughLogged) {
|
|
2446
2453
|
ccPassthroughLogged = true;
|
|
2447
2454
|
console.log('[dario] genuine Claude Code client — system + tools forwarded verbatim (byte-faithful passthrough)');
|
|
@@ -2688,15 +2695,31 @@ export async function startProxy(opts = {}) {
|
|
|
2688
2695
|
console.log(`[dario] #${requestCount} session: rotate (${assigned.reason})${poolAccount ? ` [${poolAccount.alias}]` : ''}`);
|
|
2689
2696
|
}
|
|
2690
2697
|
}
|
|
2698
|
+
// On the passthrough path `isGenuineCCClient` has already established the
|
|
2699
|
+
// caller IS Claude Code, so its own identity headers are the authentic
|
|
2700
|
+
// article — forward them instead of substituting template values. The
|
|
2701
|
+
// template is for synthesising CC's shape when the client is not CC; here it
|
|
2702
|
+
// would only be an imitation of headers we already hold. Measured before
|
|
2703
|
+
// this: 12 of 13 client CC-identity headers were replaced and 1 dropped,
|
|
2704
|
+
// 0 forwarded (dario#885). Spread AFTER staticHeaders so the client wins
|
|
2705
|
+
// over template values, and BEFORE the dario-owned block below so auth,
|
|
2706
|
+
// session rotation and the merged beta set still win over the client.
|
|
2707
|
+
const forwardedIdentity = (passthrough || genuineCCRequest)
|
|
2708
|
+
? forwardClientCCIdentityHeaders(req.headers)
|
|
2709
|
+
: {};
|
|
2691
2710
|
const headers = {
|
|
2692
2711
|
...staticHeaders,
|
|
2712
|
+
...forwardedIdentity,
|
|
2693
2713
|
...upstreamAuthHeaders(upstreamApiKey, accessToken),
|
|
2694
2714
|
'x-claude-code-session-id': outboundSessionId,
|
|
2695
2715
|
'anthropic-version': passthrough ? (req.headers['anthropic-version'] || '2023-06-01') : '2023-06-01',
|
|
2696
2716
|
'anthropic-beta': beta,
|
|
2697
|
-
|
|
2717
|
+
// Prefer the client's own when passthrough forwarded one: a genuine CC
|
|
2718
|
+
// request already carries a real request id, and synthesising over it
|
|
2719
|
+
// discards information for no gain. Falls back to a fresh uuid otherwise.
|
|
2720
|
+
'x-client-request-id': forwardedIdentity['x-client-request-id'] ?? randomUUID(),
|
|
2698
2721
|
// CC sends 600 on first request per session. With rotation, every request is "first"
|
|
2699
|
-
'x-stainless-timeout': '600',
|
|
2722
|
+
'x-stainless-timeout': forwardedIdentity['x-stainless-timeout'] ?? '600',
|
|
2700
2723
|
};
|
|
2701
2724
|
// Client-disconnect abort: if the client drops the connection before
|
|
2702
2725
|
// we've finished sending the response, we default to aborting the
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/docs/wire-fidelity.md
CHANGED
|
@@ -11,4 +11,6 @@ Between v3.22 and v3.28, dario's Claude backend closed six axes along which a pr
|
|
|
11
11
|
| **Session-ID lifecycle** | v3.28 | Generalizes the v3.19 hardcoded 15-minute idle rotation into a tunable `SessionRegistry` with jitter, max-age, and per-client bucketing. Fixes a v3.27 body/header rotation race as a side effect. | `--session-idle-rotate=MS` (default 900000), `--session-rotate-jitter=MS`, `--session-max-age=MS`, `--session-per-client`. Env mirrors `DARIO_SESSION_*`. Defaults are bit-identical to v3.27. |
|
|
12
12
|
| **MCP / sub-agent reach** | v3.26 + v3.27 | Not a wire axis — a *surface* axis. CC-aware tools can now address dario directly (sub-agent from inside CC, MCP server for any MCP client), so operators don't have to switch terminals to introspect the proxy. Read-only by design. | `dario subagent install` / `dario mcp`. See [`mcp-server.md`](./mcp-server.md) and [`sub-agent.md`](./sub-agent.md). |
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
| **Client identity headers** | v5.4.19 | On the passthrough path `isGenuineCCClient` has already established the caller *is* Claude Code, so its own identity headers (`user-agent`, `x-app`, `x-stainless-*`, `x-claude-code-*`, `x-client-*`) are forwarded unchanged instead of being replaced with template values. The template exists to synthesise CC's shape for clients that are not CC; where the genuine article is in hand, forwarding beats imitating. Measured before the change: of 13 identity headers a real client sent, 12 were replaced and 1 dropped — 0 forwarded (#885). Auth, session id and the merged beta set stay dario's. | Automatic on the genuine-CC path; nothing to tune. Non-CC clients are unaffected, and the gate reads the request **body**, so headers cannot self-authorise. |
|
|
15
|
+
|
|
16
|
+
The original six-direction roadmap is complete; the axes below it are later findings. Note that header **order** is deliberately absent as an axis: `orderHeadersForOutbound` builds the captured sequence, but `fetch()` re-normalises it before the wire, and on Bun you cannot have both CC's JA3 and raw header control in one process (#813). The honest framing of the whole table is byte-identical **body**, structurally close **headers** — not packet-identical.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.19",
|
|
4
4
|
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc && cp src/cc-template-data.json dist/",
|
|
26
26
|
"test": "node --test --test-concurrency=8 test/all.test.mjs",
|
|
27
|
-
"test:serial": "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/live-fingerprint.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/stream-drain.mjs && node test/subagent.mjs && node test/mcp-protocol.mjs && node test/mcp-tools.mjs && node test/mcp-e2e.mjs && node test/session-rotation.mjs && node test/drift-detection.mjs && node test/cc-authorize-probe-classifier.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/doctor-identity-drift.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/durable-token-persist.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs && node test/context-bleed.mjs && node test/capture-provenance.mjs && node test/sanitize-messages.mjs && node test/platform-tools.mjs && node test/strict-template-flags.mjs && node test/request-queue.mjs && node test/effort-flag.mjs && node test/template-invariants.mjs",
|
|
27
|
+
"test:serial": "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/live-fingerprint.mjs && node test/proxy-header-order.mjs && node test/proxy-body-order.mjs && node test/runtime-fingerprint.mjs && node test/pacing.mjs && node test/stream-drain.mjs && node test/subagent.mjs && node test/mcp-protocol.mjs && node test/mcp-tools.mjs && node test/mcp-e2e.mjs && node test/session-rotation.mjs && node test/drift-detection.mjs && node test/cc-authorize-probe-classifier.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/doctor-identity-drift.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/durable-token-persist.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs && node test/scrub-template.mjs && node test/context-bleed.mjs && node test/passthrough-header-forwarding.mjs && node test/capture-provenance.mjs && node test/sanitize-messages.mjs && node test/platform-tools.mjs && node test/strict-template-flags.mjs && node test/request-queue.mjs && node test/effort-flag.mjs && node test/template-invariants.mjs",
|
|
28
28
|
"audit": "npm audit --production --audit-level=high",
|
|
29
29
|
"prepublishOnly": "npm run build",
|
|
30
30
|
"start": "node dist/cli.js",
|