@askalf/dario 2.1.1 → 2.1.2

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 CHANGED
@@ -32,13 +32,13 @@ export ANTHROPIC_BASE_URL=http://localhost:3456 # or OPENAI_BASE_URL=http://lo
32
32
  export ANTHROPIC_API_KEY=dario # or OPENAI_API_KEY=dario
33
33
  ```
34
34
 
35
- Opus, Sonnet, Haiku — all models, streaming, tool use. Works with OpenClaw, Cursor, Continue, Aider, Hermes, or any tool that speaks the Anthropic or OpenAI API. When rate limited, `--cli` routes through Claude Code for uninterrupted Opus access.
35
+ Opus, Sonnet, Haiku — all models, streaming, tool use. Works with Cursor, Continue, Aider, LiteLLM, Hermes, OpenClaw, or any tool that speaks the Anthropic or OpenAI API. When rate limited, `--cli` routes through Claude Code for uninterrupted Opus access.
36
36
 
37
37
  ---
38
38
 
39
39
  ## The Problem
40
40
 
41
- You pay $100-200/mo for Claude Max or Pro. But that subscription only works on claude.ai and Claude Code. If you want to use Claude with **any other tool** — OpenClaw, Cursor, Continue, Aider, your own scripts — you need a separate API key with separate billing.
41
+ You pay $100-200/mo for Claude Max or Pro. But that subscription only works on claude.ai and Claude Code. If you want to use Claude with **any other tool** — Cursor, Continue, Aider, your own scripts — you need a separate API key with separate billing.
42
42
 
43
43
  **Note:** Claude subscriptions have [usage limits](https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work) that reset on rolling 5-hour and 7-day windows. When exceeded, Opus and Sonnet may return 429 errors while Haiku continues working. You can check your utilization via Claude Code's `/usage` command or [statusline](https://code.claude.com/docs/en/statusline). Use `--cli` mode to route through Claude Code's binary, which is not affected by these limits.
44
44
 
@@ -240,8 +240,8 @@ curl http://localhost:3456/v1/messages \
240
240
  ### With Other Tools
241
241
 
242
242
  ```bash
243
- # OpenClaw
244
- ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario openclaw
243
+ # Cursor / Continue / any OpenAI-compatible tool
244
+ OPENAI_BASE_URL=http://localhost:3456/v1 OPENAI_API_KEY=dario cursor
245
245
 
246
246
  # Aider
247
247
  ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario aider --model claude-opus-4-6
@@ -250,6 +250,19 @@ ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario aider --model c
250
250
  ANTHROPIC_BASE_URL=http://localhost:3456 ANTHROPIC_API_KEY=dario your-tool-here
251
251
  ```
252
252
 
253
+ ### Hermes
254
+
255
+ Add to `~/.hermes/config.yaml`:
256
+
257
+ ```yaml
258
+ model:
259
+ base_url: "http://localhost:3456/v1"
260
+ api_key: "dario"
261
+ default: claude-opus-4-6
262
+ ```
263
+
264
+ Then run `hermes` normally — it routes through dario using your Claude subscription.
265
+
253
266
  ## How It Works
254
267
 
255
268
  ### Direct API Mode (default)
@@ -415,7 +428,7 @@ Dario handles your OAuth tokens. Here's why you can trust it:
415
428
 
416
429
  | Signal | Status |
417
430
  |--------|--------|
418
- | **Source code** | ~1000 lines of TypeScript — small enough to read in one sitting |
431
+ | **Source code** | ~1100 lines of TypeScript — small enough to read in one sitting |
419
432
  | **Dependencies** | 1 production dep (`@anthropic-ai/sdk`). Verify: `npm ls --production` |
420
433
  | **npm provenance** | Every release is [SLSA attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions |
421
434
  | **Security scanning** | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) runs on every push and weekly |
@@ -437,7 +450,7 @@ cd $(npm root -g)/@askalf/dario && npm ls --production
437
450
 
438
451
  ## Contributing
439
452
 
440
- PRs welcome. The codebase is ~1000 lines of TypeScript across 4 files:
453
+ PRs welcome. The codebase is ~1100 lines of TypeScript across 4 files:
441
454
 
442
455
  | File | Purpose |
443
456
  |------|---------|
@@ -453,6 +466,15 @@ npm install
453
466
  npm run dev # runs with tsx (no build needed)
454
467
  ```
455
468
 
469
+ ## Also by AskAlf
470
+
471
+ | Project | What it does |
472
+ |---------|-------------|
473
+ | [platform](https://github.com/askalf/platform) | AI workforce with autonomous agents, teams, memory, and self-healing |
474
+ | [agent](https://github.com/askalf/agent) | Connect any device to the workforce over WebSocket |
475
+ | [claude-re](https://github.com/askalf/claude-re) | Claude Code reimplemented in Python |
476
+ | [amnesia](https://github.com/askalf/amnesia) | Privacy search engine — 155 engines, zero tracking |
477
+
456
478
  ## License
457
479
 
458
480
  MIT
package/dist/proxy.d.ts CHANGED
@@ -1,12 +1,3 @@
1
- /**
2
- * Dario — API Proxy Server
3
- *
4
- * Sits between your app and the Anthropic API.
5
- * Transparently swaps API key auth for OAuth bearer tokens.
6
- *
7
- * Point any Anthropic SDK client at http://localhost:3456 and it just works.
8
- * No API key needed — your Claude subscription pays for it.
9
- */
10
1
  interface ProxyOptions {
11
2
  port?: number;
12
3
  verbose?: boolean;
package/dist/proxy.js CHANGED
@@ -1,12 +1,3 @@
1
- /**
2
- * Dario — API Proxy Server
3
- *
4
- * Sits between your app and the Anthropic API.
5
- * Transparently swaps API key auth for OAuth bearer tokens.
6
- *
7
- * Point any Anthropic SDK client at http://localhost:3456 and it just works.
8
- * No API key needed — your Claude subscription pays for it.
9
- */
10
1
  import { createServer } from 'node:http';
11
2
  import { randomUUID, timingSafeEqual } from 'node:crypto';
12
3
  import { execSync, spawn } from 'node:child_process';
@@ -28,73 +19,38 @@ function detectClaudeVersion() {
28
19
  return '2.1.96';
29
20
  }
30
21
  }
31
- function getOsName() {
32
- const p = platform;
33
- if (p === 'win32')
34
- return 'Windows';
35
- if (p === 'darwin')
36
- return 'MacOS';
37
- return 'Linux';
38
- }
39
- // Persistent session ID per proxy lifetime (like Claude Code does per session)
40
22
  const SESSION_ID = randomUUID();
41
- // Detect @anthropic-ai/sdk version from installed package
42
- function detectSdkVersion() {
43
- try {
44
- const pkg = require('@anthropic-ai/sdk/package.json');
45
- return pkg.version ?? '0.81.0';
46
- }
47
- catch {
48
- return '0.81.0';
49
- }
50
- }
23
+ const OS_NAME = platform === 'win32' ? 'Windows' : platform === 'darwin' ? 'MacOS' : 'Linux';
51
24
  // Model shortcuts — users can pass short names
52
25
  const MODEL_ALIASES = {
53
26
  'opus': 'claude-opus-4-6',
54
27
  'sonnet': 'claude-sonnet-4-6',
55
28
  'haiku': 'claude-haiku-4-5',
56
29
  };
57
- // OpenAI model name → Anthropic model name
30
+ // OpenAI model names → Anthropic (fallback if client sends GPT names)
58
31
  const OPENAI_MODEL_MAP = {
59
- 'gpt-4.1': 'claude-opus-4-6',
60
- 'gpt-4.1-mini': 'claude-sonnet-4-6',
61
- 'gpt-4.1-nano': 'claude-haiku-4-5',
62
- 'gpt-4o': 'claude-opus-4-6',
63
- 'gpt-4o-mini': 'claude-haiku-4-5',
64
- 'gpt-4-turbo': 'claude-opus-4-6',
32
+ 'gpt-5.4': 'claude-opus-4-6',
33
+ 'gpt-5.4-mini': 'claude-sonnet-4-6',
34
+ 'gpt-5.4-nano': 'claude-haiku-4-5',
35
+ 'gpt-5.3': 'claude-opus-4-6',
65
36
  'gpt-4': 'claude-opus-4-6',
66
37
  'gpt-3.5-turbo': 'claude-haiku-4-5',
67
- 'o3': 'claude-opus-4-6',
68
- 'o3-mini': 'claude-sonnet-4-6',
69
- 'o4-mini': 'claude-sonnet-4-6',
70
- 'o1': 'claude-opus-4-6',
71
- 'o1-mini': 'claude-sonnet-4-6',
72
- 'o1-pro': 'claude-opus-4-6',
73
38
  };
74
- /**
75
- * Translate OpenAI chat completion request → Anthropic Messages request.
76
- */
39
+ /** Translate OpenAI chat completion request → Anthropic Messages request. */
77
40
  function openaiToAnthropic(body, modelOverride) {
78
41
  const messages = body.messages;
79
42
  if (!messages)
80
43
  return body;
81
- // Extract system messages
82
44
  const systemMessages = messages.filter(m => m.role === 'system');
83
45
  const nonSystemMessages = messages.filter(m => m.role !== 'system');
84
- // Map model name
85
- const requestModel = String(body.model || '');
86
- const model = modelOverride || OPENAI_MODEL_MAP[requestModel] || requestModel;
46
+ const model = modelOverride || OPENAI_MODEL_MAP[String(body.model || '')] || String(body.model || 'claude-opus-4-6');
87
47
  const result = {
88
48
  model,
89
- messages: nonSystemMessages.map(m => ({
90
- role: m.role === 'assistant' ? 'assistant' : 'user',
91
- content: m.content,
92
- })),
49
+ messages: nonSystemMessages.map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
93
50
  max_tokens: body.max_tokens ?? body.max_completion_tokens ?? 8192,
94
51
  };
95
- if (systemMessages.length > 0) {
52
+ if (systemMessages.length > 0)
96
53
  result.system = systemMessages.map(m => typeof m.content === 'string' ? m.content : JSON.stringify(m.content)).join('\n');
97
- }
98
54
  if (body.stream)
99
55
  result.stream = true;
100
56
  if (body.temperature != null)
@@ -105,33 +61,20 @@ function openaiToAnthropic(body, modelOverride) {
105
61
  result.stop_sequences = Array.isArray(body.stop) ? body.stop : [body.stop];
106
62
  return result;
107
63
  }
108
- /**
109
- * Translate Anthropic Messages response → OpenAI chat completion response.
110
- */
64
+ /** Translate Anthropic Messages response → OpenAI chat completion response. */
111
65
  function anthropicToOpenai(body) {
112
- const content = body.content;
113
- const text = content?.find(c => c.type === 'text')?.text ?? '';
114
- const usage = body.usage;
66
+ const text = body.content?.find(c => c.type === 'text')?.text ?? '';
67
+ const u = body.usage;
115
68
  return {
116
69
  id: `chatcmpl-${(body.id || '').replace('msg_', '')}`,
117
70
  object: 'chat.completion',
118
71
  created: Math.floor(Date.now() / 1000),
119
72
  model: body.model,
120
- choices: [{
121
- index: 0,
122
- message: { role: 'assistant', content: text },
123
- finish_reason: body.stop_reason === 'end_turn' ? 'stop' : body.stop_reason === 'max_tokens' ? 'length' : 'stop',
124
- }],
125
- usage: {
126
- prompt_tokens: usage?.input_tokens ?? 0,
127
- completion_tokens: usage?.output_tokens ?? 0,
128
- total_tokens: (usage?.input_tokens ?? 0) + (usage?.output_tokens ?? 0),
129
- },
73
+ choices: [{ index: 0, message: { role: 'assistant', content: text }, finish_reason: body.stop_reason === 'end_turn' ? 'stop' : 'length' }],
74
+ usage: { prompt_tokens: u?.input_tokens ?? 0, completion_tokens: u?.output_tokens ?? 0, total_tokens: (u?.input_tokens ?? 0) + (u?.output_tokens ?? 0) },
130
75
  };
131
76
  }
132
- /**
133
- * Translate Anthropic SSE stream → OpenAI SSE stream.
134
- */
77
+ /** Translate Anthropic SSE → OpenAI SSE. */
135
78
  function translateStreamChunk(line) {
136
79
  if (!line.startsWith('data: '))
137
80
  return null;
@@ -139,47 +82,19 @@ function translateStreamChunk(line) {
139
82
  if (json === '[DONE]')
140
83
  return 'data: [DONE]\n\n';
141
84
  try {
142
- const event = JSON.parse(json);
143
- if (event.type === 'content_block_delta') {
144
- const delta = event.delta;
145
- if (delta?.type === 'text_delta' && delta.text) {
146
- return `data: ${JSON.stringify({
147
- id: 'chatcmpl-dario',
148
- object: 'chat.completion.chunk',
149
- created: Math.floor(Date.now() / 1000),
150
- model: 'claude',
151
- choices: [{ index: 0, delta: { content: delta.text }, finish_reason: null }],
152
- })}\n\n`;
153
- }
154
- }
155
- if (event.type === 'message_stop') {
156
- return `data: ${JSON.stringify({
157
- id: 'chatcmpl-dario',
158
- object: 'chat.completion.chunk',
159
- created: Math.floor(Date.now() / 1000),
160
- model: 'claude',
161
- choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
162
- })}\n\ndata: [DONE]\n\n`;
85
+ const e = JSON.parse(json);
86
+ if (e.type === 'content_block_delta') {
87
+ const d = e.delta;
88
+ if (d?.type === 'text_delta' && d.text)
89
+ return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'claude', choices: [{ index: 0, delta: { content: d.text }, finish_reason: null }] })}\n\n`;
163
90
  }
91
+ if (e.type === 'message_stop')
92
+ return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'claude', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] })}\n\ndata: [DONE]\n\n`;
164
93
  }
165
- catch { /* skip unparseable */ }
94
+ catch { }
166
95
  return null;
167
96
  }
168
- /**
169
- * OpenAI-compatible models list.
170
- */
171
- function openaiModelsList() {
172
- const models = ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5'];
173
- return {
174
- object: 'list',
175
- data: models.map(id => ({
176
- id,
177
- object: 'model',
178
- created: 1700000000,
179
- owned_by: 'anthropic',
180
- })),
181
- };
182
- }
97
+ const OPENAI_MODELS_LIST = { object: 'list', data: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5'].map(id => ({ id, object: 'model', created: 1700000000, owned_by: 'anthropic' })) };
183
98
  export function sanitizeError(err) {
184
99
  const msg = err instanceof Error ? err.message : String(err);
185
100
  // Never leak tokens, JWTs, or bearer values in error messages
@@ -289,37 +204,57 @@ export async function startProxy(opts = {}) {
289
204
  process.exit(1);
290
205
  }
291
206
  const cliVersion = detectClaudeVersion();
292
- const sdkVersion = detectSdkVersion();
293
207
  const modelOverride = opts.model ? (MODEL_ALIASES[opts.model] ?? opts.model) : null;
208
+ // Pre-build static headers (only auth, version, beta, request-id change per request)
209
+ const staticHeaders = {
210
+ 'accept': 'application/json',
211
+ 'Content-Type': 'application/json',
212
+ 'anthropic-dangerous-direct-browser-access': 'true',
213
+ 'anthropic-client-platform': 'cli',
214
+ 'user-agent': `claude-cli/${cliVersion} (external, cli)`,
215
+ 'x-app': 'cli',
216
+ 'x-claude-code-session-id': SESSION_ID,
217
+ 'x-stainless-arch': arch,
218
+ 'x-stainless-lang': 'js',
219
+ 'x-stainless-os': OS_NAME,
220
+ 'x-stainless-package-version': '0.81.0',
221
+ 'x-stainless-retry-count': '0',
222
+ 'x-stainless-runtime': 'node',
223
+ 'x-stainless-runtime-version': nodeVersion,
224
+ 'x-stainless-timeout': '600',
225
+ };
294
226
  const useCli = opts.cliBackend ?? false;
295
227
  let requestCount = 0;
296
- let tokenCostEstimate = 0;
297
- // Optional proxy authentication
228
+ // Optional proxy authentication — pre-encode key buffer for performance
298
229
  const apiKey = process.env.DARIO_API_KEY;
230
+ const apiKeyBuf = apiKey ? Buffer.from(apiKey) : null;
299
231
  const corsOrigin = `http://localhost:${port}`;
232
+ // Pre-serialize static responses
233
+ const CORS_HEADERS = {
234
+ 'Access-Control-Allow-Origin': corsOrigin,
235
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
236
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta',
237
+ 'Access-Control-Max-Age': '86400',
238
+ };
239
+ const MODELS_JSON = JSON.stringify(OPENAI_MODELS_LIST);
240
+ const ERR_UNAUTH = JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' });
241
+ const ERR_FORBIDDEN = JSON.stringify({ error: 'Forbidden', message: 'Path not allowed' });
242
+ const ERR_METHOD = JSON.stringify({ error: 'Method not allowed' });
300
243
  function checkAuth(req) {
301
- if (!apiKey)
302
- return true; // no key set = open access
244
+ if (!apiKeyBuf)
245
+ return true;
303
246
  const provided = req.headers['x-api-key']
304
247
  || req.headers.authorization?.replace(/^Bearer\s+/i, '');
305
248
  if (!provided)
306
249
  return false;
307
- try {
308
- return timingSafeEqual(Buffer.from(provided), Buffer.from(apiKey));
309
- }
310
- catch {
250
+ const providedBuf = Buffer.from(provided);
251
+ if (providedBuf.length !== apiKeyBuf.length)
311
252
  return false;
312
- }
253
+ return timingSafeEqual(providedBuf, apiKeyBuf);
313
254
  }
314
255
  const server = createServer(async (req, res) => {
315
- // CORS preflight
316
256
  if (req.method === 'OPTIONS') {
317
- res.writeHead(204, {
318
- 'Access-Control-Allow-Origin': corsOrigin,
319
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
320
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta',
321
- 'Access-Control-Max-Age': '86400',
322
- });
257
+ res.writeHead(204, CORS_HEADERS);
323
258
  res.end();
324
259
  return;
325
260
  }
@@ -337,10 +272,9 @@ export async function startProxy(opts = {}) {
337
272
  }));
338
273
  return;
339
274
  }
340
- // Auth gate — everything below health requires auth when DARIO_API_KEY is set
341
275
  if (!checkAuth(req)) {
342
276
  res.writeHead(401, { 'Content-Type': 'application/json' });
343
- res.end(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }));
277
+ res.end(ERR_UNAUTH);
344
278
  return;
345
279
  }
346
280
  // Status endpoint
@@ -350,11 +284,10 @@ export async function startProxy(opts = {}) {
350
284
  res.end(JSON.stringify(s));
351
285
  return;
352
286
  }
353
- // OpenAI-compatible models list
354
287
  if (urlPath === '/v1/models' && req.method === 'GET') {
355
288
  requestCount++;
356
289
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': corsOrigin });
357
- res.end(JSON.stringify(openaiModelsList()));
290
+ res.end(MODELS_JSON);
358
291
  return;
359
292
  }
360
293
  // Detect OpenAI-format requests
@@ -367,13 +300,12 @@ export async function startProxy(opts = {}) {
367
300
  const targetBase = isOpenAI ? `${ANTHROPIC_API}/v1/messages` : allowedPaths[urlPath];
368
301
  if (!targetBase) {
369
302
  res.writeHead(403, { 'Content-Type': 'application/json' });
370
- res.end(JSON.stringify({ error: 'Forbidden', message: 'Path not allowed' }));
303
+ res.end(ERR_FORBIDDEN);
371
304
  return;
372
305
  }
373
- // Only allow POST (Messages/Chat API) and GET (models)
374
306
  if (req.method !== 'POST') {
375
307
  res.writeHead(405, { 'Content-Type': 'application/json' });
376
- res.end(JSON.stringify({ error: 'Method not allowed' }));
308
+ res.end(ERR_METHOD);
377
309
  return;
378
310
  }
379
311
  // Proxy to Anthropic
@@ -404,22 +336,13 @@ export async function startProxy(opts = {}) {
404
336
  res.end(cliResult.body);
405
337
  return;
406
338
  }
407
- // Translate OpenAI Anthropic format if needed
339
+ // Parse body once, apply OpenAI translation or model override
408
340
  let finalBody = body.length > 0 ? body : undefined;
409
- if (isOpenAI && body.length > 0) {
410
- try {
411
- const parsed = JSON.parse(body.toString());
412
- const translated = openaiToAnthropic(parsed, modelOverride);
413
- finalBody = Buffer.from(JSON.stringify(translated));
414
- }
415
- catch { /* not JSON, send as-is */ }
416
- }
417
- else if (modelOverride && body.length > 0) {
418
- // Override model in request body if --model flag was set
341
+ if (body.length > 0 && (isOpenAI || modelOverride)) {
419
342
  try {
420
343
  const parsed = JSON.parse(body.toString());
421
- parsed.model = modelOverride;
422
- finalBody = Buffer.from(JSON.stringify(parsed));
344
+ const result = isOpenAI ? openaiToAnthropic(parsed, modelOverride) : (modelOverride ? { ...parsed, model: modelOverride } : parsed);
345
+ finalBody = Buffer.from(JSON.stringify(result));
423
346
  }
424
347
  catch { /* not JSON, send as-is */ }
425
348
  }
@@ -427,46 +350,19 @@ export async function startProxy(opts = {}) {
427
350
  const modelInfo = modelOverride ? ` (model: ${modelOverride})` : '';
428
351
  console.log(`[dario] #${requestCount} ${req.method} ${req.url}${modelInfo}`);
429
352
  }
430
- // Build target URL from allowlist (no user input in URL construction)
431
- const targetUrl = targetBase;
432
- // Merge any client-provided beta flags with the required oauth flag
353
+ // Merge client beta flags with defaults
433
354
  const clientBeta = req.headers['anthropic-beta'];
434
- const betaFlags = new Set([
435
- 'oauth-2025-04-20',
436
- 'interleaved-thinking-2025-05-14',
437
- 'prompt-caching-scope-2026-01-05',
438
- 'claude-code-20250219',
439
- 'context-management-2025-06-27',
440
- ]);
441
- if (clientBeta) {
442
- for (const flag of clientBeta.split(',')) {
443
- const trimmed = flag.trim();
444
- if (trimmed.length > 0 && trimmed.length < 100)
445
- betaFlags.add(trimmed);
446
- }
447
- }
355
+ let beta = 'oauth-2025-04-20,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,claude-code-20250219,context-management-2025-06-27';
356
+ if (clientBeta)
357
+ beta += ',' + clientBeta.split(',').map(f => f.trim()).filter(f => f.length > 0 && f.length < 100).join(',');
448
358
  const headers = {
449
- 'accept': 'application/json',
359
+ ...staticHeaders,
450
360
  'Authorization': `Bearer ${accessToken}`,
451
- 'Content-Type': 'application/json',
452
361
  'anthropic-version': req.headers['anthropic-version'] || '2023-06-01',
453
- 'anthropic-beta': [...betaFlags].join(','),
454
- 'anthropic-dangerous-direct-browser-access': 'true',
455
- 'anthropic-client-platform': 'cli',
456
- 'user-agent': `claude-cli/${cliVersion} (external, cli)`,
457
- 'x-app': 'cli',
458
- 'x-claude-code-session-id': SESSION_ID,
362
+ 'anthropic-beta': beta,
459
363
  'x-client-request-id': randomUUID(),
460
- 'x-stainless-arch': arch,
461
- 'x-stainless-lang': 'js',
462
- 'x-stainless-os': getOsName(),
463
- 'x-stainless-package-version': sdkVersion,
464
- 'x-stainless-retry-count': '0',
465
- 'x-stainless-runtime': 'node',
466
- 'x-stainless-runtime-version': nodeVersion,
467
- 'x-stainless-timeout': '600',
468
364
  };
469
- const upstream = await fetch(targetUrl, {
365
+ const upstream = await fetch(targetBase, {
470
366
  method: req.method ?? 'POST',
471
367
  headers,
472
368
  body: finalBody ? new Uint8Array(finalBody) : undefined,
@@ -547,18 +443,8 @@ export async function startProxy(opts = {}) {
547
443
  else {
548
444
  res.end(responseBody);
549
445
  }
550
- // Quick token estimate for logging
551
- if (verbose && responseBody) {
552
- try {
553
- const parsed = JSON.parse(responseBody);
554
- if (parsed.usage) {
555
- const tokens = (parsed.usage.input_tokens ?? 0) + (parsed.usage.output_tokens ?? 0);
556
- tokenCostEstimate += tokens;
557
- console.log(`[dario] #${requestCount} ${upstream.status} — ${tokens} tokens (session total: ${tokenCostEstimate})`);
558
- }
559
- }
560
- catch { /* not JSON, skip */ }
561
- }
446
+ if (verbose)
447
+ console.log(`[dario] #${requestCount} ${upstream.status}`);
562
448
  }
563
449
  }
564
450
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
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": {