@askalf/dario 6.9.1 → 6.9.3
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 +1 -0
- package/dist/cc-template.d.ts +14 -4
- package/dist/cc-template.js +29 -4
- package/dist/proxy.d.ts +15 -0
- package/dist/proxy.js +50 -5
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -473,6 +473,7 @@ The split isn't live, but it was announced once on short notice and could return
|
|
|
473
473
|
| Credentials | Your own subscription tokens, never logged, redacted from errors, `0600` on disk in `0700` dirs |
|
|
474
474
|
| Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list; refuses a non-loopback bind without `DARIO_API_KEY` |
|
|
475
475
|
| Telemetry | **None.** No analytics, no tracking, nothing phones home |
|
|
476
|
+
| Overhead | Measured in the open on every PR: [`scripts/bench-overhead.mjs`](./scripts/bench-overhead.mjs) runs a real proxy against an instant upstream beside a bare http server serving the same bytes. On loopback dario adds no measurable p50 wall time over that floor; the CPU per request is the number to watch, and the per-request [timing split](./docs/analytics.md#the-timing-split) shows it live |
|
|
476
477
|
| This README | CI fails if the line count above drifts from `src/` or a link or anchor here stops resolving ([`check-readme-line-count.mjs`](./scripts/check-readme-line-count.mjs), [`check-readme-links.mjs`](./scripts/check-readme-links.mjs)); the TUI screenshots are rendered from the real TUI and the diagrams are briefed art, not screenshots ([how](./scripts/readme/README.md)) |
|
|
477
478
|
|
|
478
479
|
```bash
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -16,11 +16,22 @@ export declare const CC_TEMPLATE: TemplateData;
|
|
|
16
16
|
export declare function filterToolsForPlatform<T extends {
|
|
17
17
|
name: string;
|
|
18
18
|
}>(tools: T[], platform: string): T[];
|
|
19
|
+
/**
|
|
20
|
+
* A tool definition the API will accept: an `input_schema` object with a
|
|
21
|
+
* string `type`. A capture can carry less — the 2026-09-18T01:26Z rebake on
|
|
22
|
+
* CC v2.1.275 recorded `advisor` as `{"name":"advisor","description":"",
|
|
23
|
+
* "input_schema":{}}`, a remote-config tool caught half-loaded — and Fable
|
|
24
|
+
* refuses any request advertising it (`tools.0.custom.input_schema.type: Field
|
|
25
|
+
* required`, dario#1376) while other families let it through. The name stays
|
|
26
|
+
* KNOWN (CC_NATIVE_NAMES_UNION, identity mapping, the config-scoped
|
|
27
|
+
* preservation); the definition is never put on the wire.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isAdvertisableToolDefinition(def: unknown): boolean;
|
|
30
|
+
/** Names in the bundle whose definition is not advertisable (see above). Empty on a clean bake. */
|
|
31
|
+
export declare const CC_TOOL_DEFINITIONS_UNADVERTISABLE: Set<string>;
|
|
19
32
|
/** CC's exact tool definitions for the current platform — filtered from the bundled union. */
|
|
20
33
|
export declare const CC_TOOL_DEFINITIONS: {
|
|
21
34
|
name: string;
|
|
22
|
-
description: string;
|
|
23
|
-
input_schema: Record<string, unknown>;
|
|
24
35
|
}[];
|
|
25
36
|
/** The UNFILTERED bundled union — every tool the bake knows across platforms
|
|
26
37
|
* (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
|
|
@@ -36,9 +47,8 @@ export declare const CC_TOOL_DEFINITIONS: {
|
|
|
36
47
|
* merge-mode base array, and Fable's no-tools shape. */
|
|
37
48
|
export declare const CC_TOOL_DEFINITIONS_UNION: {
|
|
38
49
|
name: string;
|
|
39
|
-
description: string;
|
|
40
|
-
input_schema: Record<string, unknown>;
|
|
41
50
|
}[];
|
|
51
|
+
/** Every name the bundle knows — including one whose definition is not advertisable (dario#1376). */
|
|
42
52
|
export declare const CC_NATIVE_NAMES_UNION: Set<string>;
|
|
43
53
|
/** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
|
|
44
54
|
* tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
|
package/dist/cc-template.js
CHANGED
|
@@ -29,8 +29,26 @@ export function filterToolsForPlatform(tools, platform) {
|
|
|
29
29
|
return true;
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A tool definition the API will accept: an `input_schema` object with a
|
|
34
|
+
* string `type`. A capture can carry less — the 2026-09-18T01:26Z rebake on
|
|
35
|
+
* CC v2.1.275 recorded `advisor` as `{"name":"advisor","description":"",
|
|
36
|
+
* "input_schema":{}}`, a remote-config tool caught half-loaded — and Fable
|
|
37
|
+
* refuses any request advertising it (`tools.0.custom.input_schema.type: Field
|
|
38
|
+
* required`, dario#1376) while other families let it through. The name stays
|
|
39
|
+
* KNOWN (CC_NATIVE_NAMES_UNION, identity mapping, the config-scoped
|
|
40
|
+
* preservation); the definition is never put on the wire.
|
|
41
|
+
*/
|
|
42
|
+
export function isAdvertisableToolDefinition(def) {
|
|
43
|
+
if (!def || typeof def !== 'object')
|
|
44
|
+
return false;
|
|
45
|
+
const schema = def.input_schema;
|
|
46
|
+
return !!schema && typeof schema === 'object' && typeof schema.type === 'string';
|
|
47
|
+
}
|
|
48
|
+
/** Names in the bundle whose definition is not advertisable (see above). Empty on a clean bake. */
|
|
49
|
+
export const CC_TOOL_DEFINITIONS_UNADVERTISABLE = new Set(TEMPLATE.tools.filter((t) => !isAdvertisableToolDefinition(t)).map((t) => String(t.name)));
|
|
32
50
|
/** CC's exact tool definitions for the current platform — filtered from the bundled union. */
|
|
33
|
-
export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, process.platform);
|
|
51
|
+
export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools.filter(isAdvertisableToolDefinition), process.platform);
|
|
34
52
|
/** The UNFILTERED bundled union — every tool the bake knows across platforms
|
|
35
53
|
* (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
|
|
36
54
|
* detection, and advertise paths intersect with what the CLIENT declared,
|
|
@@ -43,7 +61,8 @@ export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, proces
|
|
|
43
61
|
* upstream). Host-filtered CC_TOOL_DEFINITIONS stays correct for the paths
|
|
44
62
|
* with no client declaration to mirror: the full-template fallback, the
|
|
45
63
|
* merge-mode base array, and Fable's no-tools shape. */
|
|
46
|
-
export const CC_TOOL_DEFINITIONS_UNION = TEMPLATE.tools;
|
|
64
|
+
export const CC_TOOL_DEFINITIONS_UNION = TEMPLATE.tools.filter(isAdvertisableToolDefinition);
|
|
65
|
+
/** Every name the bundle knows — including one whose definition is not advertisable (dario#1376). */
|
|
47
66
|
export const CC_NATIVE_NAMES_UNION = new Set(TEMPLATE.tools.map((t) => String(t.name)));
|
|
48
67
|
/** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
|
|
49
68
|
* tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
|
|
@@ -2068,8 +2087,14 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
|
|
|
2068
2087
|
// authoritative source, never the template.
|
|
2069
2088
|
const availableCC = CC_TOOL_DEFINITIONS_UNION.filter((t) => !isMcpToolName(t.name) && clientToolNames.has(t.name.toLowerCase()));
|
|
2070
2089
|
const mcpTools = clientTools.filter((t) => isMcpToolName(t.name));
|
|
2071
|
-
|
|
2072
|
-
|
|
2090
|
+
// A CC-native name the bundle knows but cannot advertise (dario#1376:
|
|
2091
|
+
// `advisor` captured with an empty schema) is still identity-mapped
|
|
2092
|
+
// above; the only usable definition is the client's own, so it goes out
|
|
2093
|
+
// verbatim, as MCP tools do. The client's schema is what its parser
|
|
2094
|
+
// expects back in any case.
|
|
2095
|
+
const clientOwnNative = clientTools.filter((t) => typeof t.name === 'string' && CC_TOOL_DEFINITIONS_UNADVERTISABLE.has(t.name) && isAdvertisableToolDefinition(t));
|
|
2096
|
+
ccRequest.tools = availableCC.length > 0 || mcpTools.length > 0 || clientOwnNative.length > 0
|
|
2097
|
+
? dedupeToolsByName([...availableCC, ...clientOwnNative, ...mcpTools])
|
|
2073
2098
|
: CC_TOOL_DEFINITIONS;
|
|
2074
2099
|
}
|
|
2075
2100
|
}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -636,6 +636,21 @@ export interface ProxyLogEntry {
|
|
|
636
636
|
* null (logFile not configured). Errors are swallowed — log writes
|
|
637
637
|
* must never break the request path.
|
|
638
638
|
*/
|
|
639
|
+
/**
|
|
640
|
+
* The `data:` line of one SSE frame, without allocating a per-line array
|
|
641
|
+
* (the frame is `event: x\ndata: {...}\n\n`; the data line is the one that
|
|
642
|
+
* starts with the field name, at the start of the frame or after a newline).
|
|
643
|
+
* Null when the frame has no data line (a comment, a bare event).
|
|
644
|
+
*/
|
|
645
|
+
export declare function sseDataLine(frame: string): string | null;
|
|
646
|
+
/**
|
|
647
|
+
* Whether the analytics tap needs to parse this frame at all: only the
|
|
648
|
+
* message_start usage, the message_delta usage and thinking deltas feed a
|
|
649
|
+
* number it keeps. A text or tool-input delta — most of any stream — is
|
|
650
|
+
* skipped before JSON.parse. Substring tests on the data line; a frame that
|
|
651
|
+
* happens to contain these words inside a text delta merely costs a parse.
|
|
652
|
+
*/
|
|
653
|
+
export declare function analyticsFrameOfInterest(dataLine: string): boolean;
|
|
639
654
|
export declare function writeLogLine(stream: WriteStream | null, entry: ProxyLogEntry): void;
|
|
640
655
|
export declare function sanitizeError(err: unknown): string;
|
|
641
656
|
/**
|
package/dist/proxy.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getAccessToken, getStatus, ignoreCcCredentials } from './oauth.js';
|
|
|
9
9
|
import { buildHealthResponse, derivePoolStatus, probeRequested, shouldDiscloseHealthInternals, shouldRunServingProbe } from './health-response.js';
|
|
10
10
|
import { getServingProbe } from './serving-probe.js';
|
|
11
11
|
import { darioVersion } from './version.js';
|
|
12
|
-
import { buildCCRequest, applyCcPromptCaching, isGenuineCCClient, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
12
|
+
import { CC_TOOL_DEFINITIONS_UNADVERTISABLE, buildCCRequest, applyCcPromptCaching, isGenuineCCClient, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, overlayTemplateHeaderValues, forwardClientCCIdentityHeaders, isMcpToolName, CC_TEMPLATE, effectiveCacheControl, withForced1hBeta } from './cc-template.js';
|
|
13
13
|
import { stampCch, hasCchSeed } from './cch.js';
|
|
14
14
|
import { foldTiming, timingHeaders, timingLogFields } from './timing.js';
|
|
15
15
|
import { describeTemplate, detectDrift, checkCCCompat, probeInstalledCCVersion } from './live-fingerprint.js';
|
|
@@ -990,6 +990,31 @@ export function requiresClaudeLogin(poolSize, adminEnabled, hasUpstreamApiKey, n
|
|
|
990
990
|
* null (logFile not configured). Errors are swallowed — log writes
|
|
991
991
|
* must never break the request path.
|
|
992
992
|
*/
|
|
993
|
+
/**
|
|
994
|
+
* The `data:` line of one SSE frame, without allocating a per-line array
|
|
995
|
+
* (the frame is `event: x\ndata: {...}\n\n`; the data line is the one that
|
|
996
|
+
* starts with the field name, at the start of the frame or after a newline).
|
|
997
|
+
* Null when the frame has no data line (a comment, a bare event).
|
|
998
|
+
*/
|
|
999
|
+
export function sseDataLine(frame) {
|
|
1000
|
+
let at = frame.startsWith('data: ') ? 0 : frame.indexOf('\ndata: ');
|
|
1001
|
+
if (at < 0)
|
|
1002
|
+
return null;
|
|
1003
|
+
if (at > 0)
|
|
1004
|
+
at += 1;
|
|
1005
|
+
const end = frame.indexOf('\n', at);
|
|
1006
|
+
return end < 0 ? frame.slice(at) : frame.slice(at, end);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Whether the analytics tap needs to parse this frame at all: only the
|
|
1010
|
+
* message_start usage, the message_delta usage and thinking deltas feed a
|
|
1011
|
+
* number it keeps. A text or tool-input delta — most of any stream — is
|
|
1012
|
+
* skipped before JSON.parse. Substring tests on the data line; a frame that
|
|
1013
|
+
* happens to contain these words inside a text delta merely costs a parse.
|
|
1014
|
+
*/
|
|
1015
|
+
export function analyticsFrameOfInterest(dataLine) {
|
|
1016
|
+
return dataLine.includes('"message_start"') || dataLine.includes('"message_delta"') || dataLine.includes('thinking_delta');
|
|
1017
|
+
}
|
|
993
1018
|
export function writeLogLine(stream, entry) {
|
|
994
1019
|
if (!stream)
|
|
995
1020
|
return;
|
|
@@ -4077,7 +4102,14 @@ export async function startProxy(opts = {}) {
|
|
|
4077
4102
|
// hand the client an OpenAI-shaped response for a Messages request. This
|
|
4078
4103
|
// route still has no reverse translation; the codex route above does,
|
|
4079
4104
|
// which is why it takes both shapes and this one does not.
|
|
4080
|
-
|
|
4105
|
+
// Only the drained-pool branch below reads this, and computing it means
|
|
4106
|
+
// parsing the whole client body again — on a 200 KB Claude Code turn
|
|
4107
|
+
// that was ~2% of dario's own CPU on every request that never took the
|
|
4108
|
+
// branch (scripts/bench-overhead.mjs profile, v6.9.2). Resolved only
|
|
4109
|
+
// when the branch can be taken.
|
|
4110
|
+
const fallbackModel = (!upstreamApiKey && !poolAccount && openaiBackend && isOpenAI)
|
|
4111
|
+
? (selectPoolFallbackForBody(body)[0] ?? null)
|
|
4112
|
+
: null;
|
|
4081
4113
|
if (!upstreamApiKey && !poolAccount && fallbackModel && openaiBackend && isOpenAI) {
|
|
4082
4114
|
const fallbackBody = buildPoolFallbackBody(body, fallbackModel);
|
|
4083
4115
|
if (!fallbackBody) {
|
|
@@ -5287,14 +5319,22 @@ export async function startProxy(opts = {}) {
|
|
|
5287
5319
|
upstreamDoneAt = Date.now();
|
|
5288
5320
|
break;
|
|
5289
5321
|
}
|
|
5290
|
-
// Parse SSE events for analytics regardless of routing branch
|
|
5322
|
+
// Parse SSE events for analytics regardless of routing branch.
|
|
5323
|
+
// Only three frame kinds carry a number this tap reads — the
|
|
5324
|
+
// message_start usage, the message_delta usage, and thinking
|
|
5325
|
+
// deltas (for the ~4-chars-per-token estimate). Every other frame
|
|
5326
|
+
// is a text or tool delta, i.e. most of a stream, and parsing
|
|
5327
|
+
// them was the single largest cost in dario's own streaming path
|
|
5328
|
+
// (scripts/bench-overhead.mjs profile, v6.9.2). A substring test
|
|
5329
|
+
// on the data line decides before JSON.parse; the parse itself
|
|
5330
|
+
// is unchanged for the frames that pass.
|
|
5291
5331
|
if (analyticsDecoder && value) {
|
|
5292
5332
|
analyticsBuffer += analyticsDecoder.decode(value, { stream: true });
|
|
5293
5333
|
const parts = analyticsBuffer.split('\n\n');
|
|
5294
5334
|
analyticsBuffer = parts.pop() ?? '';
|
|
5295
5335
|
for (const part of parts) {
|
|
5296
|
-
const dataLine = part
|
|
5297
|
-
if (!dataLine)
|
|
5336
|
+
const dataLine = sseDataLine(part);
|
|
5337
|
+
if (!dataLine || !analyticsFrameOfInterest(dataLine))
|
|
5298
5338
|
continue;
|
|
5299
5339
|
try {
|
|
5300
5340
|
const e = JSON.parse(dataLine.slice(6));
|
|
@@ -5610,6 +5650,11 @@ export async function startProxy(opts = {}) {
|
|
|
5610
5650
|
// One-line template summary so users can tell at a glance whether they
|
|
5611
5651
|
// booted on a fresh live capture or a stale bundled fallback.
|
|
5612
5652
|
console.log(`[dario] template: ${describeTemplate(CC_TEMPLATE)}`);
|
|
5653
|
+
// A bundled definition the API refuses (dario#1376: advisor captured with an
|
|
5654
|
+
// empty schema) is kept as a known name and never advertised; say so once.
|
|
5655
|
+
if (CC_TOOL_DEFINITIONS_UNADVERTISABLE.size > 0) {
|
|
5656
|
+
console.log(`[dario] template: ${CC_TOOL_DEFINITIONS_UNADVERTISABLE.size} tool definition${CC_TOOL_DEFINITIONS_UNADVERTISABLE.size === 1 ? '' : 's'} without input_schema.type — known, never advertised: ${[...CC_TOOL_DEFINITIONS_UNADVERTISABLE].join(', ')}`);
|
|
5657
|
+
}
|
|
5613
5658
|
// Drift check: compare captured CC version to the installed binary. If
|
|
5614
5659
|
// they differ, force the background refresh to bypass TTL so the next
|
|
5615
5660
|
// startup picks up the new capture. Drifted caches still serve the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.9.
|
|
3
|
+
"version": "6.9.3",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"fix:pkg": "node -e \"const fs=require('fs');fs.writeFileSync('package.json',JSON.stringify(JSON.parse(fs.readFileSync('package.json','utf-8')),null,2)+'\\n')\"",
|
|
43
43
|
"audit:tui": "node tools/tui-audit/audit.mjs",
|
|
44
44
|
"readme:assets": "node scripts/readme/terminal.mjs && node scripts/readme/tui.mjs",
|
|
45
|
-
"check:readme": "node scripts/check-readme-line-count.mjs && node scripts/check-readme-links.mjs"
|
|
45
|
+
"check:readme": "node scripts/check-readme-line-count.mjs && node scripts/check-readme-links.mjs",
|
|
46
|
+
"bench": "node scripts/bench-overhead.mjs"
|
|
46
47
|
},
|
|
47
48
|
"keywords": [
|
|
48
49
|
"llm",
|