@askalf/dario 2.0.0 → 2.1.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 +5 -21
- package/dist/cli.js +5 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.js +43 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,34 +159,18 @@ dario proxy --cli --model=opus
|
|
|
159
159
|
|
|
160
160
|
## OpenAI Compatibility
|
|
161
161
|
|
|
162
|
-
Dario
|
|
162
|
+
Dario implements `/v1/chat/completions` — any tool built for the OpenAI API works with your Claude subscription. No code changes needed.
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
|
|
165
|
+
dario proxy --model=opus
|
|
166
|
+
|
|
166
167
|
export OPENAI_BASE_URL=http://localhost:3456/v1
|
|
167
168
|
export OPENAI_API_KEY=dario
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
```python
|
|
171
|
-
from openai import OpenAI
|
|
172
169
|
|
|
173
|
-
|
|
174
|
-
response = client.chat.completions.create(
|
|
175
|
-
model="claude-opus-4-6", # or use "gpt-4" — auto-maps to Opus
|
|
176
|
-
messages=[{"role": "user", "content": "Hello!"}]
|
|
177
|
-
)
|
|
170
|
+
# Cursor, Continue, LiteLLM, any OpenAI SDK — all work
|
|
178
171
|
```
|
|
179
172
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
| OpenAI model | Maps to |
|
|
183
|
-
|---|---|
|
|
184
|
-
| `gpt-4`, `gpt-4o`, `o1`, `o3` | `claude-opus-4-6` |
|
|
185
|
-
| `o1-mini`, `o3-mini` | `claude-sonnet-4-6` |
|
|
186
|
-
| `gpt-3.5-turbo`, `gpt-4o-mini` | `claude-haiku-4-5` |
|
|
187
|
-
| Any `claude-*` model | Passed through directly |
|
|
188
|
-
|
|
189
|
-
Streaming, system prompts, temperature, and stop sequences all translate automatically.
|
|
173
|
+
Use `--model=opus` to force the model regardless of what the client sends. Or pass `claude-opus-4-6` as the model name directly — Claude model names work as-is.
|
|
190
174
|
|
|
191
175
|
## Usage Examples
|
|
192
176
|
|
package/dist/cli.js
CHANGED
|
@@ -13,9 +13,9 @@ import { readFile, unlink } from 'node:fs/promises';
|
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { startAutoOAuthFlow, getStatus, refreshTokens } from './oauth.js';
|
|
16
|
-
import { startProxy } from './proxy.js';
|
|
16
|
+
import { startProxy, sanitizeError } from './proxy.js';
|
|
17
17
|
const args = process.argv.slice(2);
|
|
18
|
-
const command = args[0] ??
|
|
18
|
+
const command = args[0] ?? 'proxy';
|
|
19
19
|
async function login() {
|
|
20
20
|
console.log('');
|
|
21
21
|
console.log(' dario — Claude Login');
|
|
@@ -50,7 +50,7 @@ async function login() {
|
|
|
50
50
|
}
|
|
51
51
|
catch (err) {
|
|
52
52
|
console.error('');
|
|
53
|
-
console.error(` Login failed: ${err
|
|
53
|
+
console.error(` Login failed: ${sanitizeError(err)}`);
|
|
54
54
|
console.error(' Try again with `dario login`.');
|
|
55
55
|
process.exit(1);
|
|
56
56
|
}
|
|
@@ -89,7 +89,7 @@ async function refresh() {
|
|
|
89
89
|
console.log(`[dario] Token refreshed. Expires in ${expiresIn} minutes.`);
|
|
90
90
|
}
|
|
91
91
|
catch (err) {
|
|
92
|
-
console.error(`[dario] Refresh failed: ${err
|
|
92
|
+
console.error(`[dario] Refresh failed: ${sanitizeError(err)}`);
|
|
93
93
|
process.exit(1);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -172,7 +172,6 @@ if (!handler) {
|
|
|
172
172
|
process.exit(1);
|
|
173
173
|
}
|
|
174
174
|
handler().catch(err => {
|
|
175
|
-
|
|
176
|
-
console.error('Fatal error:', msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]'));
|
|
175
|
+
console.error('Fatal error:', sanitizeError(err));
|
|
177
176
|
process.exit(1);
|
|
178
177
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { startAutoOAuthFlow, refreshTokens, getAccessToken, getStatus, loadCredentials } from './oauth.js';
|
|
8
8
|
export type { OAuthTokens, CredentialsFile } from './oauth.js';
|
|
9
|
-
export { startProxy } from './proxy.js';
|
|
9
|
+
export { startProxy, sanitizeError } from './proxy.js';
|
package/dist/index.js
CHANGED
package/dist/proxy.d.ts
CHANGED
package/dist/proxy.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* No API key needed — your Claude subscription pays for it.
|
|
9
9
|
*/
|
|
10
10
|
import { createServer } from 'node:http';
|
|
11
|
-
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { randomUUID, timingSafeEqual } from 'node:crypto';
|
|
12
12
|
import { execSync, spawn } from 'node:child_process';
|
|
13
13
|
import { arch, platform, version as nodeVersion } from 'node:process';
|
|
14
14
|
import { getAccessToken, getStatus } from './oauth.js';
|
|
@@ -17,7 +17,6 @@ const DEFAULT_PORT = 3456;
|
|
|
17
17
|
const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB — generous for large prompts, prevents abuse
|
|
18
18
|
const UPSTREAM_TIMEOUT_MS = 300_000; // 5 min — matches Anthropic SDK default
|
|
19
19
|
const LOCALHOST = '127.0.0.1';
|
|
20
|
-
const CORS_ORIGIN = 'http://localhost';
|
|
21
20
|
// Detect installed Claude Code version at startup
|
|
22
21
|
function detectClaudeVersion() {
|
|
23
22
|
try {
|
|
@@ -57,16 +56,20 @@ const MODEL_ALIASES = {
|
|
|
57
56
|
};
|
|
58
57
|
// OpenAI model name → Anthropic model name
|
|
59
58
|
const OPENAI_MODEL_MAP = {
|
|
60
|
-
'gpt-4': 'claude-opus-4-6',
|
|
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',
|
|
61
62
|
'gpt-4o': 'claude-opus-4-6',
|
|
62
|
-
'gpt-4-turbo': 'claude-opus-4-6',
|
|
63
63
|
'gpt-4o-mini': 'claude-haiku-4-5',
|
|
64
|
+
'gpt-4-turbo': 'claude-opus-4-6',
|
|
65
|
+
'gpt-4': 'claude-opus-4-6',
|
|
64
66
|
'gpt-3.5-turbo': 'claude-haiku-4-5',
|
|
65
|
-
'o1': 'claude-opus-4-6',
|
|
66
|
-
'o1-mini': 'claude-sonnet-4-6',
|
|
67
|
-
'o1-preview': 'claude-opus-4-6',
|
|
68
67
|
'o3': 'claude-opus-4-6',
|
|
69
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',
|
|
70
73
|
};
|
|
71
74
|
/**
|
|
72
75
|
* Translate OpenAI chat completion request → Anthropic Messages request.
|
|
@@ -177,10 +180,13 @@ function openaiModelsList() {
|
|
|
177
180
|
})),
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
|
-
function sanitizeError(err) {
|
|
183
|
+
export function sanitizeError(err) {
|
|
181
184
|
const msg = err instanceof Error ? err.message : String(err);
|
|
182
|
-
// Never leak tokens in error messages
|
|
183
|
-
return msg
|
|
185
|
+
// Never leak tokens, JWTs, or bearer values in error messages
|
|
186
|
+
return msg
|
|
187
|
+
.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]')
|
|
188
|
+
.replace(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g, '[REDACTED_JWT]')
|
|
189
|
+
.replace(/Bearer\s+[a-zA-Z0-9_-]+/gi, 'Bearer [REDACTED]');
|
|
184
190
|
}
|
|
185
191
|
/**
|
|
186
192
|
* CLI Backend: route requests through `claude --print` instead of direct API.
|
|
@@ -286,11 +292,28 @@ export async function startProxy(opts = {}) {
|
|
|
286
292
|
const useCli = opts.cliBackend ?? false;
|
|
287
293
|
let requestCount = 0;
|
|
288
294
|
let tokenCostEstimate = 0;
|
|
295
|
+
// Optional proxy authentication
|
|
296
|
+
const apiKey = process.env.DARIO_API_KEY;
|
|
297
|
+
const corsOrigin = `http://localhost:${port}`;
|
|
298
|
+
function checkAuth(req) {
|
|
299
|
+
if (!apiKey)
|
|
300
|
+
return true; // no key set = open access
|
|
301
|
+
const provided = req.headers['x-api-key']
|
|
302
|
+
|| req.headers.authorization?.replace(/^Bearer\s+/i, '');
|
|
303
|
+
if (!provided)
|
|
304
|
+
return false;
|
|
305
|
+
try {
|
|
306
|
+
return timingSafeEqual(Buffer.from(provided), Buffer.from(apiKey));
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
289
312
|
const server = createServer(async (req, res) => {
|
|
290
313
|
// CORS preflight
|
|
291
314
|
if (req.method === 'OPTIONS') {
|
|
292
315
|
res.writeHead(204, {
|
|
293
|
-
'Access-Control-Allow-Origin':
|
|
316
|
+
'Access-Control-Allow-Origin': corsOrigin,
|
|
294
317
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
295
318
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta',
|
|
296
319
|
'Access-Control-Max-Age': '86400',
|
|
@@ -312,6 +335,12 @@ export async function startProxy(opts = {}) {
|
|
|
312
335
|
}));
|
|
313
336
|
return;
|
|
314
337
|
}
|
|
338
|
+
// Auth gate — everything below health requires auth when DARIO_API_KEY is set
|
|
339
|
+
if (!checkAuth(req)) {
|
|
340
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
341
|
+
res.end(JSON.stringify({ error: 'Unauthorized', message: 'Invalid or missing API key' }));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
315
344
|
// Status endpoint
|
|
316
345
|
if (urlPath === '/status') {
|
|
317
346
|
const s = await getStatus();
|
|
@@ -322,7 +351,7 @@ export async function startProxy(opts = {}) {
|
|
|
322
351
|
// OpenAI-compatible models list
|
|
323
352
|
if (urlPath === '/v1/models' && req.method === 'GET') {
|
|
324
353
|
requestCount++;
|
|
325
|
-
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin':
|
|
354
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': corsOrigin });
|
|
326
355
|
res.end(JSON.stringify(openaiModelsList()));
|
|
327
356
|
return;
|
|
328
357
|
}
|
|
@@ -368,7 +397,7 @@ export async function startProxy(opts = {}) {
|
|
|
368
397
|
requestCount++;
|
|
369
398
|
res.writeHead(cliResult.status, {
|
|
370
399
|
'Content-Type': cliResult.contentType,
|
|
371
|
-
'Access-Control-Allow-Origin':
|
|
400
|
+
'Access-Control-Allow-Origin': corsOrigin,
|
|
372
401
|
});
|
|
373
402
|
res.end(cliResult.body);
|
|
374
403
|
return;
|
|
@@ -447,7 +476,7 @@ export async function startProxy(opts = {}) {
|
|
|
447
476
|
// Forward response headers
|
|
448
477
|
const responseHeaders = {
|
|
449
478
|
'Content-Type': contentType || 'application/json',
|
|
450
|
-
'Access-Control-Allow-Origin':
|
|
479
|
+
'Access-Control-Allow-Origin': corsOrigin,
|
|
451
480
|
};
|
|
452
481
|
// Forward rate limit headers (including unified subscription headers)
|
|
453
482
|
for (const [key, value] of upstream.headers.entries()) {
|