@askalf/dario 2.9.4 → 2.10.0

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
@@ -51,7 +51,7 @@ Opus, Sonnet, Haiku — all models, streaming, tool use. **Zero dependencies.**
51
51
 
52
52
  **Copilot** (Microsoft)
53
53
 
54
- *"Verdict: Safe for local use — well-implemented with strong security practices. Minimal attack surface: 1 production dependency, PKCE OAuth, localhost-only binding, timing-safe auth, zero telemetry. The main risk vector is operator error rather than code defects."*
54
+ *"Verdict: Safe for local use — well-implemented with strong security practices. Minimal attack surface: zero runtime dependencies, PKCE OAuth, localhost-only binding, timing-safe auth, zero telemetry. The main risk vector is operator error rather than code defects."*
55
55
 
56
56
  </td>
57
57
  <td width="33%" valign="top">
package/dist/proxy.js CHANGED
@@ -267,6 +267,84 @@ function stripThinkingFromHistory(body) {
267
267
  * specific order. We rebuild the object to match.
268
268
  */
269
269
  const NON_CC_FIELDS = new Set(['service_tier', 'top_p', 'top_k', 'stop_sequences', 'temperature']);
270
+ // ── Tool name rewriting ──
271
+ // Anthropic fingerprints on tool names — non-CC names trigger overage classification.
272
+ // Map third-party tool names to CC equivalents on the way in, reverse on the way out.
273
+ const CC_TOOLS = new Set([
274
+ 'Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'Browser', 'WebFetch', 'WebSearch',
275
+ 'NotebookEdit', 'NotebookRead', 'TodoRead', 'TodoWrite',
276
+ 'Agent', 'MCPListTools', 'MCPCallTool',
277
+ 'AskUserQuestion', 'EnterPlanMode', 'ExitPlanMode',
278
+ 'EnterWorktree', 'ExitWorktree', 'TaskCreate', 'TaskUpdate',
279
+ ]);
280
+ // Common third-party tool names → CC equivalents
281
+ const TOOL_NAME_MAP = {
282
+ bash: 'Bash', sh: 'Bash', exec: 'Bash', shell: 'Bash', run: 'Bash', execute: 'Bash',
283
+ command: 'Bash', terminal: 'Bash', process: 'Bash',
284
+ read: 'Read', read_file: 'Read', file_read: 'Read', get_file: 'Read',
285
+ write: 'Write', write_file: 'Write', file_write: 'Write', create_file: 'Write', save_file: 'Write',
286
+ edit: 'Edit', edit_file: 'Edit', modify_file: 'Edit', patch: 'Edit', replace: 'Edit',
287
+ glob: 'Glob', find_files: 'Glob', list_files: 'Glob', ls: 'Glob',
288
+ grep: 'Grep', search: 'Grep', search_files: 'Grep', find_in_files: 'Grep', rg: 'Grep',
289
+ web_search: 'WebSearch', websearch: 'WebSearch', google: 'WebSearch',
290
+ web_fetch: 'WebFetch', webfetch: 'WebFetch', fetch: 'WebFetch', http: 'WebFetch', curl: 'WebFetch',
291
+ browse: 'Browser', browser: 'Browser', open_url: 'Browser',
292
+ notebook: 'NotebookEdit', notebook_edit: 'NotebookEdit',
293
+ };
294
+ /**
295
+ * Rewrite tool names in the request to match CC toolset.
296
+ * Returns the mapping so we can reverse it in the response.
297
+ * Tools that don't map to a known CC name get wrapped as MCPCallTool.
298
+ */
299
+ function rewriteToolNames(body) {
300
+ const tools = body.tools;
301
+ if (!tools || !Array.isArray(tools))
302
+ return [];
303
+ const mappings = [];
304
+ const usedNames = new Set();
305
+ // First pass: collect CC tool names already in the list
306
+ for (const tool of tools) {
307
+ if (CC_TOOLS.has(tool.name))
308
+ usedNames.add(tool.name);
309
+ }
310
+ let mcpIndex = 0;
311
+ for (const tool of tools) {
312
+ const originalName = tool.name;
313
+ if (!originalName)
314
+ continue;
315
+ // Already a CC tool name
316
+ if (CC_TOOLS.has(originalName))
317
+ continue;
318
+ // Check direct map — but avoid duplicates
319
+ const directMap = TOOL_NAME_MAP[originalName.toLowerCase()];
320
+ if (directMap && !usedNames.has(directMap)) {
321
+ mappings.push({ original: originalName, mapped: directMap });
322
+ tool.name = directMap;
323
+ usedNames.add(directMap);
324
+ }
325
+ else {
326
+ // Wrap as mcp_<original_name> — MCP tools use this prefix in real CC
327
+ const mcpName = `mcp_${originalName}`;
328
+ mappings.push({ original: originalName, mapped: mcpName });
329
+ tool.name = mcpName;
330
+ }
331
+ }
332
+ return mappings;
333
+ }
334
+ /**
335
+ * Reverse tool name mapping in the response body.
336
+ * Restores original tool names in tool_use content blocks.
337
+ */
338
+ function reverseToolNames(body, mappings) {
339
+ if (mappings.length === 0)
340
+ return body;
341
+ let result = body;
342
+ for (const { original, mapped } of mappings) {
343
+ // Replace in tool_use blocks: "name":"MCPCallTool" → "name":"original"
344
+ result = result.replace(new RegExp(`"name"\\s*:\\s*"${mapped}"`, 'g'), `"name":"${original}"`);
345
+ }
346
+ return result;
347
+ }
270
348
  // Claude Code's field order (from MITM capture). Fields not in this list are appended at end.
271
349
  const CC_FIELD_ORDER = [
272
350
  'model', 'messages', 'system', 'max_tokens', 'thinking', 'output_config',
@@ -752,6 +830,7 @@ export async function startProxy(opts = {}) {
752
830
  }
753
831
  // Parse body once, apply OpenAI translation, model override, and sanitization
754
832
  let finalBody = body.length > 0 ? body : undefined;
833
+ let toolMappings = [];
755
834
  if (body.length > 0) {
756
835
  try {
757
836
  const parsed = JSON.parse(body.toString());
@@ -766,9 +845,10 @@ export async function startProxy(opts = {}) {
766
845
  // context_management: clear_thinking does NOT reduce input token billing.
767
846
  // Real Claude Code strips thinking before building the next request.
768
847
  stripThinkingFromHistory(r);
769
- // 2. Scrub non-CC fields and normalize field ordering
848
+ // 2. Rewrite tool names to CC equivalents (Anthropic fingerprints on tool names)
849
+ toolMappings = rewriteToolNames(r);
850
+ // 3. Scrub non-CC fields and normalize field ordering
770
851
  const reordered = scrubAndReorderFields(r);
771
- // Copy reordered keys back (r is a reference to result)
772
852
  for (const key of Object.keys(r))
773
853
  delete r[key];
774
854
  Object.assign(r, reordered);
@@ -788,10 +868,13 @@ export async function startProxy(opts = {}) {
788
868
  if (supportsThinking && !r.thinking) {
789
869
  r.thinking = { type: 'adaptive' };
790
870
  }
791
- if (!r.max_tokens || r.max_tokens < 16000) {
871
+ // Claude Code always sends max_tokens: 64000. Values above this
872
+ // are a fingerprint — cap to match real CC behavior.
873
+ if (!r.max_tokens || r.max_tokens !== 64000) {
792
874
  r.max_tokens = 64000;
793
875
  }
794
- if (supportsThinking && !r.output_config) {
876
+ // Force effort to medium — CC default. Client 'high' is a fingerprint.
877
+ if (supportsThinking) {
795
878
  r.output_config = { effort: 'medium' };
796
879
  }
797
880
  if (supportsThinking && !r.context_management) {
@@ -939,7 +1022,14 @@ export async function startProxy(opts = {}) {
939
1022
  }
940
1023
  }
941
1024
  else {
942
- res.write(value);
1025
+ // Reverse tool names in streaming chunks
1026
+ if (toolMappings.length > 0) {
1027
+ const text = new TextDecoder().decode(value);
1028
+ res.write(reverseToolNames(text, toolMappings));
1029
+ }
1030
+ else {
1031
+ res.write(value);
1032
+ }
943
1033
  }
944
1034
  }
945
1035
  // Flush remaining buffer
@@ -957,9 +1047,10 @@ export async function startProxy(opts = {}) {
957
1047
  }
958
1048
  else {
959
1049
  // Buffer and forward
960
- const responseBody = await upstream.text();
1050
+ let responseBody = await upstream.text();
1051
+ // Reverse tool name mapping so client sees original names
1052
+ responseBody = reverseToolNames(responseBody, toolMappings);
961
1053
  if (isOpenAI && upstream.status >= 200 && upstream.status < 300) {
962
- // Translate Anthropic response → OpenAI format
963
1054
  try {
964
1055
  const parsed = JSON.parse(responseBody);
965
1056
  res.end(JSON.stringify(anthropicToOpenai(parsed)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "2.9.4",
3
+ "version": "2.10.0",
4
4
  "description": "Use your Claude subscription as an API. No API key needed. Local proxy for Claude Max/Pro subscriptions.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,4 +61,4 @@
61
61
  "tsx": "^4.19.0",
62
62
  "typescript": "^5.7.0"
63
63
  }
64
- }
64
+ }