@askalf/dario 2.11.0 → 3.0.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
@@ -455,7 +455,7 @@ Add to your `openclaw.json` models config:
455
455
  ### Direct API Mode
456
456
  - All Claude models (Opus 4.6, Sonnet 4.6, Haiku 4.5) + 1M extended context aliases (`opus1m`, `sonnet1m`)
457
457
  - **Native billing classification** — device identity, per-request billing tag with SHA-256 checksums matching real Claude Code (extracted via binary RE), ensures Max plan limits work correctly
458
- - **Stealth layer** (v2.9.0) — strips thinking blocks from conversation history (saves 50-80% input tokens), scrubs non-CC fields (`temperature`, `top_p`, `top_k`, `stop_sequences`, `service_tier`), reorders JSON fields to match Claude Code's exact field order, and normalizes system prompts to exactly 3 blocks. Every request is indistinguishable from real Claude Code traffic.
458
+ - **Template replay** (v3.0) — instead of transforming requests signal-by-signal, dario replaces the entire request with a Claude Code template. CC's exact tool definitions, field structure, and parameters are sent upstream. Only your conversation content is preserved. Tested with 40 third-party tools all route to `five_hour`. See [Discussion #13](https://github.com/askalf/dario/discussions/13) for why this matters.
459
459
  - **Adaptive thinking** — matches Claude Code's `{ type: 'adaptive' }` mode for optimal reasoning (auto-skipped for Haiku 4.5)
460
460
  - **Effort control** — injects `output_config: { effort: 'medium' }` matching Claude Code's default, or passes through client-specified effort level
461
461
  - **Enriched 429 errors** — rate limit errors include utilization %, limiting window, and reset time instead of Anthropic's default `"Error"` message
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Claude Code request template — the exact tool definitions, system structure,
3
+ * and request shape that real Claude Code sends.
4
+ *
5
+ * Instead of transforming third-party requests signal-by-signal, we replace
6
+ * the entire request with a CC template and inject only the conversation content.
7
+ * The upstream sees a genuine CC request. Anthropic can't detect it without
8
+ * flagging their own binary.
9
+ *
10
+ * Source: MITM capture + binary RE of Claude Code v2.1.100
11
+ */
12
+ /** Claude Code's exact tool definitions (from binary RE + MITM capture). */
13
+ export declare const CC_TOOL_DEFINITIONS: ({
14
+ name: string;
15
+ description: string;
16
+ input_schema: {
17
+ type: "object";
18
+ properties: {
19
+ command: {
20
+ type: "string";
21
+ description: string;
22
+ };
23
+ timeout: {
24
+ type: "number";
25
+ description: string;
26
+ };
27
+ file_path?: undefined;
28
+ offset?: undefined;
29
+ limit?: undefined;
30
+ content?: undefined;
31
+ old_string?: undefined;
32
+ new_string?: undefined;
33
+ replace_all?: undefined;
34
+ pattern?: undefined;
35
+ path?: undefined;
36
+ output_mode?: undefined;
37
+ url?: undefined;
38
+ query?: undefined;
39
+ notebook_path?: undefined;
40
+ cell_number?: undefined;
41
+ new_source?: undefined;
42
+ prompt?: undefined;
43
+ description?: undefined;
44
+ question?: undefined;
45
+ };
46
+ required: string[];
47
+ };
48
+ } | {
49
+ name: string;
50
+ description: string;
51
+ input_schema: {
52
+ type: "object";
53
+ properties: {
54
+ file_path: {
55
+ type: "string";
56
+ description: string;
57
+ };
58
+ offset: {
59
+ type: "integer";
60
+ description: string;
61
+ };
62
+ limit: {
63
+ type: "integer";
64
+ description: string;
65
+ };
66
+ command?: undefined;
67
+ timeout?: undefined;
68
+ content?: undefined;
69
+ old_string?: undefined;
70
+ new_string?: undefined;
71
+ replace_all?: undefined;
72
+ pattern?: undefined;
73
+ path?: undefined;
74
+ output_mode?: undefined;
75
+ url?: undefined;
76
+ query?: undefined;
77
+ notebook_path?: undefined;
78
+ cell_number?: undefined;
79
+ new_source?: undefined;
80
+ prompt?: undefined;
81
+ description?: undefined;
82
+ question?: undefined;
83
+ };
84
+ required: string[];
85
+ };
86
+ } | {
87
+ name: string;
88
+ description: string;
89
+ input_schema: {
90
+ type: "object";
91
+ properties: {
92
+ file_path: {
93
+ type: "string";
94
+ description: string;
95
+ };
96
+ content: {
97
+ type: "string";
98
+ description: string;
99
+ };
100
+ command?: undefined;
101
+ timeout?: undefined;
102
+ offset?: undefined;
103
+ limit?: undefined;
104
+ old_string?: undefined;
105
+ new_string?: undefined;
106
+ replace_all?: undefined;
107
+ pattern?: undefined;
108
+ path?: undefined;
109
+ output_mode?: undefined;
110
+ url?: undefined;
111
+ query?: undefined;
112
+ notebook_path?: undefined;
113
+ cell_number?: undefined;
114
+ new_source?: undefined;
115
+ prompt?: undefined;
116
+ description?: undefined;
117
+ question?: undefined;
118
+ };
119
+ required: string[];
120
+ };
121
+ } | {
122
+ name: string;
123
+ description: string;
124
+ input_schema: {
125
+ type: "object";
126
+ properties: {
127
+ file_path: {
128
+ type: "string";
129
+ description: string;
130
+ };
131
+ old_string: {
132
+ type: "string";
133
+ description: string;
134
+ };
135
+ new_string: {
136
+ type: "string";
137
+ description: string;
138
+ };
139
+ replace_all: {
140
+ type: "boolean";
141
+ description: string;
142
+ default: boolean;
143
+ };
144
+ command?: undefined;
145
+ timeout?: undefined;
146
+ offset?: undefined;
147
+ limit?: undefined;
148
+ content?: undefined;
149
+ pattern?: undefined;
150
+ path?: undefined;
151
+ output_mode?: undefined;
152
+ url?: undefined;
153
+ query?: undefined;
154
+ notebook_path?: undefined;
155
+ cell_number?: undefined;
156
+ new_source?: undefined;
157
+ prompt?: undefined;
158
+ description?: undefined;
159
+ question?: undefined;
160
+ };
161
+ required: string[];
162
+ };
163
+ } | {
164
+ name: string;
165
+ description: string;
166
+ input_schema: {
167
+ type: "object";
168
+ properties: {
169
+ pattern: {
170
+ type: "string";
171
+ description: string;
172
+ };
173
+ path: {
174
+ type: "string";
175
+ description: string;
176
+ };
177
+ command?: undefined;
178
+ timeout?: undefined;
179
+ file_path?: undefined;
180
+ offset?: undefined;
181
+ limit?: undefined;
182
+ content?: undefined;
183
+ old_string?: undefined;
184
+ new_string?: undefined;
185
+ replace_all?: undefined;
186
+ output_mode?: undefined;
187
+ url?: undefined;
188
+ query?: undefined;
189
+ notebook_path?: undefined;
190
+ cell_number?: undefined;
191
+ new_source?: undefined;
192
+ prompt?: undefined;
193
+ description?: undefined;
194
+ question?: undefined;
195
+ };
196
+ required: string[];
197
+ };
198
+ } | {
199
+ name: string;
200
+ description: string;
201
+ input_schema: {
202
+ type: "object";
203
+ properties: {
204
+ pattern: {
205
+ type: "string";
206
+ description: string;
207
+ };
208
+ path: {
209
+ type: "string";
210
+ description: string;
211
+ };
212
+ output_mode: {
213
+ type: "string";
214
+ enum: string[];
215
+ description: string;
216
+ };
217
+ command?: undefined;
218
+ timeout?: undefined;
219
+ file_path?: undefined;
220
+ offset?: undefined;
221
+ limit?: undefined;
222
+ content?: undefined;
223
+ old_string?: undefined;
224
+ new_string?: undefined;
225
+ replace_all?: undefined;
226
+ url?: undefined;
227
+ query?: undefined;
228
+ notebook_path?: undefined;
229
+ cell_number?: undefined;
230
+ new_source?: undefined;
231
+ prompt?: undefined;
232
+ description?: undefined;
233
+ question?: undefined;
234
+ };
235
+ required: string[];
236
+ };
237
+ } | {
238
+ name: string;
239
+ description: string;
240
+ input_schema: {
241
+ type: "object";
242
+ properties: {
243
+ url: {
244
+ type: "string";
245
+ description: string;
246
+ };
247
+ command?: undefined;
248
+ timeout?: undefined;
249
+ file_path?: undefined;
250
+ offset?: undefined;
251
+ limit?: undefined;
252
+ content?: undefined;
253
+ old_string?: undefined;
254
+ new_string?: undefined;
255
+ replace_all?: undefined;
256
+ pattern?: undefined;
257
+ path?: undefined;
258
+ output_mode?: undefined;
259
+ query?: undefined;
260
+ notebook_path?: undefined;
261
+ cell_number?: undefined;
262
+ new_source?: undefined;
263
+ prompt?: undefined;
264
+ description?: undefined;
265
+ question?: undefined;
266
+ };
267
+ required: string[];
268
+ };
269
+ } | {
270
+ name: string;
271
+ description: string;
272
+ input_schema: {
273
+ type: "object";
274
+ properties: {
275
+ query: {
276
+ type: "string";
277
+ description: string;
278
+ };
279
+ command?: undefined;
280
+ timeout?: undefined;
281
+ file_path?: undefined;
282
+ offset?: undefined;
283
+ limit?: undefined;
284
+ content?: undefined;
285
+ old_string?: undefined;
286
+ new_string?: undefined;
287
+ replace_all?: undefined;
288
+ pattern?: undefined;
289
+ path?: undefined;
290
+ output_mode?: undefined;
291
+ url?: undefined;
292
+ notebook_path?: undefined;
293
+ cell_number?: undefined;
294
+ new_source?: undefined;
295
+ prompt?: undefined;
296
+ description?: undefined;
297
+ question?: undefined;
298
+ };
299
+ required: string[];
300
+ };
301
+ } | {
302
+ name: string;
303
+ description: string;
304
+ input_schema: {
305
+ type: "object";
306
+ properties: {
307
+ notebook_path: {
308
+ type: "string";
309
+ description: string;
310
+ };
311
+ cell_number: {
312
+ type: "integer";
313
+ description: string;
314
+ };
315
+ new_source: {
316
+ type: "string";
317
+ description: string;
318
+ };
319
+ command?: undefined;
320
+ timeout?: undefined;
321
+ file_path?: undefined;
322
+ offset?: undefined;
323
+ limit?: undefined;
324
+ content?: undefined;
325
+ old_string?: undefined;
326
+ new_string?: undefined;
327
+ replace_all?: undefined;
328
+ pattern?: undefined;
329
+ path?: undefined;
330
+ output_mode?: undefined;
331
+ url?: undefined;
332
+ query?: undefined;
333
+ prompt?: undefined;
334
+ description?: undefined;
335
+ question?: undefined;
336
+ };
337
+ required: string[];
338
+ };
339
+ } | {
340
+ name: string;
341
+ description: string;
342
+ input_schema: {
343
+ type: "object";
344
+ properties: {
345
+ prompt: {
346
+ type: "string";
347
+ description: string;
348
+ };
349
+ description: {
350
+ type: "string";
351
+ description: string;
352
+ };
353
+ command?: undefined;
354
+ timeout?: undefined;
355
+ file_path?: undefined;
356
+ offset?: undefined;
357
+ limit?: undefined;
358
+ content?: undefined;
359
+ old_string?: undefined;
360
+ new_string?: undefined;
361
+ replace_all?: undefined;
362
+ pattern?: undefined;
363
+ path?: undefined;
364
+ output_mode?: undefined;
365
+ url?: undefined;
366
+ query?: undefined;
367
+ notebook_path?: undefined;
368
+ cell_number?: undefined;
369
+ new_source?: undefined;
370
+ question?: undefined;
371
+ };
372
+ required: string[];
373
+ };
374
+ } | {
375
+ name: string;
376
+ description: string;
377
+ input_schema: {
378
+ type: "object";
379
+ properties: {
380
+ question: {
381
+ type: "string";
382
+ description: string;
383
+ };
384
+ command?: undefined;
385
+ timeout?: undefined;
386
+ file_path?: undefined;
387
+ offset?: undefined;
388
+ limit?: undefined;
389
+ content?: undefined;
390
+ old_string?: undefined;
391
+ new_string?: undefined;
392
+ replace_all?: undefined;
393
+ pattern?: undefined;
394
+ path?: undefined;
395
+ output_mode?: undefined;
396
+ url?: undefined;
397
+ query?: undefined;
398
+ notebook_path?: undefined;
399
+ cell_number?: undefined;
400
+ new_source?: undefined;
401
+ prompt?: undefined;
402
+ description?: undefined;
403
+ };
404
+ required: string[];
405
+ };
406
+ })[];
407
+ /** Client tool name → CC tool mapping with parameter translation. */
408
+ interface ToolMapping {
409
+ ccTool: string;
410
+ translateArgs?: (args: Record<string, unknown>) => Record<string, unknown>;
411
+ translateBack?: (args: Record<string, unknown>) => Record<string, unknown>;
412
+ }
413
+ /**
414
+ * Build a CC-template request from a client request.
415
+ * Replaces the entire request structure — tools, fields, ordering — with
416
+ * what real CC sends. Only the conversation content is preserved.
417
+ */
418
+ export declare function buildCCRequest(clientBody: Record<string, unknown>, billingTag: string, agentIdentity: string, cache1h: {
419
+ type: 'ephemeral';
420
+ ttl: '1h';
421
+ }, identity: {
422
+ deviceId: string;
423
+ accountUuid: string;
424
+ sessionId: string;
425
+ }): {
426
+ body: Record<string, unknown>;
427
+ toolMap: Map<string, ToolMapping>;
428
+ unmappedTools: string[];
429
+ };
430
+ /**
431
+ * Reverse-map CC tool calls in a response back to client tool names.
432
+ */
433
+ export declare function reverseMapResponse(responseBody: string, toolMap: Map<string, ToolMapping>): string;
434
+ export {};
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Claude Code request template — the exact tool definitions, system structure,
3
+ * and request shape that real Claude Code sends.
4
+ *
5
+ * Instead of transforming third-party requests signal-by-signal, we replace
6
+ * the entire request with a CC template and inject only the conversation content.
7
+ * The upstream sees a genuine CC request. Anthropic can't detect it without
8
+ * flagging their own binary.
9
+ *
10
+ * Source: MITM capture + binary RE of Claude Code v2.1.100
11
+ */
12
+ /** Claude Code's exact tool definitions (from binary RE + MITM capture). */
13
+ export const CC_TOOL_DEFINITIONS = [
14
+ {
15
+ name: 'Bash',
16
+ description: 'Execute a bash command and return its output. The working directory persists between commands. Use this for system commands, file operations, git, npm, etc.',
17
+ input_schema: {
18
+ type: 'object',
19
+ properties: {
20
+ command: { type: 'string', description: 'The command to execute' },
21
+ timeout: { type: 'number', description: 'Optional timeout in milliseconds (max 600000)' },
22
+ },
23
+ required: ['command'],
24
+ },
25
+ },
26
+ {
27
+ name: 'Read',
28
+ description: 'Reads a file from the local filesystem. The file_path parameter must be an absolute path.',
29
+ input_schema: {
30
+ type: 'object',
31
+ properties: {
32
+ file_path: { type: 'string', description: 'The absolute path to the file to read' },
33
+ offset: { type: 'integer', description: 'The line number to start reading from' },
34
+ limit: { type: 'integer', description: 'The number of lines to read' },
35
+ },
36
+ required: ['file_path'],
37
+ },
38
+ },
39
+ {
40
+ name: 'Write',
41
+ description: 'Writes a file to the local filesystem. This tool will overwrite the existing file if there is one.',
42
+ input_schema: {
43
+ type: 'object',
44
+ properties: {
45
+ file_path: { type: 'string', description: 'The absolute path to the file to write' },
46
+ content: { type: 'string', description: 'The content to write to the file' },
47
+ },
48
+ required: ['file_path', 'content'],
49
+ },
50
+ },
51
+ {
52
+ name: 'Edit',
53
+ description: 'Performs exact string replacements in files. The edit will FAIL if old_string is not unique in the file.',
54
+ input_schema: {
55
+ type: 'object',
56
+ properties: {
57
+ file_path: { type: 'string', description: 'The absolute path to the file to modify' },
58
+ old_string: { type: 'string', description: 'The text to replace' },
59
+ new_string: { type: 'string', description: 'The text to replace it with' },
60
+ replace_all: { type: 'boolean', description: 'Replace all occurrences', default: false },
61
+ },
62
+ required: ['file_path', 'old_string', 'new_string'],
63
+ },
64
+ },
65
+ {
66
+ name: 'Glob',
67
+ description: 'Fast file pattern matching tool that works with any codebase size. Returns matching file paths.',
68
+ input_schema: {
69
+ type: 'object',
70
+ properties: {
71
+ pattern: { type: 'string', description: 'The glob pattern to match files against' },
72
+ path: { type: 'string', description: 'The directory to search in' },
73
+ },
74
+ required: ['pattern'],
75
+ },
76
+ },
77
+ {
78
+ name: 'Grep',
79
+ description: 'A powerful search tool built on ripgrep. Supports full regex syntax.',
80
+ input_schema: {
81
+ type: 'object',
82
+ properties: {
83
+ pattern: { type: 'string', description: 'The regular expression pattern to search for' },
84
+ path: { type: 'string', description: 'File or directory to search in' },
85
+ output_mode: { type: 'string', enum: ['content', 'files_with_matches', 'count'], description: 'Output mode' },
86
+ },
87
+ required: ['pattern'],
88
+ },
89
+ },
90
+ {
91
+ name: 'WebFetch',
92
+ description: 'Fetches a URL from the internet and returns the content.',
93
+ input_schema: {
94
+ type: 'object',
95
+ properties: {
96
+ url: { type: 'string', description: 'The URL to fetch' },
97
+ },
98
+ required: ['url'],
99
+ },
100
+ },
101
+ {
102
+ name: 'WebSearch',
103
+ description: 'Searches the web using a search engine and returns results.',
104
+ input_schema: {
105
+ type: 'object',
106
+ properties: {
107
+ query: { type: 'string', description: 'The search query' },
108
+ },
109
+ required: ['query'],
110
+ },
111
+ },
112
+ {
113
+ name: 'NotebookEdit',
114
+ description: 'Edits a Jupyter notebook cell.',
115
+ input_schema: {
116
+ type: 'object',
117
+ properties: {
118
+ notebook_path: { type: 'string', description: 'Path to the notebook file' },
119
+ cell_number: { type: 'integer', description: 'Cell number to edit' },
120
+ new_source: { type: 'string', description: 'New cell source code' },
121
+ },
122
+ required: ['notebook_path', 'cell_number', 'new_source'],
123
+ },
124
+ },
125
+ {
126
+ name: 'Agent',
127
+ description: 'Launch a new agent to handle complex tasks. The agent runs in an isolated context.',
128
+ input_schema: {
129
+ type: 'object',
130
+ properties: {
131
+ prompt: { type: 'string', description: 'The task for the agent to perform' },
132
+ description: { type: 'string', description: 'A short description of the task' },
133
+ },
134
+ required: ['description', 'prompt'],
135
+ },
136
+ },
137
+ {
138
+ name: 'AskUserQuestion',
139
+ description: 'Ask the user a question and wait for their response.',
140
+ input_schema: {
141
+ type: 'object',
142
+ properties: {
143
+ question: { type: 'string', description: 'The question to ask' },
144
+ },
145
+ required: ['question'],
146
+ },
147
+ },
148
+ ];
149
+ const TOOL_MAP = {
150
+ // Direct maps
151
+ bash: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }) },
152
+ exec: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }) },
153
+ shell: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || a.c || '' }) },
154
+ run: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || '' }) },
155
+ command: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || '' }) },
156
+ terminal: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.cmd || a.command || '' }) },
157
+ process: { ccTool: 'Bash', translateArgs: (a) => ({ command: a.action || a.cmd || '' }) },
158
+ read: { ccTool: 'Read', translateArgs: (a) => ({ file_path: a.path || a.file_path || '' }) },
159
+ read_file: { ccTool: 'Read', translateArgs: (a) => ({ file_path: a.path || a.file_path || '' }) },
160
+ write: { ccTool: 'Write', translateArgs: (a) => ({ file_path: a.path || a.file_path || '', content: a.content || '' }) },
161
+ write_file: { ccTool: 'Write', translateArgs: (a) => ({ file_path: a.path || a.file_path || '', content: a.content || '' }) },
162
+ edit: { ccTool: 'Edit', translateArgs: (a) => ({ file_path: a.path || a.file_path || '', old_string: a.old || a.old_string || '', new_string: a.new || a.new_string || '' }) },
163
+ edit_file: { ccTool: 'Edit' },
164
+ glob: { ccTool: 'Glob' },
165
+ find_files: { ccTool: 'Glob', translateArgs: (a) => ({ pattern: a.pattern || a.query || '' }) },
166
+ list_files: { ccTool: 'Glob', translateArgs: (a) => ({ pattern: a.pattern || '*' }) },
167
+ grep: { ccTool: 'Grep' },
168
+ search: { ccTool: 'Grep', translateArgs: (a) => ({ pattern: a.query || a.pattern || '' }) },
169
+ search_files: { ccTool: 'Grep', translateArgs: (a) => ({ pattern: a.query || a.pattern || '' }) },
170
+ web_search: { ccTool: 'WebSearch', translateArgs: (a) => ({ query: a.query || a.q || '' }) },
171
+ websearch: { ccTool: 'WebSearch', translateArgs: (a) => ({ query: a.query || a.q || '' }) },
172
+ web_fetch: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: a.url || a.u || '' }) },
173
+ webfetch: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: a.url || a.u || '' }) },
174
+ fetch: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: a.url || '' }) },
175
+ browse: { ccTool: 'WebFetch', translateArgs: (a) => ({ url: a.url || '' }) },
176
+ notebook: { ccTool: 'NotebookEdit' },
177
+ notebook_edit: { ccTool: 'NotebookEdit' },
178
+ };
179
+ /**
180
+ * Build a CC-template request from a client request.
181
+ * Replaces the entire request structure — tools, fields, ordering — with
182
+ * what real CC sends. Only the conversation content is preserved.
183
+ */
184
+ export function buildCCRequest(clientBody, billingTag, agentIdentity, cache1h, identity) {
185
+ const model = clientBody.model || 'claude-sonnet-4-6';
186
+ const isHaiku = model.toLowerCase().includes('haiku');
187
+ const messages = clientBody.messages || [];
188
+ const clientTools = clientBody.tools;
189
+ const stream = clientBody.stream ?? false;
190
+ // ── Strip thinking from history ──
191
+ for (const msg of messages) {
192
+ if (msg.role === 'assistant' && Array.isArray(msg.content)) {
193
+ msg.content = msg.content.filter(b => b.type !== 'thinking');
194
+ }
195
+ // Strip cache_control from message blocks
196
+ if (Array.isArray(msg.content)) {
197
+ for (const block of msg.content) {
198
+ delete block.cache_control;
199
+ }
200
+ }
201
+ }
202
+ // ── Build tool mapping ──
203
+ const activeToolMap = new Map();
204
+ const unmappedTools = [];
205
+ if (clientTools) {
206
+ for (const tool of clientTools) {
207
+ const name = (tool.name || '').toLowerCase();
208
+ const mapping = TOOL_MAP[name];
209
+ if (mapping) {
210
+ activeToolMap.set(tool.name, mapping);
211
+ }
212
+ else {
213
+ unmappedTools.push(tool.name);
214
+ // Unknown tools become Bash commands with description as context
215
+ activeToolMap.set(tool.name, {
216
+ ccTool: 'Bash',
217
+ translateArgs: (a) => ({
218
+ command: `echo "Tool ${tool.name} called with: ${JSON.stringify(a).slice(0, 200)}"`,
219
+ }),
220
+ });
221
+ }
222
+ }
223
+ }
224
+ // ── Remap tool_use references in message history ──
225
+ for (const msg of messages) {
226
+ if (Array.isArray(msg.content)) {
227
+ for (const block of msg.content) {
228
+ if (block.type === 'tool_use' && typeof block.name === 'string') {
229
+ const mapping = activeToolMap.get(block.name);
230
+ if (mapping) {
231
+ block.name = mapping.ccTool;
232
+ if (mapping.translateArgs && block.input) {
233
+ block.input = mapping.translateArgs(block.input);
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ // ── Merge system prompt ──
241
+ let systemText = '';
242
+ const sys = clientBody.system;
243
+ if (typeof sys === 'string') {
244
+ systemText = sys;
245
+ }
246
+ else if (Array.isArray(sys)) {
247
+ systemText = sys
248
+ .filter(b => b.text && !b.text.includes('x-anthropic-billing-header:'))
249
+ .map(b => b.text)
250
+ .join('\n\n');
251
+ }
252
+ // ── Build the CC request from template ──
253
+ const ccRequest = {
254
+ model,
255
+ messages,
256
+ system: [
257
+ { type: 'text', text: billingTag },
258
+ { type: 'text', text: agentIdentity, cache_control: cache1h },
259
+ { type: 'text', text: systemText || 'You are a helpful assistant.', cache_control: cache1h },
260
+ ],
261
+ max_tokens: 64000,
262
+ };
263
+ // Model-specific fields
264
+ if (!isHaiku) {
265
+ ccRequest.thinking = { type: 'adaptive' };
266
+ ccRequest.output_config = { effort: 'medium' };
267
+ ccRequest.context_management = { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] };
268
+ }
269
+ // Always include metadata
270
+ ccRequest.metadata = {
271
+ user_id: JSON.stringify({
272
+ device_id: identity.deviceId,
273
+ account_uuid: identity.accountUuid,
274
+ session_id: identity.sessionId,
275
+ }),
276
+ };
277
+ ccRequest.stream = stream;
278
+ // Use CC's exact tool definitions — not the client's
279
+ if (clientTools && clientTools.length > 0) {
280
+ ccRequest.tools = CC_TOOL_DEFINITIONS;
281
+ }
282
+ return { body: ccRequest, toolMap: activeToolMap, unmappedTools };
283
+ }
284
+ /**
285
+ * Reverse-map CC tool calls in a response back to client tool names.
286
+ */
287
+ export function reverseMapResponse(responseBody, toolMap) {
288
+ if (toolMap.size === 0)
289
+ return responseBody;
290
+ let result = responseBody;
291
+ // Build reverse map: CC tool name → original client tool name
292
+ const reverseMap = new Map();
293
+ for (const [clientName, mapping] of toolMap) {
294
+ // Only add if not a direct CC tool name
295
+ if (clientName.toLowerCase() !== mapping.ccTool.toLowerCase()) {
296
+ reverseMap.set(mapping.ccTool, clientName);
297
+ }
298
+ }
299
+ for (const [ccName, clientName] of reverseMap) {
300
+ result = result.replace(new RegExp(`"name"\\s*:\\s*"${ccName}"`, 'g'), `"name":"${clientName}"`);
301
+ }
302
+ return result;
303
+ }
package/dist/proxy.js CHANGED
@@ -6,6 +6,7 @@ import { join } from 'node:path';
6
6
  import { homedir, tmpdir } from 'node:os';
7
7
  import { arch, platform } from 'node:process';
8
8
  import { getAccessToken, getStatus } from './oauth.js';
9
+ import { buildCCRequest, reverseMapResponse } from './cc-template.js';
9
10
  const ANTHROPIC_API = 'https://api.anthropic.com';
10
11
  const DEFAULT_PORT = 3456;
11
12
  const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB — generous for large prompts, prevents abuse
@@ -859,6 +860,7 @@ export async function startProxy(opts = {}) {
859
860
  // Parse body once, apply OpenAI translation, model override, and sanitization
860
861
  let finalBody = body.length > 0 ? body : undefined;
861
862
  let toolMappings = [];
863
+ let ccToolMap = null;
862
864
  if (body.length > 0) {
863
865
  try {
864
866
  const parsed = JSON.parse(body.toString());
@@ -868,67 +870,24 @@ export async function startProxy(opts = {}) {
868
870
  const r = result;
869
871
  // In passthrough mode, skip all Claude-specific injection — OAuth swap only
870
872
  if (!passthrough) {
871
- // ── Stealth layer: make request indistinguishable from real Claude Code ──
872
- // 1. Strip thinking blocks from prior assistant turns (client-side).
873
- // context_management: clear_thinking does NOT reduce input token billing.
874
- // Real Claude Code strips thinking before building the next request.
875
- stripThinkingFromHistory(r);
876
- // 2. Strip client cache_control from messages (prevents overflow — max 4 breakpoints)
877
- const msgs = r.messages;
878
- if (msgs) {
879
- for (const msg of msgs) {
880
- if (Array.isArray(msg.content)) {
881
- for (const block of msg.content) {
882
- delete block.cache_control;
883
- }
884
- }
885
- }
886
- }
887
- // 3. Rewrite tool names to CC equivalents (Anthropic fingerprints on tool names)
888
- toolMappings = rewriteToolNames(r);
889
- // 3. Scrub non-CC fields and normalize field ordering
890
- const reordered = scrubAndReorderFields(r);
891
- for (const key of Object.keys(r))
892
- delete r[key];
893
- Object.assign(r, reordered);
894
- // 3. Inject device identity metadata for session tracking
895
- if (identity.deviceId) {
896
- r.metadata = {
897
- user_id: JSON.stringify({
898
- device_id: identity.deviceId,
899
- account_uuid: identity.accountUuid,
900
- session_id: SESSION_ID,
901
- }),
902
- };
903
- }
904
- // 4. Model-aware defaults matching Claude Code behavior
905
- const modelName = (r.model || '').toLowerCase();
906
- const supportsThinking = !modelName.includes('haiku');
907
- if (supportsThinking && !r.thinking) {
908
- r.thinking = { type: 'adaptive' };
909
- }
910
- // Claude Code always sends max_tokens: 64000. Values above this
911
- // are a fingerprint — cap to match real CC behavior.
912
- if (!r.max_tokens || r.max_tokens !== 64000) {
913
- r.max_tokens = 64000;
914
- }
915
- // Force effort to medium — CC default. Client 'high' is a fingerprint.
916
- if (supportsThinking) {
917
- r.output_config = { effort: 'medium' };
918
- }
919
- if (supportsThinking && !r.context_management) {
920
- r.context_management = { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] };
921
- }
922
- // 5. Build per-request billing tag matching Claude Code binary (Oz$ algorithm)
873
+ // ── Template replay: replace the entire request with a CC template ──
874
+ // Instead of transforming signals one by one, we build a new request
875
+ // from CC's exact template and inject only the conversation content.
876
+ // The upstream sees a genuine CC request structure.
923
877
  const userMsg = extractFirstUserMessage(r);
924
878
  const buildTag = computeBuildTag(userMsg, cliVersion);
925
879
  const cch = computeCch();
926
880
  const fullVersion = `${cliVersion}.${buildTag}`;
927
881
  const billingTag = `x-anthropic-billing-header: cc_version=${fullVersion}; cc_entrypoint=cli; cch=${cch};`;
928
- // 6. Normalize system prompt to exactly 3 blocks (real Claude Code always sends 3)
929
882
  const AGENT_IDENTITY = 'You are a Claude agent, built on Anthropic\'s Claude Agent SDK.';
930
883
  const CACHE_1H = { type: 'ephemeral', ttl: '1h' };
931
- r.system = normalizeSystemTo3Blocks(r.system, billingTag, AGENT_IDENTITY, CACHE_1H);
884
+ const { body: ccBody, toolMap } = buildCCRequest(r, billingTag, AGENT_IDENTITY, CACHE_1H, { deviceId: identity.deviceId, accountUuid: identity.accountUuid, sessionId: SESSION_ID });
885
+ // Store tool map for response reverse-mapping
886
+ ccToolMap = toolMap;
887
+ // Replace request body entirely with CC template
888
+ for (const key of Object.keys(r))
889
+ delete r[key];
890
+ Object.assign(r, ccBody);
932
891
  }
933
892
  finalBody = Buffer.from(JSON.stringify(r));
934
893
  }
@@ -1062,9 +1021,9 @@ export async function startProxy(opts = {}) {
1062
1021
  }
1063
1022
  else {
1064
1023
  // Reverse tool names in streaming chunks
1065
- if (toolMappings.length > 0) {
1024
+ if (ccToolMap && ccToolMap.size > 0) {
1066
1025
  const text = new TextDecoder().decode(value);
1067
- res.write(reverseToolNames(text, toolMappings));
1026
+ res.write(reverseMapResponse(text, ccToolMap));
1068
1027
  }
1069
1028
  else {
1070
1029
  res.write(value);
@@ -1088,7 +1047,8 @@ export async function startProxy(opts = {}) {
1088
1047
  // Buffer and forward
1089
1048
  let responseBody = await upstream.text();
1090
1049
  // Reverse tool name mapping so client sees original names
1091
- responseBody = reverseToolNames(responseBody, toolMappings);
1050
+ if (ccToolMap)
1051
+ responseBody = reverseMapResponse(responseBody, ccToolMap);
1092
1052
  if (isOpenAI && upstream.status >= 200 && upstream.status < 300) {
1093
1053
  try {
1094
1054
  const parsed = JSON.parse(responseBody);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "2.11.0",
3
+ "version": "3.0.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": {