@askalf/dario 4.8.134 → 4.8.136

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 CHANGED
@@ -224,7 +224,7 @@ Tune via `~/.dario/config.json` → `overageGuard`, or CLI flags: `--overage-beh
224
224
  - **Multi-account pool.** Drop Claude accounts in `~/.dario/accounts/` and pool mode auto-activates — one is enough to serve, so `dario accounts add` alone bootstraps a proxy with no `dario login` step. With more, every request routes to the account with the most headroom, multi-turn sessions pin to one account so the prompt cache survives, in-flight 429s fail over to a peer before your client sees an error. → [`docs/multi-account-pool.md`](./docs/multi-account-pool.md)
225
225
  - **Headless admin API (`DARIO_ADMIN=1`).** Provision and manage pool accounts entirely over HTTP — start with zero accounts, `POST /admin/login/start`, paste the code back, and the account is routable the moment the `200` lands (live pool hot-reload, no restart). Token-gated even on loopback, audit-logged, rate-limited; `GET /admin/accounts` reports live per-account headroom. Built for Docker / k8s / Raspberry-Pi deployments where a console is the awkward part. → [`docs/admin-api.md`](./docs/admin-api.md)
226
226
  - **Behavioral stealth (`--stealth`).** Static wire fidelity covers *what* the request looks like; `--stealth` adds *when* it arrives — response-length-correlated think time and 1.2–4.2s session-start latency, the inter-arrival pattern real interactive sessions have and agent loops don't. → [`docs/wire-fidelity.md`](./docs/wire-fidelity.md)
227
- - **Runs any non-Claude-Code agent.** A 64-entry schema-verified `TOOL_MAP` pre-maps Cline, Roo, Kilo, Cursor, Windsurf, Continue, Copilot, OpenHands, OpenClaw, Hermes, [hands](https://github.com/askalf/hands) tool names to CC's native set. No flag, no validator errors. → [`docs/integrations/agent-compat.md`](./docs/integrations/agent-compat.md)
227
+ - **Runs any non-Claude-Code agent.** A 64-entry schema-verified `TOOL_MAP` pre-maps Cline, Roo, Kilo, Cursor, Windsurf, Continue, Copilot, OpenHands, OpenClaw, Hermes, [hands](https://github.com/askalf/hands) tool names to CC's native set. No flag, no validator errors. MCP tools (`mcp__server__tool`) forward verbatim — the shape real CC uses for session-attached servers (v4.8.135). → [`docs/integrations/agent-compat.md`](./docs/integrations/agent-compat.md)
228
228
  - **Recover output capability.** `dario proxy --system-prompt=partial` strips CC's tone/verbosity/no-comments constraints for 1.2–2.8× more output on open-ended work — empirically without flipping billing (the classifier doesn't read that slot). [Discussion #183](https://github.com/askalf/dario/discussions/183) has the per-prompt receipts. → [`docs/system-prompt.md`](./docs/system-prompt.md)
229
229
  - **Honor client thinking (`--honor-client-thinking`).** By default dario rebuilds the outbound request with CC's interactive thinking shape regardless of what the client sent. Pass this flag (or `DARIO_HONOR_CLIENT_THINKING=1`) to pass a non-CC client's own `thinking` block through unchanged. Off by default; the rebuild-to-CC path is what keeps the subscription pool routing.
230
230
  - **Preserve structured output (`--preserve-output-format`).** By default dario rebuilds `output_config` from CC's template (effort only), dropping a client's `output_config.format` JSON schema, so structured-output clients get unconstrained prose their strict parser rejects. Pass this flag (or `DARIO_PRESERVE_OUTPUT_FORMAT=1`) to carry the client's schema through unchanged — SDK clients like the Vercel AI SDK's `generateObject` then get schema-constrained output. Off by default; empirically without flipping billing (verified `five_hour` on `claude-sonnet-4-6`).
@@ -56,13 +56,45 @@ export declare const CC_TOOL_DEFINITIONS: {
56
56
  description: string;
57
57
  input_schema: Record<string, unknown>;
58
58
  }[];
59
+ /** The UNFILTERED bundled union — every tool the bake knows across platforms
60
+ * (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
61
+ * detection, and advertise paths intersect with what the CLIENT declared,
62
+ * and the client's declaration already encodes its platform — a Linux CC
63
+ * never declares PowerShell. Filtering those paths by the PROXY HOST's
64
+ * process.platform (pre-v4.8.136) made a Linux-hosted dario treat a win32
65
+ * client's PowerShell/Glob/Grep as non-native: PowerShell fell to the
66
+ * unmapped round-robin, and all three were dropped from the advertised
67
+ * array (Glob/Grep translated via lowercase aliases but were never sent
68
+ * upstream). Host-filtered CC_TOOL_DEFINITIONS stays correct for the paths
69
+ * with no client declaration to mirror: the full-template fallback, the
70
+ * merge-mode base array, and Fable's no-tools shape. */
71
+ export declare const CC_TOOL_DEFINITIONS_UNION: {
72
+ name: string;
73
+ description: string;
74
+ input_schema: Record<string, unknown>;
75
+ }[];
76
+ export declare const CC_NATIVE_NAMES_UNION: Set<string>;
59
77
  /** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
60
78
  * tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
61
79
  * cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
62
80
  * a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
63
81
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
64
- * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
82
+ * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake).
83
+ * HOST-filtered — the routing, detection, and advertise paths use the
84
+ * _UNION variants below so a client on a different platform than the proxy
85
+ * host still identity-maps (v4.8.136). */
65
86
  export declare const CC_NATIVE_NAMES: Set<string>;
87
+ /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
88
+ * Real CC advertises them VERBATIM after its built-ins — the schemas are
89
+ * operator-supplied, so there is no canonical template entry to substitute.
90
+ * Passing them through unchanged IS CC's wire shape; remapping them was the
91
+ * divergence. Before v4.8.135 they fell through to the unmapped-tool
92
+ * round-robin: dropped from the advertised array (the model never saw them)
93
+ * while history tool_use blocks were renamed onto Bash/Read/… with junk args
94
+ * — seen live as "tool substitution: 28/52 client tools not in TOOL_MAP" on
95
+ * a CC session with an MCP server attached. Like CC_NATIVE_NAMES, these
96
+ * identity-map and skip TOOL_MAP entirely. */
97
+ export declare function isMcpToolName(name: unknown): boolean;
66
98
  /** CC's static system prompt (~25KB). The shared base — baked from a non-Fable
67
99
  * model (Opus). CC ships some models a larger, model-specific prompt; see
68
100
  * CC_SYSTEM_PROMPT_FABLE / systemPromptForModel. */
@@ -66,13 +66,43 @@ export const INTERACTIVE_ONLY_TOOLS = new Set([
66
66
  ]);
67
67
  /** CC's exact tool definitions for the current platform — filtered from the bundled union. */
68
68
  export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, process.platform);
69
+ /** The UNFILTERED bundled union — every tool the bake knows across platforms
70
+ * (PLATFORM_ONLY_TOOLS keeps the bundle a superset). The identity-mapping,
71
+ * detection, and advertise paths intersect with what the CLIENT declared,
72
+ * and the client's declaration already encodes its platform — a Linux CC
73
+ * never declares PowerShell. Filtering those paths by the PROXY HOST's
74
+ * process.platform (pre-v4.8.136) made a Linux-hosted dario treat a win32
75
+ * client's PowerShell/Glob/Grep as non-native: PowerShell fell to the
76
+ * unmapped round-robin, and all three were dropped from the advertised
77
+ * array (Glob/Grep translated via lowercase aliases but were never sent
78
+ * upstream). Host-filtered CC_TOOL_DEFINITIONS stays correct for the paths
79
+ * with no client declaration to mirror: the full-template fallback, the
80
+ * merge-mode base array, and Fable's no-tools shape. */
81
+ export const CC_TOOL_DEFINITIONS_UNION = TEMPLATE.tools;
82
+ export const CC_NATIVE_NAMES_UNION = new Set(TEMPLATE.tools.map((t) => String(t.name)));
69
83
  /** CC's own tool names, EXACT case ("Read", "Bash", "Agent", …). A CC client's
70
84
  * tools identity-map to themselves and OVERRIDE TOOL_MAP — whose lowercase
71
85
  * cross-client aliases ('read' → {path}/{filePath}) would otherwise mistranslate
72
86
  * a CC tool (Read's file_path → path). Exact case is the discriminator: CC sends
73
87
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
74
- * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
88
+ * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake).
89
+ * HOST-filtered — the routing, detection, and advertise paths use the
90
+ * _UNION variants below so a client on a different platform than the proxy
91
+ * host still identity-maps (v4.8.136). */
75
92
  export const CC_NATIVE_NAMES = new Set(CC_TOOL_DEFINITIONS.map((t) => String(t.name)));
93
+ /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
94
+ * Real CC advertises them VERBATIM after its built-ins — the schemas are
95
+ * operator-supplied, so there is no canonical template entry to substitute.
96
+ * Passing them through unchanged IS CC's wire shape; remapping them was the
97
+ * divergence. Before v4.8.135 they fell through to the unmapped-tool
98
+ * round-robin: dropped from the advertised array (the model never saw them)
99
+ * while history tool_use blocks were renamed onto Bash/Read/… with junk args
100
+ * — seen live as "tool substitution: 28/52 client tools not in TOOL_MAP" on
101
+ * a CC session with an MCP server attached. Like CC_NATIVE_NAMES, these
102
+ * identity-map and skip TOOL_MAP entirely. */
103
+ export function isMcpToolName(name) {
104
+ return typeof name === 'string' && name.startsWith('mcp__');
105
+ }
76
106
  /** CC's static system prompt (~25KB). The shared base — baked from a non-Fable
77
107
  * model (Opus). CC ships some models a larger, model-specific prompt; see
78
108
  * CC_SYSTEM_PROMPT_FABLE / systemPromptForModel. */
@@ -526,14 +556,21 @@ export function detectNonCCByTools(clientTools) {
526
556
  const rawName = tool.name || '';
527
557
  // A tool is "foreign" only if dario can neither map it (TOOL_MAP, by
528
558
  // lowercased cross-client alias) nor recognize it as CC's own (CC_NATIVE_NAMES,
529
- // by exact PascalCase). CC-native tools identity-map to themselves in the remap
559
+ // by exact PascalCase) nor pass it through as an MCP tool (mcp__* — identity,
560
+ // like CC natives). CC-native tools identity-map to themselves in the remap
530
561
  // path (see buildCCRequest), so counting them here inflates the ratio and
531
562
  // mis-flags a modern, agentic-heavy CC client (Agent, Skill, Workflow, Task*,
532
563
  // … — in the live bundle but absent from TOOL_MAP's alias table) as
533
564
  // 'unknown-non-cc', flipping it to preserve and discarding the CC tool
534
- // fingerprint dario exists to present. Mirror the routing's membership test so
535
- // detection and routing agree.
536
- if (!TOOL_MAP[rawName.toLowerCase()] && !CC_NATIVE_NAMES.has(rawName))
565
+ // fingerprint dario exists to present. Same trap for MCP tools: two or three
566
+ // attached servers push a real CC session past the 80% line on their own
567
+ // (28/52 seen live). Mirror the routing's membership test so detection and
568
+ // routing agree. An ALL-mcp surface now scores ratio 0 and stays in default
569
+ // mode — safe, because the advertise path sends an mcp-only declaration
570
+ // verbatim rather than falling back to the full CC template.
571
+ // Union set, not the host-filtered one: a win32 client's PowerShell is
572
+ // native regardless of the platform dario itself runs on (v4.8.136).
573
+ if (!TOOL_MAP[rawName.toLowerCase()] && !CC_NATIVE_NAMES_UNION.has(rawName) && !isMcpToolName(rawName))
537
574
  unmapped++;
538
575
  }
539
576
  const ratio = unmapped / clientTools.length;
@@ -1336,10 +1373,16 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1336
1373
  // 2. CC's newer built-ins (Agent, AskUserQuestion, Cron*, Task*, Workflow,
1337
1374
  // NotebookEdit, Enter/ExitPlanMode, …) aren't in TOOL_MAP at all, so
1338
1375
  // they were round-robined onto Read/Bash/etc. and collided.
1376
+ // 3. MCP tools (mcp__<server>__<tool>) carry operator-supplied schemas
1377
+ // that never have a TOOL_MAP or template entry. Real CC forwards them
1378
+ // verbatim, so they identity-map too and are advertised as-is below.
1339
1379
  // Exact case is the discriminator (CC sends PascalCase; {path}-style clients
1340
1380
  // send lowercase/snake) so a genuine non-CC `read` still routes via TOOL_MAP.
1341
1381
  // Tracks the live bundle, so future CC tools are covered after the next bake.
1342
- const mapping = CC_NATIVE_NAMES.has(tool.name)
1382
+ // UNION set: identity must hold for a win32 client's PowerShell/Glob/Grep
1383
+ // even when dario itself runs on Linux (v4.8.136) — the exact-case check
1384
+ // still keeps a non-CC lowercase `glob`/`grep` on its TOOL_MAP alias.
1385
+ const mapping = CC_NATIVE_NAMES_UNION.has(tool.name) || isMcpToolName(tool.name)
1343
1386
  ? { ccTool: tool.name, translateArgs: (a) => a, translateBack: (a) => a }
1344
1387
  : TOOL_MAP[name];
1345
1388
  if (mapping) {
@@ -1377,8 +1420,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1377
1420
  const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
1378
1421
  for (const tool of clientTools) {
1379
1422
  const name = (tool.name || '').toLowerCase();
1380
- if (CC_NATIVE_NAMES.has(tool.name) || TOOL_MAP[name])
1381
- continue; // CC-native (identity in pass 1) or mapped
1423
+ if (CC_NATIVE_NAMES_UNION.has(tool.name) || isMcpToolName(tool.name) || TOOL_MAP[name])
1424
+ continue; // CC-native (union) / MCP (identity in pass 1) or mapped
1382
1425
  unmappedTools.push(tool.name);
1383
1426
  if (opts.hybridTools)
1384
1427
  continue; // dropped — see comment above
@@ -1555,13 +1598,31 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1555
1598
  // dario-routed CC session). A real CC client with a reduced tool set sends
1556
1599
  // exactly this reduced array (every --disallowedTools / MCP delta does
1557
1600
  // this), so filtering tracks CC's wire shape rather than diverging from it.
1558
- // If the client declared no CC-native tool at all it isn't really CC; keep
1559
- // the full template as the safer fingerprint default in that case.
1601
+ //
1602
+ // MCP tools (mcp__<server>__<tool>) are appended VERBATIM after the
1603
+ // canonical natives — that mirrors real CC, which sends operator-supplied
1604
+ // MCP schemas after its built-ins. There is no template entry to
1605
+ // substitute, and omitting them here (pre-v4.8.135) meant the model never
1606
+ // saw the session's MCP surface at all while history references were
1607
+ // round-robined onto fallback slots.
1608
+ //
1609
+ // If the client declared neither a CC-native nor an MCP tool it isn't
1610
+ // really CC; keep the full template as the safer fingerprint default in
1611
+ // that case. An mcp-only declaration goes out verbatim instead — falling
1612
+ // back to the full template there would advertise natives the client
1613
+ // can't execute (the AskUserQuestion failure mode above).
1560
1614
  const clientToolNames = new Set(clientTools
1561
1615
  .map((t) => t.name?.toLowerCase())
1562
1616
  .filter((n) => Boolean(n)));
1563
- const availableCC = CC_TOOL_DEFINITIONS.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1564
- ccRequest.tools = availableCC.length > 0 ? availableCC : CC_TOOL_DEFINITIONS;
1617
+ // Intersect against the UNION, not the host-filtered set: the client's
1618
+ // declaration encodes the client's platform, so a win32 CC declaring
1619
+ // PowerShell/Glob/Grep gets their canonical defs even from a Linux-hosted
1620
+ // dario. The host filter only governs the no-declaration fallbacks.
1621
+ const availableCC = CC_TOOL_DEFINITIONS_UNION.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1622
+ const mcpTools = clientTools.filter((t) => isMcpToolName(t.name));
1623
+ ccRequest.tools = availableCC.length > 0 || mcpTools.length > 0
1624
+ ? [...availableCC, ...mcpTools]
1625
+ : CC_TOOL_DEFINITIONS;
1565
1626
  }
1566
1627
  }
1567
1628
  else if (effectiveMergeTools) {
package/dist/proxy.js CHANGED
@@ -9,7 +9,7 @@ import { arch, platform } from 'node:process';
9
9
  import { getAccessToken, getStatus } from './oauth.js';
10
10
  import { buildHealthResponse, derivePoolStatus, shouldDiscloseHealthInternals } from './health-response.js';
11
11
  import { darioVersion } from './version.js';
12
- import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, CC_TEMPLATE } from './cc-template.js';
12
+ import { buildCCRequest, applyCcPromptCaching, parseEffortSuffix, reverseMapResponse, createStreamingReverseMapper, orderHeadersForOutbound, isMcpToolName, CC_TEMPLATE } from './cc-template.js';
13
13
  import { stampCch, hasCchSeed } from './cch.js';
14
14
  import { describeTemplate, detectDrift, checkCCCompat } from './live-fingerprint.js';
15
15
  import { AccountPool, computeStickyKey, parseRateLimits, modelFamily, isInAuthCooldown, authCooldownMs, reconcilePoolAccounts } from './pool.js';
@@ -892,6 +892,8 @@ export async function startProxy(opts = {}) {
892
892
  // tool-substitution warn line. Same de-dup contract as
893
893
  // detectedClientsLogged so mixed-traffic proxies don't spam.
894
894
  const toolSubLogged = new Set();
895
+ // Same de-dup contract for the verbose-only MCP-passthrough note.
896
+ const mcpPassthroughLogged = new Set();
895
897
  // Body-dump mode: set via --verbose=2 / -vv or DARIO_LOG_BODIES=1.
896
898
  // When on, every request emits a redacted JSON body to stderr so
897
899
  // operators can see exactly what dario forwards upstream. Default
@@ -2146,6 +2148,20 @@ export async function startProxy(opts = {}) {
2146
2148
  const more = unmappedTools.length > 5 ? `, +${unmappedTools.length - 5} more` : '';
2147
2149
  console.log(`[dario] tool substitution: ${unmappedTools.length}/${totalTools} client tool${unmappedTools.length === 1 ? '' : 's'} not in TOOL_MAP — remapped onto CC fallback slots (${sample}${more}). Pass --preserve-tools to forward your schemas verbatim instead.`);
2148
2150
  }
2151
+ // MCP tools (mcp__<server>__<tool>) forward verbatim in default
2152
+ // mode — real CC advertises session-attached MCP schemas as-is,
2153
+ // so this is passthrough, not substitution. Verbose-only note so
2154
+ // operators can confirm their MCP surface survived; same de-dupe
2155
+ // as the substitution warn.
2156
+ if (verbose && !preserveToolsEffective && !mcpPassthroughLogged.has(subKey)) {
2157
+ const mcpCount = Array.isArray(r.tools)
2158
+ ? r.tools.filter((t) => isMcpToolName(t?.name)).length
2159
+ : 0;
2160
+ if (mcpCount > 0) {
2161
+ mcpPassthroughLogged.add(subKey);
2162
+ console.log(`[dario] ${mcpCount} MCP tool${mcpCount === 1 ? '' : 's'} (mcp__*) forwarded verbatim alongside CC's native set`);
2163
+ }
2164
+ }
2149
2165
  // Store tool map for response reverse-mapping
2150
2166
  ccToolMap = toolMap;
2151
2167
  // Replace request body entirely with CC template
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "4.8.134",
3
+ "version": "4.8.136",
4
4
  "description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
5
5
  "type": "module",
6
6
  "bin": {