@xtruder/opencode-claude-max-plugin 0.1.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 +99 -0
- package/build/agent-index.d.ts +24 -0
- package/build/agent-index.d.ts.map +1 -0
- package/build/agent-index.js +294 -0
- package/build/agent-model.d.ts +34 -0
- package/build/agent-model.d.ts.map +1 -0
- package/build/agent-test.d.ts +2 -0
- package/build/agent-test.d.ts.map +1 -0
- package/build/credentials.d.ts +21 -0
- package/build/credentials.d.ts.map +1 -0
- package/build/index.d.ts +66 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +881 -0
- package/build/model.d.ts +19 -0
- package/build/model.d.ts.map +1 -0
- package/build/prompt.d.ts +11 -0
- package/build/prompt.d.ts.map +1 -0
- package/build/stream.d.ts +6 -0
- package/build/stream.d.ts.map +1 -0
- package/build/tool-names.d.ts +24 -0
- package/build/tool-names.d.ts.map +1 -0
- package/build/tools.d.ts +8 -0
- package/build/tools.d.ts.map +1 -0
- package/build/usage-cli.d.ts +3 -0
- package/build/usage-cli.d.ts.map +1 -0
- package/build/usage-cli.js +139 -0
- package/build/usage.d.ts +28 -0
- package/build/usage.d.ts.map +1 -0
- package/package.json +50 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
|
|
4
|
+
// src/tool-names.ts
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
var BUILTIN_OPENCODE_TO_CLAUDE = {
|
|
9
|
+
task: "Agent",
|
|
10
|
+
question: "AskUserQuestion",
|
|
11
|
+
plan_exit: "ExitPlanMode",
|
|
12
|
+
plan_enter: "EnterPlanMode",
|
|
13
|
+
bash: "Bash",
|
|
14
|
+
glob: "Glob",
|
|
15
|
+
grep: "Grep",
|
|
16
|
+
read: "Read",
|
|
17
|
+
edit: "Edit",
|
|
18
|
+
write: "Write",
|
|
19
|
+
webfetch: "WebFetch",
|
|
20
|
+
websearch: "WebSearch",
|
|
21
|
+
todowrite: "TodoWrite",
|
|
22
|
+
todoread: "TodoRead",
|
|
23
|
+
skill: "Skill",
|
|
24
|
+
multiedit: "MultiEdit",
|
|
25
|
+
list: "List",
|
|
26
|
+
apply_patch: "ApplyPatch",
|
|
27
|
+
batch: "Batch",
|
|
28
|
+
codesearch: "CodeSearch",
|
|
29
|
+
lsp: "LSP"
|
|
30
|
+
};
|
|
31
|
+
var BUILTIN_OPENCODE_NAMES = new Set(Object.keys(BUILTIN_OPENCODE_TO_CLAUDE));
|
|
32
|
+
var BUILTIN_CLAUDE_TO_OPENCODE = {};
|
|
33
|
+
for (const [oc, cc] of Object.entries(BUILTIN_OPENCODE_TO_CLAUDE)) {
|
|
34
|
+
BUILTIN_CLAUDE_TO_OPENCODE[cc] = oc;
|
|
35
|
+
}
|
|
36
|
+
var mcpServerNames = null;
|
|
37
|
+
function detectMcpServers() {
|
|
38
|
+
const servers = new Set;
|
|
39
|
+
const paths = [
|
|
40
|
+
join(process.cwd(), ".opencode", "opencode.json"),
|
|
41
|
+
join(homedir(), ".config", "opencode", "opencode.json"),
|
|
42
|
+
join(homedir(), ".config", "opencode", "opencode.jsonc")
|
|
43
|
+
];
|
|
44
|
+
for (const p of paths) {
|
|
45
|
+
try {
|
|
46
|
+
if (!existsSync(p))
|
|
47
|
+
continue;
|
|
48
|
+
let raw = readFileSync(p, "utf-8");
|
|
49
|
+
raw = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
50
|
+
const config = JSON.parse(raw);
|
|
51
|
+
if (config.mcp && typeof config.mcp === "object") {
|
|
52
|
+
for (const name of Object.keys(config.mcp)) {
|
|
53
|
+
servers.add(name.replace(/[^a-zA-Z0-9_-]/g, "_"));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
return [...servers].sort((a, b) => b.length - a.length);
|
|
59
|
+
}
|
|
60
|
+
function getMcpServers() {
|
|
61
|
+
if (mcpServerNames === null) {
|
|
62
|
+
mcpServerNames = detectMcpServers();
|
|
63
|
+
}
|
|
64
|
+
return mcpServerNames;
|
|
65
|
+
}
|
|
66
|
+
function splitMcpToolName(name) {
|
|
67
|
+
const servers = getMcpServers();
|
|
68
|
+
for (const server of servers) {
|
|
69
|
+
if (name.startsWith(server + "_")) {
|
|
70
|
+
return { server, tool: name.slice(server.length + 1) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const idx = name.indexOf("_");
|
|
74
|
+
if (idx > 0) {
|
|
75
|
+
return { server: name.slice(0, idx), tool: name.slice(idx + 1) };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function toClaudeToolName(opencodeName) {
|
|
80
|
+
const builtin = BUILTIN_OPENCODE_TO_CLAUDE[opencodeName];
|
|
81
|
+
if (builtin)
|
|
82
|
+
return builtin;
|
|
83
|
+
const parts = splitMcpToolName(opencodeName);
|
|
84
|
+
if (parts) {
|
|
85
|
+
return `mcp__${parts.server}__${parts.tool}`;
|
|
86
|
+
}
|
|
87
|
+
return opencodeName;
|
|
88
|
+
}
|
|
89
|
+
function toOpencodeToolName(claudeName) {
|
|
90
|
+
const builtin = BUILTIN_CLAUDE_TO_OPENCODE[claudeName];
|
|
91
|
+
if (builtin)
|
|
92
|
+
return builtin;
|
|
93
|
+
if (claudeName.startsWith("mcp__")) {
|
|
94
|
+
const rest = claudeName.slice(5);
|
|
95
|
+
const idx = rest.indexOf("__");
|
|
96
|
+
if (idx > 0) {
|
|
97
|
+
const server = rest.slice(0, idx);
|
|
98
|
+
const tool = rest.slice(idx + 2);
|
|
99
|
+
return `${server}_${tool}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return claudeName;
|
|
103
|
+
}
|
|
104
|
+
var SYSTEM_PROMPT_REPLACEMENTS = [
|
|
105
|
+
[
|
|
106
|
+
/^You are OpenCode, the best coding agent on the planet\.\s*\n\s*You are an interactive CLI tool that helps users with software engineering tasks\./m,
|
|
107
|
+
"You are an interactive agent that helps users with software engineering tasks."
|
|
108
|
+
],
|
|
109
|
+
[/\bTask\s+tool/g, "Agent tool"],
|
|
110
|
+
[/\btask\s+tool/g, "Agent tool"],
|
|
111
|
+
[/\bthe\s+Task\b/g, "the Agent"],
|
|
112
|
+
[/\buse\s+(?:the\s+)?Task\b/g, "use the Agent"],
|
|
113
|
+
[/\bquestion\s+tool/g, "AskUserQuestion tool"],
|
|
114
|
+
[/\bthe\s+question\b(?=\s+to\s+ask)/g, "the AskUserQuestion"],
|
|
115
|
+
[/\buse\s+(?:the\s+)?bash\b/gi, "use the Bash"],
|
|
116
|
+
[/\buse\s+(?:the\s+)?read\b/gi, "use the Read"],
|
|
117
|
+
[/\buse\s+(?:the\s+)?write\b/gi, "use the Write"],
|
|
118
|
+
[/\buse\s+(?:the\s+)?edit\b/gi, "use the Edit"],
|
|
119
|
+
[/\buse\s+(?:the\s+)?glob\b/gi, "use the Glob"],
|
|
120
|
+
[/\buse\s+(?:the\s+)?grep\b/gi, "use the Grep"],
|
|
121
|
+
[/\buse\s+(?:the\s+)?skill\b/gi, "use the Skill"],
|
|
122
|
+
[/\buse\s+(?:the\s+)?webfetch\b/gi, "use the WebFetch"],
|
|
123
|
+
[/\buse\s+(?:the\s+)?websearch\b/gi, "use the WebSearch"],
|
|
124
|
+
[/\buse\s+(?:the\s+)?todowrite\b/gi, "use the TodoWrite"]
|
|
125
|
+
];
|
|
126
|
+
function rewriteToolNamesInText(text) {
|
|
127
|
+
let result = text;
|
|
128
|
+
for (const [pattern, replacement] of SYSTEM_PROMPT_REPLACEMENTS) {
|
|
129
|
+
result = result.replace(pattern, replacement);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/prompt.ts
|
|
135
|
+
function convertPrompt(prompt) {
|
|
136
|
+
const systemParts = [];
|
|
137
|
+
const messages = [];
|
|
138
|
+
for (const message of prompt) {
|
|
139
|
+
switch (message.role) {
|
|
140
|
+
case "system":
|
|
141
|
+
systemParts.push(message.content);
|
|
142
|
+
break;
|
|
143
|
+
case "user":
|
|
144
|
+
messages.push(convertUserMessage(message));
|
|
145
|
+
break;
|
|
146
|
+
case "assistant":
|
|
147
|
+
messages.push(convertAssistantMessage(message));
|
|
148
|
+
break;
|
|
149
|
+
case "tool":
|
|
150
|
+
messages.push(convertToolMessage(message));
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
system: systemParts.length > 0 ? systemParts.join(`
|
|
156
|
+
|
|
157
|
+
`) : undefined,
|
|
158
|
+
messages
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function convertUserMessage(message) {
|
|
162
|
+
const content = [];
|
|
163
|
+
for (const part of message.content) {
|
|
164
|
+
switch (part.type) {
|
|
165
|
+
case "text":
|
|
166
|
+
content.push({ type: "text", text: part.text });
|
|
167
|
+
break;
|
|
168
|
+
case "file":
|
|
169
|
+
if (typeof part.data === "string" && part.mediaType?.startsWith("image/")) {
|
|
170
|
+
content.push({
|
|
171
|
+
type: "image",
|
|
172
|
+
source: {
|
|
173
|
+
type: "base64",
|
|
174
|
+
media_type: part.mediaType,
|
|
175
|
+
data: part.data
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} else if (part.data instanceof URL) {
|
|
179
|
+
content.push({
|
|
180
|
+
type: "image",
|
|
181
|
+
source: {
|
|
182
|
+
type: "url",
|
|
183
|
+
url: part.data.toString()
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (content.length === 1 && content[0].type === "text") {
|
|
191
|
+
return { role: "user", content: content[0].text };
|
|
192
|
+
}
|
|
193
|
+
return { role: "user", content };
|
|
194
|
+
}
|
|
195
|
+
function convertAssistantMessage(message) {
|
|
196
|
+
const content = [];
|
|
197
|
+
for (const part of message.content) {
|
|
198
|
+
switch (part.type) {
|
|
199
|
+
case "text":
|
|
200
|
+
if (part.text.length > 0) {
|
|
201
|
+
content.push({ type: "text", text: part.text });
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case "reasoning":
|
|
205
|
+
{
|
|
206
|
+
const signature = part.providerMetadata?.anthropic?.signature ?? part.providerOptions?.anthropic?.signature ?? "";
|
|
207
|
+
if (signature) {
|
|
208
|
+
content.push({
|
|
209
|
+
type: "thinking",
|
|
210
|
+
thinking: part.text,
|
|
211
|
+
signature
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
case "tool-call":
|
|
218
|
+
content.push({
|
|
219
|
+
type: "tool_use",
|
|
220
|
+
id: part.toolCallId,
|
|
221
|
+
name: toClaudeToolName(part.toolName),
|
|
222
|
+
input: typeof part.input === "string" ? JSON.parse(part.input) : part.input
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
case "file":
|
|
226
|
+
break;
|
|
227
|
+
case "tool-result":
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { role: "assistant", content };
|
|
232
|
+
}
|
|
233
|
+
function convertToolMessage(message) {
|
|
234
|
+
const content = [];
|
|
235
|
+
for (const part of message.content) {
|
|
236
|
+
const resultContent = formatToolResultContent(part.output);
|
|
237
|
+
content.push({
|
|
238
|
+
type: "tool_result",
|
|
239
|
+
tool_use_id: part.toolCallId,
|
|
240
|
+
content: resultContent,
|
|
241
|
+
is_error: part.output.type === "error-text" || part.output.type === "error-json" ? true : undefined
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return { role: "user", content };
|
|
245
|
+
}
|
|
246
|
+
function formatToolResultContent(output) {
|
|
247
|
+
switch (output.type) {
|
|
248
|
+
case "text":
|
|
249
|
+
case "error-text":
|
|
250
|
+
return output.value;
|
|
251
|
+
case "json":
|
|
252
|
+
case "error-json":
|
|
253
|
+
return JSON.stringify(output.value);
|
|
254
|
+
case "content": {
|
|
255
|
+
const parts = [];
|
|
256
|
+
for (const item of output.value) {
|
|
257
|
+
if (item.type === "text") {
|
|
258
|
+
parts.push({ type: "text", text: item.text });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return parts.length === 1 ? parts[0].text : parts;
|
|
262
|
+
}
|
|
263
|
+
default:
|
|
264
|
+
return String(output.value);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/tools.ts
|
|
269
|
+
function cleanSchema(schema) {
|
|
270
|
+
const cleaned = {};
|
|
271
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
272
|
+
if (key === "custom")
|
|
273
|
+
continue;
|
|
274
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
275
|
+
cleaned[key] = cleanSchema(value);
|
|
276
|
+
} else {
|
|
277
|
+
cleaned[key] = value;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (cleaned.properties && !cleaned.type) {
|
|
281
|
+
cleaned.type = "object";
|
|
282
|
+
}
|
|
283
|
+
return cleaned;
|
|
284
|
+
}
|
|
285
|
+
function convertTools(tools) {
|
|
286
|
+
if (!tools || tools.length === 0)
|
|
287
|
+
return;
|
|
288
|
+
return tools.filter((t) => t.type === "function").map((tool) => ({
|
|
289
|
+
name: toClaudeToolName(tool.name),
|
|
290
|
+
description: tool.description ?? "",
|
|
291
|
+
input_schema: cleanSchema(tool.inputSchema)
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
function convertToolChoice(toolChoice) {
|
|
295
|
+
if (!toolChoice)
|
|
296
|
+
return;
|
|
297
|
+
switch (toolChoice.type) {
|
|
298
|
+
case "auto":
|
|
299
|
+
return { type: "auto" };
|
|
300
|
+
case "required":
|
|
301
|
+
return { type: "any" };
|
|
302
|
+
case "none":
|
|
303
|
+
return;
|
|
304
|
+
case "tool":
|
|
305
|
+
return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/stream.ts
|
|
310
|
+
var idCounter = 0;
|
|
311
|
+
function generateId() {
|
|
312
|
+
return `block-${Date.now()}-${idCounter++}`;
|
|
313
|
+
}
|
|
314
|
+
function mapFinishReason(stopReason) {
|
|
315
|
+
switch (stopReason) {
|
|
316
|
+
case "end_turn":
|
|
317
|
+
return "stop";
|
|
318
|
+
case "stop_sequence":
|
|
319
|
+
return "stop";
|
|
320
|
+
case "max_tokens":
|
|
321
|
+
return "length";
|
|
322
|
+
case "tool_use":
|
|
323
|
+
return "tool-calls";
|
|
324
|
+
default:
|
|
325
|
+
return "unknown";
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function convertStream(anthropicStream, modelId) {
|
|
329
|
+
const blockStates = new Map;
|
|
330
|
+
let inputTokens;
|
|
331
|
+
let outputTokens;
|
|
332
|
+
let cachedInputTokens;
|
|
333
|
+
let cacheCreationTokens;
|
|
334
|
+
return new ReadableStream({
|
|
335
|
+
async start(controller) {
|
|
336
|
+
try {
|
|
337
|
+
for await (const event of anthropicStream) {
|
|
338
|
+
const parts = processEvent(event, blockStates, modelId);
|
|
339
|
+
if (event.type === "message_start" && event.message.usage) {
|
|
340
|
+
const u = event.message.usage;
|
|
341
|
+
inputTokens = u.input_tokens;
|
|
342
|
+
cachedInputTokens = u.cache_read_input_tokens ?? 0;
|
|
343
|
+
cacheCreationTokens = u.cache_creation_input_tokens ?? 0;
|
|
344
|
+
}
|
|
345
|
+
if (event.type === "message_delta") {
|
|
346
|
+
const delta = event;
|
|
347
|
+
outputTokens = delta.usage?.output_tokens;
|
|
348
|
+
const finishReason = mapFinishReason(delta.delta?.stop_reason);
|
|
349
|
+
controller.enqueue({
|
|
350
|
+
type: "finish",
|
|
351
|
+
finishReason,
|
|
352
|
+
usage: {
|
|
353
|
+
inputTokens: inputTokens ?? 0,
|
|
354
|
+
outputTokens: outputTokens ?? 0,
|
|
355
|
+
totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0),
|
|
356
|
+
cachedInputTokens
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
for (const part of parts) {
|
|
361
|
+
controller.enqueue(part);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
controller.enqueue({ type: "error", error });
|
|
366
|
+
} finally {
|
|
367
|
+
controller.close();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function processEvent(event, blockStates, modelId) {
|
|
373
|
+
const parts = [];
|
|
374
|
+
switch (event.type) {
|
|
375
|
+
case "message_start": {
|
|
376
|
+
parts.push({
|
|
377
|
+
type: "response-metadata",
|
|
378
|
+
id: event.message.id,
|
|
379
|
+
modelId: event.message.model ?? modelId,
|
|
380
|
+
timestamp: new Date
|
|
381
|
+
});
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "content_block_start": {
|
|
385
|
+
const index = event.index;
|
|
386
|
+
const block = event.content_block;
|
|
387
|
+
if (block.type === "text") {
|
|
388
|
+
const id = generateId();
|
|
389
|
+
blockStates.set(index, { type: "text", id });
|
|
390
|
+
parts.push({ type: "text-start", id });
|
|
391
|
+
} else if (block.type === "thinking") {
|
|
392
|
+
const id = generateId();
|
|
393
|
+
blockStates.set(index, { type: "thinking", id });
|
|
394
|
+
parts.push({ type: "reasoning-start", id });
|
|
395
|
+
} else if (block.type === "tool_use") {
|
|
396
|
+
const toolId = block.id;
|
|
397
|
+
const toolName = toOpencodeToolName(block.name);
|
|
398
|
+
blockStates.set(index, {
|
|
399
|
+
type: "tool_use",
|
|
400
|
+
id: toolId,
|
|
401
|
+
toolUseId: toolId,
|
|
402
|
+
toolName,
|
|
403
|
+
argsText: ""
|
|
404
|
+
});
|
|
405
|
+
parts.push({
|
|
406
|
+
type: "tool-input-start",
|
|
407
|
+
id: toolId,
|
|
408
|
+
toolName
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "content_block_delta": {
|
|
414
|
+
const index = event.index;
|
|
415
|
+
const state = blockStates.get(index);
|
|
416
|
+
if (!state)
|
|
417
|
+
break;
|
|
418
|
+
const delta = event.delta;
|
|
419
|
+
if (delta.type === "text_delta" && state.type === "text") {
|
|
420
|
+
parts.push({
|
|
421
|
+
type: "text-delta",
|
|
422
|
+
id: state.id,
|
|
423
|
+
delta: delta.text
|
|
424
|
+
});
|
|
425
|
+
} else if (delta.type === "thinking_delta" && state.type === "thinking") {
|
|
426
|
+
parts.push({
|
|
427
|
+
type: "reasoning-delta",
|
|
428
|
+
id: state.id,
|
|
429
|
+
delta: delta.thinking
|
|
430
|
+
});
|
|
431
|
+
} else if (delta.type === "signature_delta" && state.type === "thinking") {
|
|
432
|
+
state.signature = (state.signature ?? "") + delta.signature;
|
|
433
|
+
} else if (delta.type === "input_json_delta" && state.type === "tool_use") {
|
|
434
|
+
state.argsText = (state.argsText ?? "") + delta.partial_json;
|
|
435
|
+
parts.push({
|
|
436
|
+
type: "tool-input-delta",
|
|
437
|
+
id: state.id,
|
|
438
|
+
delta: delta.partial_json
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case "content_block_stop": {
|
|
444
|
+
const index = event.index;
|
|
445
|
+
const state = blockStates.get(index);
|
|
446
|
+
if (!state)
|
|
447
|
+
break;
|
|
448
|
+
if (state.type === "text") {
|
|
449
|
+
parts.push({ type: "text-end", id: state.id });
|
|
450
|
+
} else if (state.type === "thinking") {
|
|
451
|
+
parts.push({
|
|
452
|
+
type: "reasoning-end",
|
|
453
|
+
id: state.id,
|
|
454
|
+
...state.signature ? {
|
|
455
|
+
providerMetadata: { anthropic: { signature: state.signature } }
|
|
456
|
+
} : {}
|
|
457
|
+
});
|
|
458
|
+
} else if (state.type === "tool_use") {
|
|
459
|
+
parts.push({ type: "tool-input-end", id: state.id });
|
|
460
|
+
parts.push({
|
|
461
|
+
type: "tool-call",
|
|
462
|
+
toolCallId: state.toolUseId,
|
|
463
|
+
toolName: state.toolName,
|
|
464
|
+
input: state.argsText || "{}"
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
blockStates.delete(index);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return parts;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/model.ts
|
|
475
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
476
|
+
import { APIError, RateLimitError } from "@anthropic-ai/sdk";
|
|
477
|
+
function handleApiError(error) {
|
|
478
|
+
if (error instanceof RateLimitError || error instanceof APIError && error.status === 429) {
|
|
479
|
+
const h = error.headers;
|
|
480
|
+
const getHeader = (name) => h?.get?.(name) ?? h?.[name] ?? null;
|
|
481
|
+
const errorMsg = error.error?.error?.message ?? error.message ?? "";
|
|
482
|
+
if (errorMsg.includes("Extra usage is required for long context")) {
|
|
483
|
+
throw new Error(`Long context request requires "Extra usage" to be enabled in your Claude subscription. ` + `Go to claude.ai/settings and enable Extra usage, or reduce context size.`);
|
|
484
|
+
}
|
|
485
|
+
const unifiedStatus = getHeader("anthropic-ratelimit-unified-status") ?? "";
|
|
486
|
+
const retryAfter = parseInt(getHeader("retry-after") ?? "0");
|
|
487
|
+
const isSubscriptionLimit = unifiedStatus === "over_limit" || retryAfter > 120;
|
|
488
|
+
if (isSubscriptionLimit) {
|
|
489
|
+
const resetInfo = retryAfter > 0 ? ` Resets in ~${Math.ceil(retryAfter / 60)} minutes.` : "";
|
|
490
|
+
throw new Error(`Claude subscription rate limit reached.${resetInfo} ` + `Use /rate-limit-options in Claude Code to check your options, ` + `or wait for your limit to reset.`);
|
|
491
|
+
}
|
|
492
|
+
throw new Error(`Anthropic API rate limit exceeded after retries. ` + (retryAfter > 0 ? `Retry after ${retryAfter}s.` : `Please try again shortly.`));
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
function mapFinishReason2(stopReason) {
|
|
497
|
+
switch (stopReason) {
|
|
498
|
+
case "end_turn":
|
|
499
|
+
return "stop";
|
|
500
|
+
case "stop_sequence":
|
|
501
|
+
return "stop";
|
|
502
|
+
case "max_tokens":
|
|
503
|
+
return "length";
|
|
504
|
+
case "tool_use":
|
|
505
|
+
return "tool-calls";
|
|
506
|
+
default:
|
|
507
|
+
return "unknown";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
var BILLING_SYSTEM_BLOCK = {
|
|
511
|
+
type: "text",
|
|
512
|
+
text: "x-anthropic-billing-header: cc_version=2.1.81.df2; cc_entrypoint=sdk-cli; cch=00000;"
|
|
513
|
+
};
|
|
514
|
+
var IDENTITY_SYSTEM_BLOCK = {
|
|
515
|
+
type: "text",
|
|
516
|
+
text: "You are a Claude agent, built on Anthropic's Claude Agent SDK."
|
|
517
|
+
};
|
|
518
|
+
var DEVICE_ID = createHash("sha256").update(randomBytes(32)).digest("hex");
|
|
519
|
+
function buildMetadata() {
|
|
520
|
+
return {
|
|
521
|
+
user_id: JSON.stringify({ device_id: DEVICE_ID })
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
class AnthropicSDKModel {
|
|
526
|
+
client;
|
|
527
|
+
providerName;
|
|
528
|
+
isOAuth;
|
|
529
|
+
specificationVersion = "v2";
|
|
530
|
+
provider;
|
|
531
|
+
modelId;
|
|
532
|
+
supportedUrls = {};
|
|
533
|
+
constructor(modelId, client, providerName, isOAuth = false) {
|
|
534
|
+
this.client = client;
|
|
535
|
+
this.providerName = providerName;
|
|
536
|
+
this.isOAuth = isOAuth;
|
|
537
|
+
this.modelId = modelId;
|
|
538
|
+
this.provider = providerName;
|
|
539
|
+
}
|
|
540
|
+
buildParams(options) {
|
|
541
|
+
const { system, messages } = convertPrompt(options.prompt);
|
|
542
|
+
const warnings = [];
|
|
543
|
+
if (options.presencePenalty != null) {
|
|
544
|
+
warnings.push({ type: "unsupported-setting", setting: "presencePenalty" });
|
|
545
|
+
}
|
|
546
|
+
if (options.frequencyPenalty != null) {
|
|
547
|
+
warnings.push({ type: "unsupported-setting", setting: "frequencyPenalty" });
|
|
548
|
+
}
|
|
549
|
+
if (options.seed != null) {
|
|
550
|
+
warnings.push({ type: "unsupported-setting", setting: "seed" });
|
|
551
|
+
}
|
|
552
|
+
const tools = options.toolChoice?.type === "none" ? undefined : convertTools(options.tools);
|
|
553
|
+
const toolChoice = options.toolChoice?.type === "none" ? undefined : convertToolChoice(options.toolChoice);
|
|
554
|
+
const params = {
|
|
555
|
+
model: this.modelId,
|
|
556
|
+
max_tokens: options.maxOutputTokens ?? 64000,
|
|
557
|
+
messages
|
|
558
|
+
};
|
|
559
|
+
if (this.isOAuth) {
|
|
560
|
+
const rewrite = (s) => rewriteToolNamesInText(s);
|
|
561
|
+
const SYSTEM_CACHE = { type: "ephemeral", ttl: "1h", scope: "global" };
|
|
562
|
+
if (system) {
|
|
563
|
+
const contentBlocks = typeof system === "string" ? [{ type: "text", text: rewrite(system), cache_control: SYSTEM_CACHE }] : Array.isArray(system) ? system.map((b, i) => b.type === "text" ? { ...b, text: rewrite(b.text), ...i === system.length - 1 ? { cache_control: SYSTEM_CACHE } : {} } : b) : [{ ...system, cache_control: SYSTEM_CACHE }];
|
|
564
|
+
params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK, ...contentBlocks];
|
|
565
|
+
} else {
|
|
566
|
+
params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK];
|
|
567
|
+
}
|
|
568
|
+
params.metadata = buildMetadata();
|
|
569
|
+
const supportsEffort = !this.modelId.includes("haiku") && !this.modelId.includes("claude-3-");
|
|
570
|
+
if (supportsEffort) {
|
|
571
|
+
if (options.temperature == null) {
|
|
572
|
+
params.temperature = 1;
|
|
573
|
+
}
|
|
574
|
+
params.output_config = { effort: "medium" };
|
|
575
|
+
}
|
|
576
|
+
} else if (system) {
|
|
577
|
+
const CACHE = { type: "ephemeral" };
|
|
578
|
+
if (typeof system === "string") {
|
|
579
|
+
params.system = [
|
|
580
|
+
{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE },
|
|
581
|
+
{ type: "text", text: system, cache_control: CACHE }
|
|
582
|
+
];
|
|
583
|
+
} else if (Array.isArray(system)) {
|
|
584
|
+
const blocks = system.map((b, i) => b.type === "text" && i === system.length - 1 ? { ...b, cache_control: CACHE } : b);
|
|
585
|
+
params.system = [{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE }, ...blocks];
|
|
586
|
+
} else {
|
|
587
|
+
params.system = [{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE }, { ...system, cache_control: CACHE }];
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (tools && tools.length > 0)
|
|
591
|
+
params.tools = tools;
|
|
592
|
+
if (toolChoice)
|
|
593
|
+
params.tool_choice = toolChoice;
|
|
594
|
+
if (params.messages && params.messages.length > 1) {
|
|
595
|
+
const msgs = params.messages;
|
|
596
|
+
const msgCache = this.isOAuth ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
|
|
597
|
+
let cachedSomething = false;
|
|
598
|
+
for (let i = msgs.length - 1;i >= 0; i--) {
|
|
599
|
+
const msg = msgs[i];
|
|
600
|
+
if (msg.role !== "user" || !Array.isArray(msg.content))
|
|
601
|
+
continue;
|
|
602
|
+
for (let j = msg.content.length - 1;j >= 0; j--) {
|
|
603
|
+
const block = msg.content[j];
|
|
604
|
+
if (block.type === "tool_result" && !block.cache_control) {
|
|
605
|
+
block.cache_control = msgCache;
|
|
606
|
+
cachedSomething = true;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (cachedSomething)
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
if (!cachedSomething) {
|
|
614
|
+
const msg = msgs[msgs.length - 2];
|
|
615
|
+
if (msg && Array.isArray(msg.content) && msg.content.length > 0) {
|
|
616
|
+
const lastContent = msg.content[msg.content.length - 1];
|
|
617
|
+
if (lastContent && !lastContent.cache_control) {
|
|
618
|
+
lastContent.cache_control = msgCache;
|
|
619
|
+
}
|
|
620
|
+
} else if (msg && typeof msg.content === "string") {
|
|
621
|
+
msgs[msgs.length - 2] = {
|
|
622
|
+
...msg,
|
|
623
|
+
content: [{ type: "text", text: msg.content, cache_control: msgCache }]
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (options.temperature != null)
|
|
629
|
+
params.temperature = options.temperature;
|
|
630
|
+
if (options.topP != null)
|
|
631
|
+
params.top_p = options.topP;
|
|
632
|
+
if (options.topK != null)
|
|
633
|
+
params.top_k = options.topK;
|
|
634
|
+
if (options.stopSequences && options.stopSequences.length > 0) {
|
|
635
|
+
params.stop_sequences = options.stopSequences;
|
|
636
|
+
}
|
|
637
|
+
const anthropicOptions = options.providerOptions?.["anthropic-sdk"] ?? options.providerOptions?.anthropic;
|
|
638
|
+
if (anthropicOptions) {
|
|
639
|
+
if (anthropicOptions.thinking) {
|
|
640
|
+
const t = anthropicOptions.thinking;
|
|
641
|
+
if (t.type === "enabled" && t.budgetTokens) {
|
|
642
|
+
params.thinking = { type: "enabled", budget_tokens: t.budgetTokens };
|
|
643
|
+
} else if (t.type === "adaptive") {
|
|
644
|
+
params.thinking = { type: "enabled", budget_tokens: 1e4 };
|
|
645
|
+
} else {
|
|
646
|
+
params.thinking = t;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (anthropicOptions.metadata)
|
|
650
|
+
params.metadata = anthropicOptions.metadata;
|
|
651
|
+
}
|
|
652
|
+
return { params, warnings };
|
|
653
|
+
}
|
|
654
|
+
async doGenerate(options) {
|
|
655
|
+
const { params, warnings } = this.buildParams(options);
|
|
656
|
+
let response;
|
|
657
|
+
try {
|
|
658
|
+
response = await this.client.messages.create({
|
|
659
|
+
...params,
|
|
660
|
+
stream: false
|
|
661
|
+
}, { signal: options.abortSignal });
|
|
662
|
+
} catch (error) {
|
|
663
|
+
handleApiError(error);
|
|
664
|
+
}
|
|
665
|
+
const content = [];
|
|
666
|
+
for (const block of response.content) {
|
|
667
|
+
switch (block.type) {
|
|
668
|
+
case "text":
|
|
669
|
+
content.push({ type: "text", text: block.text });
|
|
670
|
+
break;
|
|
671
|
+
case "tool_use":
|
|
672
|
+
content.push({
|
|
673
|
+
type: "tool-call",
|
|
674
|
+
toolCallId: block.id,
|
|
675
|
+
toolName: toOpencodeToolName(block.name),
|
|
676
|
+
input: JSON.stringify(block.input)
|
|
677
|
+
});
|
|
678
|
+
break;
|
|
679
|
+
default: {
|
|
680
|
+
const anyBlock = block;
|
|
681
|
+
if (anyBlock.type === "thinking" && anyBlock.thinking) {
|
|
682
|
+
content.push({
|
|
683
|
+
type: "reasoning",
|
|
684
|
+
text: anyBlock.thinking,
|
|
685
|
+
providerMetadata: anyBlock.signature ? { anthropic: { signature: anyBlock.signature } } : undefined
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
content,
|
|
694
|
+
finishReason: mapFinishReason2(response.stop_reason),
|
|
695
|
+
usage: {
|
|
696
|
+
inputTokens: response.usage.input_tokens,
|
|
697
|
+
outputTokens: response.usage.output_tokens,
|
|
698
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
699
|
+
cachedInputTokens: response.usage.cache_read_input_tokens
|
|
700
|
+
},
|
|
701
|
+
response: {
|
|
702
|
+
id: response.id,
|
|
703
|
+
modelId: response.model,
|
|
704
|
+
timestamp: new Date
|
|
705
|
+
},
|
|
706
|
+
warnings
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
async doStream(options) {
|
|
710
|
+
const { params, warnings } = this.buildParams(options);
|
|
711
|
+
let anthropicStream;
|
|
712
|
+
try {
|
|
713
|
+
anthropicStream = await this.client.messages.create({
|
|
714
|
+
...params,
|
|
715
|
+
stream: true
|
|
716
|
+
}, { signal: options.abortSignal });
|
|
717
|
+
} catch (error) {
|
|
718
|
+
handleApiError(error);
|
|
719
|
+
}
|
|
720
|
+
const stream = convertStream(anthropicStream, this.modelId);
|
|
721
|
+
const wrappedStream = new ReadableStream({
|
|
722
|
+
async start(controller) {
|
|
723
|
+
controller.enqueue({
|
|
724
|
+
type: "stream-start",
|
|
725
|
+
warnings
|
|
726
|
+
});
|
|
727
|
+
const reader = stream.getReader();
|
|
728
|
+
try {
|
|
729
|
+
while (true) {
|
|
730
|
+
const { done, value } = await reader.read();
|
|
731
|
+
if (done)
|
|
732
|
+
break;
|
|
733
|
+
controller.enqueue(value);
|
|
734
|
+
}
|
|
735
|
+
} finally {
|
|
736
|
+
reader.releaseLock();
|
|
737
|
+
controller.close();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
return {
|
|
742
|
+
stream: wrappedStream,
|
|
743
|
+
request: { body: params }
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/credentials.ts
|
|
749
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
750
|
+
import { join as join2 } from "node:path";
|
|
751
|
+
import { homedir as homedir2 } from "node:os";
|
|
752
|
+
function getCredentialsPath() {
|
|
753
|
+
return join2(homedir2(), ".claude", ".credentials.json");
|
|
754
|
+
}
|
|
755
|
+
function readClaudeCredentials(path) {
|
|
756
|
+
const credPath = path ?? getCredentialsPath();
|
|
757
|
+
if (!existsSync2(credPath))
|
|
758
|
+
return null;
|
|
759
|
+
try {
|
|
760
|
+
const raw = readFileSync2(credPath, "utf-8");
|
|
761
|
+
const parsed = JSON.parse(raw);
|
|
762
|
+
if (!parsed.claudeAiOauth?.accessToken)
|
|
763
|
+
return null;
|
|
764
|
+
return parsed.claudeAiOauth;
|
|
765
|
+
} catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function isExpired(creds) {
|
|
770
|
+
const buffer = 5 * 60 * 1000;
|
|
771
|
+
return Date.now() > creds.expiresAt - buffer;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/index.ts
|
|
775
|
+
var CLAUDE_CODE_VERSION = "2.1.81";
|
|
776
|
+
var OAUTH_BETAS = [
|
|
777
|
+
"claude-code-20250219",
|
|
778
|
+
"oauth-2025-04-20",
|
|
779
|
+
"interleaved-thinking-2025-05-14",
|
|
780
|
+
"context-management-2025-06-27",
|
|
781
|
+
"prompt-caching-scope-2026-01-05",
|
|
782
|
+
"effort-2025-11-24"
|
|
783
|
+
];
|
|
784
|
+
var CONTEXT_1M_BETA = "context-1m-2025-08-07";
|
|
785
|
+
var API_KEY_BETAS = [
|
|
786
|
+
"interleaved-thinking-2025-05-14",
|
|
787
|
+
"fine-grained-tool-streaming-2025-05-14"
|
|
788
|
+
];
|
|
789
|
+
function resolveAuth(options) {
|
|
790
|
+
if (options.apiKey) {
|
|
791
|
+
return { apiKey: options.apiKey, isOAuth: false };
|
|
792
|
+
}
|
|
793
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
794
|
+
return { apiKey: process.env.ANTHROPIC_API_KEY, isOAuth: false };
|
|
795
|
+
}
|
|
796
|
+
const creds = readClaudeCredentials(options.credentialsPath);
|
|
797
|
+
if (creds) {
|
|
798
|
+
if (isExpired(creds)) {
|
|
799
|
+
console.warn("[anthropic-sdk-provider] Claude Code OAuth token is expired. " + "Run 'claude' to re-authenticate, then restart.");
|
|
800
|
+
}
|
|
801
|
+
return { apiKey: null, authToken: creds.accessToken, isOAuth: true };
|
|
802
|
+
}
|
|
803
|
+
return { isOAuth: false };
|
|
804
|
+
}
|
|
805
|
+
function createAnthropicSDK(options = {}) {
|
|
806
|
+
const {
|
|
807
|
+
apiKey,
|
|
808
|
+
baseURL,
|
|
809
|
+
headers,
|
|
810
|
+
fetch: customFetch,
|
|
811
|
+
name = "anthropic-sdk",
|
|
812
|
+
credentialsPath,
|
|
813
|
+
...rest
|
|
814
|
+
} = options;
|
|
815
|
+
const auth = resolveAuth(options);
|
|
816
|
+
const defaultHeaders = {
|
|
817
|
+
...headers,
|
|
818
|
+
"anthropic-beta": (auth.isOAuth ? OAUTH_BETAS : API_KEY_BETAS).join(",")
|
|
819
|
+
};
|
|
820
|
+
if (auth.isOAuth) {
|
|
821
|
+
defaultHeaders["user-agent"] = `claude-cli/${CLAUDE_CODE_VERSION} (external, sdk-cli)`;
|
|
822
|
+
defaultHeaders["x-app"] = "cli";
|
|
823
|
+
defaultHeaders["anthropic-dangerous-direct-browser-access"] = "true";
|
|
824
|
+
defaultHeaders["x-stainless-package-version"] = "0.74.0";
|
|
825
|
+
}
|
|
826
|
+
const CONTEXT_1M_BODY_THRESHOLD = 600000;
|
|
827
|
+
const baseFetch = customFetch ?? globalThis.fetch;
|
|
828
|
+
const wrappedFetch = async (url, init) => {
|
|
829
|
+
if (init?.body && typeof init.body === "string" && init.body.length > CONTEXT_1M_BODY_THRESHOLD) {
|
|
830
|
+
const headers2 = new Headers(init.headers);
|
|
831
|
+
const existingBetas = headers2.get("anthropic-beta") ?? "";
|
|
832
|
+
if (!existingBetas.includes(CONTEXT_1M_BETA)) {
|
|
833
|
+
headers2.set("anthropic-beta", existingBetas + "," + CONTEXT_1M_BETA);
|
|
834
|
+
init = { ...init, headers: headers2 };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const resp = await baseFetch(url, init);
|
|
838
|
+
if (resp.status === 429) {
|
|
839
|
+
const unifiedStatus = resp.headers.get("anthropic-ratelimit-unified-status") ?? "";
|
|
840
|
+
const retryAfter = parseInt(resp.headers.get("retry-after") ?? "0");
|
|
841
|
+
const bodyText = await resp.clone().text();
|
|
842
|
+
const isLongContextLimit = bodyText.includes("Extra usage is required for long context");
|
|
843
|
+
const isSubscriptionLimit = isLongContextLimit || unifiedStatus === "over_limit" || retryAfter > 120;
|
|
844
|
+
if (isSubscriptionLimit) {
|
|
845
|
+
const body = bodyText;
|
|
846
|
+
const headers2 = new Headers(resp.headers);
|
|
847
|
+
headers2.set("x-should-retry", "false");
|
|
848
|
+
return new Response(body, {
|
|
849
|
+
status: resp.status,
|
|
850
|
+
statusText: resp.statusText,
|
|
851
|
+
headers: headers2
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return resp;
|
|
856
|
+
};
|
|
857
|
+
const client = new Anthropic({
|
|
858
|
+
apiKey: auth.apiKey ?? null,
|
|
859
|
+
authToken: auth.authToken ?? null,
|
|
860
|
+
baseURL,
|
|
861
|
+
defaultHeaders,
|
|
862
|
+
fetch: wrappedFetch
|
|
863
|
+
});
|
|
864
|
+
return {
|
|
865
|
+
languageModel(modelId) {
|
|
866
|
+
return new AnthropicSDKModel(modelId, client, name, auth.isOAuth);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
var src_default = createAnthropicSDK;
|
|
871
|
+
export {
|
|
872
|
+
readClaudeCredentials,
|
|
873
|
+
isExpired,
|
|
874
|
+
getCredentialsPath,
|
|
875
|
+
src_default as default,
|
|
876
|
+
createAnthropicSDK,
|
|
877
|
+
OAUTH_BETAS,
|
|
878
|
+
CONTEXT_1M_BETA,
|
|
879
|
+
AnthropicSDKModel,
|
|
880
|
+
API_KEY_BETAS
|
|
881
|
+
};
|