@askalf/dario 2.8.2 → 2.8.5
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/proxy.js +48 -7
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import { randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { randomUUID, timingSafeEqual, createHash } from 'node:crypto';
|
|
3
3
|
import { execSync, spawn } from 'node:child_process';
|
|
4
4
|
import { readFileSync, readdirSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
@@ -35,17 +35,33 @@ class Semaphore {
|
|
|
35
35
|
next();
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
// Billing tag hash seed — extracted from Claude Code binary (constant XGA)
|
|
39
|
+
const BILLING_SEED = '59cf53e54c78';
|
|
40
|
+
// Compute per-request build tag matching Claude Code's Oz$ algorithm:
|
|
41
|
+
// SHA-256(seed + chars[4,7,20] of user message + version).slice(0,3)
|
|
42
|
+
function computeBuildTag(userMessage, version) {
|
|
43
|
+
const chars = [4, 7, 20].map(i => userMessage[i] || '0').join('');
|
|
44
|
+
return createHash('sha256').update(`${BILLING_SEED}${chars}${version}`).digest('hex').slice(0, 3);
|
|
45
|
+
}
|
|
46
|
+
// Compute per-request cch checksum matching Claude Code's algorithm:
|
|
47
|
+
// SHA-256(seed + chars[4,7,20] of user message + version).slice(0,5)
|
|
48
|
+
// Real Claude Code uses a similar but separate computation that produces 5 hex chars
|
|
49
|
+
function computeCch(userMessage, version) {
|
|
50
|
+
const chars = [4, 7, 20].map(i => userMessage[i] || '0').join('');
|
|
51
|
+
return createHash('sha256').update(`${BILLING_SEED}${version}${chars}`).digest('hex').slice(0, 5);
|
|
52
|
+
}
|
|
38
53
|
// Detect installed Claude Code binary at startup (single exec for both version + availability)
|
|
39
54
|
let cliAvailable = false;
|
|
40
55
|
function detectCli() {
|
|
41
56
|
try {
|
|
42
57
|
const out = execSync('claude --version', { timeout: 5000, stdio: 'pipe' }).toString().trim();
|
|
43
58
|
cliAvailable = true;
|
|
44
|
-
|
|
59
|
+
// Capture major version (e.g., 2.1.100) — build tag is computed per-request
|
|
60
|
+
return out.match(/^([\d]+\.[\d]+\.[\d]+)/)?.[1] ?? '2.1.100';
|
|
45
61
|
}
|
|
46
62
|
catch {
|
|
47
63
|
cliAvailable = false;
|
|
48
|
-
return '2.1.
|
|
64
|
+
return '2.1.100';
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
/** Convert a non-streaming Messages API response to SSE event stream. */
|
|
@@ -78,6 +94,22 @@ function jsonToSse(jsonBody) {
|
|
|
78
94
|
return '';
|
|
79
95
|
}
|
|
80
96
|
}
|
|
97
|
+
/** Extract first user message text from a request body for billing tag computation. */
|
|
98
|
+
function extractFirstUserMessage(body) {
|
|
99
|
+
const messages = body.messages;
|
|
100
|
+
if (!messages)
|
|
101
|
+
return '';
|
|
102
|
+
const userMsg = messages.find(m => m.role === 'user');
|
|
103
|
+
if (!userMsg)
|
|
104
|
+
return '';
|
|
105
|
+
if (typeof userMsg.content === 'string')
|
|
106
|
+
return userMsg.content;
|
|
107
|
+
if (Array.isArray(userMsg.content)) {
|
|
108
|
+
const textBlock = userMsg.content.find(b => b.type === 'text');
|
|
109
|
+
return textBlock?.text ?? '';
|
|
110
|
+
}
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
81
113
|
/** Convert CLI JSON response to OpenAI SSE format. */
|
|
82
114
|
function jsonToOpenaiSse(jsonBody) {
|
|
83
115
|
try {
|
|
@@ -463,7 +495,7 @@ export async function startProxy(opts = {}) {
|
|
|
463
495
|
console.warn('[dario] WARNING: No Claude Code device identity found. Requests may be billed as Extra Usage.');
|
|
464
496
|
console.warn('[dario] Run Claude Code at least once to generate ~/.claude/.claude.json');
|
|
465
497
|
}
|
|
466
|
-
// Pre-build static headers
|
|
498
|
+
// Pre-build static headers (matches real Claude Code captured via MITM)
|
|
467
499
|
const staticHeaders = passthrough ? {
|
|
468
500
|
'accept': 'application/json',
|
|
469
501
|
'Content-Type': 'application/json',
|
|
@@ -471,7 +503,6 @@ export async function startProxy(opts = {}) {
|
|
|
471
503
|
'accept': 'application/json',
|
|
472
504
|
'Content-Type': 'application/json',
|
|
473
505
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
474
|
-
'anthropic-client-platform': 'cli',
|
|
475
506
|
'user-agent': `claude-cli/${cliVersion} (external, cli)`,
|
|
476
507
|
'x-app': 'cli',
|
|
477
508
|
'x-claude-code-session-id': SESSION_ID,
|
|
@@ -482,7 +513,6 @@ export async function startProxy(opts = {}) {
|
|
|
482
513
|
'x-stainless-retry-count': '0',
|
|
483
514
|
'x-stainless-runtime': 'node',
|
|
484
515
|
'x-stainless-runtime-version': nodeVersion,
|
|
485
|
-
'x-stainless-timeout': '600',
|
|
486
516
|
};
|
|
487
517
|
const useCli = opts.cliBackend ?? false;
|
|
488
518
|
let requestCount = 0;
|
|
@@ -679,7 +709,16 @@ export async function startProxy(opts = {}) {
|
|
|
679
709
|
// instead of the general API quota. Without it, Opus/Sonnet get 429
|
|
680
710
|
// when overall utilization is high, even though model-specific limits
|
|
681
711
|
// have headroom. The CLI binary embeds this in its system prompt.
|
|
682
|
-
|
|
712
|
+
//
|
|
713
|
+
// Build tag and cch are computed per-request using the same algorithm
|
|
714
|
+
// as the real Claude Code binary (Oz$ function):
|
|
715
|
+
// - build tag = SHA-256(seed + msg_chars[4,7,20] + version).slice(0,3)
|
|
716
|
+
// - cch = SHA-256(seed + version + msg_chars[4,7,20]).slice(0,5)
|
|
717
|
+
const userMsg = extractFirstUserMessage(r);
|
|
718
|
+
const buildTag = computeBuildTag(userMsg, cliVersion);
|
|
719
|
+
const cch = computeCch(userMsg, cliVersion);
|
|
720
|
+
const fullVersion = `${cliVersion}.${buildTag}`;
|
|
721
|
+
const billingTag = `x-anthropic-billing-header: cc_version=${fullVersion}; cc_entrypoint=cli; cch=${cch};`;
|
|
683
722
|
if (typeof r.system === 'string') {
|
|
684
723
|
if (!r.system.includes('x-anthropic-billing-header:')) {
|
|
685
724
|
r.system = billingTag + '\n' + r.system;
|
|
@@ -727,6 +766,8 @@ export async function startProxy(opts = {}) {
|
|
|
727
766
|
'anthropic-version': req.headers['anthropic-version'] || '2023-06-01',
|
|
728
767
|
'anthropic-beta': beta,
|
|
729
768
|
'x-client-request-id': randomUUID(),
|
|
769
|
+
// Real Claude Code sends 600 on first request, 300 on subsequent
|
|
770
|
+
'x-stainless-timeout': requestCount <= 1 ? '600' : '300',
|
|
730
771
|
};
|
|
731
772
|
const upstream = await fetch(targetBase, {
|
|
732
773
|
method: req.method ?? 'POST',
|