@askalf/dario 4.8.133 → 4.8.135

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`).
@@ -63,6 +63,17 @@ export declare const CC_TOOL_DEFINITIONS: {
63
63
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
64
64
  * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
65
65
  export declare const CC_NATIVE_NAMES: Set<string>;
66
+ /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
67
+ * Real CC advertises them VERBATIM after its built-ins — the schemas are
68
+ * operator-supplied, so there is no canonical template entry to substitute.
69
+ * Passing them through unchanged IS CC's wire shape; remapping them was the
70
+ * divergence. Before v4.8.135 they fell through to the unmapped-tool
71
+ * round-robin: dropped from the advertised array (the model never saw them)
72
+ * while history tool_use blocks were renamed onto Bash/Read/… with junk args
73
+ * — seen live as "tool substitution: 28/52 client tools not in TOOL_MAP" on
74
+ * a CC session with an MCP server attached. Like CC_NATIVE_NAMES, these
75
+ * identity-map and skip TOOL_MAP entirely. */
76
+ export declare function isMcpToolName(name: unknown): boolean;
66
77
  /** CC's static system prompt (~25KB). The shared base — baked from a non-Fable
67
78
  * model (Opus). CC ships some models a larger, model-specific prompt; see
68
79
  * CC_SYSTEM_PROMPT_FABLE / systemPromptForModel. */
@@ -73,6 +73,19 @@ export const CC_TOOL_DEFINITIONS = filterToolsForPlatform(TEMPLATE.tools, proces
73
73
  * PascalCase, the {path}-style clients send lowercase/snake, so a non-CC `read`
74
74
  * still routes through TOOL_MAP. Tracks the live bundle (refreshed each bake). */
75
75
  export const CC_NATIVE_NAMES = new Set(CC_TOOL_DEFINITIONS.map((t) => String(t.name)));
76
+ /** MCP tools attached to a CC session are namespaced `mcp__<server>__<tool>`.
77
+ * Real CC advertises them VERBATIM after its built-ins — the schemas are
78
+ * operator-supplied, so there is no canonical template entry to substitute.
79
+ * Passing them through unchanged IS CC's wire shape; remapping them was the
80
+ * divergence. Before v4.8.135 they fell through to the unmapped-tool
81
+ * round-robin: dropped from the advertised array (the model never saw them)
82
+ * while history tool_use blocks were renamed onto Bash/Read/… with junk args
83
+ * — seen live as "tool substitution: 28/52 client tools not in TOOL_MAP" on
84
+ * a CC session with an MCP server attached. Like CC_NATIVE_NAMES, these
85
+ * identity-map and skip TOOL_MAP entirely. */
86
+ export function isMcpToolName(name) {
87
+ return typeof name === 'string' && name.startsWith('mcp__');
88
+ }
76
89
  /** CC's static system prompt (~25KB). The shared base — baked from a non-Fable
77
90
  * model (Opus). CC ships some models a larger, model-specific prompt; see
78
91
  * CC_SYSTEM_PROMPT_FABLE / systemPromptForModel. */
@@ -526,14 +539,19 @@ export function detectNonCCByTools(clientTools) {
526
539
  const rawName = tool.name || '';
527
540
  // A tool is "foreign" only if dario can neither map it (TOOL_MAP, by
528
541
  // 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
542
+ // by exact PascalCase) nor pass it through as an MCP tool (mcp__* — identity,
543
+ // like CC natives). CC-native tools identity-map to themselves in the remap
530
544
  // path (see buildCCRequest), so counting them here inflates the ratio and
531
545
  // mis-flags a modern, agentic-heavy CC client (Agent, Skill, Workflow, Task*,
532
546
  // … — in the live bundle but absent from TOOL_MAP's alias table) as
533
547
  // '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))
548
+ // fingerprint dario exists to present. Same trap for MCP tools: two or three
549
+ // attached servers push a real CC session past the 80% line on their own
550
+ // (28/52 seen live). Mirror the routing's membership test so detection and
551
+ // routing agree. An ALL-mcp surface now scores ratio 0 and stays in default
552
+ // mode — safe, because the advertise path sends an mcp-only declaration
553
+ // verbatim rather than falling back to the full CC template.
554
+ if (!TOOL_MAP[rawName.toLowerCase()] && !CC_NATIVE_NAMES.has(rawName) && !isMcpToolName(rawName))
537
555
  unmapped++;
538
556
  }
539
557
  const ratio = unmapped / clientTools.length;
@@ -1336,10 +1354,13 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1336
1354
  // 2. CC's newer built-ins (Agent, AskUserQuestion, Cron*, Task*, Workflow,
1337
1355
  // NotebookEdit, Enter/ExitPlanMode, …) aren't in TOOL_MAP at all, so
1338
1356
  // they were round-robined onto Read/Bash/etc. and collided.
1357
+ // 3. MCP tools (mcp__<server>__<tool>) carry operator-supplied schemas
1358
+ // that never have a TOOL_MAP or template entry. Real CC forwards them
1359
+ // verbatim, so they identity-map too and are advertised as-is below.
1339
1360
  // Exact case is the discriminator (CC sends PascalCase; {path}-style clients
1340
1361
  // send lowercase/snake) so a genuine non-CC `read` still routes via TOOL_MAP.
1341
1362
  // Tracks the live bundle, so future CC tools are covered after the next bake.
1342
- const mapping = CC_NATIVE_NAMES.has(tool.name)
1363
+ const mapping = CC_NATIVE_NAMES.has(tool.name) || isMcpToolName(tool.name)
1343
1364
  ? { ccTool: tool.name, translateArgs: (a) => a, translateBack: (a) => a }
1344
1365
  : TOOL_MAP[name];
1345
1366
  if (mapping) {
@@ -1377,8 +1398,8 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1377
1398
  const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
1378
1399
  for (const tool of clientTools) {
1379
1400
  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
1401
+ if (CC_NATIVE_NAMES.has(tool.name) || isMcpToolName(tool.name) || TOOL_MAP[name])
1402
+ continue; // CC-native / MCP (identity in pass 1) or mapped
1382
1403
  unmappedTools.push(tool.name);
1383
1404
  if (opts.hybridTools)
1384
1405
  continue; // dropped — see comment above
@@ -1555,13 +1576,27 @@ export function buildCCRequest(clientBody, billingTag, cacheControl, identity, o
1555
1576
  // dario-routed CC session). A real CC client with a reduced tool set sends
1556
1577
  // exactly this reduced array (every --disallowedTools / MCP delta does
1557
1578
  // 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.
1579
+ //
1580
+ // MCP tools (mcp__<server>__<tool>) are appended VERBATIM after the
1581
+ // canonical natives — that mirrors real CC, which sends operator-supplied
1582
+ // MCP schemas after its built-ins. There is no template entry to
1583
+ // substitute, and omitting them here (pre-v4.8.135) meant the model never
1584
+ // saw the session's MCP surface at all while history references were
1585
+ // round-robined onto fallback slots.
1586
+ //
1587
+ // If the client declared neither a CC-native nor an MCP tool it isn't
1588
+ // really CC; keep the full template as the safer fingerprint default in
1589
+ // that case. An mcp-only declaration goes out verbatim instead — falling
1590
+ // back to the full template there would advertise natives the client
1591
+ // can't execute (the AskUserQuestion failure mode above).
1560
1592
  const clientToolNames = new Set(clientTools
1561
1593
  .map((t) => t.name?.toLowerCase())
1562
1594
  .filter((n) => Boolean(n)));
1563
1595
  const availableCC = CC_TOOL_DEFINITIONS.filter((t) => clientToolNames.has(t.name.toLowerCase()));
1564
- ccRequest.tools = availableCC.length > 0 ? availableCC : CC_TOOL_DEFINITIONS;
1596
+ const mcpTools = clientTools.filter((t) => isMcpToolName(t.name));
1597
+ ccRequest.tools = availableCC.length > 0 || mcpTools.length > 0
1598
+ ? [...availableCC, ...mcpTools]
1599
+ : CC_TOOL_DEFINITIONS;
1565
1600
  }
1566
1601
  }
1567
1602
  else if (effectiveMergeTools) {
package/dist/proxy.d.ts CHANGED
@@ -64,9 +64,8 @@ export declare const CLAUDE_CODE_BETA = "claude-code-20250219";
64
64
  * it, ORDER included:
65
65
  *
66
66
  * opus-4-8 = base (unchanged)
67
- * sonnet-5 = base (unchanged KEEPS
68
- * mid-conversation-system; the old 2.1.170 sonnet-4-6 drop is
69
- * gone: 2.1.199 sonnet == opus)
67
+ * sonnet-5 = base − {mid-conversation-system} (CC 2.1.201 dropped it
68
+ * from sonnet; 2.1.199 sonnet == opus and still kept it — #667)
70
69
  * haiku-4-5 = base − {mid-conversation-system, effort, afk-mode}, and
71
70
  * claude-code-20250219 MOVED to position 5 (before advisor-tool)
72
71
  * fable-5 = base + fallback-credit-2026-06-01 inserted BEFORE afk-mode
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';
@@ -323,9 +323,8 @@ function moveBetaBefore(flags, flag, anchor) {
323
323
  * it, ORDER included:
324
324
  *
325
325
  * opus-4-8 = base (unchanged)
326
- * sonnet-5 = base (unchanged KEEPS
327
- * mid-conversation-system; the old 2.1.170 sonnet-4-6 drop is
328
- * gone: 2.1.199 sonnet == opus)
326
+ * sonnet-5 = base − {mid-conversation-system} (CC 2.1.201 dropped it
327
+ * from sonnet; 2.1.199 sonnet == opus and still kept it — #667)
329
328
  * haiku-4-5 = base − {mid-conversation-system, effort, afk-mode}, and
330
329
  * claude-code-20250219 MOVED to position 5 (before advisor-tool)
331
330
  * fable-5 = base + fallback-credit-2026-06-01 inserted BEFORE afk-mode
@@ -354,10 +353,15 @@ export function betaForModel(base, model, skipContext1m = false) {
354
353
  flags = flags.filter((f) => !drop.has(f));
355
354
  flags = moveBetaBefore(flags, CLAUDE_CODE_BETA, ADVISOR_TOOL_BETA);
356
355
  }
356
+ else if (m.includes('sonnet')) {
357
+ // CC 2.1.201 dropped mid-conversation-system from sonnet's beta set (2.1.199
358
+ // sonnet == opus and still carried it — live capture #667). opus + fable keep it.
359
+ flags = flags.filter((f) => f !== MID_CONVERSATION_SYSTEM_BETA);
360
+ }
357
361
  else if (m.includes('fable')) {
358
362
  flags = insertBetaBefore(flags, FABLE_FALLBACK_CREDIT_BETA, AFK_MODE_BETA);
359
363
  }
360
- // opus + sonnet + unknown families keep the base set unchanged.
364
+ // opus + unknown families keep the base set unchanged.
361
365
  if (/\[1m\]$/.test(m) && !skipContext1m) {
362
366
  flags = insertBetaAfter(flags, CONTEXT_1M_BETA, CLAUDE_CODE_BETA);
363
367
  }
@@ -888,6 +892,8 @@ export async function startProxy(opts = {}) {
888
892
  // tool-substitution warn line. Same de-dup contract as
889
893
  // detectedClientsLogged so mixed-traffic proxies don't spam.
890
894
  const toolSubLogged = new Set();
895
+ // Same de-dup contract for the verbose-only MCP-passthrough note.
896
+ const mcpPassthroughLogged = new Set();
891
897
  // Body-dump mode: set via --verbose=2 / -vv or DARIO_LOG_BODIES=1.
892
898
  // When on, every request emits a redacted JSON body to stderr so
893
899
  // operators can see exactly what dario forwards upstream. Default
@@ -2142,6 +2148,20 @@ export async function startProxy(opts = {}) {
2142
2148
  const more = unmappedTools.length > 5 ? `, +${unmappedTools.length - 5} more` : '';
2143
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.`);
2144
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
+ }
2145
2165
  // Store tool map for response reverse-mapping
2146
2166
  ccToolMap = toolMap;
2147
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.133",
3
+ "version": "4.8.135",
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": {