@askalf/dario 3.19.5 → 3.20.1
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/cc-template.d.ts +1 -0
- package/dist/cc-template.js +8 -1
- package/dist/cli.js +39 -6
- package/dist/oauth.d.ts +53 -0
- package/dist/oauth.js +154 -0
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.js +1 -0
- package/package.json +2 -2
package/dist/cc-template.d.ts
CHANGED
|
@@ -150,6 +150,7 @@ export declare function buildCCRequest(clientBody: Record<string, unknown>, bill
|
|
|
150
150
|
}, opts?: {
|
|
151
151
|
preserveTools?: boolean;
|
|
152
152
|
hybridTools?: boolean;
|
|
153
|
+
noAutoDetect?: boolean;
|
|
153
154
|
}): {
|
|
154
155
|
body: Record<string, unknown>;
|
|
155
156
|
toolMap: Map<string, ToolMapping>;
|
package/dist/cc-template.js
CHANGED
|
@@ -653,8 +653,15 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
|
|
|
653
653
|
// brand name is still present, decide whether to auto-switch into
|
|
654
654
|
// preserve-tools behavior below. Explicit --hybrid-tools outranks the
|
|
655
655
|
// heuristic (operator opt-in wins). dario#40.
|
|
656
|
+
//
|
|
657
|
+
// `noAutoDetect` skips the detector entirely — operators who want the
|
|
658
|
+
// full CC fingerprint restored (tools array included) even when their
|
|
659
|
+
// client is Cline/Kilo/Roo can opt out. They keep explicit control via
|
|
660
|
+
// --preserve-tools per session. dario#40 (ringge's fingerprint concern).
|
|
656
661
|
const rawSystemForDetection = extractSystemText(clientBody);
|
|
657
|
-
const detectedClient =
|
|
662
|
+
const detectedClient = opts.noAutoDetect
|
|
663
|
+
? undefined
|
|
664
|
+
: (detectTextToolClient(rawSystemForDetection) ?? undefined);
|
|
658
665
|
const autoPreserve = Boolean(detectedClient) && !opts.hybridTools;
|
|
659
666
|
const effectivePreserveTools = Boolean(opts.preserveTools) || autoPreserve;
|
|
660
667
|
// ── Strip thinking from history ──
|
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ if (!('Bun' in globalThis) && !process.env.DARIO_NO_BUN) {
|
|
|
35
35
|
import { unlink } from 'node:fs/promises';
|
|
36
36
|
import { join } from 'node:path';
|
|
37
37
|
import { homedir } from 'node:os';
|
|
38
|
-
import { startAutoOAuthFlow, getStatus, refreshTokens, loadCredentials } from './oauth.js';
|
|
38
|
+
import { startAutoOAuthFlow, startManualOAuthFlow, detectHeadlessEnvironment, getStatus, refreshTokens, loadCredentials } from './oauth.js';
|
|
39
39
|
import { startProxy, sanitizeError } from './proxy.js';
|
|
40
40
|
import { listAccountAliases, loadAllAccounts, addAccountViaOAuth, removeAccount } from './accounts.js';
|
|
41
41
|
import { listBackends, saveBackend, removeBackend } from './openai-backend.js';
|
|
@@ -46,6 +46,7 @@ async function login() {
|
|
|
46
46
|
console.log(' dario — Claude Login');
|
|
47
47
|
console.log(' ───────────────────');
|
|
48
48
|
console.log('');
|
|
49
|
+
const manualFlag = args.includes('--manual') || args.includes('--headless');
|
|
49
50
|
// Check for existing credentials (Claude Code or dario's own)
|
|
50
51
|
const creds = await loadCredentials();
|
|
51
52
|
if (creds?.claudeAiOauth?.accessToken && creds.claudeAiOauth.expiresAt > Date.now()) {
|
|
@@ -79,8 +80,21 @@ async function login() {
|
|
|
79
80
|
console.log(' No Claude Code credentials found. Starting OAuth flow...');
|
|
80
81
|
console.log('');
|
|
81
82
|
}
|
|
83
|
+
// If the user didn't explicitly pick `--manual`, surface a hint when
|
|
84
|
+
// heuristics suggest the local-callback flow won't work (SSH session,
|
|
85
|
+
// container). We don't auto-flip — false positives would be more
|
|
86
|
+
// annoying than false negatives — but the hint keeps users from
|
|
87
|
+
// waiting for a browser redirect that can't land.
|
|
88
|
+
if (!manualFlag) {
|
|
89
|
+
const reason = detectHeadlessEnvironment();
|
|
90
|
+
if (reason) {
|
|
91
|
+
console.log(` Note: ${reason}. If the browser redirect doesn't land,`);
|
|
92
|
+
console.log(' re-run with: dario login --manual');
|
|
93
|
+
console.log('');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
82
96
|
try {
|
|
83
|
-
const tokens = await startAutoOAuthFlow();
|
|
97
|
+
const tokens = manualFlag ? await startManualOAuthFlow() : await startAutoOAuthFlow();
|
|
84
98
|
const expiresIn = Math.round((tokens.expiresAt - Date.now()) / 60000);
|
|
85
99
|
console.log(' Login successful!');
|
|
86
100
|
console.log(` Token expires in ${expiresIn} minutes (auto-refreshes).`);
|
|
@@ -89,9 +103,15 @@ async function login() {
|
|
|
89
103
|
console.log('');
|
|
90
104
|
}
|
|
91
105
|
catch (err) {
|
|
106
|
+
const msg = sanitizeError(err);
|
|
92
107
|
console.error('');
|
|
93
|
-
console.error(` Login failed: ${
|
|
94
|
-
|
|
108
|
+
console.error(` Login failed: ${msg}`);
|
|
109
|
+
if (!manualFlag && /callback server|EADDRINUSE|bind|timed out/i.test(msg)) {
|
|
110
|
+
console.error(' Hint: try `dario login --manual` for headless / container setups.');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.error(' Try again with `dario login`.');
|
|
114
|
+
}
|
|
95
115
|
process.exit(1);
|
|
96
116
|
}
|
|
97
117
|
}
|
|
@@ -175,9 +195,14 @@ async function proxy() {
|
|
|
175
195
|
console.error('[dario] --preserve-tools and --hybrid-tools are mutually exclusive. Pick one.');
|
|
176
196
|
process.exit(1);
|
|
177
197
|
}
|
|
198
|
+
// Opt-out for v3.19.3's text-tool-client auto-detection. Operators who
|
|
199
|
+
// want the full CC fingerprint restored (tools array included) even
|
|
200
|
+
// when Cline/Kilo/Roo is detected can pass --no-auto-detect; they keep
|
|
201
|
+
// explicit control with --preserve-tools per session. dario#40 (ringge).
|
|
202
|
+
const noAutoDetect = args.includes('--no-auto-detect') || args.includes('--no-auto-preserve');
|
|
178
203
|
const modelArg = args.find(a => a.startsWith('--model='));
|
|
179
204
|
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
180
|
-
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools });
|
|
205
|
+
await startProxy({ port, host, verbose, verboseBodies, model, passthrough, preserveTools, hybridTools, noAutoDetect });
|
|
181
206
|
}
|
|
182
207
|
async function accounts() {
|
|
183
208
|
const sub = args[1];
|
|
@@ -384,7 +409,10 @@ async function help() {
|
|
|
384
409
|
dario — Use your Claude subscription as an API.
|
|
385
410
|
|
|
386
411
|
Usage:
|
|
387
|
-
dario login
|
|
412
|
+
dario login [--manual] Detect credentials + start proxy (or run OAuth)
|
|
413
|
+
--manual (alias: --headless) for container / SSH
|
|
414
|
+
setups — prints an authorize URL and reads the
|
|
415
|
+
code you paste back instead of a local redirect
|
|
388
416
|
dario proxy [options] Start the API proxy server
|
|
389
417
|
dario status Check authentication status
|
|
390
418
|
dario refresh Force token refresh
|
|
@@ -413,6 +441,11 @@ async function help() {
|
|
|
413
441
|
Loses subscription routing; use for custom agents
|
|
414
442
|
--hybrid-tools Remap to CC tools, inject sessionId/requestId/etc.
|
|
415
443
|
Keeps subscription routing for custom agents
|
|
444
|
+
--no-auto-detect Disable Cline/Kilo/Roo auto-preserve-tools
|
|
445
|
+
(v3.19.3 behavior). Keeps CC fingerprint
|
|
446
|
+
intact even when a text-tool client is
|
|
447
|
+
detected; use --preserve-tools per session
|
|
448
|
+
when edits are needed. (dario#40)
|
|
416
449
|
--port=PORT Port to listen on (default: 3456)
|
|
417
450
|
--host=ADDRESS Address to bind to (default: 127.0.0.1)
|
|
418
451
|
Use 0.0.0.0 for LAN; see README for DARIO_API_KEY
|
package/dist/oauth.d.ts
CHANGED
|
@@ -19,6 +19,59 @@ export declare function loadCredentials(): Promise<CredentialsFile | null>;
|
|
|
19
19
|
* Opens browser, captures the authorization code automatically.
|
|
20
20
|
*/
|
|
21
21
|
export declare function startAutoOAuthFlow(): Promise<OAuthTokens>;
|
|
22
|
+
/**
|
|
23
|
+
* Build the authorize URL used by the manual / headless flow. Exported
|
|
24
|
+
* so tests can assert the shape (code=true, MANUAL_REDIRECT_URI, PKCE)
|
|
25
|
+
* without exercising the full interactive flow.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildManualAuthorizeUrl(cfg: {
|
|
28
|
+
clientId: string;
|
|
29
|
+
authorizeUrl: string;
|
|
30
|
+
scopes: string;
|
|
31
|
+
}, codeChallenge: string, state: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse whatever the user pastes back from Anthropic's success page.
|
|
34
|
+
*
|
|
35
|
+
* The success page renders the authorization code and state joined with
|
|
36
|
+
* a `#` (the fragment-identifier convention CC itself uses for its
|
|
37
|
+
* `claude setup-token` flow), so the happy-path paste is `code#state`.
|
|
38
|
+
* Some browsers / copy UIs strip the fragment, so we also accept a bare
|
|
39
|
+
* code. When state is present, callers should verify it matches the
|
|
40
|
+
* state they generated; when absent, callers can prompt separately or
|
|
41
|
+
* accept the trade-off (code + PKCE + client_id are still verified on
|
|
42
|
+
* the token exchange).
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseManualPaste(input: string): {
|
|
45
|
+
code: string;
|
|
46
|
+
state: string | null;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Heuristic for "dario is probably running somewhere the local-callback
|
|
50
|
+
* OAuth flow won't work because the browser is on a different host."
|
|
51
|
+
* Returns a short reason string when the heuristic fires, null otherwise.
|
|
52
|
+
*
|
|
53
|
+
* Callers use this to *offer* `--manual` to the user, never to force it —
|
|
54
|
+
* false positives are more annoying than false negatives (the user can
|
|
55
|
+
* always opt in explicitly).
|
|
56
|
+
*/
|
|
57
|
+
export declare function detectHeadlessEnvironment(): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Manual / headless OAuth flow (dario #43).
|
|
60
|
+
*
|
|
61
|
+
* Mirrors Claude Code's own `claude setup-token` flow: asks Anthropic to
|
|
62
|
+
* display the authorization code as text instead of redirecting to a
|
|
63
|
+
* local callback server, then reads the code the user copies back.
|
|
64
|
+
* Works for container installs (browser on host, dario in container),
|
|
65
|
+
* SSH installs (no browser on the remote box), and any other setup
|
|
66
|
+
* where a localhost redirect can't reach the dario process.
|
|
67
|
+
*
|
|
68
|
+
* Security posture is unchanged from the auto flow: PKCE + client_id +
|
|
69
|
+
* single-use code + server-side code expiry. State parameter is
|
|
70
|
+
* verified when the pasted input includes it; bare-code pastes still
|
|
71
|
+
* exchange because state isn't load-bearing for the token endpoint
|
|
72
|
+
* (it's CSRF protection for a redirect we don't have here).
|
|
73
|
+
*/
|
|
74
|
+
export declare function startManualOAuthFlow(): Promise<OAuthTokens>;
|
|
22
75
|
/**
|
|
23
76
|
* Refresh the access token using the refresh token.
|
|
24
77
|
* Retries with exponential backoff on transient failures.
|
package/dist/oauth.js
CHANGED
|
@@ -5,11 +5,18 @@
|
|
|
5
5
|
* Handles authorization, token exchange, storage, and auto-refresh.
|
|
6
6
|
*/
|
|
7
7
|
import { randomBytes, createHash } from 'node:crypto';
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
9
|
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
9
10
|
import { execFile } from 'node:child_process';
|
|
10
11
|
import { dirname, join } from 'node:path';
|
|
11
12
|
import { homedir, platform } from 'node:os';
|
|
12
13
|
import { detectCCOAuthConfig } from './cc-oauth-detect.js';
|
|
14
|
+
// Manual-flow redirect URI. Anthropic's authorize endpoint special-cases
|
|
15
|
+
// this value (also baked into CC as MANUAL_REDIRECT_URL) to render the
|
|
16
|
+
// authorization code + state on a copy-paste success page instead of
|
|
17
|
+
// redirecting back to a localhost callback. Used by startManualOAuthFlow
|
|
18
|
+
// for container / headless / SSH installs where a local bind won't work.
|
|
19
|
+
const MANUAL_REDIRECT_URI = 'https://platform.claude.com/oauth/code/callback';
|
|
13
20
|
// OAuth config is auto-detected at runtime from the installed Claude Code
|
|
14
21
|
// binary. This eliminates the "Anthropic rotated the client_id again" class
|
|
15
22
|
// of bugs — dario stays in sync with whatever CC version the user has
|
|
@@ -293,6 +300,153 @@ async function exchangeCodeWithRedirect(code, codeVerifier, state, port) {
|
|
|
293
300
|
await saveCredentials({ claudeAiOauth: tokens });
|
|
294
301
|
return tokens;
|
|
295
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Build the authorize URL used by the manual / headless flow. Exported
|
|
305
|
+
* so tests can assert the shape (code=true, MANUAL_REDIRECT_URI, PKCE)
|
|
306
|
+
* without exercising the full interactive flow.
|
|
307
|
+
*/
|
|
308
|
+
export function buildManualAuthorizeUrl(cfg, codeChallenge, state) {
|
|
309
|
+
const params = new URLSearchParams({
|
|
310
|
+
code: 'true',
|
|
311
|
+
client_id: cfg.clientId,
|
|
312
|
+
response_type: 'code',
|
|
313
|
+
redirect_uri: MANUAL_REDIRECT_URI,
|
|
314
|
+
scope: cfg.scopes,
|
|
315
|
+
code_challenge: codeChallenge,
|
|
316
|
+
code_challenge_method: 'S256',
|
|
317
|
+
state,
|
|
318
|
+
});
|
|
319
|
+
return `${cfg.authorizeUrl}?${params.toString()}`;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Parse whatever the user pastes back from Anthropic's success page.
|
|
323
|
+
*
|
|
324
|
+
* The success page renders the authorization code and state joined with
|
|
325
|
+
* a `#` (the fragment-identifier convention CC itself uses for its
|
|
326
|
+
* `claude setup-token` flow), so the happy-path paste is `code#state`.
|
|
327
|
+
* Some browsers / copy UIs strip the fragment, so we also accept a bare
|
|
328
|
+
* code. When state is present, callers should verify it matches the
|
|
329
|
+
* state they generated; when absent, callers can prompt separately or
|
|
330
|
+
* accept the trade-off (code + PKCE + client_id are still verified on
|
|
331
|
+
* the token exchange).
|
|
332
|
+
*/
|
|
333
|
+
export function parseManualPaste(input) {
|
|
334
|
+
const trimmed = input.trim();
|
|
335
|
+
if (!trimmed)
|
|
336
|
+
return { code: '', state: null };
|
|
337
|
+
const hashIdx = trimmed.indexOf('#');
|
|
338
|
+
if (hashIdx === -1)
|
|
339
|
+
return { code: trimmed, state: null };
|
|
340
|
+
return {
|
|
341
|
+
code: trimmed.slice(0, hashIdx).trim(),
|
|
342
|
+
state: trimmed.slice(hashIdx + 1).trim(),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Heuristic for "dario is probably running somewhere the local-callback
|
|
347
|
+
* OAuth flow won't work because the browser is on a different host."
|
|
348
|
+
* Returns a short reason string when the heuristic fires, null otherwise.
|
|
349
|
+
*
|
|
350
|
+
* Callers use this to *offer* `--manual` to the user, never to force it —
|
|
351
|
+
* false positives are more annoying than false negatives (the user can
|
|
352
|
+
* always opt in explicitly).
|
|
353
|
+
*/
|
|
354
|
+
export function detectHeadlessEnvironment() {
|
|
355
|
+
if (process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.SSH_CONNECTION) {
|
|
356
|
+
return 'SSH session detected';
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
if (existsSync('/.dockerenv')) {
|
|
360
|
+
return 'container detected (/.dockerenv)';
|
|
361
|
+
}
|
|
362
|
+
if (existsSync('/proc/1/cgroup')) {
|
|
363
|
+
const cg = readFileSync('/proc/1/cgroup', 'utf-8');
|
|
364
|
+
if (/\b(docker|containerd|lxc|kubepods)\b/.test(cg)) {
|
|
365
|
+
return 'container detected (cgroup)';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch { /* best-effort — /proc is Linux-only, absence is fine */ }
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Manual / headless OAuth flow (dario #43).
|
|
374
|
+
*
|
|
375
|
+
* Mirrors Claude Code's own `claude setup-token` flow: asks Anthropic to
|
|
376
|
+
* display the authorization code as text instead of redirecting to a
|
|
377
|
+
* local callback server, then reads the code the user copies back.
|
|
378
|
+
* Works for container installs (browser on host, dario in container),
|
|
379
|
+
* SSH installs (no browser on the remote box), and any other setup
|
|
380
|
+
* where a localhost redirect can't reach the dario process.
|
|
381
|
+
*
|
|
382
|
+
* Security posture is unchanged from the auto flow: PKCE + client_id +
|
|
383
|
+
* single-use code + server-side code expiry. State parameter is
|
|
384
|
+
* verified when the pasted input includes it; bare-code pastes still
|
|
385
|
+
* exchange because state isn't load-bearing for the token endpoint
|
|
386
|
+
* (it's CSRF protection for a redirect we don't have here).
|
|
387
|
+
*/
|
|
388
|
+
export async function startManualOAuthFlow() {
|
|
389
|
+
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
390
|
+
const state = base64url(randomBytes(16));
|
|
391
|
+
const cfg = await getOAuthConfig();
|
|
392
|
+
const authUrl = buildManualAuthorizeUrl(cfg, codeChallenge, state);
|
|
393
|
+
console.log('');
|
|
394
|
+
console.log(' Open this URL in any browser (on any machine):');
|
|
395
|
+
console.log('');
|
|
396
|
+
console.log(` ${authUrl}`);
|
|
397
|
+
console.log('');
|
|
398
|
+
console.log(' After you approve, Anthropic will display an authorization code.');
|
|
399
|
+
console.log(' Paste it below (format: "code#state" or just the code).');
|
|
400
|
+
console.log('');
|
|
401
|
+
const pasted = await readLineFromStdin(' Code: ');
|
|
402
|
+
const { code, state: returnedState } = parseManualPaste(pasted);
|
|
403
|
+
if (!code) {
|
|
404
|
+
throw new Error('No authorization code entered. Re-run `dario login --manual`.');
|
|
405
|
+
}
|
|
406
|
+
if (returnedState && returnedState !== state) {
|
|
407
|
+
throw new Error('State mismatch — the pasted code is from a different login attempt. Re-run `dario login --manual` and paste the most recent code.');
|
|
408
|
+
}
|
|
409
|
+
return exchangeCodeManual(code, codeVerifier, state);
|
|
410
|
+
}
|
|
411
|
+
async function exchangeCodeManual(code, codeVerifier, state) {
|
|
412
|
+
const cfg = await getOAuthConfig();
|
|
413
|
+
const res = await fetch(cfg.tokenUrl, {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: { 'Content-Type': 'application/json' },
|
|
416
|
+
body: JSON.stringify({
|
|
417
|
+
grant_type: 'authorization_code',
|
|
418
|
+
client_id: cfg.clientId,
|
|
419
|
+
code,
|
|
420
|
+
redirect_uri: MANUAL_REDIRECT_URI,
|
|
421
|
+
code_verifier: codeVerifier,
|
|
422
|
+
state,
|
|
423
|
+
}),
|
|
424
|
+
signal: AbortSignal.timeout(30000),
|
|
425
|
+
});
|
|
426
|
+
if (!res.ok) {
|
|
427
|
+
const body = await res.text().catch(() => '');
|
|
428
|
+
throw new Error(`Token exchange failed (HTTP ${res.status}): ${body.slice(0, 200)}`);
|
|
429
|
+
}
|
|
430
|
+
const data = await res.json();
|
|
431
|
+
const tokens = {
|
|
432
|
+
accessToken: data.access_token,
|
|
433
|
+
refreshToken: data.refresh_token,
|
|
434
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
435
|
+
scopes: data.scope?.split(' ') || ['user:inference'],
|
|
436
|
+
};
|
|
437
|
+
await saveCredentials({ claudeAiOauth: tokens });
|
|
438
|
+
return tokens;
|
|
439
|
+
}
|
|
440
|
+
async function readLineFromStdin(prompt) {
|
|
441
|
+
const { createInterface } = await import('node:readline/promises');
|
|
442
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
443
|
+
try {
|
|
444
|
+
return (await rl.question(prompt)).trim();
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
rl.close();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
296
450
|
/**
|
|
297
451
|
* Refresh the access token using the refresh token.
|
|
298
452
|
* Retries with exponential backoff on transient failures.
|
package/dist/proxy.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ interface ProxyOptions {
|
|
|
11
11
|
passthrough?: boolean;
|
|
12
12
|
preserveTools?: boolean;
|
|
13
13
|
hybridTools?: boolean;
|
|
14
|
+
noAutoDetect?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export declare function sanitizeError(err: unknown): string;
|
|
16
17
|
export declare function startProxy(opts?: ProxyOptions): Promise<void>;
|
package/dist/proxy.js
CHANGED
|
@@ -982,6 +982,7 @@ export async function startProxy(opts = {}) {
|
|
|
982
982
|
const { body: ccBody, toolMap, detectedClient } = buildCCRequest(r, billingTag, CACHE_1H, bodyIdentity, {
|
|
983
983
|
preserveTools: opts.preserveTools ?? false,
|
|
984
984
|
hybridTools: opts.hybridTools ?? false,
|
|
985
|
+
noAutoDetect: opts.noAutoDetect ?? false,
|
|
985
986
|
});
|
|
986
987
|
// Log the auto-preserve-tools switch once per text-tool
|
|
987
988
|
// client family. Skip when the operator already opted into
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.1",
|
|
4
4
|
"description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && cp src/cc-template-data.json dist/ && node -e \"require('fs').mkdirSync('dist/shim',{recursive:true})\" && cp src/shim/runtime.cjs dist/shim/",
|
|
24
|
-
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs",
|
|
24
|
+
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/tool-schema-contract.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/pool-sticky.mjs && node test/sealed-pool.mjs && node test/live-fingerprint.mjs && node test/shim-runtime.mjs && node test/shim-e2e.mjs && node test/proxy-header-order.mjs && node test/drift-detection.mjs && node test/compat-range.mjs && node test/doctor-formatter.mjs && node test/atomic-write.mjs && node test/account-refresh-singleflight.mjs && node test/streaming-edge-cases.mjs && node test/client-detection.mjs && node test/manual-oauth-flow.mjs",
|
|
25
25
|
"audit": "npm audit --production --audit-level=high",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"start": "node dist/cli.js",
|