@askalf/dario 1.0.8 → 1.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/dist/cli.js +8 -1
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +125 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -132,7 +132,10 @@ async function proxy() {
|
|
|
132
132
|
process.exit(1);
|
|
133
133
|
}
|
|
134
134
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
135
|
-
|
|
135
|
+
const cliBackend = args.includes('--cli');
|
|
136
|
+
const modelArg = args.find(a => a.startsWith('--model='));
|
|
137
|
+
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
138
|
+
await startProxy({ port, verbose, model, cliBackend });
|
|
136
139
|
}
|
|
137
140
|
async function help() {
|
|
138
141
|
console.log(`
|
|
@@ -146,6 +149,10 @@ async function help() {
|
|
|
146
149
|
dario logout Remove saved credentials
|
|
147
150
|
|
|
148
151
|
Proxy options:
|
|
152
|
+
--model=MODEL Force a model for all requests
|
|
153
|
+
Shortcuts: opus, sonnet, haiku
|
|
154
|
+
Default: passthrough (client decides)
|
|
155
|
+
--cli Use Claude CLI as backend (bypasses rate limits)
|
|
149
156
|
--port=PORT Port to listen on (default: 3456)
|
|
150
157
|
--verbose, -v Log all requests
|
|
151
158
|
|
package/dist/proxy.d.ts
CHANGED
package/dist/proxy.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createServer } from 'node:http';
|
|
11
11
|
import { randomUUID } from 'node:crypto';
|
|
12
|
-
import { execSync } from 'node:child_process';
|
|
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';
|
|
15
15
|
const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
@@ -49,11 +49,106 @@ function detectSdkVersion() {
|
|
|
49
49
|
return '0.81.0';
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
// Model shortcuts — users can pass short names
|
|
53
|
+
const MODEL_ALIASES = {
|
|
54
|
+
'opus': 'claude-opus-4-6',
|
|
55
|
+
'sonnet': 'claude-sonnet-4-6',
|
|
56
|
+
'haiku': 'claude-haiku-4-5',
|
|
57
|
+
};
|
|
52
58
|
function sanitizeError(err) {
|
|
53
59
|
const msg = err instanceof Error ? err.message : String(err);
|
|
54
60
|
// Never leak tokens in error messages
|
|
55
61
|
return msg.replace(/sk-ant-[a-zA-Z0-9_-]+/g, '[REDACTED]');
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* CLI Backend: route requests through `claude --print` instead of direct API.
|
|
65
|
+
* This bypasses rate limiting because Claude Code's binary has priority routing.
|
|
66
|
+
*/
|
|
67
|
+
async function handleViaCli(body, model, verbose) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(body.toString());
|
|
70
|
+
// Extract the last user message as the prompt
|
|
71
|
+
const messages = parsed.messages ?? [];
|
|
72
|
+
const lastUser = [...messages].reverse().find(m => m.role === 'user');
|
|
73
|
+
if (!lastUser) {
|
|
74
|
+
return { status: 400, body: JSON.stringify({ error: 'No user message' }), contentType: 'application/json' };
|
|
75
|
+
}
|
|
76
|
+
const effectiveModel = model ?? parsed.model ?? 'claude-opus-4-6';
|
|
77
|
+
const prompt = typeof lastUser.content === 'string'
|
|
78
|
+
? lastUser.content
|
|
79
|
+
: JSON.stringify(lastUser.content);
|
|
80
|
+
// Build claude --print command
|
|
81
|
+
const args = ['--print', '--model', effectiveModel];
|
|
82
|
+
// Build system prompt from messages context
|
|
83
|
+
let systemPrompt = parsed.system ?? '';
|
|
84
|
+
// Include conversation history as context
|
|
85
|
+
const history = messages.slice(0, -1);
|
|
86
|
+
if (history.length > 0) {
|
|
87
|
+
const historyText = history.map(m => `${m.role}: ${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`).join('\n');
|
|
88
|
+
systemPrompt = systemPrompt ? `${systemPrompt}\n\nConversation history:\n${historyText}` : `Conversation history:\n${historyText}`;
|
|
89
|
+
}
|
|
90
|
+
if (systemPrompt) {
|
|
91
|
+
args.push('--append-system-prompt', systemPrompt);
|
|
92
|
+
}
|
|
93
|
+
if (verbose) {
|
|
94
|
+
console.log(`[dario:cli] model=${effectiveModel} prompt=${prompt.substring(0, 60)}...`);
|
|
95
|
+
}
|
|
96
|
+
// Spawn claude --print
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const child = spawn('claude', args, {
|
|
99
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
100
|
+
timeout: 300_000,
|
|
101
|
+
});
|
|
102
|
+
let stdout = '';
|
|
103
|
+
let stderr = '';
|
|
104
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
105
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
106
|
+
child.stdin.write(prompt);
|
|
107
|
+
child.stdin.end();
|
|
108
|
+
child.on('close', (code) => {
|
|
109
|
+
if (code !== 0 || !stdout.trim()) {
|
|
110
|
+
resolve({
|
|
111
|
+
status: 502,
|
|
112
|
+
body: JSON.stringify({ type: 'error', error: { type: 'api_error', message: stderr.substring(0, 200) || 'CLI backend failed' } }),
|
|
113
|
+
contentType: 'application/json',
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Build a proper Messages API response
|
|
118
|
+
const text = stdout.trim();
|
|
119
|
+
const estimatedTokens = Math.ceil(text.length / 4);
|
|
120
|
+
const response = {
|
|
121
|
+
id: `msg_${randomUUID().replace(/-/g, '').substring(0, 24)}`,
|
|
122
|
+
type: 'message',
|
|
123
|
+
role: 'assistant',
|
|
124
|
+
model: effectiveModel,
|
|
125
|
+
content: [{ type: 'text', text }],
|
|
126
|
+
stop_reason: 'end_turn',
|
|
127
|
+
stop_sequence: null,
|
|
128
|
+
usage: {
|
|
129
|
+
input_tokens: Math.ceil(prompt.length / 4),
|
|
130
|
+
output_tokens: estimatedTokens,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
resolve({ status: 200, body: JSON.stringify(response), contentType: 'application/json' });
|
|
134
|
+
});
|
|
135
|
+
child.on('error', (err) => {
|
|
136
|
+
resolve({
|
|
137
|
+
status: 502,
|
|
138
|
+
body: JSON.stringify({ type: 'error', error: { type: 'api_error', message: 'Claude CLI not found. Install Claude Code first.' } }),
|
|
139
|
+
contentType: 'application/json',
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return {
|
|
146
|
+
status: 400,
|
|
147
|
+
body: JSON.stringify({ type: 'error', error: { type: 'invalid_request_error', message: 'Invalid request body' } }),
|
|
148
|
+
contentType: 'application/json',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
57
152
|
export async function startProxy(opts = {}) {
|
|
58
153
|
const port = opts.port ?? DEFAULT_PORT;
|
|
59
154
|
const verbose = opts.verbose ?? false;
|
|
@@ -65,6 +160,8 @@ export async function startProxy(opts = {}) {
|
|
|
65
160
|
}
|
|
66
161
|
const cliVersion = detectClaudeVersion();
|
|
67
162
|
const sdkVersion = detectSdkVersion();
|
|
163
|
+
const modelOverride = opts.model ? (MODEL_ALIASES[opts.model] ?? opts.model) : null;
|
|
164
|
+
const useCli = opts.cliBackend ?? false;
|
|
68
165
|
let requestCount = 0;
|
|
69
166
|
let tokenCostEstimate = 0;
|
|
70
167
|
const server = createServer(async (req, res) => {
|
|
@@ -134,8 +231,30 @@ export async function startProxy(opts = {}) {
|
|
|
134
231
|
chunks.push(buf);
|
|
135
232
|
}
|
|
136
233
|
const body = Buffer.concat(chunks);
|
|
234
|
+
// CLI backend mode: route through claude --print
|
|
235
|
+
if (useCli && rawPath === '/v1/messages' && req.method === 'POST' && body.length > 0) {
|
|
236
|
+
const cliResult = await handleViaCli(body, modelOverride, verbose);
|
|
237
|
+
requestCount++;
|
|
238
|
+
res.writeHead(cliResult.status, {
|
|
239
|
+
'Content-Type': cliResult.contentType,
|
|
240
|
+
'Access-Control-Allow-Origin': CORS_ORIGIN,
|
|
241
|
+
});
|
|
242
|
+
res.end(cliResult.body);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Override model in request body if --model flag was set
|
|
246
|
+
let finalBody = body.length > 0 ? body : undefined;
|
|
247
|
+
if (modelOverride && body.length > 0) {
|
|
248
|
+
try {
|
|
249
|
+
const parsed = JSON.parse(body.toString());
|
|
250
|
+
parsed.model = modelOverride;
|
|
251
|
+
finalBody = Buffer.from(JSON.stringify(parsed));
|
|
252
|
+
}
|
|
253
|
+
catch { /* not JSON, send as-is */ }
|
|
254
|
+
}
|
|
137
255
|
if (verbose) {
|
|
138
|
-
|
|
256
|
+
const modelInfo = modelOverride ? ` (model: ${modelOverride})` : '';
|
|
257
|
+
console.log(`[dario] #${requestCount} ${req.method} ${req.url}${modelInfo}`);
|
|
139
258
|
}
|
|
140
259
|
// Build target URL from allowlist (no user input in URL construction)
|
|
141
260
|
const targetUrl = targetBase;
|
|
@@ -177,10 +296,8 @@ export async function startProxy(opts = {}) {
|
|
|
177
296
|
const upstream = await fetch(targetUrl, {
|
|
178
297
|
method: req.method ?? 'POST',
|
|
179
298
|
headers,
|
|
180
|
-
body:
|
|
299
|
+
body: finalBody ? new Uint8Array(finalBody) : undefined,
|
|
181
300
|
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
|
|
182
|
-
// @ts-expect-error — duplex needed for streaming
|
|
183
|
-
duplex: 'half',
|
|
184
301
|
});
|
|
185
302
|
// Detect streaming from content-type (reliable) or body (fallback)
|
|
186
303
|
const contentType = upstream.headers.get('content-type') ?? '';
|
|
@@ -250,7 +367,8 @@ export async function startProxy(opts = {}) {
|
|
|
250
367
|
process.exit(1);
|
|
251
368
|
});
|
|
252
369
|
server.listen(port, LOCALHOST, () => {
|
|
253
|
-
const oauthLine = `OAuth: ${status.status} (expires in ${status.expiresIn})`;
|
|
370
|
+
const oauthLine = useCli ? 'Backend: Claude CLI (bypasses rate limits)' : `OAuth: ${status.status} (expires in ${status.expiresIn})`;
|
|
371
|
+
const modelLine = modelOverride ? `Model: ${modelOverride} (all requests)` : 'Model: passthrough (client decides)';
|
|
254
372
|
console.log('');
|
|
255
373
|
console.log(` dario — http://localhost:${port}`);
|
|
256
374
|
console.log('');
|
|
@@ -261,6 +379,7 @@ export async function startProxy(opts = {}) {
|
|
|
261
379
|
console.log(' ANTHROPIC_API_KEY=dario');
|
|
262
380
|
console.log('');
|
|
263
381
|
console.log(` ${oauthLine}`);
|
|
382
|
+
console.log(` ${modelLine}`);
|
|
264
383
|
console.log('');
|
|
265
384
|
});
|
|
266
385
|
// Periodic token refresh (every 15 minutes)
|