@askalf/dario 2.2.5 → 2.3.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/proxy.js +57 -6
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -8,7 +8,30 @@ const DEFAULT_PORT = 3456;
|
|
|
8
8
|
const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB — generous for large prompts, prevents abuse
|
|
9
9
|
const UPSTREAM_TIMEOUT_MS = 300_000; // 5 min — matches Anthropic SDK default
|
|
10
10
|
const BODY_READ_TIMEOUT_MS = 30_000; // 30s — prevents slow-loris on body reads
|
|
11
|
+
const MAX_CONCURRENT = 10; // Max concurrent upstream requests
|
|
11
12
|
const LOCALHOST = '127.0.0.1';
|
|
13
|
+
// Simple semaphore for concurrency control
|
|
14
|
+
class Semaphore {
|
|
15
|
+
max;
|
|
16
|
+
queue = [];
|
|
17
|
+
active = 0;
|
|
18
|
+
constructor(max) {
|
|
19
|
+
this.max = max;
|
|
20
|
+
}
|
|
21
|
+
async acquire() {
|
|
22
|
+
if (this.active < this.max) {
|
|
23
|
+
this.active++;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
return new Promise(resolve => { this.queue.push(() => { this.active++; resolve(); }); });
|
|
27
|
+
}
|
|
28
|
+
release() {
|
|
29
|
+
this.active--;
|
|
30
|
+
const next = this.queue.shift();
|
|
31
|
+
if (next)
|
|
32
|
+
next();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
12
35
|
// Detect installed Claude Code version at startup
|
|
13
36
|
function detectClaudeVersion() {
|
|
14
37
|
try {
|
|
@@ -76,6 +99,9 @@ function anthropicToOpenai(body) {
|
|
|
76
99
|
};
|
|
77
100
|
}
|
|
78
101
|
/** Translate Anthropic SSE → OpenAI SSE. */
|
|
102
|
+
// Track tool call state across stream chunks
|
|
103
|
+
let _streamToolIndex = 0;
|
|
104
|
+
let _streamToolId = '';
|
|
79
105
|
function translateStreamChunk(line) {
|
|
80
106
|
if (!line.startsWith('data: '))
|
|
81
107
|
return null;
|
|
@@ -84,13 +110,33 @@ function translateStreamChunk(line) {
|
|
|
84
110
|
return 'data: [DONE]\n\n';
|
|
85
111
|
try {
|
|
86
112
|
const e = JSON.parse(json);
|
|
113
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
114
|
+
if (e.type === 'content_block_start') {
|
|
115
|
+
const block = e.content_block;
|
|
116
|
+
if (block?.type === 'tool_use' && block.name) {
|
|
117
|
+
_streamToolId = block.id ?? `call_${_streamToolIndex}`;
|
|
118
|
+
return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: ts, model: 'claude', choices: [{ index: 0, delta: { tool_calls: [{ index: _streamToolIndex, id: _streamToolId, type: 'function', function: { name: block.name, arguments: '' } }] }, finish_reason: null }] })}\n\n`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
87
121
|
if (e.type === 'content_block_delta') {
|
|
88
122
|
const d = e.delta;
|
|
89
123
|
if (d?.type === 'text_delta' && d.text)
|
|
90
|
-
return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created:
|
|
124
|
+
return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: ts, model: 'claude', choices: [{ index: 0, delta: { content: d.text }, finish_reason: null }] })}\n\n`;
|
|
125
|
+
if (d?.type === 'input_json_delta' && d.partial_json)
|
|
126
|
+
return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: ts, model: 'claude', choices: [{ index: 0, delta: { tool_calls: [{ index: _streamToolIndex, function: { arguments: d.partial_json } }] }, finish_reason: null }] })}\n\n`;
|
|
127
|
+
}
|
|
128
|
+
if (e.type === 'content_block_stop') {
|
|
129
|
+
if (_streamToolId) {
|
|
130
|
+
_streamToolIndex++;
|
|
131
|
+
_streamToolId = '';
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (e.type === 'message_stop') {
|
|
136
|
+
_streamToolIndex = 0;
|
|
137
|
+
_streamToolId = '';
|
|
138
|
+
return `data: ${JSON.stringify({ id: 'chatcmpl-dario', object: 'chat.completion.chunk', created: ts, model: 'claude', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] })}\n\ndata: [DONE]\n\n`;
|
|
91
139
|
}
|
|
92
|
-
if (e.type === 'message_stop')
|
|
93
|
-
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`;
|
|
94
140
|
}
|
|
95
141
|
catch { }
|
|
96
142
|
return null;
|
|
@@ -229,6 +275,7 @@ export async function startProxy(opts = {}) {
|
|
|
229
275
|
};
|
|
230
276
|
const useCli = opts.cliBackend ?? false;
|
|
231
277
|
let requestCount = 0;
|
|
278
|
+
const semaphore = new Semaphore(MAX_CONCURRENT);
|
|
232
279
|
// Optional proxy authentication — pre-encode key buffer for performance
|
|
233
280
|
const apiKey = process.env.DARIO_API_KEY;
|
|
234
281
|
const apiKeyBuf = apiKey ? Buffer.from(apiKey) : null;
|
|
@@ -320,7 +367,8 @@ export async function startProxy(opts = {}) {
|
|
|
320
367
|
res.end(ERR_METHOD);
|
|
321
368
|
return;
|
|
322
369
|
}
|
|
323
|
-
// Proxy to Anthropic
|
|
370
|
+
// Proxy to Anthropic (with concurrency control)
|
|
371
|
+
await semaphore.acquire();
|
|
324
372
|
try {
|
|
325
373
|
const accessToken = await getAccessToken();
|
|
326
374
|
// Read request body with size limit and timeout (prevents slow-loris)
|
|
@@ -385,7 +433,7 @@ export async function startProxy(opts = {}) {
|
|
|
385
433
|
}
|
|
386
434
|
if (verbose) {
|
|
387
435
|
const modelInfo = modelOverride ? ` (model: ${modelOverride})` : '';
|
|
388
|
-
console.log(`[dario] #${requestCount} ${req.method} ${
|
|
436
|
+
console.log(`[dario] #${requestCount} ${req.method} ${urlPath}${modelInfo}`);
|
|
389
437
|
}
|
|
390
438
|
// Merge client beta flags with defaults
|
|
391
439
|
const clientBeta = req.headers['anthropic-beta'];
|
|
@@ -491,6 +539,9 @@ export async function startProxy(opts = {}) {
|
|
|
491
539
|
res.writeHead(502, JSON_HEADERS);
|
|
492
540
|
res.end(JSON.stringify({ error: 'Proxy error', message: 'Failed to reach upstream API' }));
|
|
493
541
|
}
|
|
542
|
+
finally {
|
|
543
|
+
semaphore.release();
|
|
544
|
+
}
|
|
494
545
|
});
|
|
495
546
|
server.on('error', (err) => {
|
|
496
547
|
if (err.code === 'EADDRINUSE') {
|
|
@@ -548,7 +599,7 @@ export async function startProxy(opts = {}) {
|
|
|
548
599
|
const refreshInterval = setInterval(async () => {
|
|
549
600
|
try {
|
|
550
601
|
const s = await getStatus();
|
|
551
|
-
if (s.status === 'expiring') {
|
|
602
|
+
if (s.status === 'expiring' || s.status === 'expired') {
|
|
552
603
|
console.log('[dario] Token expiring, refreshing...');
|
|
553
604
|
await getAccessToken(); // triggers refresh
|
|
554
605
|
}
|