@khalilgharbaoui/opencode-claude-code-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/dist/index.js ADDED
@@ -0,0 +1,2631 @@
1
+ // src/claude-code-language-model.ts
2
+ import { generateId } from "@ai-sdk/provider-utils";
3
+
4
+ // src/logger.ts
5
+ var DEBUG = process.env.DEBUG?.includes("opencode-claude-code") ?? false;
6
+ function fmt(level, msg, data) {
7
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
8
+ const base = `[${ts}] [opencode-claude-code] ${level}: ${msg}`;
9
+ if (data && Object.keys(data).length > 0) {
10
+ return `${base} ${JSON.stringify(data)}`;
11
+ }
12
+ return base;
13
+ }
14
+ var log = {
15
+ info(msg, data) {
16
+ if (DEBUG) console.error(fmt("INFO", msg, data));
17
+ },
18
+ warn(msg, data) {
19
+ if (DEBUG) console.error(fmt("WARN", msg, data));
20
+ },
21
+ error(msg, data) {
22
+ console.error(fmt("ERROR", msg, data));
23
+ },
24
+ debug(msg, data) {
25
+ if (DEBUG) console.error(fmt("DEBUG", msg, data));
26
+ }
27
+ };
28
+
29
+ // src/tool-mapping.ts
30
+ function mapToolInput(name, input) {
31
+ if (!input) return input;
32
+ switch (name) {
33
+ case "Write":
34
+ return {
35
+ filePath: input.file_path ?? input.filePath,
36
+ content: input.content
37
+ };
38
+ case "Edit":
39
+ return {
40
+ filePath: input.file_path ?? input.filePath,
41
+ oldString: input.old_string ?? input.oldString,
42
+ newString: input.new_string ?? input.newString,
43
+ replaceAll: input.replace_all ?? input.replaceAll
44
+ };
45
+ case "Read":
46
+ return {
47
+ filePath: input.file_path ?? input.filePath,
48
+ offset: input.offset,
49
+ limit: input.limit
50
+ };
51
+ case "Bash":
52
+ return {
53
+ command: input.command,
54
+ description: input.description || `Execute: ${String(input.command || "").slice(0, 50)}${String(input.command || "").length > 50 ? "..." : ""}`,
55
+ timeout: input.timeout
56
+ };
57
+ case "NotebookEdit":
58
+ return {
59
+ notebookPath: input.notebook_path ?? input.notebookPath,
60
+ cellNumber: input.cell_number ?? input.cellNumber,
61
+ newSource: input.new_source ?? input.newSource,
62
+ cellType: input.cell_type ?? input.cellType,
63
+ editMode: input.edit_mode ?? input.editMode
64
+ };
65
+ case "Glob":
66
+ return {
67
+ pattern: input.pattern,
68
+ path: input.path
69
+ };
70
+ case "Grep":
71
+ return {
72
+ pattern: input.pattern,
73
+ path: input.path,
74
+ include: input.include
75
+ };
76
+ case "TodoWrite":
77
+ if (Array.isArray(input.todos)) {
78
+ const mappedTodos = input.todos.map((todo, index) => ({
79
+ content: todo.content,
80
+ status: todo.status || "pending",
81
+ priority: todo.priority || "medium",
82
+ id: todo.id || `todo_${Date.now()}_${index}`
83
+ }));
84
+ return { todos: mappedTodos };
85
+ }
86
+ return input;
87
+ default:
88
+ return input;
89
+ }
90
+ }
91
+ var OPENCODE_HANDLED_TOOLS = /* @__PURE__ */ new Set([
92
+ "Edit",
93
+ "Write",
94
+ "Bash",
95
+ "NotebookEdit",
96
+ "Read",
97
+ "Glob",
98
+ "Grep"
99
+ ]);
100
+ var CLAUDE_INTERNAL_TOOLS = /* @__PURE__ */ new Set([
101
+ "ToolSearch",
102
+ "Agent",
103
+ "AskFollowupQuestion"
104
+ ]);
105
+ function mapTool(name, input) {
106
+ if (CLAUDE_INTERNAL_TOOLS.has(name)) {
107
+ log.debug("skipping Claude CLI internal tool", { name });
108
+ return { name, input, executed: true, skip: true };
109
+ }
110
+ if (name === "EnterPlanMode") return { name: "plan_enter", input: {}, executed: false };
111
+ if (name === "ExitPlanMode") return { name: "plan_exit", input, executed: false };
112
+ if (name === "TodoWrite") {
113
+ const mappedInput = mapToolInput(name, input);
114
+ return { name: "todowrite", input: mappedInput, executed: false };
115
+ }
116
+ if (name === "WebSearch" || name === "web_search") {
117
+ const mappedInput = input?.query ? { query: input.query } : input;
118
+ log.debug("mapping WebSearch", { originalInput: input, mappedInput });
119
+ return { name: "websearch_web_search_exa", input: mappedInput, executed: false };
120
+ }
121
+ if (name === "TaskOutput") {
122
+ if (!input) return { name: "bash", executed: false };
123
+ const output = input?.content || input?.output || JSON.stringify(input);
124
+ return {
125
+ name: "bash",
126
+ input: {
127
+ command: `echo "TASK OUTPUT: ${String(output).replace(/"/g, '\\"')}"`,
128
+ description: "Displaying task output"
129
+ },
130
+ executed: false
131
+ };
132
+ }
133
+ if (name.startsWith("mcp__")) {
134
+ const parts = name.slice(5).split("__");
135
+ if (parts.length >= 2) {
136
+ const serverName = parts[0];
137
+ const toolName = parts.slice(1).join("_");
138
+ const openCodeName = `${serverName}_${toolName}`;
139
+ log.debug("mapping MCP tool", { original: name, mapped: openCodeName });
140
+ return { name: openCodeName, input, executed: false };
141
+ }
142
+ }
143
+ if (OPENCODE_HANDLED_TOOLS.has(name)) {
144
+ const mappedInput = mapToolInput(name, input);
145
+ const openCodeName = name.toLowerCase();
146
+ log.debug("mapping CLI-executed tool", { name, openCodeName });
147
+ return { name: openCodeName, input: mappedInput, executed: true };
148
+ }
149
+ return { name, input, executed: true };
150
+ }
151
+
152
+ // src/message-builder.ts
153
+ var THINKING_KEYWORDS = {
154
+ minimal: null,
155
+ low: "think",
156
+ medium: "think hard",
157
+ high: "think harder",
158
+ xhigh: "megathink",
159
+ max: "ultrathink"
160
+ };
161
+ function reasoningKeyword(effort) {
162
+ if (!effort) return null;
163
+ return THINKING_KEYWORDS[effort] ?? null;
164
+ }
165
+ var SUPPORTED_IMAGE_TYPES = /* @__PURE__ */ new Set([
166
+ "image/jpeg",
167
+ "image/png",
168
+ "image/gif",
169
+ "image/webp"
170
+ ]);
171
+ function toImageBlock(part) {
172
+ const raw = part.data ?? part.url ?? part.source?.data;
173
+ if (!raw) {
174
+ log.warn("file part without data, skipping");
175
+ return null;
176
+ }
177
+ let resolvedMediaType = part.mediaType || part.mimeType || part.mime || "";
178
+ let base64 = null;
179
+ if (typeof raw === "string") {
180
+ if (raw.startsWith("data:")) {
181
+ const match = /^data:([^;,]+)(?:;[^,]*)*(?:;base64)?,(.*)$/s.exec(raw);
182
+ if (!match) {
183
+ log.warn("malformed data URI, skipping file part");
184
+ return null;
185
+ }
186
+ resolvedMediaType = resolvedMediaType || match[1];
187
+ base64 = match[2];
188
+ } else if (/^https?:\/\//i.test(raw)) {
189
+ log.warn("remote URL images are not supported by Claude CLI, skipping");
190
+ return null;
191
+ } else {
192
+ base64 = raw;
193
+ }
194
+ } else if (raw instanceof URL) {
195
+ log.warn("remote URL images are not supported by Claude CLI, skipping");
196
+ return null;
197
+ } else if (raw instanceof Uint8Array || Buffer.isBuffer(raw)) {
198
+ base64 = Buffer.from(raw).toString("base64");
199
+ } else {
200
+ log.warn("unsupported file part data type", { dataType: typeof raw });
201
+ return null;
202
+ }
203
+ if (!resolvedMediaType || !SUPPORTED_IMAGE_TYPES.has(resolvedMediaType)) {
204
+ log.warn("unsupported media type for Claude image block, skipping", {
205
+ mediaType: resolvedMediaType
206
+ });
207
+ return null;
208
+ }
209
+ return {
210
+ type: "image",
211
+ source: { type: "base64", media_type: resolvedMediaType, data: base64 }
212
+ };
213
+ }
214
+ function getToolResultText(part) {
215
+ const value = part.output ?? part.result;
216
+ if (typeof value === "string") {
217
+ return value;
218
+ }
219
+ if (!value || typeof value !== "object") {
220
+ return JSON.stringify(value);
221
+ }
222
+ switch (value.type) {
223
+ case "text":
224
+ case "error-text":
225
+ return String(value.value);
226
+ case "json":
227
+ case "error-json":
228
+ return JSON.stringify(value.value);
229
+ case "execution-denied":
230
+ return value.reason ? `Execution denied: ${value.reason}` : "Execution denied";
231
+ case "content":
232
+ return Array.isArray(value.value) ? value.value.map((item) => {
233
+ if (item?.type === "text") return item.text;
234
+ return JSON.stringify(item);
235
+ }).join("\n") : JSON.stringify(value.value);
236
+ default:
237
+ return JSON.stringify(value);
238
+ }
239
+ }
240
+ function compactConversationHistory(prompt) {
241
+ const conversationMessages = prompt.filter(
242
+ (m) => m.role === "user" || m.role === "assistant"
243
+ );
244
+ if (conversationMessages.length <= 1) {
245
+ return null;
246
+ }
247
+ const historyParts = [];
248
+ for (let i = 0; i < conversationMessages.length - 1; i++) {
249
+ const msg = conversationMessages[i];
250
+ const role = msg.role === "user" ? "User" : "Assistant";
251
+ let text = "";
252
+ if (typeof msg.content === "string") {
253
+ text = msg.content;
254
+ } else if (Array.isArray(msg.content)) {
255
+ const textParts = msg.content.filter((p) => p.type === "text" && p.text).map((p) => p.text);
256
+ text = textParts.join("\n");
257
+ const toolCalls = msg.content.filter(
258
+ (p) => p.type === "tool-call"
259
+ );
260
+ const toolResults = msg.content.filter(
261
+ (p) => p.type === "tool-result"
262
+ );
263
+ if (toolCalls.length > 0) {
264
+ text += `
265
+ [Called ${toolCalls.length} tool(s): ${toolCalls.map((t) => t.toolName).join(", ")}]`;
266
+ }
267
+ if (toolResults.length > 0) {
268
+ text += `
269
+ [Received ${toolResults.length} tool result(s)]`;
270
+ }
271
+ }
272
+ if (text.trim()) {
273
+ const truncated = text.length > 2e3 ? text.slice(0, 2e3) + "..." : text;
274
+ historyParts.push(`${role}: ${truncated}`);
275
+ }
276
+ }
277
+ if (historyParts.length === 0) {
278
+ return null;
279
+ }
280
+ return historyParts.join("\n\n");
281
+ }
282
+ function getClaudeUserMessage(prompt, includeHistoryContext = false, reasoningEffort) {
283
+ const content = [];
284
+ if (includeHistoryContext) {
285
+ const historyContext = compactConversationHistory(prompt);
286
+ if (historyContext) {
287
+ log.info("including conversation history context", {
288
+ historyLength: historyContext.length
289
+ });
290
+ content.push({
291
+ type: "text",
292
+ text: `<conversation_history>
293
+ The following is a summary of our conversation so far (from a previous session that couldn't be resumed):
294
+
295
+ ${historyContext}
296
+
297
+ </conversation_history>
298
+
299
+ Now continuing with the current message:
300
+
301
+ `
302
+ });
303
+ }
304
+ }
305
+ const messages = [];
306
+ for (let i = prompt.length - 1; i >= 0; i--) {
307
+ if (prompt[i].role === "assistant") break;
308
+ messages.unshift(prompt[i]);
309
+ }
310
+ for (const msg of messages) {
311
+ if (msg.role === "user") {
312
+ if (typeof msg.content === "string") {
313
+ const str = msg.content;
314
+ if (str.trim()) {
315
+ content.push({ type: "text", text: str });
316
+ }
317
+ } else if (Array.isArray(msg.content)) {
318
+ for (const part of msg.content) {
319
+ if (part.type === "text") {
320
+ if (part.text && part.text.trim()) {
321
+ content.push({ type: "text", text: part.text });
322
+ }
323
+ } else if (part.type === "file" || part.type === "image") {
324
+ const block = toImageBlock(part);
325
+ if (block) {
326
+ content.push(block);
327
+ } else {
328
+ log.debug("skipped non-image file part", {
329
+ mediaType: part.mediaType
330
+ });
331
+ }
332
+ } else if (part.type === "tool-result") {
333
+ const p = part;
334
+ content.push({
335
+ type: "tool_result",
336
+ tool_use_id: p.toolCallId,
337
+ content: getToolResultText(p)
338
+ });
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ if (content.length === 0) {
345
+ log.warn("empty user content; sending sentinel to satisfy CLI");
346
+ return JSON.stringify({
347
+ type: "user",
348
+ message: {
349
+ role: "user",
350
+ content: [{ type: "text", text: "(empty)" }]
351
+ }
352
+ });
353
+ }
354
+ const keyword = reasoningKeyword(reasoningEffort);
355
+ if (keyword) {
356
+ const lastTextPart = [...content].reverse().find((p) => p.type === "text");
357
+ if (lastTextPart) {
358
+ lastTextPart.text = lastTextPart.text ? `${lastTextPart.text}
359
+
360
+ (${keyword})` : `(${keyword})`;
361
+ } else {
362
+ content.push({ type: "text", text: `(${keyword})` });
363
+ }
364
+ log.debug("injected reasoning keyword", { effort: reasoningEffort, keyword });
365
+ }
366
+ return JSON.stringify({
367
+ type: "user",
368
+ message: {
369
+ role: "user",
370
+ content
371
+ }
372
+ });
373
+ }
374
+
375
+ // src/mcp-bridge.ts
376
+ import * as fs from "fs";
377
+ import * as path from "path";
378
+ import * as os from "os";
379
+ import * as crypto from "crypto";
380
+ var CONFIG_NAMES = ["opencode.jsonc", "opencode.json", "config.json"];
381
+ function fileExists(p) {
382
+ try {
383
+ return fs.statSync(p).isFile();
384
+ } catch {
385
+ return false;
386
+ }
387
+ }
388
+ function findConfigInDir(dir) {
389
+ for (const name of CONFIG_NAMES) {
390
+ const p = path.join(dir, name);
391
+ if (fileExists(p)) return p;
392
+ }
393
+ return null;
394
+ }
395
+ function walkUpForConfig(startDir) {
396
+ const closestFirst = [];
397
+ let dir = path.resolve(startDir);
398
+ while (true) {
399
+ const hit = findConfigInDir(dir);
400
+ if (hit) closestFirst.push(hit);
401
+ const dotdir = path.join(dir, ".opencode");
402
+ const dothit = findConfigInDir(dotdir);
403
+ if (dothit) closestFirst.push(dothit);
404
+ const parent = path.dirname(dir);
405
+ if (parent === dir) break;
406
+ dir = parent;
407
+ }
408
+ return closestFirst.reverse();
409
+ }
410
+ function globalConfigs() {
411
+ const out = [];
412
+ const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
413
+ const dir = path.join(xdg, "opencode");
414
+ const hit = findConfigInDir(dir);
415
+ if (hit) out.push(hit);
416
+ return out;
417
+ }
418
+ function stripJsonComments(text) {
419
+ let out = "";
420
+ let i = 0;
421
+ let inString = null;
422
+ while (i < text.length) {
423
+ const c = text[i];
424
+ if (inString) {
425
+ out += c;
426
+ if (c === "\\" && i + 1 < text.length) {
427
+ out += text[i + 1];
428
+ i += 2;
429
+ continue;
430
+ }
431
+ if (c === inString) inString = null;
432
+ i++;
433
+ continue;
434
+ }
435
+ if (c === '"' || c === "'") {
436
+ inString = c;
437
+ out += c;
438
+ i++;
439
+ continue;
440
+ }
441
+ if (c === "/" && text[i + 1] === "/") {
442
+ while (i < text.length && text[i] !== "\n") i++;
443
+ continue;
444
+ }
445
+ if (c === "/" && text[i + 1] === "*") {
446
+ i += 2;
447
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
448
+ i++;
449
+ i += 2;
450
+ continue;
451
+ }
452
+ out += c;
453
+ i++;
454
+ }
455
+ return out;
456
+ }
457
+ function discoverConfigFiles(cwd) {
458
+ const files = [];
459
+ files.push(...globalConfigs());
460
+ files.push(...walkUpForConfig(cwd));
461
+ const dir = process.env.OPENCODE_CONFIG_DIR;
462
+ if (dir) {
463
+ const hit = findConfigInDir(dir);
464
+ if (hit) files.push(hit);
465
+ }
466
+ const explicit = process.env.OPENCODE_CONFIG;
467
+ if (explicit && fileExists(explicit)) files.push(explicit);
468
+ const resolvedOrder = files.map((f) => path.resolve(f));
469
+ const lastIndex = /* @__PURE__ */ new Map();
470
+ resolvedOrder.forEach((f, i) => lastIndex.set(f, i));
471
+ return resolvedOrder.filter((f, i) => lastIndex.get(f) === i);
472
+ }
473
+ function translateServer(name, spec) {
474
+ if (!spec || typeof spec !== "object") return null;
475
+ if (spec.enabled === false) return null;
476
+ if (spec.type === "local") {
477
+ const cmd = spec.command;
478
+ if (!Array.isArray(cmd) || cmd.length === 0) {
479
+ log.warn("skipping local MCP server with no command", { name });
480
+ return null;
481
+ }
482
+ const out = {
483
+ type: "stdio",
484
+ command: String(cmd[0])
485
+ };
486
+ if (cmd.length > 1) out.args = cmd.slice(1).map((s) => String(s));
487
+ if (spec.environment && typeof spec.environment === "object") {
488
+ out.env = spec.environment;
489
+ }
490
+ return out;
491
+ }
492
+ if (spec.type === "remote") {
493
+ if (!spec.url || typeof spec.url !== "string") {
494
+ log.warn("skipping remote MCP server with no url", { name });
495
+ return null;
496
+ }
497
+ const out = {
498
+ type: "http",
499
+ url: spec.url
500
+ };
501
+ if (spec.headers && typeof spec.headers === "object") {
502
+ out.headers = spec.headers;
503
+ }
504
+ return out;
505
+ }
506
+ log.warn("skipping MCP server with unknown type", {
507
+ name,
508
+ type: spec?.type
509
+ });
510
+ return null;
511
+ }
512
+ function readAndParse(file) {
513
+ try {
514
+ const raw = fs.readFileSync(file, "utf8");
515
+ return JSON.parse(stripJsonComments(raw));
516
+ } catch (e) {
517
+ log.warn("failed to parse opencode config", {
518
+ file,
519
+ error: e instanceof Error ? e.message : String(e)
520
+ });
521
+ return null;
522
+ }
523
+ }
524
+ function bridgeOpencodeMcp(cwd) {
525
+ const files = discoverConfigFiles(cwd);
526
+ if (files.length === 0) return null;
527
+ const merged = {};
528
+ for (const file of files) {
529
+ const parsed = readAndParse(file);
530
+ const mcp = parsed?.mcp ?? null;
531
+ if (!mcp || typeof mcp !== "object") continue;
532
+ for (const [name, spec] of Object.entries(mcp)) {
533
+ merged[name] = spec;
534
+ }
535
+ }
536
+ const servers = {};
537
+ for (const [name, spec] of Object.entries(merged)) {
538
+ const translated = translateServer(name, spec);
539
+ if (translated) servers[name] = translated;
540
+ }
541
+ if (Object.keys(servers).length === 0) return null;
542
+ const body = JSON.stringify({ mcpServers: servers }, null, 2);
543
+ const hash = crypto.createHash("sha256").update(body).digest("hex").slice(0, 12);
544
+ const outPath = path.join(
545
+ os.tmpdir(),
546
+ `opencode-claude-code-mcp-${hash}.json`
547
+ );
548
+ try {
549
+ if (!fileExists(outPath)) {
550
+ fs.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
551
+ }
552
+ } catch (e) {
553
+ log.warn("failed to write bridged MCP config", {
554
+ error: e instanceof Error ? e.message : String(e)
555
+ });
556
+ return null;
557
+ }
558
+ log.info("bridged opencode MCP config", {
559
+ sources: files,
560
+ target: outPath,
561
+ servers: Object.keys(servers)
562
+ });
563
+ return outPath;
564
+ }
565
+
566
+ // src/session-manager.ts
567
+ import { spawn } from "child_process";
568
+ import { createInterface } from "readline";
569
+ import { EventEmitter } from "events";
570
+ var activeProcesses = /* @__PURE__ */ new Map();
571
+ var claudeSessions = /* @__PURE__ */ new Map();
572
+ var MAX_ACTIVE_PROCESSES = 16;
573
+ function touch(key) {
574
+ const existing = activeProcesses.get(key);
575
+ if (existing) {
576
+ activeProcesses.delete(key);
577
+ activeProcesses.set(key, existing);
578
+ }
579
+ }
580
+ function evictIfNeeded() {
581
+ while (activeProcesses.size >= MAX_ACTIVE_PROCESSES) {
582
+ const oldestKey = activeProcesses.keys().next().value;
583
+ if (!oldestKey) break;
584
+ log.info("evicting LRU claude process", { sessionKey: oldestKey });
585
+ deleteActiveProcess(oldestKey);
586
+ }
587
+ }
588
+ function getActiveProcess(key) {
589
+ const ap = activeProcesses.get(key);
590
+ if (ap) touch(key);
591
+ return ap;
592
+ }
593
+ function deleteActiveProcess(key) {
594
+ const ap = activeProcesses.get(key);
595
+ if (ap) {
596
+ void ap.proxyServer?.close();
597
+ ap.proc.kill();
598
+ activeProcesses.delete(key);
599
+ }
600
+ }
601
+ function getClaudeSessionId(key) {
602
+ return claudeSessions.get(key);
603
+ }
604
+ function setClaudeSessionId(key, sessionId) {
605
+ claudeSessions.set(key, sessionId);
606
+ }
607
+ function deleteClaudeSessionId(key) {
608
+ claudeSessions.delete(key);
609
+ }
610
+ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer) {
611
+ evictIfNeeded();
612
+ log.info("spawning new claude process", { cliPath, cliArgs, cwd, sessionKey: sessionKey2 });
613
+ const proc = spawn(cliPath, cliArgs, {
614
+ cwd,
615
+ stdio: ["pipe", "pipe", "pipe"],
616
+ env: { ...process.env, TERM: "xterm-256color" },
617
+ shell: process.platform === "win32"
618
+ });
619
+ const lineEmitter = new EventEmitter();
620
+ const rl = createInterface({ input: proc.stdout });
621
+ rl.on("line", (line) => {
622
+ lineEmitter.emit("line", line);
623
+ });
624
+ rl.on("close", () => {
625
+ lineEmitter.emit("close");
626
+ });
627
+ const ap = { proc, lineEmitter, proxyServer: proxyServer ?? null };
628
+ activeProcesses.set(sessionKey2, ap);
629
+ proc.on("exit", (code, signal) => {
630
+ log.info("claude process exited", { code, signal, sessionKey: sessionKey2 });
631
+ void proxyServer?.close();
632
+ activeProcesses.delete(sessionKey2);
633
+ if (code !== 0 && code !== null) {
634
+ log.info("process exited with error, clearing session", {
635
+ code,
636
+ sessionKey: sessionKey2
637
+ });
638
+ claudeSessions.delete(sessionKey2);
639
+ }
640
+ });
641
+ proc.stderr?.on("data", (data) => {
642
+ const stderr = data.toString();
643
+ log.debug("stderr", { data: stderr.slice(0, 200) });
644
+ if (stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
645
+ log.warn("claude session ID error, clearing session", {
646
+ sessionKey: sessionKey2,
647
+ error: stderr.slice(0, 200)
648
+ });
649
+ claudeSessions.delete(sessionKey2);
650
+ }
651
+ });
652
+ return ap;
653
+ }
654
+ function buildCliArgs(opts) {
655
+ const {
656
+ sessionKey: sessionKey2,
657
+ skipPermissions,
658
+ includeSessionId = true,
659
+ model,
660
+ permissionMode,
661
+ mcpConfig,
662
+ strictMcpConfig,
663
+ disallowedTools
664
+ } = opts;
665
+ const args = [
666
+ "--output-format",
667
+ "stream-json",
668
+ "--input-format",
669
+ "stream-json",
670
+ "--verbose"
671
+ ];
672
+ if (model) {
673
+ args.push("--model", model);
674
+ }
675
+ if (permissionMode) {
676
+ args.push("--permission-mode", permissionMode);
677
+ }
678
+ if (includeSessionId) {
679
+ const sessionId = claudeSessions.get(sessionKey2);
680
+ if (sessionId && !activeProcesses.has(sessionKey2)) {
681
+ args.push("--session-id", sessionId);
682
+ }
683
+ }
684
+ if (mcpConfig) {
685
+ const configs = Array.isArray(mcpConfig) ? mcpConfig : [mcpConfig];
686
+ const filtered = configs.filter((c) => typeof c === "string" && c.length > 0);
687
+ if (filtered.length > 0) {
688
+ args.push("--mcp-config", ...filtered);
689
+ }
690
+ }
691
+ if (strictMcpConfig) {
692
+ args.push("--strict-mcp-config");
693
+ }
694
+ if (disallowedTools && disallowedTools.length > 0) {
695
+ args.push("--disallowedTools", ...disallowedTools);
696
+ }
697
+ if (skipPermissions) {
698
+ args.push("--dangerously-skip-permissions");
699
+ }
700
+ return args;
701
+ }
702
+ function sessionKey(cwd, modelId) {
703
+ return `${cwd}::${modelId}`;
704
+ }
705
+
706
+ // src/proxy-mcp.ts
707
+ import { createServer } from "http";
708
+ import * as fs2 from "fs";
709
+ import * as path2 from "path";
710
+ import * as os2 from "os";
711
+ import * as crypto2 from "crypto";
712
+ import { EventEmitter as EventEmitter2 } from "events";
713
+ var PROTOCOL_VERSION = "2024-11-05";
714
+ var SERVER_NAME = "opencode_proxy";
715
+ var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
716
+ var DEFAULT_PROXY_TOOLS = [
717
+ {
718
+ name: "bash",
719
+ description: "Execute a shell command. Routed through opencode's bash tool so permission prompts flow through opencode's UI.",
720
+ inputSchema: {
721
+ type: "object",
722
+ properties: {
723
+ command: {
724
+ type: "string",
725
+ description: "The shell command to execute."
726
+ },
727
+ description: {
728
+ type: "string",
729
+ description: "Short human-readable description of what the command does."
730
+ },
731
+ timeout: {
732
+ type: "number",
733
+ description: "Optional timeout in milliseconds."
734
+ }
735
+ },
736
+ required: ["command"]
737
+ }
738
+ },
739
+ {
740
+ name: "write",
741
+ description: "Write a file. Routed through opencode's write tool so permission prompts flow through opencode's UI.",
742
+ inputSchema: {
743
+ type: "object",
744
+ properties: {
745
+ filePath: {
746
+ type: "string",
747
+ description: "The file to write. Absolute paths are preferred."
748
+ },
749
+ content: {
750
+ type: "string",
751
+ description: "The full content to write to the file."
752
+ }
753
+ },
754
+ required: ["filePath", "content"]
755
+ }
756
+ },
757
+ {
758
+ name: "edit",
759
+ description: "Replace text in an existing file. Routed through opencode's edit tool so permission prompts flow through opencode's UI.",
760
+ inputSchema: {
761
+ type: "object",
762
+ properties: {
763
+ filePath: {
764
+ type: "string",
765
+ description: "The file to edit. Absolute paths are preferred."
766
+ },
767
+ oldString: {
768
+ type: "string",
769
+ description: "The exact text to replace."
770
+ },
771
+ newString: {
772
+ type: "string",
773
+ description: "The replacement text."
774
+ },
775
+ replaceAll: {
776
+ type: "boolean",
777
+ description: "Replace all occurrences instead of just the first one."
778
+ }
779
+ },
780
+ required: ["filePath", "oldString", "newString"]
781
+ }
782
+ },
783
+ {
784
+ name: "webfetch",
785
+ description: "Fetch content from a URL. Routed through opencode's webfetch tool so permission prompts flow through opencode's UI. Returns the page content in the requested format.",
786
+ inputSchema: {
787
+ type: "object",
788
+ properties: {
789
+ url: {
790
+ type: "string",
791
+ description: "The URL to fetch content from. Must start with http:// or https://."
792
+ },
793
+ format: {
794
+ type: "string",
795
+ enum: ["text", "markdown", "html"],
796
+ description: "The format to return the content in. Defaults to markdown."
797
+ },
798
+ timeout: {
799
+ type: "number",
800
+ description: "Optional timeout in seconds (max 120)."
801
+ }
802
+ },
803
+ required: ["url"]
804
+ }
805
+ }
806
+ ];
807
+ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
808
+ const calls = new EventEmitter2();
809
+ const pending = /* @__PURE__ */ new Map();
810
+ const server2 = createServer(async (req, res) => {
811
+ if (req.method !== "POST" || !req.url?.startsWith("/mcp")) {
812
+ res.statusCode = 404;
813
+ res.end();
814
+ return;
815
+ }
816
+ try {
817
+ const body = await readBody(req);
818
+ const request = JSON.parse(body);
819
+ if (request?.jsonrpc !== "2.0" || typeof request.method !== "string") {
820
+ writeJson(res, {
821
+ jsonrpc: "2.0",
822
+ id: request?.id ?? null,
823
+ error: { code: -32600, message: "Invalid request" }
824
+ });
825
+ return;
826
+ }
827
+ log.debug("proxy-mcp request", {
828
+ method: request.method,
829
+ id: request.id
830
+ });
831
+ if (request.method === "initialize") {
832
+ writeJson(res, {
833
+ jsonrpc: "2.0",
834
+ id: request.id ?? null,
835
+ result: {
836
+ protocolVersion: PROTOCOL_VERSION,
837
+ capabilities: { tools: {} },
838
+ serverInfo: {
839
+ name: SERVER_NAME,
840
+ version: "0.1.0"
841
+ }
842
+ }
843
+ });
844
+ return;
845
+ }
846
+ if (request.method === "notifications/initialized") {
847
+ res.statusCode = 204;
848
+ res.end();
849
+ return;
850
+ }
851
+ if (request.method === "tools/list") {
852
+ writeJson(res, {
853
+ jsonrpc: "2.0",
854
+ id: request.id ?? null,
855
+ result: {
856
+ tools: tools.map((t) => ({
857
+ name: t.name,
858
+ description: t.description,
859
+ inputSchema: t.inputSchema
860
+ }))
861
+ }
862
+ });
863
+ return;
864
+ }
865
+ if (request.method === "tools/call") {
866
+ const params = request.params ?? {};
867
+ const toolName = String(params.name ?? "");
868
+ const input = params.arguments ?? {};
869
+ if (!tools.some((t) => t.name === toolName)) {
870
+ writeJson(res, {
871
+ jsonrpc: "2.0",
872
+ id: request.id ?? null,
873
+ error: {
874
+ code: -32601,
875
+ message: `Unknown proxy tool: ${toolName}`
876
+ }
877
+ });
878
+ return;
879
+ }
880
+ const callId = crypto2.randomUUID();
881
+ log.info("proxy-mcp tool call received", {
882
+ callId,
883
+ toolName,
884
+ hasInput: input != null
885
+ });
886
+ const result = await new Promise(
887
+ (resolve2, reject) => {
888
+ const entry = {
889
+ id: callId,
890
+ toolName,
891
+ input,
892
+ resolve: resolve2,
893
+ reject
894
+ };
895
+ pending.set(callId, entry);
896
+ calls.emit("call", entry);
897
+ }
898
+ ).finally(() => {
899
+ pending.delete(callId);
900
+ });
901
+ if (result.kind === "error") {
902
+ writeJson(res, {
903
+ jsonrpc: "2.0",
904
+ id: request.id ?? null,
905
+ error: {
906
+ code: -32e3,
907
+ message: result.message
908
+ }
909
+ });
910
+ return;
911
+ }
912
+ writeJson(res, {
913
+ jsonrpc: "2.0",
914
+ id: request.id ?? null,
915
+ result: {
916
+ content: [{ type: "text", text: result.text }],
917
+ isError: result.isError === true
918
+ }
919
+ });
920
+ return;
921
+ }
922
+ writeJson(res, {
923
+ jsonrpc: "2.0",
924
+ id: request.id ?? null,
925
+ error: { code: -32601, message: `Unknown method: ${request.method}` }
926
+ });
927
+ } catch (error) {
928
+ log.warn("proxy-mcp error handling request", {
929
+ error: error instanceof Error ? error.message : String(error)
930
+ });
931
+ try {
932
+ writeJson(res, {
933
+ jsonrpc: "2.0",
934
+ id: null,
935
+ error: {
936
+ code: -32603,
937
+ message: error instanceof Error ? error.message : "Internal error"
938
+ }
939
+ });
940
+ } catch {
941
+ try {
942
+ res.statusCode = 500;
943
+ res.end();
944
+ } catch {
945
+ }
946
+ }
947
+ }
948
+ });
949
+ await new Promise((resolve2, reject) => {
950
+ server2.once("error", reject);
951
+ server2.listen(0, "127.0.0.1", () => {
952
+ server2.off("error", reject);
953
+ resolve2();
954
+ });
955
+ });
956
+ const addr = server2.address();
957
+ if (!addr) {
958
+ server2.close();
959
+ throw new Error("Failed to bind proxy MCP server");
960
+ }
961
+ const url = `http://127.0.0.1:${addr.port}/mcp`;
962
+ log.info("proxy-mcp server started", {
963
+ url,
964
+ tools: tools.map((t) => t.name)
965
+ });
966
+ let configFilePath = null;
967
+ const api = {
968
+ url,
969
+ serverName: SERVER_NAME,
970
+ tools,
971
+ calls,
972
+ configPath() {
973
+ if (configFilePath) return configFilePath;
974
+ const body = JSON.stringify(
975
+ {
976
+ mcpServers: {
977
+ [SERVER_NAME]: {
978
+ type: "http",
979
+ url
980
+ }
981
+ }
982
+ },
983
+ null,
984
+ 2
985
+ );
986
+ const hash = crypto2.createHash("sha256").update(body).digest("hex").slice(0, 12);
987
+ const outPath = path2.join(
988
+ os2.tmpdir(),
989
+ `opencode-claude-code-proxy-${hash}.json`
990
+ );
991
+ fs2.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
992
+ configFilePath = outPath;
993
+ return outPath;
994
+ },
995
+ async close() {
996
+ for (const entry of pending.values()) {
997
+ entry.reject(new Error("proxy MCP server closed"));
998
+ }
999
+ pending.clear();
1000
+ await new Promise((resolve2) => {
1001
+ server2.close(() => resolve2());
1002
+ });
1003
+ }
1004
+ };
1005
+ return api;
1006
+ }
1007
+ function disallowedToolFlags(tools) {
1008
+ const nameMap = {
1009
+ bash: "Bash",
1010
+ read: "Read",
1011
+ write: "Write",
1012
+ edit: "Edit",
1013
+ glob: "Glob",
1014
+ grep: "Grep",
1015
+ webfetch: "WebFetch"
1016
+ };
1017
+ const out = [];
1018
+ for (const t of tools) {
1019
+ const mapped = nameMap[t.name.toLowerCase()];
1020
+ if (mapped) out.push(mapped);
1021
+ }
1022
+ return out;
1023
+ }
1024
+ function readBody(req) {
1025
+ return new Promise((resolve2, reject) => {
1026
+ const chunks = [];
1027
+ req.on("data", (chunk) => chunks.push(chunk));
1028
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
1029
+ req.on("error", reject);
1030
+ });
1031
+ }
1032
+ function writeJson(res, body) {
1033
+ const payload = JSON.stringify(body);
1034
+ res.statusCode = 200;
1035
+ res.setHeader("Content-Type", "application/json");
1036
+ res.setHeader("Content-Length", Buffer.byteLength(payload).toString());
1037
+ res.end(payload);
1038
+ }
1039
+
1040
+ // src/proxy-broker.ts
1041
+ import { EventEmitter as EventEmitter3 } from "events";
1042
+ var pendingBySession = /* @__PURE__ */ new Map();
1043
+ var emitter = new EventEmitter3();
1044
+ function eventName(sessionKey2) {
1045
+ return `pending:${sessionKey2}`;
1046
+ }
1047
+ function onPendingProxyCall(sessionKey2, handler) {
1048
+ const name = eventName(sessionKey2);
1049
+ emitter.on(name, handler);
1050
+ return () => emitter.off(name, handler);
1051
+ }
1052
+ function queuePendingProxyCall(sessionKey2, call) {
1053
+ const existing = pendingBySession.get(sessionKey2);
1054
+ if (existing) {
1055
+ existing.reject(
1056
+ new Error(`Another proxy tool call is already pending for ${sessionKey2}`)
1057
+ );
1058
+ pendingBySession.delete(sessionKey2);
1059
+ }
1060
+ const pending = {
1061
+ sessionKey: sessionKey2,
1062
+ toolCallId: call.id,
1063
+ toolName: call.toolName,
1064
+ input: call.input,
1065
+ resolve: call.resolve,
1066
+ reject: call.reject
1067
+ };
1068
+ pendingBySession.set(sessionKey2, pending);
1069
+ emitter.emit(eventName(sessionKey2), pending);
1070
+ log.info("queued pending proxy call", {
1071
+ sessionKey: sessionKey2,
1072
+ toolCallId: call.id,
1073
+ toolName: call.toolName
1074
+ });
1075
+ return pending;
1076
+ }
1077
+ function getPendingProxyCall(sessionKey2) {
1078
+ return pendingBySession.get(sessionKey2);
1079
+ }
1080
+ function resolvePendingProxyCall(sessionKey2, result) {
1081
+ const pending = pendingBySession.get(sessionKey2);
1082
+ if (!pending) return false;
1083
+ pendingBySession.delete(sessionKey2);
1084
+ pending.resolve(result);
1085
+ log.info("resolved pending proxy call", {
1086
+ sessionKey: sessionKey2,
1087
+ toolCallId: pending.toolCallId,
1088
+ toolName: pending.toolName
1089
+ });
1090
+ return true;
1091
+ }
1092
+
1093
+ // src/claude-code-language-model.ts
1094
+ var ClaudeCodeLanguageModel = class {
1095
+ specificationVersion = "v3";
1096
+ modelId;
1097
+ config;
1098
+ constructor(modelId, config) {
1099
+ this.modelId = modelId;
1100
+ this.config = config;
1101
+ }
1102
+ supportedUrls = {};
1103
+ get provider() {
1104
+ return this.config.provider;
1105
+ }
1106
+ toUsage(rawUsage) {
1107
+ const iter = rawUsage?.iterations;
1108
+ const effective = iter?.length ? iter[iter.length - 1] : rawUsage;
1109
+ const noCache = effective?.input_tokens ?? 0;
1110
+ const cacheRead = effective?.cache_read_input_tokens ?? 0;
1111
+ const cacheWrite = effective?.cache_creation_input_tokens ?? 0;
1112
+ return {
1113
+ inputTokens: {
1114
+ total: noCache + cacheRead + cacheWrite,
1115
+ noCache,
1116
+ cacheRead: cacheRead || void 0,
1117
+ cacheWrite: cacheWrite || void 0
1118
+ },
1119
+ outputTokens: {
1120
+ total: effective?.output_tokens,
1121
+ text: effective?.output_tokens,
1122
+ reasoning: void 0
1123
+ },
1124
+ raw: rawUsage
1125
+ };
1126
+ }
1127
+ toFinishReason(reason = "stop") {
1128
+ return {
1129
+ unified: reason,
1130
+ raw: reason
1131
+ };
1132
+ }
1133
+ requestScope(options) {
1134
+ const tools = options?.tools;
1135
+ if (Array.isArray(tools)) return "tools";
1136
+ if (tools && typeof tools === "object") {
1137
+ return Object.keys(tools).length > 0 ? "tools" : "no-tools";
1138
+ }
1139
+ return "no-tools";
1140
+ }
1141
+ /**
1142
+ * Build the combined `--mcp-config` list: user-configured paths plus the
1143
+ * auto-bridged opencode MCP config (when enabled and present) and the
1144
+ * proxy MCP scratch file (when proxyTools are enabled).
1145
+ */
1146
+ effectiveMcpConfig(cwd, proxyConfigPath) {
1147
+ const user = Array.isArray(this.config.mcpConfig) ? this.config.mcpConfig.slice() : this.config.mcpConfig ? [this.config.mcpConfig] : [];
1148
+ if (this.config.bridgeOpencodeMcp !== false) {
1149
+ const bridged = bridgeOpencodeMcp(cwd);
1150
+ if (bridged) user.push(bridged);
1151
+ }
1152
+ if (proxyConfigPath) user.push(proxyConfigPath);
1153
+ return user;
1154
+ }
1155
+ /** Resolve ProxyToolDef[] for the configured proxyTools names. */
1156
+ resolvedProxyTools() {
1157
+ const names = this.config.proxyTools;
1158
+ if (!names || names.length === 0) return null;
1159
+ const defsByName = new Map(
1160
+ DEFAULT_PROXY_TOOLS.map((t) => [t.name.toLowerCase(), t])
1161
+ );
1162
+ const picked = [];
1163
+ for (const n of names) {
1164
+ const def = defsByName.get(String(n).toLowerCase());
1165
+ if (def) picked.push(def);
1166
+ }
1167
+ return picked.length > 0 ? picked : null;
1168
+ }
1169
+ /**
1170
+ * Create a proxy MCP server for a single active Claude process/session.
1171
+ * The process lifecycle owns the server lifecycle via session-manager.
1172
+ */
1173
+ async ensureProxyServer(tools, sessionKeyForCalls) {
1174
+ const srv = await createProxyMcpServer(tools);
1175
+ srv.calls.on("call", (call) => {
1176
+ queuePendingProxyCall(sessionKeyForCalls, call);
1177
+ });
1178
+ return srv;
1179
+ }
1180
+ extractPendingProxyResult(prompt, toolCallId) {
1181
+ for (let i = prompt.length - 1; i >= 0; i--) {
1182
+ const msg = prompt[i];
1183
+ if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
1184
+ for (const part of msg.content) {
1185
+ if (part.type !== "tool-result" || part.toolCallId !== toolCallId) continue;
1186
+ const output = part.output;
1187
+ if (!output || typeof output !== "object") {
1188
+ return {
1189
+ kind: "text",
1190
+ text: String(output ?? "")
1191
+ };
1192
+ }
1193
+ if (output.type === "text") {
1194
+ return {
1195
+ kind: "text",
1196
+ text: String(output.value ?? "")
1197
+ };
1198
+ }
1199
+ if (output.type === "json") {
1200
+ return {
1201
+ kind: "text",
1202
+ text: JSON.stringify(output.value)
1203
+ };
1204
+ }
1205
+ if (output.type === "content" && Array.isArray(output.value)) {
1206
+ const text = output.value.filter((v) => v?.type === "text" && typeof v.text === "string").map((v) => v.text).join("\n");
1207
+ return {
1208
+ kind: "text",
1209
+ text
1210
+ };
1211
+ }
1212
+ return {
1213
+ kind: "text",
1214
+ text: JSON.stringify(output)
1215
+ };
1216
+ }
1217
+ }
1218
+ return null;
1219
+ }
1220
+ /**
1221
+ * Opencode sets `x-session-affinity: <sessionID>` on LLM calls for
1222
+ * third-party providers (packages/opencode/src/session/llm.ts). Use it so
1223
+ * two chats in the same cwd+model get separate CLI processes instead of
1224
+ * stomping on each other. Falls back to "default" when absent (older
1225
+ * opencode, direct AI-SDK use, title synthesis paths, etc).
1226
+ */
1227
+ sessionAffinity(options) {
1228
+ const headers = options?.headers;
1229
+ if (!headers) return "default";
1230
+ for (const key of Object.keys(headers)) {
1231
+ if (key.toLowerCase() === "x-session-affinity") {
1232
+ const v = headers[key];
1233
+ if (typeof v === "string" && v.length > 0) return v;
1234
+ }
1235
+ }
1236
+ return "default";
1237
+ }
1238
+ controlRequestBehaviorForTool(toolName) {
1239
+ const configured = this.config.controlRequestToolBehaviors;
1240
+ if (configured && toolName) {
1241
+ const direct = configured[toolName] ?? configured[toolName.toLowerCase()];
1242
+ if (direct === "allow" || direct === "deny") return direct;
1243
+ const lower = toolName.toLowerCase();
1244
+ for (const [key, behavior] of Object.entries(configured)) {
1245
+ if (key.toLowerCase() === lower && (behavior === "allow" || behavior === "deny")) {
1246
+ return behavior;
1247
+ }
1248
+ }
1249
+ }
1250
+ return this.config.controlRequestBehavior ?? "allow";
1251
+ }
1252
+ writeControlResponse(proc, requestId, response) {
1253
+ const payload = {
1254
+ type: "control_response",
1255
+ response: {
1256
+ subtype: "success",
1257
+ request_id: requestId,
1258
+ response
1259
+ }
1260
+ };
1261
+ try {
1262
+ proc.stdin?.write(JSON.stringify(payload) + "\n");
1263
+ } catch (error) {
1264
+ log.warn("failed to write control response", {
1265
+ requestId,
1266
+ error: error instanceof Error ? error.message : String(error)
1267
+ });
1268
+ }
1269
+ }
1270
+ /**
1271
+ * Handle Claude stream-json control requests (`can_use_tool`, etc.) and
1272
+ * respond via stdin with a matching `control_response`.
1273
+ */
1274
+ handleControlRequest(msg, proc) {
1275
+ if (msg.type !== "control_request") return false;
1276
+ const requestId = msg.request_id;
1277
+ const request = msg.request;
1278
+ if (!requestId || !request?.subtype) return false;
1279
+ if (request.subtype === "can_use_tool") {
1280
+ const toolName = request.tool_name ?? "unknown";
1281
+ const behavior = this.controlRequestBehaviorForTool(toolName);
1282
+ if (behavior === "allow") {
1283
+ this.writeControlResponse(proc, requestId, {
1284
+ behavior: "allow",
1285
+ updatedInput: request.input ?? {},
1286
+ toolUseID: request.tool_use_id
1287
+ });
1288
+ log.info("control request auto-allowed", {
1289
+ requestId,
1290
+ toolName
1291
+ });
1292
+ } else {
1293
+ this.writeControlResponse(proc, requestId, {
1294
+ behavior: "deny",
1295
+ message: this.config.controlRequestDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`,
1296
+ toolUseID: request.tool_use_id
1297
+ });
1298
+ log.info("control request auto-denied", {
1299
+ requestId,
1300
+ toolName
1301
+ });
1302
+ }
1303
+ return true;
1304
+ }
1305
+ this.writeControlResponse(proc, requestId, {});
1306
+ log.debug("control request acknowledged", {
1307
+ requestId,
1308
+ subtype: request.subtype
1309
+ });
1310
+ return true;
1311
+ }
1312
+ getReasoningEffort(providerOptions) {
1313
+ if (!providerOptions) return void 0;
1314
+ const ownKey = this.config.provider;
1315
+ const bag = providerOptions[ownKey] ?? providerOptions["claude-code"];
1316
+ const effort = bag?.reasoningEffort;
1317
+ const valid = [
1318
+ "minimal",
1319
+ "low",
1320
+ "medium",
1321
+ "high",
1322
+ "xhigh",
1323
+ "max"
1324
+ ];
1325
+ return valid.includes(effort) ? effort : void 0;
1326
+ }
1327
+ latestUserText(prompt) {
1328
+ for (let i = prompt.length - 1; i >= 0; i--) {
1329
+ const msg = prompt[i];
1330
+ if (msg.role !== "user") continue;
1331
+ if (typeof msg.content === "string") {
1332
+ return String(msg.content).trim();
1333
+ }
1334
+ if (Array.isArray(msg.content)) {
1335
+ const text = msg.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => String(part.text).trim()).filter(Boolean).join(" ");
1336
+ if (text) return text;
1337
+ }
1338
+ }
1339
+ return "";
1340
+ }
1341
+ synthesizeTitle(prompt) {
1342
+ const source = this.latestUserText(prompt).replace(/\s+/g, " ").replace(/[^\p{L}\p{N}\s-]/gu, " ").trim();
1343
+ if (!source) return "New Session";
1344
+ const stop = /* @__PURE__ */ new Set([
1345
+ "a",
1346
+ "an",
1347
+ "the",
1348
+ "and",
1349
+ "or",
1350
+ "but",
1351
+ "to",
1352
+ "for",
1353
+ "of",
1354
+ "in",
1355
+ "on",
1356
+ "at",
1357
+ "with",
1358
+ "can",
1359
+ "could",
1360
+ "would",
1361
+ "should",
1362
+ "please",
1363
+ "hi",
1364
+ "hello",
1365
+ "hey",
1366
+ "there",
1367
+ "you",
1368
+ "your",
1369
+ "this",
1370
+ "that",
1371
+ "is",
1372
+ "are",
1373
+ "was",
1374
+ "were",
1375
+ "be",
1376
+ "do",
1377
+ "does",
1378
+ "did",
1379
+ "summarize",
1380
+ "summary",
1381
+ "project"
1382
+ ]);
1383
+ const words = source.split(" ").map((word) => word.trim()).filter(Boolean).filter((word) => !stop.has(word.toLowerCase()));
1384
+ const picked = (words.length > 0 ? words : source.split(" ").filter(Boolean)).slice(0, 6).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1385
+ return picked || "New Session";
1386
+ }
1387
+ async doGenerateViaStream(options) {
1388
+ const result = await this.doStream(options);
1389
+ const reader = result.stream.getReader();
1390
+ let text = "";
1391
+ let reasoning = "";
1392
+ const toolCalls = [];
1393
+ let finishReason = this.toFinishReason("stop");
1394
+ let usage = this.toUsage();
1395
+ let providerMetadata;
1396
+ while (true) {
1397
+ const { value, done } = await reader.read();
1398
+ if (done) break;
1399
+ switch (value.type) {
1400
+ case "text-delta":
1401
+ text += value.delta ?? "";
1402
+ break;
1403
+ case "reasoning-delta":
1404
+ reasoning += value.delta ?? "";
1405
+ break;
1406
+ case "tool-call":
1407
+ toolCalls.push({
1408
+ type: "tool-call",
1409
+ toolCallId: value.toolCallId,
1410
+ toolName: value.toolName,
1411
+ input: value.input,
1412
+ providerExecuted: value.providerExecuted
1413
+ });
1414
+ break;
1415
+ case "finish":
1416
+ finishReason = value.finishReason ?? finishReason;
1417
+ usage = value.usage ?? usage;
1418
+ providerMetadata = value.providerMetadata ?? providerMetadata;
1419
+ break;
1420
+ }
1421
+ }
1422
+ const content = [];
1423
+ if (reasoning) {
1424
+ content.push({ type: "reasoning", text: reasoning });
1425
+ }
1426
+ if (text) {
1427
+ content.push({ type: "text", text, providerMetadata });
1428
+ }
1429
+ content.push(...toolCalls);
1430
+ return {
1431
+ content,
1432
+ finishReason,
1433
+ usage,
1434
+ request: result.request,
1435
+ response: {
1436
+ id: generateId(),
1437
+ timestamp: /* @__PURE__ */ new Date(),
1438
+ modelId: this.modelId
1439
+ },
1440
+ providerMetadata,
1441
+ warnings: []
1442
+ };
1443
+ }
1444
+ async doGenerate(options) {
1445
+ const warnings = [];
1446
+ const cwd = this.config.cwd ?? process.cwd();
1447
+ const scope = this.requestScope(options);
1448
+ const affinity = this.sessionAffinity(options);
1449
+ const sk = sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
1450
+ if (scope === "tools" && this.resolvedProxyTools()) {
1451
+ return this.doGenerateViaStream(options);
1452
+ }
1453
+ if (scope === "no-tools") {
1454
+ const text = this.synthesizeTitle(options.prompt);
1455
+ return {
1456
+ content: [{ type: "text", text }],
1457
+ finishReason: this.toFinishReason("stop"),
1458
+ usage: this.toUsage({ input_tokens: 0, output_tokens: 0 }),
1459
+ request: { body: { text: "" } },
1460
+ response: {
1461
+ id: generateId(),
1462
+ timestamp: /* @__PURE__ */ new Date(),
1463
+ modelId: this.modelId
1464
+ },
1465
+ providerMetadata: {
1466
+ "claude-code": {
1467
+ synthetic: true,
1468
+ path: "no-tools"
1469
+ }
1470
+ },
1471
+ warnings
1472
+ };
1473
+ }
1474
+ const hasPriorConversation = options.prompt.filter((m) => m.role === "user" || m.role === "assistant").length > 1;
1475
+ if (!hasPriorConversation) {
1476
+ deleteClaudeSessionId(sk);
1477
+ deleteActiveProcess(sk);
1478
+ }
1479
+ const hasExistingSession = !!getClaudeSessionId(sk);
1480
+ const includeHistoryContext = !hasExistingSession && hasPriorConversation;
1481
+ const reasoningEffort = this.getReasoningEffort(options.providerOptions);
1482
+ const userMsg = getClaudeUserMessage(
1483
+ options.prompt,
1484
+ includeHistoryContext,
1485
+ reasoningEffort
1486
+ );
1487
+ const cliArgs = buildCliArgs({
1488
+ sessionKey: sk,
1489
+ skipPermissions: this.config.skipPermissions !== false,
1490
+ includeSessionId: false,
1491
+ model: this.modelId,
1492
+ permissionMode: this.config.permissionMode,
1493
+ mcpConfig: this.effectiveMcpConfig(cwd),
1494
+ strictMcpConfig: this.config.strictMcpConfig
1495
+ });
1496
+ log.info("doGenerate starting", {
1497
+ cwd,
1498
+ model: this.modelId,
1499
+ textLength: userMsg.length,
1500
+ includeHistoryContext
1501
+ });
1502
+ const { spawn: spawn2 } = await import("child_process");
1503
+ const { createInterface: createInterface2 } = await import("readline");
1504
+ const proc = spawn2(this.config.cliPath, cliArgs, {
1505
+ cwd,
1506
+ stdio: ["pipe", "pipe", "pipe"],
1507
+ env: { ...process.env, TERM: "xterm-256color" },
1508
+ shell: process.platform === "win32"
1509
+ });
1510
+ const rl = createInterface2({ input: proc.stdout });
1511
+ let responseText = "";
1512
+ let thinkingText = "";
1513
+ let resultMeta = {};
1514
+ const toolCalls = [];
1515
+ const result = await new Promise((resolve2, reject) => {
1516
+ rl.on("line", (line) => {
1517
+ if (!line.trim()) return;
1518
+ try {
1519
+ const msg = JSON.parse(line);
1520
+ if (this.handleControlRequest(msg, proc)) {
1521
+ return;
1522
+ }
1523
+ if (msg.type === "system" && msg.subtype === "init") {
1524
+ if (msg.session_id) {
1525
+ setClaudeSessionId(sk, msg.session_id);
1526
+ }
1527
+ }
1528
+ if (msg.type === "assistant" && msg.message?.content) {
1529
+ for (const block of msg.message.content) {
1530
+ if (block.type === "text" && block.text) {
1531
+ responseText += block.text;
1532
+ }
1533
+ if (block.type === "thinking" && block.thinking) {
1534
+ thinkingText += block.thinking;
1535
+ }
1536
+ if (block.type === "tool_use" && block.id && block.name) {
1537
+ if (block.name === "AskUserQuestion" || block.name === "ask_user_question") {
1538
+ const parsedInput = block.input ?? {};
1539
+ const question = parsedInput?.question || "Question?";
1540
+ responseText += `
1541
+
1542
+ _Asking: ${question}_
1543
+
1544
+ `;
1545
+ continue;
1546
+ }
1547
+ if (block.name === "ExitPlanMode") {
1548
+ const parsedInput = block.input ?? {};
1549
+ const plan = parsedInput?.plan || "";
1550
+ responseText += `
1551
+
1552
+ ${plan}
1553
+
1554
+ ---
1555
+ **Do you want to proceed with this plan?** (yes/no)
1556
+ `;
1557
+ continue;
1558
+ }
1559
+ toolCalls.push({
1560
+ id: block.id,
1561
+ name: block.name,
1562
+ args: block.input ?? {}
1563
+ });
1564
+ }
1565
+ }
1566
+ }
1567
+ if (msg.type === "content_block_start" && msg.content_block) {
1568
+ if (msg.content_block.type === "tool_use" && msg.content_block.id && msg.content_block.name) {
1569
+ toolCalls.push({
1570
+ id: msg.content_block.id,
1571
+ name: msg.content_block.name,
1572
+ args: {}
1573
+ });
1574
+ }
1575
+ }
1576
+ if (msg.type === "content_block_delta" && msg.delta) {
1577
+ if (msg.delta.type === "text_delta" && msg.delta.text) {
1578
+ responseText += msg.delta.text;
1579
+ }
1580
+ if (msg.delta.type === "thinking_delta" && msg.delta.thinking) {
1581
+ thinkingText += msg.delta.thinking;
1582
+ }
1583
+ if (msg.delta.type === "input_json_delta" && msg.delta.partial_json && msg.index !== void 0) {
1584
+ const tc = toolCalls[msg.index];
1585
+ if (tc) {
1586
+ try {
1587
+ tc.args = JSON.parse(msg.delta.partial_json);
1588
+ } catch {
1589
+ }
1590
+ }
1591
+ }
1592
+ }
1593
+ if (msg.type === "result") {
1594
+ if (msg.session_id) {
1595
+ setClaudeSessionId(sk, msg.session_id);
1596
+ }
1597
+ if (!responseText && msg.is_error && typeof msg.result === "string" && msg.result.trim().length > 0) {
1598
+ responseText = msg.result;
1599
+ }
1600
+ resultMeta = {
1601
+ sessionId: msg.session_id,
1602
+ costUsd: msg.total_cost_usd,
1603
+ durationMs: msg.duration_ms,
1604
+ usage: msg.usage
1605
+ };
1606
+ resolve2({
1607
+ ...resultMeta,
1608
+ text: responseText,
1609
+ thinking: thinkingText,
1610
+ toolCalls
1611
+ });
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ });
1616
+ rl.on("close", () => {
1617
+ resolve2({
1618
+ ...resultMeta,
1619
+ text: responseText,
1620
+ thinking: thinkingText,
1621
+ toolCalls
1622
+ });
1623
+ });
1624
+ proc.on("error", (err) => {
1625
+ log.error("process error", { error: err.message });
1626
+ reject(err);
1627
+ });
1628
+ proc.stderr?.on("data", (data) => {
1629
+ log.debug("stderr", { data: data.toString().slice(0, 200) });
1630
+ });
1631
+ proc.stdin?.write(userMsg + "\n");
1632
+ });
1633
+ const content = [];
1634
+ if (result.thinking) {
1635
+ content.push({
1636
+ type: "reasoning",
1637
+ text: result.thinking
1638
+ });
1639
+ }
1640
+ if (result.text) {
1641
+ content.push({
1642
+ type: "text",
1643
+ text: result.text,
1644
+ providerMetadata: {
1645
+ "claude-code": {
1646
+ sessionId: result.sessionId ?? null,
1647
+ costUsd: result.costUsd ?? null,
1648
+ durationMs: result.durationMs ?? null
1649
+ },
1650
+ ...typeof result.usage?.cache_creation_input_tokens === "number" ? {
1651
+ anthropic: {
1652
+ cacheCreationInputTokens: result.usage.cache_creation_input_tokens
1653
+ }
1654
+ } : {}
1655
+ }
1656
+ });
1657
+ }
1658
+ for (const tc of result.toolCalls) {
1659
+ const {
1660
+ name: mappedName,
1661
+ input: mappedInput,
1662
+ executed,
1663
+ skip
1664
+ } = mapTool(tc.name, tc.args);
1665
+ if (skip) continue;
1666
+ content.push({
1667
+ type: "tool-call",
1668
+ toolCallId: tc.id,
1669
+ toolName: mappedName,
1670
+ input: JSON.stringify(mappedInput),
1671
+ providerExecuted: executed
1672
+ });
1673
+ }
1674
+ const usage = this.toUsage(result.usage);
1675
+ return {
1676
+ content,
1677
+ // Claude CLI's `result` message signals a fully-completed turn —
1678
+ // tools have already been executed internally and final assistant
1679
+ // text has been produced. Always report "stop" so opencode doesn't
1680
+ // loop expecting to run tools itself.
1681
+ finishReason: this.toFinishReason("stop"),
1682
+ usage,
1683
+ request: { body: { text: userMsg } },
1684
+ response: {
1685
+ id: result.sessionId ?? generateId(),
1686
+ timestamp: /* @__PURE__ */ new Date(),
1687
+ modelId: this.modelId
1688
+ },
1689
+ providerMetadata: {
1690
+ "claude-code": {
1691
+ sessionId: result.sessionId ?? null,
1692
+ costUsd: result.costUsd ?? null,
1693
+ durationMs: result.durationMs ?? null
1694
+ },
1695
+ ...typeof result.usage?.cache_creation_input_tokens === "number" ? {
1696
+ anthropic: {
1697
+ cacheCreationInputTokens: result.usage.cache_creation_input_tokens
1698
+ }
1699
+ } : {}
1700
+ },
1701
+ warnings
1702
+ };
1703
+ }
1704
+ async doStream(options) {
1705
+ const warnings = [];
1706
+ const cwd = this.config.cwd ?? process.cwd();
1707
+ const cliPath = this.config.cliPath;
1708
+ const skipPermissions = this.config.skipPermissions !== false;
1709
+ const scope = this.requestScope(options);
1710
+ const affinity = this.sessionAffinity(options);
1711
+ const sk = sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
1712
+ const toUsage = this.toUsage.bind(this);
1713
+ const toFinishReason = this.toFinishReason.bind(this);
1714
+ const handleControlRequest = this.handleControlRequest.bind(this);
1715
+ if (scope === "no-tools") {
1716
+ const text = this.synthesizeTitle(options.prompt);
1717
+ const textId = generateId();
1718
+ const stream2 = new ReadableStream({
1719
+ start(controller) {
1720
+ controller.enqueue({ type: "stream-start", warnings });
1721
+ controller.enqueue({ type: "text-start", id: textId });
1722
+ controller.enqueue({
1723
+ type: "text-delta",
1724
+ id: textId,
1725
+ delta: text
1726
+ });
1727
+ controller.enqueue({ type: "text-end", id: textId });
1728
+ controller.enqueue({
1729
+ type: "finish",
1730
+ finishReason: toFinishReason("stop"),
1731
+ usage: toUsage({ input_tokens: 0, output_tokens: 0 }),
1732
+ providerMetadata: {
1733
+ "claude-code": {
1734
+ synthetic: true,
1735
+ path: "no-tools"
1736
+ }
1737
+ }
1738
+ });
1739
+ controller.close();
1740
+ }
1741
+ });
1742
+ return {
1743
+ stream: stream2,
1744
+ request: { body: { text: "" } }
1745
+ };
1746
+ }
1747
+ const hasPriorConversation = options.prompt.filter((m) => m.role === "user" || m.role === "assistant").length > 1;
1748
+ if (!hasPriorConversation) {
1749
+ deleteClaudeSessionId(sk);
1750
+ deleteActiveProcess(sk);
1751
+ }
1752
+ const hasExistingSession = !!getClaudeSessionId(sk);
1753
+ const hasActiveProcess = !!getActiveProcess(sk);
1754
+ const includeHistoryContext = !hasExistingSession && !hasActiveProcess && hasPriorConversation;
1755
+ const reasoningEffort = this.getReasoningEffort(options.providerOptions);
1756
+ const userMsg = getClaudeUserMessage(
1757
+ options.prompt,
1758
+ includeHistoryContext,
1759
+ reasoningEffort
1760
+ );
1761
+ const resolvedProxy = this.resolvedProxyTools();
1762
+ const self = this;
1763
+ const pendingProxyCall = getPendingProxyCall(sk);
1764
+ const pendingProxyResult = pendingProxyCall ? this.extractPendingProxyResult(options.prompt, pendingProxyCall.toolCallId) : null;
1765
+ log.info("doStream starting", {
1766
+ cwd,
1767
+ model: this.modelId,
1768
+ textLength: userMsg.length,
1769
+ includeHistoryContext,
1770
+ hasActiveProcess,
1771
+ reasoningEffort,
1772
+ proxyTools: resolvedProxy?.map((t) => t.name) ?? null
1773
+ });
1774
+ const stream = new ReadableStream({
1775
+ start(controller) {
1776
+ let activeProcess = getActiveProcess(sk);
1777
+ let proc;
1778
+ let lineEmitter;
1779
+ let proxyServer = activeProcess?.proxyServer ?? null;
1780
+ const setup = async () => {
1781
+ if (!proxyServer && resolvedProxy) {
1782
+ proxyServer = await self.ensureProxyServer(resolvedProxy, sk);
1783
+ }
1784
+ const cliArgs = buildCliArgs({
1785
+ sessionKey: sk,
1786
+ skipPermissions,
1787
+ model: self.modelId,
1788
+ permissionMode: self.config.permissionMode,
1789
+ mcpConfig: self.effectiveMcpConfig(cwd, proxyServer?.configPath()),
1790
+ strictMcpConfig: self.config.strictMcpConfig,
1791
+ disallowedTools: resolvedProxy ? disallowedToolFlags(resolvedProxy) : void 0
1792
+ });
1793
+ if (activeProcess) {
1794
+ proc = activeProcess.proc;
1795
+ lineEmitter = activeProcess.lineEmitter;
1796
+ log.debug("reusing active process", { sk });
1797
+ } else {
1798
+ const ap = spawnClaudeProcess(cliPath, cliArgs, cwd, sk, proxyServer);
1799
+ proc = ap.proc;
1800
+ lineEmitter = ap.lineEmitter;
1801
+ activeProcess = ap;
1802
+ }
1803
+ controller.enqueue({ type: "stream-start", warnings });
1804
+ let currentTextId = null;
1805
+ const textBlockIndices = /* @__PURE__ */ new Set();
1806
+ const startTextBlock = () => {
1807
+ if (currentTextId) {
1808
+ controller.enqueue({ type: "text-end", id: currentTextId });
1809
+ }
1810
+ const id = generateId();
1811
+ currentTextId = id;
1812
+ controller.enqueue({ type: "text-start", id });
1813
+ return id;
1814
+ };
1815
+ const endTextBlock = () => {
1816
+ if (currentTextId) {
1817
+ controller.enqueue({ type: "text-end", id: currentTextId });
1818
+ currentTextId = null;
1819
+ }
1820
+ };
1821
+ const reasoningIds = /* @__PURE__ */ new Map();
1822
+ const reasoningStarted = /* @__PURE__ */ new Map();
1823
+ let turnCompleted = false;
1824
+ let controllerClosed = false;
1825
+ let pendingProxyUnsubscribe = null;
1826
+ let resultFallbackTimer = null;
1827
+ let hasReceivedContent = false;
1828
+ const clearFallbackTimer = () => {
1829
+ if (resultFallbackTimer) {
1830
+ clearTimeout(resultFallbackTimer);
1831
+ resultFallbackTimer = null;
1832
+ }
1833
+ };
1834
+ const startResultFallback = () => {
1835
+ clearFallbackTimer();
1836
+ if (!hasReceivedContent || controllerClosed) return;
1837
+ resultFallbackTimer = setTimeout(() => {
1838
+ if (controllerClosed) return;
1839
+ log.warn("result fallback timer fired \u2014 closing stream without result event");
1840
+ closeHandler();
1841
+ }, 5e3);
1842
+ };
1843
+ const toolCallMap = /* @__PURE__ */ new Map();
1844
+ const skipResultForIds = /* @__PURE__ */ new Set();
1845
+ const toolCallsById = /* @__PURE__ */ new Map();
1846
+ let resultMeta = {};
1847
+ const finishWithToolCall = (call) => {
1848
+ if (controllerClosed) return;
1849
+ controller.enqueue({
1850
+ type: "tool-input-start",
1851
+ id: call.toolCallId,
1852
+ toolName: call.toolName
1853
+ });
1854
+ controller.enqueue({
1855
+ type: "tool-call",
1856
+ toolCallId: call.toolCallId,
1857
+ toolName: call.toolName,
1858
+ input: JSON.stringify(call.input),
1859
+ providerExecuted: false
1860
+ });
1861
+ skipResultForIds.add(call.toolCallId);
1862
+ controller.enqueue({
1863
+ type: "finish",
1864
+ finishReason: toFinishReason("tool-calls"),
1865
+ usage: toUsage(resultMeta.usage),
1866
+ providerMetadata: {
1867
+ "claude-code": resultMeta
1868
+ }
1869
+ });
1870
+ controllerClosed = true;
1871
+ lineEmitter.off("line", lineHandler);
1872
+ lineEmitter.off("close", closeHandler);
1873
+ pendingProxyUnsubscribe?.();
1874
+ pendingProxyUnsubscribe = null;
1875
+ try {
1876
+ controller.close();
1877
+ } catch {
1878
+ }
1879
+ };
1880
+ const lineHandler = (line) => {
1881
+ if (!line.trim()) return;
1882
+ if (controllerClosed) return;
1883
+ try {
1884
+ const msg = JSON.parse(line);
1885
+ if (handleControlRequest(msg, proc)) {
1886
+ return;
1887
+ }
1888
+ log.debug("stream message", {
1889
+ type: msg.type,
1890
+ subtype: msg.subtype
1891
+ });
1892
+ if (msg.type === "system" && msg.subtype === "init") {
1893
+ if (msg.session_id) {
1894
+ setClaudeSessionId(sk, msg.session_id);
1895
+ log.info("session initialized", {
1896
+ claudeSessionId: msg.session_id
1897
+ });
1898
+ }
1899
+ }
1900
+ if (msg.type === "content_block_start" && msg.content_block && msg.index !== void 0) {
1901
+ const block = msg.content_block;
1902
+ const idx = msg.index;
1903
+ if (block.type === "thinking") {
1904
+ const reasoningId = generateId();
1905
+ reasoningIds.set(idx, reasoningId);
1906
+ controller.enqueue({
1907
+ type: "reasoning-start",
1908
+ id: reasoningId
1909
+ });
1910
+ reasoningStarted.set(idx, true);
1911
+ }
1912
+ if (block.type === "text") {
1913
+ clearFallbackTimer();
1914
+ textBlockIndices.add(idx);
1915
+ if (block.text) {
1916
+ if (!currentTextId) startTextBlock();
1917
+ controller.enqueue({
1918
+ type: "text-delta",
1919
+ id: currentTextId,
1920
+ delta: block.text
1921
+ });
1922
+ hasReceivedContent = true;
1923
+ }
1924
+ }
1925
+ if (block.type === "tool_use" && block.id && block.name) {
1926
+ clearFallbackTimer();
1927
+ toolCallMap.set(idx, {
1928
+ id: block.id,
1929
+ name: block.name,
1930
+ inputJson: ""
1931
+ });
1932
+ if (block.name !== "AskUserQuestion" && block.name !== "ask_user_question" && block.name !== "ExitPlanMode" && !block.name.startsWith(PROXY_TOOL_PREFIX)) {
1933
+ const { name: mappedName, skip, executed } = mapTool(block.name);
1934
+ if (!skip) {
1935
+ controller.enqueue({
1936
+ type: "tool-input-start",
1937
+ id: block.id,
1938
+ toolName: mappedName,
1939
+ providerExecuted: executed
1940
+ });
1941
+ log.info("tool started", {
1942
+ name: block.name,
1943
+ mappedName,
1944
+ id: block.id
1945
+ });
1946
+ }
1947
+ }
1948
+ }
1949
+ }
1950
+ if (msg.type === "content_block_delta" && msg.delta && msg.index !== void 0) {
1951
+ const delta = msg.delta;
1952
+ const idx = msg.index;
1953
+ if (delta.type === "thinking_delta" && delta.thinking) {
1954
+ const reasoningId = reasoningIds.get(idx);
1955
+ if (reasoningId) {
1956
+ controller.enqueue({
1957
+ type: "reasoning-delta",
1958
+ id: reasoningId,
1959
+ delta: delta.thinking
1960
+ });
1961
+ }
1962
+ }
1963
+ if (delta.type === "text_delta" && delta.text) {
1964
+ if (!currentTextId) startTextBlock();
1965
+ controller.enqueue({
1966
+ type: "text-delta",
1967
+ id: currentTextId,
1968
+ delta: delta.text
1969
+ });
1970
+ hasReceivedContent = true;
1971
+ }
1972
+ if (delta.type === "input_json_delta" && delta.partial_json) {
1973
+ const tc = toolCallMap.get(idx);
1974
+ if (tc) {
1975
+ tc.inputJson += delta.partial_json;
1976
+ controller.enqueue({
1977
+ type: "tool-input-delta",
1978
+ id: tc.id,
1979
+ delta: delta.partial_json
1980
+ });
1981
+ }
1982
+ }
1983
+ }
1984
+ if (msg.type === "content_block_stop" && msg.index !== void 0) {
1985
+ const idx = msg.index;
1986
+ const reasoningId = reasoningIds.get(idx);
1987
+ if (reasoningId && reasoningStarted.get(idx)) {
1988
+ controller.enqueue({
1989
+ type: "reasoning-end",
1990
+ id: reasoningId
1991
+ });
1992
+ reasoningStarted.delete(idx);
1993
+ }
1994
+ if (textBlockIndices.has(idx)) {
1995
+ endTextBlock();
1996
+ textBlockIndices.delete(idx);
1997
+ startResultFallback();
1998
+ }
1999
+ const tc = toolCallMap.get(idx);
2000
+ if (tc) {
2001
+ let parsedInput = {};
2002
+ try {
2003
+ parsedInput = JSON.parse(tc.inputJson || "{}");
2004
+ } catch {
2005
+ }
2006
+ if (tc.name === "AskUserQuestion" || tc.name === "ask_user_question") {
2007
+ let question = "Question?";
2008
+ if (parsedInput?.questions && Array.isArray(parsedInput.questions) && parsedInput.questions.length > 0) {
2009
+ question = parsedInput.questions[0].question || parsedInput.questions[0].text || "Question?";
2010
+ } else {
2011
+ question = parsedInput?.question || parsedInput?.text || "Question?";
2012
+ }
2013
+ const askId = startTextBlock();
2014
+ controller.enqueue({
2015
+ type: "text-delta",
2016
+ id: askId,
2017
+ delta: `
2018
+
2019
+ _Asking: ${question}_
2020
+
2021
+ `
2022
+ });
2023
+ endTextBlock();
2024
+ } else if (tc.name === "ExitPlanMode") {
2025
+ const plan = parsedInput?.plan || "";
2026
+ const planId = startTextBlock();
2027
+ controller.enqueue({
2028
+ type: "text-delta",
2029
+ id: planId,
2030
+ delta: `
2031
+
2032
+ ${plan}
2033
+
2034
+ ---
2035
+ **Do you want to proceed with this plan?** (yes/no)
2036
+ `
2037
+ });
2038
+ endTextBlock();
2039
+ } else if (tc.name.startsWith(PROXY_TOOL_PREFIX)) {
2040
+ log.debug("ignoring proxy tool_use block; broker handles it", {
2041
+ name: tc.name,
2042
+ id: tc.id
2043
+ });
2044
+ } else {
2045
+ const {
2046
+ name: mappedName,
2047
+ input: mappedInput,
2048
+ executed,
2049
+ skip
2050
+ } = mapTool(tc.name, parsedInput);
2051
+ if (!skip) {
2052
+ toolCallsById.set(tc.id, {
2053
+ id: tc.id,
2054
+ name: tc.name,
2055
+ input: parsedInput
2056
+ });
2057
+ if (!executed) skipResultForIds.add(tc.id);
2058
+ controller.enqueue({
2059
+ type: "tool-call",
2060
+ toolCallId: tc.id,
2061
+ toolName: mappedName,
2062
+ input: JSON.stringify(mappedInput),
2063
+ providerExecuted: executed
2064
+ });
2065
+ }
2066
+ log.info("tool call complete", {
2067
+ name: tc.name,
2068
+ mappedName,
2069
+ id: tc.id,
2070
+ executed
2071
+ });
2072
+ }
2073
+ }
2074
+ }
2075
+ if (msg.type === "assistant" && msg.message?.content) {
2076
+ const hasText = msg.message.content.some(
2077
+ (b) => b.type === "text" && b.text
2078
+ );
2079
+ const hasToolUse = msg.message.content.some(
2080
+ (b) => b.type === "tool_use"
2081
+ );
2082
+ if (hasText) {
2083
+ hasReceivedContent = true;
2084
+ }
2085
+ if (hasText && !hasToolUse) {
2086
+ startResultFallback();
2087
+ }
2088
+ if (hasToolUse) {
2089
+ clearFallbackTimer();
2090
+ }
2091
+ for (const block of msg.message.content) {
2092
+ if (block.type === "text" && block.text) {
2093
+ const blockId = startTextBlock();
2094
+ controller.enqueue({
2095
+ type: "text-delta",
2096
+ id: blockId,
2097
+ delta: block.text
2098
+ });
2099
+ endTextBlock();
2100
+ hasReceivedContent = true;
2101
+ }
2102
+ if (block.type === "thinking" && block.thinking) {
2103
+ const thinkingId = generateId();
2104
+ controller.enqueue({
2105
+ type: "reasoning-start",
2106
+ id: thinkingId
2107
+ });
2108
+ controller.enqueue({
2109
+ type: "reasoning-delta",
2110
+ id: thinkingId,
2111
+ delta: block.thinking
2112
+ });
2113
+ controller.enqueue({
2114
+ type: "reasoning-end",
2115
+ id: thinkingId
2116
+ });
2117
+ }
2118
+ if (block.type === "tool_use" && block.id && block.name) {
2119
+ const parsedInput = block.input ?? {};
2120
+ toolCallsById.set(block.id, {
2121
+ id: block.id,
2122
+ name: block.name,
2123
+ input: parsedInput
2124
+ });
2125
+ if (block.name === "AskUserQuestion" || block.name === "ask_user_question") {
2126
+ let question = "Question?";
2127
+ if (parsedInput?.questions && Array.isArray(parsedInput.questions) && parsedInput.questions.length > 0) {
2128
+ const q = parsedInput.questions[0];
2129
+ question = q.question || q.text || "Question?";
2130
+ } else {
2131
+ question = parsedInput?.question || parsedInput?.text || "Question?";
2132
+ }
2133
+ const askId = startTextBlock();
2134
+ controller.enqueue({
2135
+ type: "text-delta",
2136
+ id: askId,
2137
+ delta: `
2138
+
2139
+ _Asking: ${question}_
2140
+
2141
+ `
2142
+ });
2143
+ endTextBlock();
2144
+ } else if (block.name === "ExitPlanMode") {
2145
+ const plan = parsedInput?.plan || "";
2146
+ const planId = startTextBlock();
2147
+ controller.enqueue({
2148
+ type: "text-delta",
2149
+ id: planId,
2150
+ delta: `
2151
+
2152
+ ${plan}
2153
+
2154
+ ---
2155
+ **Do you want to proceed with this plan?** (yes/no)
2156
+ `
2157
+ });
2158
+ endTextBlock();
2159
+ } else if (block.name.startsWith(PROXY_TOOL_PREFIX)) {
2160
+ log.debug("ignoring proxy tool_use from assistant message", {
2161
+ name: block.name,
2162
+ id: block.id
2163
+ });
2164
+ } else {
2165
+ const {
2166
+ name: mappedName,
2167
+ input: mappedInput,
2168
+ executed,
2169
+ skip
2170
+ } = mapTool(block.name, parsedInput);
2171
+ if (!skip) {
2172
+ if (!executed) skipResultForIds.add(block.id);
2173
+ controller.enqueue({
2174
+ type: "tool-input-start",
2175
+ id: block.id,
2176
+ toolName: mappedName,
2177
+ providerExecuted: executed
2178
+ });
2179
+ controller.enqueue({
2180
+ type: "tool-call",
2181
+ toolCallId: block.id,
2182
+ toolName: mappedName,
2183
+ input: JSON.stringify(mappedInput),
2184
+ providerExecuted: executed
2185
+ });
2186
+ }
2187
+ log.info("tool_use from assistant message", {
2188
+ name: block.name,
2189
+ mappedName,
2190
+ id: block.id,
2191
+ executed
2192
+ });
2193
+ }
2194
+ }
2195
+ if (block.type === "tool_result") {
2196
+ log.debug("tool_result", {
2197
+ toolUseId: block.tool_use_id
2198
+ });
2199
+ }
2200
+ }
2201
+ }
2202
+ if (msg.type === "user" && msg.message?.content) {
2203
+ for (const block of msg.message.content) {
2204
+ if (block.type === "tool_result" && block.tool_use_id) {
2205
+ if (skipResultForIds.has(block.tool_use_id)) {
2206
+ log.debug("skipping tool-result (opencode runs it)", {
2207
+ toolUseId: block.tool_use_id
2208
+ });
2209
+ continue;
2210
+ }
2211
+ const toolCall = toolCallsById.get(block.tool_use_id);
2212
+ if (toolCall) {
2213
+ let resultText = "";
2214
+ if (typeof block.content === "string") {
2215
+ resultText = block.content;
2216
+ } else if (Array.isArray(block.content)) {
2217
+ resultText = block.content.filter(
2218
+ (c) => c.type === "text" && typeof c.text === "string"
2219
+ ).map((c) => c.text).join("\n");
2220
+ }
2221
+ controller.enqueue({
2222
+ type: "tool-result",
2223
+ toolCallId: block.tool_use_id,
2224
+ toolName: toolCall.name,
2225
+ result: {
2226
+ output: resultText,
2227
+ title: toolCall.name,
2228
+ metadata: {}
2229
+ },
2230
+ providerExecuted: true
2231
+ });
2232
+ log.info("tool result emitted", {
2233
+ toolUseId: block.tool_use_id,
2234
+ name: toolCall.name
2235
+ });
2236
+ toolCallsById.delete(block.tool_use_id);
2237
+ }
2238
+ }
2239
+ }
2240
+ }
2241
+ if (msg.type === "result") {
2242
+ clearFallbackTimer();
2243
+ if (msg.session_id) {
2244
+ setClaudeSessionId(sk, msg.session_id);
2245
+ }
2246
+ if (!currentTextId && msg.is_error && typeof msg.result === "string" && msg.result.trim().length > 0) {
2247
+ const errId = startTextBlock();
2248
+ controller.enqueue({
2249
+ type: "text-delta",
2250
+ id: errId,
2251
+ delta: msg.result
2252
+ });
2253
+ }
2254
+ resultMeta = {
2255
+ sessionId: msg.session_id,
2256
+ costUsd: msg.total_cost_usd,
2257
+ durationMs: msg.duration_ms,
2258
+ usage: msg.usage
2259
+ };
2260
+ log.info("conversation result", {
2261
+ sessionId: msg.session_id,
2262
+ durationMs: msg.duration_ms,
2263
+ numTurns: msg.num_turns,
2264
+ isError: msg.is_error
2265
+ });
2266
+ turnCompleted = true;
2267
+ endTextBlock();
2268
+ for (const [idx, reasoningId] of reasoningIds) {
2269
+ if (reasoningStarted.get(idx)) {
2270
+ controller.enqueue({
2271
+ type: "reasoning-end",
2272
+ id: reasoningId
2273
+ });
2274
+ }
2275
+ }
2276
+ controller.enqueue({
2277
+ type: "finish",
2278
+ finishReason: toFinishReason("stop"),
2279
+ usage: toUsage(msg.usage),
2280
+ providerMetadata: {
2281
+ "claude-code": resultMeta,
2282
+ ...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
2283
+ anthropic: {
2284
+ cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
2285
+ }
2286
+ } : {}
2287
+ }
2288
+ });
2289
+ controllerClosed = true;
2290
+ lineEmitter.off("line", lineHandler);
2291
+ lineEmitter.off("close", closeHandler);
2292
+ try {
2293
+ controller.close();
2294
+ } catch {
2295
+ }
2296
+ }
2297
+ } catch (e) {
2298
+ log.debug("failed to parse line", {
2299
+ error: e instanceof Error ? e.message : String(e)
2300
+ });
2301
+ }
2302
+ };
2303
+ const closeHandler = () => {
2304
+ log.debug("readline closed");
2305
+ if (controllerClosed) return;
2306
+ clearFallbackTimer();
2307
+ controllerClosed = true;
2308
+ lineEmitter.off("line", lineHandler);
2309
+ lineEmitter.off("close", closeHandler);
2310
+ pendingProxyUnsubscribe?.();
2311
+ pendingProxyUnsubscribe = null;
2312
+ endTextBlock();
2313
+ controller.enqueue({
2314
+ type: "finish",
2315
+ finishReason: toFinishReason("stop"),
2316
+ usage: toUsage(),
2317
+ providerMetadata: {
2318
+ "claude-code": resultMeta
2319
+ }
2320
+ });
2321
+ try {
2322
+ controller.close();
2323
+ } catch {
2324
+ }
2325
+ };
2326
+ lineEmitter.on("line", lineHandler);
2327
+ lineEmitter.on("close", closeHandler);
2328
+ pendingProxyUnsubscribe = onPendingProxyCall(sk, (call) => {
2329
+ log.info("received pending proxy call for session", {
2330
+ sessionKey: sk,
2331
+ toolCallId: call.toolCallId,
2332
+ toolName: call.toolName
2333
+ });
2334
+ finishWithToolCall(call);
2335
+ });
2336
+ proc.on("error", (err) => {
2337
+ log.error("process error", { error: err.message });
2338
+ clearFallbackTimer();
2339
+ if (controllerClosed) return;
2340
+ controllerClosed = true;
2341
+ pendingProxyUnsubscribe?.();
2342
+ pendingProxyUnsubscribe = null;
2343
+ controller.enqueue({ type: "error", error: err });
2344
+ try {
2345
+ controller.close();
2346
+ } catch {
2347
+ }
2348
+ });
2349
+ if (options.abortSignal) {
2350
+ options.abortSignal.addEventListener("abort", () => {
2351
+ if (turnCompleted || controllerClosed) return;
2352
+ if (!hasReceivedContent) {
2353
+ log.info(
2354
+ "abort signal received before content, closing stream immediately",
2355
+ { cwd }
2356
+ );
2357
+ controllerClosed = true;
2358
+ lineEmitter.off("line", lineHandler);
2359
+ lineEmitter.off("close", closeHandler);
2360
+ pendingProxyUnsubscribe?.();
2361
+ pendingProxyUnsubscribe = null;
2362
+ try {
2363
+ controller.close();
2364
+ } catch {
2365
+ }
2366
+ return;
2367
+ }
2368
+ log.info(
2369
+ "abort signal received mid-turn, starting grace period",
2370
+ { cwd }
2371
+ );
2372
+ startResultFallback();
2373
+ });
2374
+ }
2375
+ if (pendingProxyCall && pendingProxyResult) {
2376
+ log.info("resolving pending proxy call from tool result prompt", {
2377
+ sessionKey: sk,
2378
+ toolCallId: pendingProxyCall.toolCallId,
2379
+ toolName: pendingProxyCall.toolName
2380
+ });
2381
+ const resolved = resolvePendingProxyCall(sk, pendingProxyResult);
2382
+ if (!resolved) {
2383
+ log.warn("failed to resolve pending proxy call; no pending state", {
2384
+ sessionKey: sk,
2385
+ toolCallId: pendingProxyCall.toolCallId
2386
+ });
2387
+ }
2388
+ return;
2389
+ }
2390
+ proc.stdin?.write(userMsg + "\n");
2391
+ log.debug("sent user message", { textLength: userMsg.length });
2392
+ };
2393
+ void setup().catch((err) => {
2394
+ log.error("failed to set up doStream", {
2395
+ error: err instanceof Error ? err.message : String(err)
2396
+ });
2397
+ controller.enqueue({
2398
+ type: "error",
2399
+ error: err instanceof Error ? err : new Error(String(err))
2400
+ });
2401
+ try {
2402
+ controller.close();
2403
+ } catch {
2404
+ }
2405
+ });
2406
+ },
2407
+ cancel() {
2408
+ }
2409
+ });
2410
+ return {
2411
+ stream,
2412
+ request: { body: { text: userMsg } },
2413
+ response: { headers: {} }
2414
+ };
2415
+ }
2416
+ };
2417
+
2418
+ // src/models.ts
2419
+ var PROVIDER_ID = "claude-code";
2420
+ var NPM = "@khalilgharbaoui/opencode-claude-code-plugin";
2421
+ var reasoningVariants = {
2422
+ low: { reasoningEffort: "low" },
2423
+ medium: { reasoningEffort: "medium" },
2424
+ high: { reasoningEffort: "high" },
2425
+ xhigh: { reasoningEffort: "xhigh" },
2426
+ max: { reasoningEffort: "max" }
2427
+ };
2428
+ var baseCapabilities = {
2429
+ temperature: false,
2430
+ attachment: true,
2431
+ toolcall: true,
2432
+ input: { text: true, audio: false, image: true, video: false, pdf: false },
2433
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
2434
+ interleaved: false
2435
+ };
2436
+ function defineModel(opts) {
2437
+ return {
2438
+ id: opts.id,
2439
+ providerID: PROVIDER_ID,
2440
+ api: { id: opts.id, url: "", npm: NPM },
2441
+ name: opts.name,
2442
+ family: opts.family,
2443
+ capabilities: { ...baseCapabilities, reasoning: opts.reasoning },
2444
+ cost: {
2445
+ input: opts.cost.input,
2446
+ output: opts.cost.output,
2447
+ cache: { read: opts.cost.cacheRead, write: opts.cost.cacheWrite }
2448
+ },
2449
+ limit: { context: opts.context, output: opts.output },
2450
+ status: opts.status ?? "active",
2451
+ options: {},
2452
+ headers: {},
2453
+ release_date: opts.releaseDate,
2454
+ variants: opts.reasoning ? reasoningVariants : void 0
2455
+ };
2456
+ }
2457
+ var haikuCost = { input: 1e-6, output: 5e-6, cacheRead: 1e-7, cacheWrite: 125e-8 };
2458
+ var sonnetCost = { input: 3e-6, output: 15e-6, cacheRead: 3e-7, cacheWrite: 375e-8 };
2459
+ var opusCost = { input: 15e-6, output: 75e-6, cacheRead: 15e-7, cacheWrite: 1875e-8 };
2460
+ var defaultModels = {
2461
+ "claude-haiku-4-5": defineModel({
2462
+ id: "claude-haiku-4-5",
2463
+ name: "Claude Code Haiku 4.5",
2464
+ family: "haiku",
2465
+ reasoning: false,
2466
+ context: 2e5,
2467
+ output: 8192,
2468
+ cost: haikuCost,
2469
+ releaseDate: "2024-10-22"
2470
+ }),
2471
+ "claude-sonnet-4-5": defineModel({
2472
+ id: "claude-sonnet-4-5",
2473
+ name: "Claude Code Sonnet 4.5",
2474
+ family: "sonnet",
2475
+ reasoning: true,
2476
+ context: 1e6,
2477
+ output: 16384,
2478
+ cost: sonnetCost,
2479
+ releaseDate: "2025-04-14"
2480
+ }),
2481
+ "claude-sonnet-4-6": defineModel({
2482
+ id: "claude-sonnet-4-6",
2483
+ name: "Claude Code Sonnet 4.6",
2484
+ family: "sonnet",
2485
+ reasoning: true,
2486
+ context: 1e6,
2487
+ output: 16384,
2488
+ cost: sonnetCost,
2489
+ releaseDate: "2025-06-19"
2490
+ }),
2491
+ "claude-opus-4-5": defineModel({
2492
+ id: "claude-opus-4-5",
2493
+ name: "Claude Code Opus 4.5",
2494
+ family: "opus",
2495
+ reasoning: true,
2496
+ context: 1e6,
2497
+ output: 16384,
2498
+ cost: opusCost,
2499
+ releaseDate: "2025-04-14"
2500
+ }),
2501
+ "claude-opus-4-6": defineModel({
2502
+ id: "claude-opus-4-6",
2503
+ name: "Claude Code Opus 4.6",
2504
+ family: "opus",
2505
+ reasoning: true,
2506
+ context: 1e6,
2507
+ output: 16384,
2508
+ cost: opusCost,
2509
+ releaseDate: "2025-06-19"
2510
+ }),
2511
+ "claude-opus-4-7": defineModel({
2512
+ id: "claude-opus-4-7",
2513
+ name: "Claude Code Opus 4.7",
2514
+ family: "opus",
2515
+ reasoning: true,
2516
+ context: 1e6,
2517
+ output: 16384,
2518
+ cost: opusCost,
2519
+ releaseDate: "2025-07-16"
2520
+ })
2521
+ };
2522
+
2523
+ // src/index.ts
2524
+ function createClaudeCode(settings = {}) {
2525
+ const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
2526
+ const providerName = settings.name ?? "claude-code";
2527
+ const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
2528
+ const createModel = (modelId) => {
2529
+ return new ClaudeCodeLanguageModel(modelId, {
2530
+ provider: providerName,
2531
+ cliPath,
2532
+ cwd: settings.cwd,
2533
+ skipPermissions: settings.skipPermissions ?? true,
2534
+ permissionMode: settings.permissionMode,
2535
+ mcpConfig: settings.mcpConfig,
2536
+ strictMcpConfig: settings.strictMcpConfig,
2537
+ bridgeOpencodeMcp: settings.bridgeOpencodeMcp ?? true,
2538
+ controlRequestBehavior: settings.controlRequestBehavior ?? "allow",
2539
+ controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
2540
+ controlRequestDenyMessage: settings.controlRequestDenyMessage,
2541
+ proxyTools
2542
+ });
2543
+ };
2544
+ const provider = function(modelId) {
2545
+ return createModel(modelId);
2546
+ };
2547
+ provider.specificationVersion = "v3";
2548
+ provider.languageModel = createModel;
2549
+ return provider;
2550
+ }
2551
+ var PROVIDER_ID2 = "claude-code";
2552
+ var PACKAGE_NPM = "@khalilgharbaoui/opencode-claude-code-plugin";
2553
+ function pluginEntrypoint() {
2554
+ return import.meta.url.startsWith("file:") ? import.meta.url : PACKAGE_NPM;
2555
+ }
2556
+ function mergeDefaultVariants(models = {}) {
2557
+ const result = { ...models };
2558
+ for (const [id, model] of Object.entries(defaultModels)) {
2559
+ if (!model.variants) continue;
2560
+ const existing = result[id] && typeof result[id] === "object" ? result[id] : {};
2561
+ const variants = existing.variants && typeof existing.variants === "object" ? existing.variants : {};
2562
+ result[id] = {
2563
+ ...existing,
2564
+ variants: {
2565
+ ...model.variants,
2566
+ ...variants
2567
+ }
2568
+ };
2569
+ }
2570
+ return result;
2571
+ }
2572
+ function defaultModelsForProvider(providerModels) {
2573
+ const models = Object.fromEntries(
2574
+ Object.entries(defaultModels).map(([id, model]) => {
2575
+ const existing = providerModels[id];
2576
+ return [
2577
+ id,
2578
+ {
2579
+ ...model,
2580
+ api: {
2581
+ ...model.api,
2582
+ npm: existing?.api?.npm ?? model.api.npm,
2583
+ url: existing?.api?.url ?? model.api.url
2584
+ }
2585
+ }
2586
+ ];
2587
+ })
2588
+ );
2589
+ for (const [id, model] of Object.entries(providerModels)) {
2590
+ if (!(id in models)) models[id] = model;
2591
+ }
2592
+ return models;
2593
+ }
2594
+ function providerConfig(existing) {
2595
+ return {
2596
+ name: existing?.name,
2597
+ npm: existing?.npm ?? pluginEntrypoint(),
2598
+ options: {
2599
+ cliPath: "claude",
2600
+ proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
2601
+ ...existing?.options ?? {}
2602
+ },
2603
+ models: mergeDefaultVariants(existing?.models)
2604
+ };
2605
+ }
2606
+ var server = async () => ({
2607
+ config: async (config) => {
2608
+ config.provider ??= {};
2609
+ const existing = config.provider[PROVIDER_ID2];
2610
+ config.provider[PROVIDER_ID2] = {
2611
+ ...existing,
2612
+ ...providerConfig(existing)
2613
+ };
2614
+ },
2615
+ provider: {
2616
+ id: PROVIDER_ID2,
2617
+ models: async (provider) => defaultModelsForProvider(provider.models)
2618
+ }
2619
+ });
2620
+ var index_default = {
2621
+ id: "@khalilgharbaoui/opencode-claude-code-plugin",
2622
+ server
2623
+ };
2624
+ export {
2625
+ ClaudeCodeLanguageModel,
2626
+ bridgeOpencodeMcp,
2627
+ createClaudeCode,
2628
+ index_default as default,
2629
+ defaultModels
2630
+ };
2631
+ //# sourceMappingURL=index.js.map