@xtruder/opencode-claude-max-plugin 0.1.17 → 0.2.2

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.
@@ -0,0 +1,1212 @@
1
+ // src/usage.ts
2
+ import {
3
+ closeSync,
4
+ constants as fsConstants,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync as readFileSync2,
8
+ renameSync,
9
+ unlinkSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import { homedir as homedir2 } from "node:os";
13
+ import { join as join2 } from "node:path";
14
+
15
+ // src/credentials.ts
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { execSync } from "node:child_process";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ var CREDENTIAL_CACHE_TTL_MS = 30000;
21
+ var EXPIRY_BUFFER_MS = 60000;
22
+ var cachedCreds = null;
23
+ var cachedAt = 0;
24
+ var cachedPath;
25
+ function getCredentialsPath() {
26
+ return join(homedir(), ".claude", ".credentials.json");
27
+ }
28
+ function readClaudeCredentials(path) {
29
+ const credPath = path ?? getCredentialsPath();
30
+ if (!existsSync(credPath))
31
+ return null;
32
+ try {
33
+ const raw = readFileSync(credPath, "utf-8");
34
+ const parsed = JSON.parse(raw);
35
+ if (!parsed.claudeAiOauth?.accessToken)
36
+ return null;
37
+ return parsed.claudeAiOauth;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function isExpired(creds) {
43
+ return Date.now() > creds.expiresAt - EXPIRY_BUFFER_MS;
44
+ }
45
+ function refreshViaCli() {
46
+ const maxAttempts = 2;
47
+ for (let i = 0;i < maxAttempts; i++) {
48
+ try {
49
+ execSync("claude -p . --model haiku", {
50
+ timeout: 60000,
51
+ encoding: "utf-8",
52
+ env: { ...process.env, TERM: "dumb" },
53
+ stdio: "ignore"
54
+ });
55
+ return true;
56
+ } catch {}
57
+ }
58
+ return false;
59
+ }
60
+ function refreshIfNeeded(path) {
61
+ const creds = readClaudeCredentials(path);
62
+ if (!creds)
63
+ return null;
64
+ if (!isExpired(creds))
65
+ return creds;
66
+ console.warn("[anthropic-sdk-provider] OAuth token near expiry, refreshing via Claude CLI...");
67
+ const refreshed = refreshViaCli();
68
+ if (!refreshed) {
69
+ console.warn("[anthropic-sdk-provider] CLI refresh failed. Token may be expired.");
70
+ return creds;
71
+ }
72
+ const fresh = readClaudeCredentials(path);
73
+ if (fresh && !isExpired(fresh)) {
74
+ console.warn("[anthropic-sdk-provider] Token refreshed successfully.");
75
+ return fresh;
76
+ }
77
+ return fresh ?? creds;
78
+ }
79
+ function getCachedCredentials(path) {
80
+ const now = Date.now();
81
+ const resolvedPath = path ?? cachedPath;
82
+ if (cachedCreds && now - cachedAt < CREDENTIAL_CACHE_TTL_MS && !isExpired(cachedCreds)) {
83
+ return cachedCreds;
84
+ }
85
+ const fresh = refreshIfNeeded(resolvedPath);
86
+ if (fresh) {
87
+ cachedCreds = fresh;
88
+ cachedAt = now;
89
+ cachedPath = resolvedPath;
90
+ } else {
91
+ cachedCreds = null;
92
+ }
93
+ return cachedCreds;
94
+ }
95
+
96
+ // src/usage.ts
97
+ var cachedUsage = {};
98
+ var XDG_STATE = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
99
+ var USAGE_CACHE_DIR = join2(XDG_STATE, "opencode");
100
+ var USAGE_CACHE_FILE = join2(USAGE_CACHE_DIR, "usage-cache.json");
101
+ function emptyCache() {
102
+ return { api: null, headers: null, updatedAt: 0, apiFetchedAt: 0, apiRateLimitUntil: 0 };
103
+ }
104
+ var USAGE_LOCK_FILE = USAGE_CACHE_FILE + ".lock";
105
+ var USAGE_TMP_FILE = USAGE_CACHE_FILE + ".tmp";
106
+ var LOCK_STALE_MS = 5000;
107
+ function acquireLock() {
108
+ try {
109
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
110
+ const fd = openSync(USAGE_LOCK_FILE, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
111
+ writeFileSync(fd, `${process.pid}
112
+ ${Date.now()}`);
113
+ closeSync(fd);
114
+ return true;
115
+ } catch (err) {
116
+ if (err?.code !== "EEXIST")
117
+ return false;
118
+ try {
119
+ const text = readFileSync2(USAGE_LOCK_FILE, "utf-8");
120
+ const ts = parseInt(text.split(`
121
+ `)[1] ?? "0");
122
+ if (Date.now() - ts > LOCK_STALE_MS) {
123
+ unlinkSync(USAGE_LOCK_FILE);
124
+ return acquireLock();
125
+ }
126
+ } catch {
127
+ try {
128
+ unlinkSync(USAGE_LOCK_FILE);
129
+ } catch {}
130
+ return acquireLock();
131
+ }
132
+ return false;
133
+ }
134
+ }
135
+ function releaseLock() {
136
+ try {
137
+ unlinkSync(USAGE_LOCK_FILE);
138
+ } catch {}
139
+ }
140
+ function readUsageCache() {
141
+ try {
142
+ const text = readFileSync2(USAGE_CACHE_FILE, "utf-8");
143
+ const data = JSON.parse(text);
144
+ if (data && typeof data.updatedAt === "number")
145
+ return data;
146
+ } catch {}
147
+ return emptyCache();
148
+ }
149
+ function writeUsageCache(update) {
150
+ if (!acquireLock())
151
+ return;
152
+ try {
153
+ const existing = readUsageCache();
154
+ const merged = {
155
+ ...existing,
156
+ ...update,
157
+ updatedAt: Date.now()
158
+ };
159
+ mkdirSync(USAGE_CACHE_DIR, { recursive: true });
160
+ writeFileSync(USAGE_TMP_FILE, JSON.stringify(merged));
161
+ renameSync(USAGE_TMP_FILE, USAGE_CACHE_FILE);
162
+ } catch {
163
+ try {
164
+ unlinkSync(USAGE_TMP_FILE);
165
+ } catch {}
166
+ } finally {
167
+ releaseLock();
168
+ }
169
+ }
170
+ function persistCachedUsage() {
171
+ if (cachedUsage.fiveHourUtil == null && cachedUsage.sevenDayUtil == null)
172
+ return;
173
+ writeUsageCache({ headers: { ...cachedUsage } });
174
+ }
175
+ var API_TTL_MS = 5 * 60000;
176
+
177
+ // src/cch.ts
178
+ import xxhashInit from "xxhash-wasm";
179
+ var CCH_SEED = 0x6e52736ac806831en;
180
+ var CCH_PLACEHOLDER = "cch=00000";
181
+ var CCH_MASK = 0xfffffn;
182
+ var hasherPromise = null;
183
+ function getHasher() {
184
+ if (!hasherPromise) {
185
+ hasherPromise = xxhashInit();
186
+ }
187
+ return hasherPromise;
188
+ }
189
+ async function computeCch(body) {
190
+ const hasher = await getHasher();
191
+ const hash = hasher.h64Raw(new TextEncoder().encode(body), CCH_SEED);
192
+ return (hash & CCH_MASK).toString(16).padStart(5, "0");
193
+ }
194
+ function replaceCchPlaceholder(body, cch) {
195
+ return body.replace(CCH_PLACEHOLDER, `cch=${cch}`);
196
+ }
197
+ function hasCchPlaceholder(body) {
198
+ return body.includes(CCH_PLACEHOLDER);
199
+ }
200
+
201
+ // src/index.ts
202
+ import Anthropic from "@anthropic-ai/sdk";
203
+
204
+ // src/model.ts
205
+ import { APIError, RateLimitError } from "@anthropic-ai/sdk";
206
+
207
+ // src/tool-names.ts
208
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
209
+ import { homedir as homedir3 } from "node:os";
210
+ import { join as join3 } from "node:path";
211
+ var BUILTIN_OPENCODE_TO_CLAUDE = {
212
+ task: "Agent",
213
+ question: "AskUserQuestion",
214
+ plan_exit: "ExitPlanMode",
215
+ plan_enter: "EnterPlanMode",
216
+ bash: "Bash",
217
+ glob: "Glob",
218
+ grep: "Grep",
219
+ read: "Read",
220
+ edit: "Edit",
221
+ write: "Write",
222
+ webfetch: "WebFetch",
223
+ websearch: "WebSearch",
224
+ todowrite: "TodoWrite",
225
+ todoread: "TodoRead",
226
+ skill: "Skill",
227
+ multiedit: "MultiEdit",
228
+ list: "List",
229
+ apply_patch: "ApplyPatch",
230
+ batch: "Batch",
231
+ codesearch: "CodeSearch",
232
+ lsp: "LSP"
233
+ };
234
+ var BUILTIN_CLAUDE_TO_OPENCODE = {};
235
+ for (const [oc, cc] of Object.entries(BUILTIN_OPENCODE_TO_CLAUDE)) {
236
+ BUILTIN_CLAUDE_TO_OPENCODE[cc] = oc;
237
+ }
238
+ var mcpServerNames = null;
239
+ function detectMcpServers() {
240
+ const servers = new Set;
241
+ const paths = [
242
+ join3(process.cwd(), ".opencode", "opencode.json"),
243
+ join3(homedir3(), ".config", "opencode", "opencode.json"),
244
+ join3(homedir3(), ".config", "opencode", "opencode.jsonc")
245
+ ];
246
+ for (const p of paths) {
247
+ try {
248
+ if (!existsSync2(p))
249
+ continue;
250
+ let raw = readFileSync3(p, "utf-8");
251
+ raw = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
252
+ const config = JSON.parse(raw);
253
+ if (config.mcp && typeof config.mcp === "object") {
254
+ for (const name of Object.keys(config.mcp)) {
255
+ servers.add(name.replace(/[^a-zA-Z0-9_-]/g, "_"));
256
+ }
257
+ }
258
+ } catch {}
259
+ }
260
+ return [...servers].toSorted((a, b) => b.length - a.length);
261
+ }
262
+ function getMcpServers() {
263
+ if (mcpServerNames === null) {
264
+ mcpServerNames = detectMcpServers();
265
+ }
266
+ return mcpServerNames;
267
+ }
268
+ function splitMcpToolName(name) {
269
+ const servers = getMcpServers();
270
+ for (const server of servers) {
271
+ if (name.startsWith(server + "_")) {
272
+ return { server, tool: name.slice(server.length + 1) };
273
+ }
274
+ }
275
+ const idx = name.indexOf("_");
276
+ if (idx > 0) {
277
+ return { server: name.slice(0, idx), tool: name.slice(idx + 1) };
278
+ }
279
+ return null;
280
+ }
281
+ function toClaudeToolName(opencodeName) {
282
+ const builtin = BUILTIN_OPENCODE_TO_CLAUDE[opencodeName];
283
+ if (builtin)
284
+ return builtin;
285
+ const parts = splitMcpToolName(opencodeName);
286
+ if (parts) {
287
+ return `mcp__${parts.server}__${parts.tool}`;
288
+ }
289
+ return opencodeName;
290
+ }
291
+ function toOpencodeToolName(claudeName) {
292
+ const builtin = BUILTIN_CLAUDE_TO_OPENCODE[claudeName];
293
+ if (builtin)
294
+ return builtin;
295
+ if (claudeName.startsWith("mcp__")) {
296
+ const rest = claudeName.slice(5);
297
+ const idx = rest.indexOf("__");
298
+ if (idx > 0) {
299
+ const server = rest.slice(0, idx);
300
+ const tool = rest.slice(idx + 2);
301
+ return `${server}_${tool}`;
302
+ }
303
+ }
304
+ return claudeName;
305
+ }
306
+ var SYSTEM_PROMPT_REPLACEMENTS = [
307
+ [
308
+ /^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,
309
+ "You are an interactive agent that helps users with software engineering tasks."
310
+ ],
311
+ [/\bTask\s+tool/g, "Agent tool"],
312
+ [/\btask\s+tool/g, "Agent tool"],
313
+ [/\bthe\s+Task\b/g, "the Agent"],
314
+ [/\buse\s+(?:the\s+)?Task\b/g, "use the Agent"],
315
+ [/\bquestion\s+tool/g, "AskUserQuestion tool"],
316
+ [/\bthe\s+question\b(?=\s+to\s+ask)/g, "the AskUserQuestion"],
317
+ [/\buse\s+(?:the\s+)?bash\b/gi, "use the Bash"],
318
+ [/\buse\s+(?:the\s+)?read\b/gi, "use the Read"],
319
+ [/\buse\s+(?:the\s+)?write\b/gi, "use the Write"],
320
+ [/\buse\s+(?:the\s+)?edit\b/gi, "use the Edit"],
321
+ [/\buse\s+(?:the\s+)?glob\b/gi, "use the Glob"],
322
+ [/\buse\s+(?:the\s+)?grep\b/gi, "use the Grep"],
323
+ [/\buse\s+(?:the\s+)?skill\b/gi, "use the Skill"],
324
+ [/\buse\s+(?:the\s+)?webfetch\b/gi, "use the WebFetch"],
325
+ [/\buse\s+(?:the\s+)?websearch\b/gi, "use the WebSearch"],
326
+ [/\buse\s+(?:the\s+)?todowrite\b/gi, "use the TodoWrite"]
327
+ ];
328
+ function rewriteToolNamesInText(text) {
329
+ let result = text;
330
+ for (const [pattern, replacement] of SYSTEM_PROMPT_REPLACEMENTS) {
331
+ result = result.replace(pattern, replacement);
332
+ }
333
+ return result;
334
+ }
335
+
336
+ // src/tools.ts
337
+ function cleanSchema(schema) {
338
+ const cleaned = {};
339
+ for (const [key, value] of Object.entries(schema)) {
340
+ if (key === "custom")
341
+ continue;
342
+ if (value && typeof value === "object" && !Array.isArray(value)) {
343
+ cleaned[key] = cleanSchema(value);
344
+ } else {
345
+ cleaned[key] = value;
346
+ }
347
+ }
348
+ if (cleaned.properties && !cleaned.type) {
349
+ cleaned.type = "object";
350
+ }
351
+ return cleaned;
352
+ }
353
+ function convertTools(tools) {
354
+ if (!tools || tools.length === 0)
355
+ return;
356
+ return tools.filter((t) => t.type === "function").map((tool) => ({
357
+ name: toClaudeToolName(tool.name),
358
+ description: tool.description ?? "",
359
+ input_schema: cleanSchema(tool.inputSchema)
360
+ }));
361
+ }
362
+ function convertToolChoice(toolChoice) {
363
+ if (!toolChoice)
364
+ return;
365
+ switch (toolChoice.type) {
366
+ case "auto":
367
+ return { type: "auto" };
368
+ case "required":
369
+ return { type: "any" };
370
+ case "none":
371
+ return;
372
+ case "tool":
373
+ return { type: "tool", name: toClaudeToolName(toolChoice.toolName) };
374
+ }
375
+ }
376
+
377
+ // src/model.ts
378
+ import { createHash, randomBytes } from "node:crypto";
379
+
380
+ // src/prompt.ts
381
+ function convertPrompt(prompt) {
382
+ const systemParts = [];
383
+ const messages = [];
384
+ for (const message of prompt) {
385
+ switch (message.role) {
386
+ case "system":
387
+ systemParts.push(message.content);
388
+ break;
389
+ case "user":
390
+ messages.push(convertUserMessage(message));
391
+ break;
392
+ case "assistant": {
393
+ const converted = convertAssistantMessage(message);
394
+ const content = converted.content;
395
+ if (Array.isArray(content) && content.length === 0)
396
+ break;
397
+ messages.push(converted);
398
+ break;
399
+ }
400
+ case "tool":
401
+ messages.push(convertToolMessage(message));
402
+ break;
403
+ }
404
+ }
405
+ return {
406
+ system: systemParts.length > 0 ? systemParts.join(`
407
+
408
+ `) : undefined,
409
+ messages
410
+ };
411
+ }
412
+ function convertUserMessage(message) {
413
+ const content = [];
414
+ for (const part of message.content) {
415
+ switch (part.type) {
416
+ case "text":
417
+ content.push({ type: "text", text: part.text });
418
+ break;
419
+ case "file":
420
+ if (typeof part.data === "string" && part.mediaType?.startsWith("image/")) {
421
+ content.push({
422
+ type: "image",
423
+ source: {
424
+ type: "base64",
425
+ media_type: part.mediaType,
426
+ data: part.data
427
+ }
428
+ });
429
+ } else if (part.data instanceof URL) {
430
+ content.push({
431
+ type: "image",
432
+ source: {
433
+ type: "url",
434
+ url: part.data.toString()
435
+ }
436
+ });
437
+ }
438
+ break;
439
+ }
440
+ }
441
+ if (content.length === 1 && content[0].type === "text") {
442
+ return { role: "user", content: content[0].text };
443
+ }
444
+ return { role: "user", content };
445
+ }
446
+ function convertAssistantMessage(message) {
447
+ const content = [];
448
+ for (const part of message.content) {
449
+ switch (part.type) {
450
+ case "text":
451
+ if (part.text.length > 0) {
452
+ content.push({ type: "text", text: part.text });
453
+ }
454
+ break;
455
+ case "reasoning":
456
+ {
457
+ const signature = part.providerMetadata?.anthropic?.signature ?? part.providerOptions?.anthropic?.signature ?? "";
458
+ if (signature) {
459
+ content.push({
460
+ type: "thinking",
461
+ thinking: part.text,
462
+ signature
463
+ });
464
+ }
465
+ break;
466
+ }
467
+ break;
468
+ case "tool-call":
469
+ content.push({
470
+ type: "tool_use",
471
+ id: part.toolCallId,
472
+ name: toClaudeToolName(part.toolName),
473
+ input: typeof part.input === "string" ? JSON.parse(part.input) : part.input
474
+ });
475
+ break;
476
+ case "file":
477
+ break;
478
+ case "tool-result":
479
+ break;
480
+ }
481
+ }
482
+ return { role: "assistant", content };
483
+ }
484
+ function convertToolMessage(message) {
485
+ const content = [];
486
+ for (const part of message.content) {
487
+ if (part.type !== "tool-result")
488
+ continue;
489
+ const output = part.output;
490
+ const resultContent = formatToolResultContent(output);
491
+ content.push({
492
+ type: "tool_result",
493
+ tool_use_id: part.toolCallId,
494
+ content: resultContent,
495
+ is_error: output.type === "error-text" || output.type === "error-json" ? true : undefined
496
+ });
497
+ }
498
+ return { role: "user", content };
499
+ }
500
+ function formatToolResultContent(output) {
501
+ switch (output.type) {
502
+ case "text":
503
+ case "error-text":
504
+ return output.value;
505
+ case "json":
506
+ case "error-json":
507
+ return JSON.stringify(output.value);
508
+ case "content": {
509
+ const parts = [];
510
+ for (const item of output.value) {
511
+ if (item.type === "text") {
512
+ parts.push({ type: "text", text: item.text });
513
+ }
514
+ }
515
+ return parts.length === 1 ? parts[0].text : parts;
516
+ }
517
+ default:
518
+ return String(output.value);
519
+ }
520
+ }
521
+
522
+ // src/stream.ts
523
+ var idCounter = 0;
524
+ function generateId() {
525
+ return `block-${Date.now()}-${idCounter++}`;
526
+ }
527
+ function mapFinishReason(stopReason) {
528
+ const unified = (() => {
529
+ switch (stopReason) {
530
+ case "end_turn":
531
+ case "stop_sequence":
532
+ return "stop";
533
+ case "max_tokens":
534
+ return "length";
535
+ case "tool_use":
536
+ return "tool-calls";
537
+ default:
538
+ return "other";
539
+ }
540
+ })();
541
+ return { unified, raw: stopReason ?? undefined };
542
+ }
543
+ function convertStream(anthropicStream, modelId) {
544
+ const blockStates = new Map;
545
+ let inputTokens;
546
+ let outputTokens;
547
+ let cachedInputTokens;
548
+ let cacheCreationTokens;
549
+ return new ReadableStream({
550
+ async start(controller) {
551
+ try {
552
+ for await (const event of anthropicStream) {
553
+ const parts = processEvent(event, blockStates, modelId);
554
+ if (event.type === "message_start" && event.message.usage) {
555
+ const u = event.message.usage;
556
+ inputTokens = u.input_tokens;
557
+ cachedInputTokens = u.cache_read_input_tokens ?? 0;
558
+ cacheCreationTokens = u.cache_creation_input_tokens ?? 0;
559
+ }
560
+ if (event.type === "message_delta") {
561
+ const delta = event;
562
+ outputTokens = delta.usage?.output_tokens;
563
+ const finishReason = mapFinishReason(delta.delta?.stop_reason);
564
+ controller.enqueue({
565
+ type: "finish",
566
+ finishReason,
567
+ usage: {
568
+ inputTokens: {
569
+ total: inputTokens ?? 0,
570
+ noCache: undefined,
571
+ cacheRead: cachedInputTokens ?? 0,
572
+ cacheWrite: cacheCreationTokens ?? 0
573
+ },
574
+ outputTokens: {
575
+ total: outputTokens ?? 0,
576
+ text: undefined,
577
+ reasoning: undefined
578
+ }
579
+ },
580
+ providerMetadata: {
581
+ anthropic: {
582
+ cacheCreationInputTokens: cacheCreationTokens ?? 0
583
+ }
584
+ }
585
+ });
586
+ }
587
+ for (const part of parts) {
588
+ controller.enqueue(part);
589
+ }
590
+ }
591
+ } catch (error) {
592
+ controller.enqueue({ type: "error", error });
593
+ } finally {
594
+ controller.close();
595
+ }
596
+ }
597
+ });
598
+ }
599
+ function processEvent(event, blockStates, modelId) {
600
+ const parts = [];
601
+ switch (event.type) {
602
+ case "message_start": {
603
+ parts.push({
604
+ type: "response-metadata",
605
+ id: event.message.id,
606
+ modelId: event.message.model ?? modelId,
607
+ timestamp: new Date
608
+ });
609
+ break;
610
+ }
611
+ case "content_block_start": {
612
+ const index = event.index;
613
+ const block = event.content_block;
614
+ if (block.type === "text") {
615
+ const id = generateId();
616
+ blockStates.set(index, { type: "text", id });
617
+ parts.push({ type: "text-start", id });
618
+ } else if (block.type === "thinking") {
619
+ const id = generateId();
620
+ blockStates.set(index, { type: "thinking", id });
621
+ parts.push({ type: "reasoning-start", id });
622
+ } else if (block.type === "tool_use") {
623
+ const toolId = block.id;
624
+ const toolName = toOpencodeToolName(block.name);
625
+ blockStates.set(index, {
626
+ type: "tool_use",
627
+ id: toolId,
628
+ toolUseId: toolId,
629
+ toolName,
630
+ argsText: ""
631
+ });
632
+ parts.push({
633
+ type: "tool-input-start",
634
+ id: toolId,
635
+ toolName
636
+ });
637
+ }
638
+ break;
639
+ }
640
+ case "content_block_delta": {
641
+ const index = event.index;
642
+ const state = blockStates.get(index);
643
+ if (!state)
644
+ break;
645
+ const delta = event.delta;
646
+ if (delta.type === "text_delta" && state.type === "text") {
647
+ parts.push({
648
+ type: "text-delta",
649
+ id: state.id,
650
+ delta: delta.text
651
+ });
652
+ } else if (delta.type === "thinking_delta" && state.type === "thinking") {
653
+ parts.push({
654
+ type: "reasoning-delta",
655
+ id: state.id,
656
+ delta: delta.thinking
657
+ });
658
+ } else if (delta.type === "signature_delta" && state.type === "thinking") {
659
+ state.signature = (state.signature ?? "") + delta.signature;
660
+ } else if (delta.type === "input_json_delta" && state.type === "tool_use") {
661
+ state.argsText = (state.argsText ?? "") + delta.partial_json;
662
+ parts.push({
663
+ type: "tool-input-delta",
664
+ id: state.id,
665
+ delta: delta.partial_json
666
+ });
667
+ }
668
+ break;
669
+ }
670
+ case "content_block_stop": {
671
+ const index = event.index;
672
+ const state = blockStates.get(index);
673
+ if (!state)
674
+ break;
675
+ if (state.type === "text") {
676
+ parts.push({ type: "text-end", id: state.id });
677
+ } else if (state.type === "thinking") {
678
+ parts.push({
679
+ type: "reasoning-end",
680
+ id: state.id,
681
+ ...state.signature ? {
682
+ providerMetadata: { anthropic: { signature: state.signature } }
683
+ } : {}
684
+ });
685
+ } else if (state.type === "tool_use") {
686
+ parts.push({ type: "tool-input-end", id: state.id });
687
+ parts.push({
688
+ type: "tool-call",
689
+ toolCallId: state.toolUseId,
690
+ toolName: state.toolName,
691
+ input: state.argsText || "{}"
692
+ });
693
+ }
694
+ blockStates.delete(index);
695
+ break;
696
+ }
697
+ }
698
+ return parts;
699
+ }
700
+
701
+ // src/model.ts
702
+ function handleApiError(error) {
703
+ if (error instanceof RateLimitError || error instanceof APIError && error.status === 429) {
704
+ const h = error.headers;
705
+ const getHeader = (name) => h?.get?.(name) ?? h?.[name] ?? null;
706
+ const errorMsg = error.error?.error?.message ?? error.message ?? "";
707
+ if (errorMsg.includes("Extra usage is required for long context")) {
708
+ 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.`);
709
+ }
710
+ const unifiedStatus = getHeader("anthropic-ratelimit-unified-status") ?? "";
711
+ const retryAfter = parseInt(getHeader("retry-after") ?? "0");
712
+ const isSubscriptionLimit = unifiedStatus === "over_limit" || retryAfter > 120;
713
+ if (isSubscriptionLimit) {
714
+ const resetInfo = retryAfter > 0 ? ` Resets in ~${Math.ceil(retryAfter / 60)} minutes.` : "";
715
+ 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.`);
716
+ }
717
+ throw new Error(`Anthropic API rate limit exceeded after retries. ` + (retryAfter > 0 ? `Retry after ${retryAfter}s.` : `Please try again shortly.`));
718
+ }
719
+ throw error;
720
+ }
721
+ function mapFinishReason2(stopReason) {
722
+ const unified = (() => {
723
+ switch (stopReason) {
724
+ case "end_turn":
725
+ case "stop_sequence":
726
+ return "stop";
727
+ case "max_tokens":
728
+ return "length";
729
+ case "tool_use":
730
+ return "tool-calls";
731
+ default:
732
+ return "other";
733
+ }
734
+ })();
735
+ return { unified, raw: stopReason ?? undefined };
736
+ }
737
+ var BILLING_SYSTEM_BLOCK = {
738
+ type: "text",
739
+ text: "x-anthropic-billing-header: cc_version=2.1.81.df2; cc_entrypoint=sdk-cli; cch=00000;"
740
+ };
741
+ var IDENTITY_SYSTEM_BLOCK = {
742
+ type: "text",
743
+ text: "You are a Claude agent, built on Anthropic's Claude Agent SDK."
744
+ };
745
+ var DEVICE_ID = createHash("sha256").update(randomBytes(32)).digest("hex");
746
+ function buildMetadata() {
747
+ return {
748
+ user_id: JSON.stringify({ device_id: DEVICE_ID })
749
+ };
750
+ }
751
+
752
+ class AnthropicSDKModel {
753
+ client;
754
+ providerName;
755
+ isOAuth;
756
+ specificationVersion = "v3";
757
+ provider;
758
+ modelId;
759
+ supportedUrls = {};
760
+ constructor(modelId, client, providerName, isOAuth = false) {
761
+ this.client = client;
762
+ this.providerName = providerName;
763
+ this.isOAuth = isOAuth;
764
+ this.modelId = modelId;
765
+ this.provider = providerName;
766
+ }
767
+ buildParams(options) {
768
+ const { system, messages } = convertPrompt(options.prompt);
769
+ const warnings = [];
770
+ if (options.presencePenalty != null) {
771
+ warnings.push({ type: "compatibility", feature: "presencePenalty" });
772
+ }
773
+ if (options.frequencyPenalty != null) {
774
+ warnings.push({ type: "compatibility", feature: "frequencyPenalty" });
775
+ }
776
+ if (options.seed != null) {
777
+ warnings.push({ type: "compatibility", feature: "seed" });
778
+ }
779
+ const tools = options.toolChoice?.type === "none" ? undefined : convertTools(options.tools);
780
+ const toolChoice = options.toolChoice?.type === "none" ? undefined : convertToolChoice(options.toolChoice);
781
+ const params = {
782
+ model: this.modelId,
783
+ max_tokens: options.maxOutputTokens ?? 64000,
784
+ messages
785
+ };
786
+ if (this.isOAuth) {
787
+ const rewrite = (s) => rewriteToolNamesInText(s);
788
+ const SYSTEM_CACHE = { type: "ephemeral", ttl: "1h", scope: "global" };
789
+ if (system) {
790
+ const contentBlocks = typeof system === "string" ? [{ type: "text", text: rewrite(system), cache_control: SYSTEM_CACHE }] : Array.isArray(system) ? system.map((b, i) => b.type === "text" ? {
791
+ ...b,
792
+ text: rewrite(b.text),
793
+ ...i === system.length - 1 ? { cache_control: SYSTEM_CACHE } : {}
794
+ } : b) : [{ ...system, cache_control: SYSTEM_CACHE }];
795
+ params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK, ...contentBlocks];
796
+ } else {
797
+ params.system = [BILLING_SYSTEM_BLOCK, IDENTITY_SYSTEM_BLOCK];
798
+ }
799
+ params.metadata = buildMetadata();
800
+ const supportsEffort = !this.modelId.includes("haiku") && !this.modelId.includes("claude-3-");
801
+ if (supportsEffort) {
802
+ if (options.temperature == null) {
803
+ params.temperature = 1;
804
+ }
805
+ const providerOpts = options.providerOptions?.["anthropic-sdk"] ?? options.providerOptions?.anthropic;
806
+ const effort = providerOpts?.effort ?? "medium";
807
+ params.output_config = { effort };
808
+ }
809
+ } else if (system) {
810
+ const CACHE = { type: "ephemeral" };
811
+ if (typeof system === "string") {
812
+ params.system = [
813
+ { ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE },
814
+ { type: "text", text: system, cache_control: CACHE }
815
+ ];
816
+ } else if (Array.isArray(system)) {
817
+ const blocks = system.map((b, i) => b.type === "text" && i === system.length - 1 ? { ...b, cache_control: CACHE } : b);
818
+ params.system = [{ ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE }, ...blocks];
819
+ } else {
820
+ params.system = [
821
+ { ...IDENTITY_SYSTEM_BLOCK, cache_control: CACHE },
822
+ { ...system, cache_control: CACHE }
823
+ ];
824
+ }
825
+ }
826
+ if (tools && tools.length > 0)
827
+ params.tools = tools;
828
+ if (toolChoice)
829
+ params.tool_choice = toolChoice;
830
+ if (params.messages && params.messages.length > 1) {
831
+ const msgs = params.messages;
832
+ const msgCache = this.isOAuth ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
833
+ let cachedSomething = false;
834
+ for (let i = msgs.length - 1;i >= 0; i--) {
835
+ const msg = msgs[i];
836
+ if (msg.role !== "user" || !Array.isArray(msg.content))
837
+ continue;
838
+ for (let j = msg.content.length - 1;j >= 0; j--) {
839
+ const block = msg.content[j];
840
+ if (block.type === "tool_result" && !block.cache_control) {
841
+ block.cache_control = msgCache;
842
+ cachedSomething = true;
843
+ break;
844
+ }
845
+ }
846
+ if (cachedSomething)
847
+ break;
848
+ }
849
+ if (!cachedSomething) {
850
+ const msg = msgs[msgs.length - 2];
851
+ if (msg && Array.isArray(msg.content) && msg.content.length > 0) {
852
+ const lastContent = msg.content[msg.content.length - 1];
853
+ if (lastContent && !lastContent.cache_control) {
854
+ lastContent.cache_control = msgCache;
855
+ }
856
+ } else if (msg && typeof msg.content === "string") {
857
+ msgs[msgs.length - 2] = {
858
+ ...msg,
859
+ content: [{ type: "text", text: msg.content, cache_control: msgCache }]
860
+ };
861
+ }
862
+ }
863
+ }
864
+ if (options.temperature != null)
865
+ params.temperature = options.temperature;
866
+ if (options.topP != null)
867
+ params.top_p = options.topP;
868
+ if (options.topK != null)
869
+ params.top_k = options.topK;
870
+ if (options.stopSequences && options.stopSequences.length > 0) {
871
+ params.stop_sequences = options.stopSequences;
872
+ }
873
+ const anthropicOptions = options.providerOptions?.["anthropic-sdk"] ?? options.providerOptions?.anthropic;
874
+ if (anthropicOptions) {
875
+ if (anthropicOptions.thinking) {
876
+ const t = anthropicOptions.thinking;
877
+ if (t.type === "enabled" && t.budgetTokens) {
878
+ params.thinking = { type: "enabled", budget_tokens: t.budgetTokens };
879
+ } else if (t.type === "adaptive") {
880
+ params.thinking = { type: "enabled", budget_tokens: 1e4 };
881
+ } else {
882
+ params.thinking = t;
883
+ }
884
+ }
885
+ if (anthropicOptions.metadata)
886
+ params.metadata = anthropicOptions.metadata;
887
+ }
888
+ return { params, warnings };
889
+ }
890
+ async doGenerate(options) {
891
+ const { params, warnings } = this.buildParams(options);
892
+ if (!options.maxOutputTokens && params.max_tokens > 4096) {
893
+ params.max_tokens = 4096;
894
+ }
895
+ let response;
896
+ try {
897
+ response = await this.client.messages.create({
898
+ ...params,
899
+ stream: false
900
+ }, { signal: options.abortSignal });
901
+ } catch (error) {
902
+ handleApiError(error);
903
+ }
904
+ const content = [];
905
+ for (const block of response.content) {
906
+ switch (block.type) {
907
+ case "text":
908
+ content.push({ type: "text", text: block.text });
909
+ break;
910
+ case "tool_use":
911
+ content.push({
912
+ type: "tool-call",
913
+ toolCallId: block.id,
914
+ toolName: toOpencodeToolName(block.name),
915
+ input: JSON.stringify(block.input)
916
+ });
917
+ break;
918
+ default: {
919
+ const anyBlock = block;
920
+ if (anyBlock.type === "thinking" && anyBlock.thinking) {
921
+ content.push({
922
+ type: "reasoning",
923
+ text: anyBlock.thinking,
924
+ providerMetadata: anyBlock.signature ? { anthropic: { signature: anyBlock.signature } } : undefined
925
+ });
926
+ }
927
+ break;
928
+ }
929
+ }
930
+ }
931
+ const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
932
+ const cacheCreateTokens = response.usage.cache_creation_input_tokens ?? 0;
933
+ return {
934
+ content,
935
+ finishReason: mapFinishReason2(response.stop_reason),
936
+ usage: {
937
+ inputTokens: {
938
+ total: response.usage.input_tokens,
939
+ noCache: undefined,
940
+ cacheRead: cacheReadTokens,
941
+ cacheWrite: cacheCreateTokens
942
+ },
943
+ outputTokens: {
944
+ total: response.usage.output_tokens,
945
+ text: undefined,
946
+ reasoning: undefined
947
+ }
948
+ },
949
+ providerMetadata: {
950
+ anthropic: {
951
+ cacheCreationInputTokens: cacheCreateTokens
952
+ }
953
+ },
954
+ response: {
955
+ id: response.id,
956
+ modelId: response.model,
957
+ timestamp: new Date
958
+ },
959
+ warnings
960
+ };
961
+ }
962
+ async doStream(options) {
963
+ const { params, warnings } = this.buildParams(options);
964
+ let anthropicStream;
965
+ try {
966
+ anthropicStream = await this.client.messages.create({
967
+ ...params,
968
+ stream: true
969
+ }, { signal: options.abortSignal });
970
+ } catch (error) {
971
+ if (error instanceof APIError && error.status === 400 && error.message?.includes("must end with a user message")) {
972
+ return {
973
+ stream: new ReadableStream({
974
+ start(controller) {
975
+ controller.enqueue({ type: "stream-start", warnings });
976
+ controller.enqueue({
977
+ type: "finish",
978
+ finishReason: { unified: "stop", raw: undefined },
979
+ usage: {
980
+ inputTokens: {
981
+ total: 0,
982
+ noCache: undefined,
983
+ cacheRead: undefined,
984
+ cacheWrite: undefined
985
+ },
986
+ outputTokens: { total: 0, text: undefined, reasoning: undefined }
987
+ }
988
+ });
989
+ controller.close();
990
+ }
991
+ }),
992
+ request: { body: params }
993
+ };
994
+ }
995
+ handleApiError(error);
996
+ }
997
+ const stream = convertStream(anthropicStream, this.modelId);
998
+ const wrappedStream = new ReadableStream({
999
+ async start(controller) {
1000
+ controller.enqueue({
1001
+ type: "stream-start",
1002
+ warnings
1003
+ });
1004
+ const reader = stream.getReader();
1005
+ try {
1006
+ while (true) {
1007
+ const { done, value } = await reader.read();
1008
+ if (done)
1009
+ break;
1010
+ controller.enqueue(value);
1011
+ }
1012
+ } finally {
1013
+ reader.releaseLock();
1014
+ controller.close();
1015
+ }
1016
+ }
1017
+ });
1018
+ return {
1019
+ stream: wrappedStream,
1020
+ request: { body: params }
1021
+ };
1022
+ }
1023
+ }
1024
+
1025
+ // src/index.ts
1026
+ var CLAUDE_CODE_VERSION = "2.1.81";
1027
+ var OAUTH_BETAS = [
1028
+ "claude-code-20250219",
1029
+ "oauth-2025-04-20",
1030
+ "interleaved-thinking-2025-05-14",
1031
+ "context-management-2025-06-27",
1032
+ "prompt-caching-scope-2026-01-05",
1033
+ "effort-2025-11-24"
1034
+ ];
1035
+ var CONTEXT_1M_BETA = "context-1m-2025-08-07";
1036
+ var API_KEY_BETAS = ["interleaved-thinking-2025-05-14", "fine-grained-tool-streaming-2025-05-14"];
1037
+ var PACKAGE_NAME = "@xtruder/opencode-claude-max-plugin";
1038
+ var PROVIDER_ID = "anthropic-sdk";
1039
+ var PLUGIN_MODELS = {
1040
+ "claude-haiku-4-5": {
1041
+ name: "Claude Haiku 4.5",
1042
+ reasoning: false,
1043
+ tool_call: true,
1044
+ attachment: true,
1045
+ temperature: true,
1046
+ limit: { context: 200000, output: 8192 },
1047
+ cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
1048
+ modalities: {
1049
+ input: ["text", "image"],
1050
+ output: ["text"]
1051
+ }
1052
+ },
1053
+ "claude-sonnet-4-6": {
1054
+ name: "Claude Sonnet 4.6",
1055
+ reasoning: true,
1056
+ tool_call: true,
1057
+ attachment: true,
1058
+ temperature: true,
1059
+ limit: { context: 200000, output: 16000 },
1060
+ cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
1061
+ modalities: {
1062
+ input: ["text", "image", "pdf"],
1063
+ output: ["text"]
1064
+ },
1065
+ options: { effort: "medium" },
1066
+ variants: {
1067
+ low: { effort: "low" },
1068
+ medium: { effort: "medium" },
1069
+ high: { effort: "high" }
1070
+ }
1071
+ },
1072
+ "claude-opus-4-6": {
1073
+ name: "Claude Opus 4.6",
1074
+ reasoning: true,
1075
+ tool_call: true,
1076
+ attachment: true,
1077
+ temperature: true,
1078
+ limit: { context: 200000, output: 32000 },
1079
+ cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
1080
+ modalities: {
1081
+ input: ["text", "image", "pdf"],
1082
+ output: ["text"]
1083
+ },
1084
+ options: { effort: "medium" },
1085
+ variants: {
1086
+ low: { effort: "low" },
1087
+ medium: { effort: "medium" },
1088
+ high: { effort: "high" }
1089
+ }
1090
+ }
1091
+ };
1092
+ function resolveAuth(options) {
1093
+ if (options.apiKey) {
1094
+ return { apiKey: options.apiKey, isOAuth: false };
1095
+ }
1096
+ if (process.env.ANTHROPIC_API_KEY) {
1097
+ return { apiKey: process.env.ANTHROPIC_API_KEY, isOAuth: false };
1098
+ }
1099
+ const credentialsPath = typeof options.credentialsPath === "string" ? options.credentialsPath : undefined;
1100
+ const creds = getCachedCredentials(credentialsPath);
1101
+ if (creds) {
1102
+ return { apiKey: null, authToken: creds.accessToken, isOAuth: true };
1103
+ }
1104
+ return { isOAuth: false };
1105
+ }
1106
+ function createAnthropicSDK(options = {}) {
1107
+ const { baseURL, headers, fetch: customFetch, name = "anthropic-sdk" } = options;
1108
+ const credentialsPath = typeof options.credentialsPath === "string" ? options.credentialsPath : undefined;
1109
+ const auth = resolveAuth(options);
1110
+ const defaultHeaders = {
1111
+ ...headers,
1112
+ "anthropic-beta": (auth.isOAuth ? OAUTH_BETAS : API_KEY_BETAS).join(",")
1113
+ };
1114
+ if (auth.isOAuth) {
1115
+ defaultHeaders["user-agent"] = `claude-cli/${CLAUDE_CODE_VERSION} (external, sdk-cli)`;
1116
+ defaultHeaders["x-app"] = "cli";
1117
+ defaultHeaders["anthropic-dangerous-direct-browser-access"] = "true";
1118
+ defaultHeaders["x-stainless-package-version"] = "0.74.0";
1119
+ }
1120
+ const CONTEXT_1M_BODY_THRESHOLD = 600000;
1121
+ const baseFetch = customFetch ?? globalThis.fetch;
1122
+ const wrappedFetch = async (url, init) => {
1123
+ if (auth.isOAuth && init) {
1124
+ const freshCreds = getCachedCredentials(credentialsPath);
1125
+ if (freshCreds) {
1126
+ const reqHeaders = new Headers(init.headers);
1127
+ reqHeaders.set("authorization", `Bearer ${freshCreds.accessToken}`);
1128
+ init = { ...init, headers: reqHeaders };
1129
+ }
1130
+ }
1131
+ if (init?.body && typeof init.body === "string" && init.body.length > CONTEXT_1M_BODY_THRESHOLD) {
1132
+ const betaHeaders = new Headers(init.headers);
1133
+ const existingBetas = betaHeaders.get("anthropic-beta") ?? "";
1134
+ if (!existingBetas.includes(CONTEXT_1M_BETA)) {
1135
+ betaHeaders.set("anthropic-beta", existingBetas + "," + CONTEXT_1M_BETA);
1136
+ init = { ...init, headers: betaHeaders };
1137
+ }
1138
+ }
1139
+ if (auth.isOAuth && init?.body && typeof init.body === "string" && hasCchPlaceholder(init.body)) {
1140
+ try {
1141
+ const cch = await computeCch(init.body);
1142
+ init = { ...init, body: replaceCchPlaceholder(init.body, cch) };
1143
+ } catch {}
1144
+ }
1145
+ const resp = await baseFetch(url, init);
1146
+ const h5 = resp.headers.get("anthropic-ratelimit-unified-5h-utilization");
1147
+ if (h5 != null) {
1148
+ cachedUsage.fiveHourUtil = parseFloat(h5);
1149
+ cachedUsage.sevenDayUtil = parseFloat(resp.headers.get("anthropic-ratelimit-unified-7d-utilization") ?? "0");
1150
+ cachedUsage.fiveHourReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-5h-reset") ?? "0");
1151
+ cachedUsage.sevenDayReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-7d-reset") ?? "0");
1152
+ cachedUsage.overageStatus = resp.headers.get("anthropic-ratelimit-unified-overage-status") ?? undefined;
1153
+ persistCachedUsage();
1154
+ }
1155
+ if (resp.status === 429) {
1156
+ const unifiedStatus = resp.headers.get("anthropic-ratelimit-unified-status") ?? "";
1157
+ const retryAfter = parseInt(resp.headers.get("retry-after") ?? "0");
1158
+ const bodyText = await resp.clone().text();
1159
+ const isLongContextLimit = bodyText.includes("Extra usage is required for long context");
1160
+ const isSubscriptionLimit = isLongContextLimit || unifiedStatus === "over_limit" || retryAfter > 120;
1161
+ if (isSubscriptionLimit) {
1162
+ const retryHeaders = new Headers(resp.headers);
1163
+ retryHeaders.set("x-should-retry", "false");
1164
+ return new Response(bodyText, {
1165
+ status: resp.status,
1166
+ statusText: resp.statusText,
1167
+ headers: retryHeaders
1168
+ });
1169
+ }
1170
+ }
1171
+ return resp;
1172
+ };
1173
+ const client = new Anthropic({
1174
+ apiKey: auth.apiKey ?? null,
1175
+ authToken: auth.authToken ?? null,
1176
+ baseURL,
1177
+ defaultHeaders,
1178
+ fetch: wrappedFetch
1179
+ });
1180
+ return {
1181
+ languageModel(modelId) {
1182
+ return new AnthropicSDKModel(modelId, client, name, auth.isOAuth);
1183
+ }
1184
+ };
1185
+ }
1186
+ var anthropicSDKPlugin = async () => {
1187
+ return {
1188
+ config: async (cfg) => {
1189
+ if (!cfg.provider) {
1190
+ cfg.provider = {};
1191
+ }
1192
+ cfg.provider[PROVIDER_ID] = {
1193
+ npm: PACKAGE_NAME,
1194
+ models: PLUGIN_MODELS,
1195
+ ...cfg.provider[PROVIDER_ID]
1196
+ };
1197
+ }
1198
+ };
1199
+ };
1200
+ var src_default = createAnthropicSDK;
1201
+
1202
+ // src/server.ts
1203
+ var id = "anthropic-sdk";
1204
+ var server = anthropicSDKPlugin;
1205
+ var plugin = {
1206
+ id,
1207
+ server
1208
+ };
1209
+ var server_default = plugin;
1210
+ export {
1211
+ server_default as default
1212
+ };