@askalf/dario 2.10.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 +1 -1
- package/dist/cc-template.d.ts +434 -0
- package/dist/cc-template.js +303 -0
- package/dist/proxy.js +45 -46
- package/package.json +1 -1
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
|
-
- **
|
|
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
|
|
@@ -329,6 +330,34 @@ function rewriteToolNames(body) {
|
|
|
329
330
|
tool.name = mcpName;
|
|
330
331
|
}
|
|
331
332
|
}
|
|
333
|
+
// Cap tool count — CC sends max ~22 tools. Excess tools get consolidated
|
|
334
|
+
// into a single MCPCallTool dispatch with routing table.
|
|
335
|
+
const MAX_TOOLS = 22;
|
|
336
|
+
if (tools.length > MAX_TOOLS) {
|
|
337
|
+
const keep = tools.slice(0, MAX_TOOLS - 1); // keep first N-1
|
|
338
|
+
const overflow = tools.slice(MAX_TOOLS - 1);
|
|
339
|
+
// Build dispatch tool that wraps all overflow tools
|
|
340
|
+
const dispatchDesc = overflow.map((t) => `${t.name}: ${(t.description || '').slice(0, 50)}`).join('\n');
|
|
341
|
+
const dispatchTool = {
|
|
342
|
+
name: 'mcp_dispatch',
|
|
343
|
+
description: `Route to one of these tools:\n${dispatchDesc}`,
|
|
344
|
+
input_schema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {
|
|
347
|
+
tool_name: { type: 'string', description: 'Which tool to call', enum: overflow.map((t) => t.name) },
|
|
348
|
+
input: { type: 'object', description: 'Arguments to pass to the tool' },
|
|
349
|
+
},
|
|
350
|
+
required: ['tool_name', 'input'],
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
// Track overflow mappings for reverse
|
|
354
|
+
for (const t of overflow) {
|
|
355
|
+
mappings.push({ original: t.name, mapped: 'mcp_dispatch' });
|
|
356
|
+
}
|
|
357
|
+
// Replace tools array
|
|
358
|
+
keep.push(dispatchTool);
|
|
359
|
+
body.tools = keep;
|
|
360
|
+
}
|
|
332
361
|
return mappings;
|
|
333
362
|
}
|
|
334
363
|
/**
|
|
@@ -831,6 +860,7 @@ export async function startProxy(opts = {}) {
|
|
|
831
860
|
// Parse body once, apply OpenAI translation, model override, and sanitization
|
|
832
861
|
let finalBody = body.length > 0 ? body : undefined;
|
|
833
862
|
let toolMappings = [];
|
|
863
|
+
let ccToolMap = null;
|
|
834
864
|
if (body.length > 0) {
|
|
835
865
|
try {
|
|
836
866
|
const parsed = JSON.parse(body.toString());
|
|
@@ -840,56 +870,24 @@ export async function startProxy(opts = {}) {
|
|
|
840
870
|
const r = result;
|
|
841
871
|
// In passthrough mode, skip all Claude-specific injection — OAuth swap only
|
|
842
872
|
if (!passthrough) {
|
|
843
|
-
// ──
|
|
844
|
-
//
|
|
845
|
-
//
|
|
846
|
-
//
|
|
847
|
-
stripThinkingFromHistory(r);
|
|
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
|
|
851
|
-
const reordered = scrubAndReorderFields(r);
|
|
852
|
-
for (const key of Object.keys(r))
|
|
853
|
-
delete r[key];
|
|
854
|
-
Object.assign(r, reordered);
|
|
855
|
-
// 3. Inject device identity metadata for session tracking
|
|
856
|
-
if (identity.deviceId) {
|
|
857
|
-
r.metadata = {
|
|
858
|
-
user_id: JSON.stringify({
|
|
859
|
-
device_id: identity.deviceId,
|
|
860
|
-
account_uuid: identity.accountUuid,
|
|
861
|
-
session_id: SESSION_ID,
|
|
862
|
-
}),
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
// 4. Model-aware defaults matching Claude Code behavior
|
|
866
|
-
const modelName = (r.model || '').toLowerCase();
|
|
867
|
-
const supportsThinking = !modelName.includes('haiku');
|
|
868
|
-
if (supportsThinking && !r.thinking) {
|
|
869
|
-
r.thinking = { type: 'adaptive' };
|
|
870
|
-
}
|
|
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) {
|
|
874
|
-
r.max_tokens = 64000;
|
|
875
|
-
}
|
|
876
|
-
// Force effort to medium — CC default. Client 'high' is a fingerprint.
|
|
877
|
-
if (supportsThinking) {
|
|
878
|
-
r.output_config = { effort: 'medium' };
|
|
879
|
-
}
|
|
880
|
-
if (supportsThinking && !r.context_management) {
|
|
881
|
-
r.context_management = { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] };
|
|
882
|
-
}
|
|
883
|
-
// 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.
|
|
884
877
|
const userMsg = extractFirstUserMessage(r);
|
|
885
878
|
const buildTag = computeBuildTag(userMsg, cliVersion);
|
|
886
879
|
const cch = computeCch();
|
|
887
880
|
const fullVersion = `${cliVersion}.${buildTag}`;
|
|
888
881
|
const billingTag = `x-anthropic-billing-header: cc_version=${fullVersion}; cc_entrypoint=cli; cch=${cch};`;
|
|
889
|
-
// 6. Normalize system prompt to exactly 3 blocks (real Claude Code always sends 3)
|
|
890
882
|
const AGENT_IDENTITY = 'You are a Claude agent, built on Anthropic\'s Claude Agent SDK.';
|
|
891
883
|
const CACHE_1H = { type: 'ephemeral', ttl: '1h' };
|
|
892
|
-
|
|
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);
|
|
893
891
|
}
|
|
894
892
|
finalBody = Buffer.from(JSON.stringify(r));
|
|
895
893
|
}
|
|
@@ -1023,9 +1021,9 @@ export async function startProxy(opts = {}) {
|
|
|
1023
1021
|
}
|
|
1024
1022
|
else {
|
|
1025
1023
|
// Reverse tool names in streaming chunks
|
|
1026
|
-
if (
|
|
1024
|
+
if (ccToolMap && ccToolMap.size > 0) {
|
|
1027
1025
|
const text = new TextDecoder().decode(value);
|
|
1028
|
-
res.write(
|
|
1026
|
+
res.write(reverseMapResponse(text, ccToolMap));
|
|
1029
1027
|
}
|
|
1030
1028
|
else {
|
|
1031
1029
|
res.write(value);
|
|
@@ -1049,7 +1047,8 @@ export async function startProxy(opts = {}) {
|
|
|
1049
1047
|
// Buffer and forward
|
|
1050
1048
|
let responseBody = await upstream.text();
|
|
1051
1049
|
// Reverse tool name mapping so client sees original names
|
|
1052
|
-
|
|
1050
|
+
if (ccToolMap)
|
|
1051
|
+
responseBody = reverseMapResponse(responseBody, ccToolMap);
|
|
1053
1052
|
if (isOpenAI && upstream.status >= 200 && upstream.status < 300) {
|
|
1054
1053
|
try {
|
|
1055
1054
|
const parsed = JSON.parse(responseBody);
|