@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/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
+ };