@askalf/dario 2.8.3 → 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.
Files changed (2) hide show
  1. package/dist/proxy.js +48 -7
  2. 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
- return out.match(/^([\d.]+)/)?.[1] ?? '2.1.96';
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.96';
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
- const billingTag = `x-anthropic-billing-header: cc_version=${cliVersion}; cc_entrypoint=cli; cch=98638;`;
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "2.8.3",
3
+ "version": "2.8.5",
4
4
  "description": "Use your Claude subscription as an API. No API key needed. Local proxy for Claude Max/Pro subscriptions.",
5
5
  "type": "module",
6
6
  "bin": {