@askalf/dario 3.9.3 → 3.9.5

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.
@@ -100,36 +100,47 @@ function injectContextFields(input, clientFields, ctx) {
100
100
  }
101
101
  const TOOL_MAP = {
102
102
  // Direct maps
103
+ // Note on translateBack field names: the vast majority of client bash-like
104
+ // tools use `command` (the Anthropic convention), not `cmd`. OpenClaw's
105
+ // `exec` tool takes `{command, workdir, env, ...}` (dario#36 triage).
106
+ // Hybrid mode overrides these with the actual client schema via clientFields,
107
+ // but default mode relies on these output names being the common case.
103
108
  bash: {
104
109
  ccTool: 'Bash',
105
110
  translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }),
106
- translateBack: (a) => ({ cmd: a.command ?? '' }),
111
+ translateBack: (a) => ({ command: a.command ?? '' }),
107
112
  },
108
113
  exec: {
109
114
  ccTool: 'Bash',
110
115
  translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }),
111
- translateBack: (a) => ({ cmd: a.command ?? '' }),
116
+ translateBack: (a) => ({ command: a.command ?? '' }),
112
117
  },
113
118
  shell: {
114
119
  ccTool: 'Bash',
115
120
  translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }),
116
- translateBack: (a) => ({ cmd: a.command ?? '' }),
121
+ translateBack: (a) => ({ command: a.command ?? '' }),
117
122
  },
118
123
  run: {
119
124
  ccTool: 'Bash',
120
125
  translateArgs: (a) => ({ command: a.cmd || a.command || '' }),
121
- translateBack: (a) => ({ cmd: a.command ?? '' }),
126
+ translateBack: (a) => ({ command: a.command ?? '' }),
122
127
  },
123
128
  command: {
124
129
  ccTool: 'Bash',
125
130
  translateArgs: (a) => ({ command: a.cmd || a.command || '' }),
126
- translateBack: (a) => ({ cmd: a.command ?? '' }),
131
+ translateBack: (a) => ({ command: a.command ?? '' }),
127
132
  },
128
133
  terminal: {
129
134
  ccTool: 'Bash',
130
135
  translateArgs: (a) => ({ command: a.cmd || a.command || '' }),
131
- translateBack: (a) => ({ cmd: a.command ?? '' }),
136
+ translateBack: (a) => ({ command: a.command ?? '' }),
132
137
  },
138
+ // `process` is OpenClaw's session-manager tool — it's an action-discriminator
139
+ // shape {action: "list"|"poll"|"log"|..., sessionId?, ...}. Flattening it onto
140
+ // Bash.command loses all sibling fields (data, keys, hex, literal, text, ...),
141
+ // so the model upstream can't actually drive it. Kept mapped for fingerprint
142
+ // continuity but the reverse translation is inherently lossy — clients with a
143
+ // process-style tool should use --preserve-tools instead of --hybrid-tools.
133
144
  process: {
134
145
  ccTool: 'Bash',
135
146
  translateArgs: (a) => ({ command: a.action || a.cmd || '' }),
@@ -307,16 +318,34 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
307
318
  claimedCC.add(mapping.ccTool);
308
319
  }
309
320
  }
321
+ // Unmapped-tool handling differs by mode:
322
+ //
323
+ // - Default mode: round-robin to CC fallback tools. The model sees the CC
324
+ // tool set, any tool call is "something", and we best-effort relay it
325
+ // back to the client tool name. Broken-by-design for clients with rich
326
+ // discriminator tools (OpenClaw lobster/memory_get, dario#36), but
327
+ // preserves the old behavior for simple clients that don't have many
328
+ // unmapped tools.
329
+ //
330
+ // - Hybrid mode: DROP unmapped tools entirely. We can't forward them to
331
+ // the upstream (adding to CC_TOOL_DEFINITIONS breaks the fingerprint),
332
+ // and round-robin mapping produces nonsense shapes on the reverse path
333
+ // (lobster.translateBack(Glob.input) → {pattern: "..."} when lobster
334
+ // wants {action: "run"}). Better to let the model not see those tools
335
+ // than to pretend they exist and corrupt every call. Users needing
336
+ // every client tool to actually work must use --preserve-tools.
310
337
  const CC_FALLBACK_TOOLS = ['Bash', 'Read', 'Grep', 'Glob', 'WebSearch', 'WebFetch'];
311
338
  for (const tool of clientTools) {
312
339
  const name = (tool.name || '').toLowerCase();
313
340
  if (TOOL_MAP[name])
314
341
  continue;
315
342
  unmappedTools.push(tool.name);
316
- // Exclude CC tools the client already uses so we never create a
317
- // two-client-names-to-one-CC-tool collision. If every fallback is
318
- // claimed (rare: client already uses 6+ CC tools), fall back to the
319
- // full pool and accept the ambiguity.
343
+ if (opts.hybridTools)
344
+ continue; // dropped see comment above
345
+ // Default mode: round-robin distribution. Exclude CC tools the client
346
+ // already uses so we never create a two-client-names-to-one-CC-tool
347
+ // collision. If every fallback is claimed (rare: client already uses 6+
348
+ // CC tools), fall back to the full pool and accept the ambiguity.
320
349
  const pool = CC_FALLBACK_TOOLS.filter(t => !claimedCC.has(t));
321
350
  const fallbackPool = pool.length > 0 ? pool : CC_FALLBACK_TOOLS;
322
351
  const fallbackTool = fallbackPool[(unmappedTools.length - 1) % fallbackPool.length];
package/dist/proxy.js CHANGED
@@ -420,6 +420,14 @@ export async function startProxy(opts = {}) {
420
420
  };
421
421
  let requestCount = 0;
422
422
  const semaphore = new Semaphore(MAX_CONCURRENT);
423
+ // Cache context-1m beta availability. Set false once per account (or process
424
+ // in single-account mode) after the first "long context" rejection, so we
425
+ // skip sending context-1m on every subsequent request instead of paying the
426
+ // round-trip + retry cost each time. Keyed by account alias; `__default__`
427
+ // is the single-account slot. Reported by @boeingchoco in dario#36 — the
428
+ // retry loop was firing on every POST with hybrid-tools + OC.
429
+ const context1mUnavailable = new Set();
430
+ const ACCOUNT_KEY_SINGLE = '__default__';
423
431
  // Rate governor — minimum 500ms between requests. Fast enough for agents,
424
432
  // slow enough to not look like a scripted flood of identical traffic.
425
433
  let lastRequestTime = 0;
@@ -693,8 +701,14 @@ export async function startProxy(opts = {}) {
693
701
  }
694
702
  else {
695
703
  // CC v2.1.104 beta set — 8 flags in the order Claude Code sends them.
696
- // context-1m requires Extra Usage — if it 400s, we auto-retry without it.
697
- beta = 'claude-code-20250219,oauth-2025-04-20,context-1m-2025-08-07,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01,effort-2025-11-24';
704
+ // context-1m requires Extra Usage — if it 400s, we auto-retry without
705
+ // it, and cache the rejection so subsequent requests on this account
706
+ // skip context-1m entirely (dario#36).
707
+ const acctKey = poolAccount?.alias ?? ACCOUNT_KEY_SINGLE;
708
+ const skipContext1m = context1mUnavailable.has(acctKey);
709
+ beta = skipContext1m
710
+ ? 'claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01,effort-2025-11-24'
711
+ : 'claude-code-20250219,oauth-2025-04-20,context-1m-2025-08-07,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01,effort-2025-11-24';
698
712
  if (clientBeta) {
699
713
  const baseSet = new Set(beta.split(','));
700
714
  const filtered = filterBillableBetas(clientBeta)
@@ -792,8 +806,13 @@ export async function startProxy(opts = {}) {
792
806
  || peekedBody.includes('Extra usage is required')
793
807
  || peekedBody.includes('long_context');
794
808
  if (isLongContextError) {
795
- if (verbose)
796
- console.log(`[dario] #${requestCount} context-1m rejected (${upstream.status}) retrying without it`);
809
+ // Cache the rejection so future requests on this account skip
810
+ // context-1m up front instead of re-paying the 400/429 round-trip.
811
+ const acctKey = poolAccount?.alias ?? ACCOUNT_KEY_SINGLE;
812
+ const firstRejection = !context1mUnavailable.has(acctKey);
813
+ context1mUnavailable.add(acctKey);
814
+ if (verbose && firstRejection)
815
+ console.log(`[dario] #${requestCount} context-1m rejected (${upstream.status}) — retrying without it (cached for session)`);
797
816
  const reducedBeta = beta.replace(',context-1m-2025-08-07', '').replace('context-1m-2025-08-07,', '');
798
817
  const retryHeaders = { ...headers, 'anthropic-beta': reducedBeta };
799
818
  const retry = await fetch(targetBase, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "3.9.3",
3
+ "version": "3.9.5",
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": {