@khalilgharbaoui/opencode-claude-code-plugin 0.1.6 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/index.d.ts +78 -9
- package/dist/index.js +469 -149
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -102,7 +102,7 @@ var CLAUDE_INTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
|
102
102
|
"Agent",
|
|
103
103
|
"AskFollowupQuestion"
|
|
104
104
|
]);
|
|
105
|
-
function mapTool(name, input) {
|
|
105
|
+
function mapTool(name, input, opts) {
|
|
106
106
|
if (CLAUDE_INTERNAL_TOOLS.has(name)) {
|
|
107
107
|
log.debug("skipping Claude CLI internal tool", { name });
|
|
108
108
|
return { name, input, executed: true, skip: true };
|
|
@@ -115,8 +115,13 @@ function mapTool(name, input) {
|
|
|
115
115
|
}
|
|
116
116
|
if (name === "WebSearch" || name === "web_search") {
|
|
117
117
|
const mappedInput = input?.query ? { query: input.query } : input;
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
const route = opts?.webSearch;
|
|
119
|
+
if (route && route !== "claude" && route !== "disabled") {
|
|
120
|
+
log.debug("routing WebSearch to opencode tool", { target: route, mappedInput });
|
|
121
|
+
return { name: route, input: mappedInput, executed: false };
|
|
122
|
+
}
|
|
123
|
+
log.debug("WebSearch executed by Claude CLI", { mappedInput });
|
|
124
|
+
return { name: "WebSearch", input: mappedInput, executed: true };
|
|
120
125
|
}
|
|
121
126
|
if (name === "TaskOutput") {
|
|
122
127
|
if (!input) return { name: "bash", executed: false };
|
|
@@ -377,7 +382,8 @@ import * as fs from "fs";
|
|
|
377
382
|
import * as path from "path";
|
|
378
383
|
import * as os from "os";
|
|
379
384
|
import * as crypto from "crypto";
|
|
380
|
-
var
|
|
385
|
+
var FILE_NAMES = ["opencode.jsonc", "opencode.json", "config.json"];
|
|
386
|
+
var PROJECT_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
381
387
|
function fileExists(p) {
|
|
382
388
|
try {
|
|
383
389
|
return fs.statSync(p).isFile();
|
|
@@ -385,35 +391,12 @@ function fileExists(p) {
|
|
|
385
391
|
return false;
|
|
386
392
|
}
|
|
387
393
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
394
|
+
function dirExists(p) {
|
|
395
|
+
try {
|
|
396
|
+
return fs.statSync(p).isDirectory();
|
|
397
|
+
} catch {
|
|
398
|
+
return false;
|
|
392
399
|
}
|
|
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
400
|
}
|
|
418
401
|
function stripJsonComments(text) {
|
|
419
402
|
let out = "";
|
|
@@ -454,26 +437,120 @@ function stripJsonComments(text) {
|
|
|
454
437
|
}
|
|
455
438
|
return out;
|
|
456
439
|
}
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
440
|
+
function readAndParse(file) {
|
|
441
|
+
try {
|
|
442
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
443
|
+
return JSON.parse(stripJsonComments(raw));
|
|
444
|
+
} catch (e) {
|
|
445
|
+
log.warn("failed to parse opencode config", {
|
|
446
|
+
file,
|
|
447
|
+
error: e instanceof Error ? e.message : String(e)
|
|
448
|
+
});
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function isPlainObject(x) {
|
|
453
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
454
|
+
}
|
|
455
|
+
function deepMerge(target, source) {
|
|
456
|
+
const out = { ...target };
|
|
457
|
+
for (const [k, v] of Object.entries(source)) {
|
|
458
|
+
if (v === void 0) continue;
|
|
459
|
+
const existing = out[k];
|
|
460
|
+
if (isPlainObject(existing) && isPlainObject(v)) {
|
|
461
|
+
out[k] = deepMerge(existing, v);
|
|
462
|
+
} else {
|
|
463
|
+
out[k] = v;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
}
|
|
468
|
+
function walkUp(opts) {
|
|
469
|
+
const out = [];
|
|
470
|
+
let current = path.resolve(opts.start);
|
|
471
|
+
while (true) {
|
|
472
|
+
for (const target of opts.targets) {
|
|
473
|
+
const candidate = path.join(current, target);
|
|
474
|
+
if (opts.predicate(candidate)) out.push(candidate);
|
|
475
|
+
}
|
|
476
|
+
if (opts.stop && current === path.resolve(opts.stop)) break;
|
|
477
|
+
const parent = path.dirname(current);
|
|
478
|
+
if (parent === current) break;
|
|
479
|
+
current = parent;
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
function detectWorktree(cwd) {
|
|
484
|
+
const override = process.env.OPENCODE_WORKTREE;
|
|
485
|
+
if (override) return path.resolve(override);
|
|
486
|
+
let current = path.resolve(cwd);
|
|
487
|
+
while (true) {
|
|
488
|
+
const gitPath = path.join(current, ".git");
|
|
489
|
+
try {
|
|
490
|
+
if (fs.existsSync(gitPath)) return current;
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
const parent = path.dirname(current);
|
|
494
|
+
if (parent === current) return void 0;
|
|
495
|
+
current = parent;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function globalConfigDir() {
|
|
499
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
500
|
+
return path.join(xdg, "opencode");
|
|
501
|
+
}
|
|
502
|
+
function loadGlobalConfig() {
|
|
503
|
+
const dir = globalConfigDir();
|
|
504
|
+
let merged = {};
|
|
505
|
+
for (const name of FILE_NAMES.slice().reverse()) {
|
|
506
|
+
const file = path.join(dir, name);
|
|
507
|
+
if (!fileExists(file)) continue;
|
|
508
|
+
const parsed = readAndParse(file);
|
|
509
|
+
if (parsed) merged = deepMerge(merged, parsed);
|
|
510
|
+
}
|
|
511
|
+
return merged;
|
|
512
|
+
}
|
|
513
|
+
function loadProjectFilesInDir(dir) {
|
|
514
|
+
let merged = {};
|
|
515
|
+
for (const name of PROJECT_FILE_NAMES) {
|
|
516
|
+
const file = path.join(dir, name);
|
|
517
|
+
if (!fileExists(file)) continue;
|
|
518
|
+
const parsed = readAndParse(file);
|
|
519
|
+
if (parsed) merged = deepMerge(merged, parsed);
|
|
520
|
+
}
|
|
521
|
+
return merged;
|
|
522
|
+
}
|
|
523
|
+
function dotOpencodeDirs(cwd, worktree) {
|
|
524
|
+
const dirs = [];
|
|
525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
526
|
+
const push = (p) => {
|
|
527
|
+
const abs = path.resolve(p);
|
|
528
|
+
if (!seen.has(abs) && dirExists(abs)) {
|
|
529
|
+
seen.add(abs);
|
|
530
|
+
dirs.push(abs);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
for (const dir of walkUp({
|
|
534
|
+
start: cwd,
|
|
535
|
+
stop: worktree,
|
|
536
|
+
targets: [".opencode"],
|
|
537
|
+
predicate: dirExists
|
|
538
|
+
})) {
|
|
539
|
+
push(dir);
|
|
540
|
+
}
|
|
541
|
+
const home = os.homedir();
|
|
542
|
+
if (home) {
|
|
543
|
+
const homeDot = path.join(home, ".opencode");
|
|
544
|
+
if (dirExists(homeDot)) push(homeDot);
|
|
545
|
+
}
|
|
546
|
+
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
547
|
+
if (envDir && dirExists(envDir)) push(envDir);
|
|
548
|
+
return dirs;
|
|
472
549
|
}
|
|
473
550
|
function translateServer(name, spec) {
|
|
474
|
-
if (!spec || typeof spec !== "object") return null;
|
|
475
551
|
if (spec.enabled === false) return null;
|
|
476
|
-
|
|
552
|
+
const type = spec.type;
|
|
553
|
+
if (type === "local") {
|
|
477
554
|
const cmd = spec.command;
|
|
478
555
|
if (!Array.isArray(cmd) || cmd.length === 0) {
|
|
479
556
|
log.warn("skipping local MCP server with no command", { name });
|
|
@@ -489,8 +566,8 @@ function translateServer(name, spec) {
|
|
|
489
566
|
}
|
|
490
567
|
return out;
|
|
491
568
|
}
|
|
492
|
-
if (
|
|
493
|
-
if (
|
|
569
|
+
if (type === "remote") {
|
|
570
|
+
if (typeof spec.url !== "string" || !spec.url) {
|
|
494
571
|
log.warn("skipping remote MCP server with no url", { name });
|
|
495
572
|
return null;
|
|
496
573
|
}
|
|
@@ -505,36 +582,73 @@ function translateServer(name, spec) {
|
|
|
505
582
|
}
|
|
506
583
|
log.warn("skipping MCP server with unknown type", {
|
|
507
584
|
name,
|
|
508
|
-
type:
|
|
585
|
+
type: type ?? null
|
|
509
586
|
});
|
|
510
587
|
return null;
|
|
511
588
|
}
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
589
|
+
function extractMcpBlock(config) {
|
|
590
|
+
const mcp = config.mcp;
|
|
591
|
+
if (!mcp || typeof mcp !== "object" || Array.isArray(mcp)) return {};
|
|
592
|
+
return mcp;
|
|
593
|
+
}
|
|
594
|
+
function mergeMcp(target, source) {
|
|
595
|
+
const out = { ...target };
|
|
596
|
+
for (const [name, spec] of Object.entries(source)) {
|
|
597
|
+
if (!spec || typeof spec !== "object") continue;
|
|
598
|
+
const existing = out[name];
|
|
599
|
+
if (existing && typeof existing === "object") {
|
|
600
|
+
out[name] = deepMerge(
|
|
601
|
+
existing,
|
|
602
|
+
spec
|
|
603
|
+
);
|
|
604
|
+
} else {
|
|
605
|
+
out[name] = spec;
|
|
606
|
+
}
|
|
522
607
|
}
|
|
608
|
+
return out;
|
|
523
609
|
}
|
|
524
|
-
function bridgeOpencodeMcp(cwd) {
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
|
|
610
|
+
function bridgeOpencodeMcp(cwd, runtimeStatus) {
|
|
611
|
+
const worktree = detectWorktree(cwd);
|
|
612
|
+
let merged = {};
|
|
613
|
+
merged = mergeMcp(merged, extractMcpBlock(loadGlobalConfig()));
|
|
614
|
+
const explicitConfig = process.env.OPENCODE_CONFIG;
|
|
615
|
+
if (explicitConfig && fileExists(explicitConfig)) {
|
|
616
|
+
const parsed = readAndParse(explicitConfig);
|
|
617
|
+
if (parsed) merged = mergeMcp(merged, extractMcpBlock(parsed));
|
|
618
|
+
}
|
|
619
|
+
const projectFiles = walkUp({
|
|
620
|
+
start: cwd,
|
|
621
|
+
stop: worktree,
|
|
622
|
+
targets: PROJECT_FILE_NAMES,
|
|
623
|
+
predicate: fileExists
|
|
624
|
+
});
|
|
625
|
+
const projectDirs = [];
|
|
626
|
+
const seenProjectDirs = /* @__PURE__ */ new Set();
|
|
627
|
+
for (const f of projectFiles) {
|
|
628
|
+
const d = path.dirname(f);
|
|
629
|
+
if (!seenProjectDirs.has(d)) {
|
|
630
|
+
seenProjectDirs.add(d);
|
|
631
|
+
projectDirs.push(d);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
for (const dir of projectDirs.slice().reverse()) {
|
|
635
|
+
merged = mergeMcp(merged, extractMcpBlock(loadProjectFilesInDir(dir)));
|
|
636
|
+
}
|
|
637
|
+
for (const dir of dotOpencodeDirs(cwd, worktree)) {
|
|
638
|
+
merged = mergeMcp(merged, extractMcpBlock(loadProjectFilesInDir(dir)));
|
|
639
|
+
}
|
|
640
|
+
if (runtimeStatus) {
|
|
641
|
+
for (const name of Object.keys(merged)) {
|
|
642
|
+
const status = runtimeStatus[name];
|
|
643
|
+
if (status === void 0) continue;
|
|
644
|
+
const existing = merged[name];
|
|
645
|
+
const base = existing && typeof existing === "object" ? existing : {};
|
|
646
|
+
merged[name] = { ...base, enabled: status === "connected" };
|
|
534
647
|
}
|
|
535
648
|
}
|
|
536
649
|
const servers = {};
|
|
537
650
|
for (const [name, spec] of Object.entries(merged)) {
|
|
651
|
+
if (!spec || typeof spec !== "object") continue;
|
|
538
652
|
const translated = translateServer(name, spec);
|
|
539
653
|
if (translated) servers[name] = translated;
|
|
540
654
|
}
|
|
@@ -556,11 +670,41 @@ function bridgeOpencodeMcp(cwd) {
|
|
|
556
670
|
return null;
|
|
557
671
|
}
|
|
558
672
|
log.info("bridged opencode MCP config", {
|
|
559
|
-
sources: files,
|
|
560
673
|
target: outPath,
|
|
674
|
+
hash,
|
|
561
675
|
servers: Object.keys(servers)
|
|
562
676
|
});
|
|
563
|
-
return outPath;
|
|
677
|
+
return { path: outPath, hash };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/runtime-status.ts
|
|
681
|
+
var opencodeClient = null;
|
|
682
|
+
function setOpencodeClient(client) {
|
|
683
|
+
if (client && typeof client === "object") {
|
|
684
|
+
opencodeClient = client;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function getRuntimeMcpStatus() {
|
|
688
|
+
const client = opencodeClient;
|
|
689
|
+
if (!client?.mcp?.status) return void 0;
|
|
690
|
+
try {
|
|
691
|
+
const res = await client.mcp.status();
|
|
692
|
+
const data = res.data;
|
|
693
|
+
if (!data || typeof data !== "object") return void 0;
|
|
694
|
+
const out = {};
|
|
695
|
+
for (const [name, entry] of Object.entries(data)) {
|
|
696
|
+
if (entry && typeof entry === "object") {
|
|
697
|
+
const status = entry.status;
|
|
698
|
+
if (typeof status === "string") out[name] = status;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return out;
|
|
702
|
+
} catch (err) {
|
|
703
|
+
log.warn("failed to fetch opencode MCP runtime status", {
|
|
704
|
+
error: err instanceof Error ? err.message : String(err)
|
|
705
|
+
});
|
|
706
|
+
return void 0;
|
|
707
|
+
}
|
|
564
708
|
}
|
|
565
709
|
|
|
566
710
|
// src/session-manager.ts
|
|
@@ -607,7 +751,7 @@ function setClaudeSessionId(key, sessionId) {
|
|
|
607
751
|
function deleteClaudeSessionId(key) {
|
|
608
752
|
claudeSessions.delete(key);
|
|
609
753
|
}
|
|
610
|
-
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer) {
|
|
754
|
+
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash) {
|
|
611
755
|
evictIfNeeded();
|
|
612
756
|
log.info("spawning new claude process", { cliPath, cliArgs, cwd, sessionKey: sessionKey2 });
|
|
613
757
|
const proc = spawn(cliPath, cliArgs, {
|
|
@@ -624,8 +768,16 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer) {
|
|
|
624
768
|
rl.on("close", () => {
|
|
625
769
|
lineEmitter.emit("close");
|
|
626
770
|
});
|
|
627
|
-
const ap = {
|
|
771
|
+
const ap = {
|
|
772
|
+
proc,
|
|
773
|
+
lineEmitter,
|
|
774
|
+
proxyServer: proxyServer ?? null,
|
|
775
|
+
mcpHash
|
|
776
|
+
};
|
|
628
777
|
activeProcesses.set(sessionKey2, ap);
|
|
778
|
+
proc.on("error", (err) => {
|
|
779
|
+
log.error("claude process error", { sessionKey: sessionKey2, error: err.message });
|
|
780
|
+
});
|
|
629
781
|
proc.on("exit", (code, signal) => {
|
|
630
782
|
log.info("claude process exited", { code, signal, sessionKey: sessionKey2 });
|
|
631
783
|
void proxyServer?.close();
|
|
@@ -884,12 +1036,12 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
884
1036
|
hasInput: input != null
|
|
885
1037
|
});
|
|
886
1038
|
const result = await new Promise(
|
|
887
|
-
(
|
|
1039
|
+
(resolve3, reject) => {
|
|
888
1040
|
const entry = {
|
|
889
1041
|
id: callId,
|
|
890
1042
|
toolName,
|
|
891
1043
|
input,
|
|
892
|
-
resolve:
|
|
1044
|
+
resolve: resolve3,
|
|
893
1045
|
reject
|
|
894
1046
|
};
|
|
895
1047
|
pending.set(callId, entry);
|
|
@@ -946,11 +1098,11 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
946
1098
|
}
|
|
947
1099
|
}
|
|
948
1100
|
});
|
|
949
|
-
await new Promise((
|
|
1101
|
+
await new Promise((resolve3, reject) => {
|
|
950
1102
|
server2.once("error", reject);
|
|
951
1103
|
server2.listen(0, "127.0.0.1", () => {
|
|
952
1104
|
server2.off("error", reject);
|
|
953
|
-
|
|
1105
|
+
resolve3();
|
|
954
1106
|
});
|
|
955
1107
|
});
|
|
956
1108
|
const addr = server2.address();
|
|
@@ -997,8 +1149,8 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
997
1149
|
entry.reject(new Error("proxy MCP server closed"));
|
|
998
1150
|
}
|
|
999
1151
|
pending.clear();
|
|
1000
|
-
await new Promise((
|
|
1001
|
-
server2.close(() =>
|
|
1152
|
+
await new Promise((resolve3) => {
|
|
1153
|
+
server2.close(() => resolve3());
|
|
1002
1154
|
});
|
|
1003
1155
|
}
|
|
1004
1156
|
};
|
|
@@ -1022,10 +1174,10 @@ function disallowedToolFlags(tools) {
|
|
|
1022
1174
|
return out;
|
|
1023
1175
|
}
|
|
1024
1176
|
function readBody(req) {
|
|
1025
|
-
return new Promise((
|
|
1177
|
+
return new Promise((resolve3, reject) => {
|
|
1026
1178
|
const chunks = [];
|
|
1027
1179
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
1028
|
-
req.on("end", () =>
|
|
1180
|
+
req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
1029
1181
|
req.on("error", reject);
|
|
1030
1182
|
});
|
|
1031
1183
|
}
|
|
@@ -1139,18 +1291,27 @@ var ClaudeCodeLanguageModel = class {
|
|
|
1139
1291
|
return "no-tools";
|
|
1140
1292
|
}
|
|
1141
1293
|
/**
|
|
1142
|
-
* Build the combined `--mcp-config` list
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1294
|
+
* Build the combined `--mcp-config` list and return both the list and the
|
|
1295
|
+
* hash of the bridged opencode MCP block (or null when bridging is off /
|
|
1296
|
+
* yields nothing). The hash is used to detect mid-session config changes
|
|
1297
|
+
* and respawn the underlying claude process.
|
|
1298
|
+
*
|
|
1299
|
+
* `runtimeStatus` is a snapshot of opencode's `client.mcp.status()`. When
|
|
1300
|
+
* provided it overlays opencode's UI-toggled state on top of disk config
|
|
1301
|
+
* so `/mcps` toggles propagate without a config file write.
|
|
1145
1302
|
*/
|
|
1146
|
-
effectiveMcpConfig(cwd, proxyConfigPath) {
|
|
1147
|
-
const
|
|
1303
|
+
effectiveMcpConfig(cwd, proxyConfigPath, runtimeStatus) {
|
|
1304
|
+
const paths = Array.isArray(this.config.mcpConfig) ? this.config.mcpConfig.slice() : this.config.mcpConfig ? [this.config.mcpConfig] : [];
|
|
1305
|
+
let bridgedHash = null;
|
|
1148
1306
|
if (this.config.bridgeOpencodeMcp !== false) {
|
|
1149
|
-
const bridged = bridgeOpencodeMcp(cwd);
|
|
1150
|
-
if (bridged)
|
|
1307
|
+
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus);
|
|
1308
|
+
if (bridged) {
|
|
1309
|
+
paths.push(bridged.path);
|
|
1310
|
+
bridgedHash = bridged.hash;
|
|
1311
|
+
}
|
|
1151
1312
|
}
|
|
1152
|
-
if (proxyConfigPath)
|
|
1153
|
-
return
|
|
1313
|
+
if (proxyConfigPath) paths.push(proxyConfigPath);
|
|
1314
|
+
return { paths, bridgedHash };
|
|
1154
1315
|
}
|
|
1155
1316
|
/** Resolve ProxyToolDef[] for the configured proxyTools names. */
|
|
1156
1317
|
resolvedProxyTools() {
|
|
@@ -1484,14 +1645,16 @@ var ClaudeCodeLanguageModel = class {
|
|
|
1484
1645
|
includeHistoryContext,
|
|
1485
1646
|
reasoningEffort
|
|
1486
1647
|
);
|
|
1648
|
+
const runtimeStatus = await getRuntimeMcpStatus();
|
|
1487
1649
|
const cliArgs = buildCliArgs({
|
|
1488
1650
|
sessionKey: sk,
|
|
1489
1651
|
skipPermissions: this.config.skipPermissions !== false,
|
|
1490
1652
|
includeSessionId: false,
|
|
1491
1653
|
model: this.modelId,
|
|
1492
1654
|
permissionMode: this.config.permissionMode,
|
|
1493
|
-
mcpConfig: this.effectiveMcpConfig(cwd),
|
|
1494
|
-
strictMcpConfig: this.config.strictMcpConfig
|
|
1655
|
+
mcpConfig: this.effectiveMcpConfig(cwd, void 0, runtimeStatus).paths,
|
|
1656
|
+
strictMcpConfig: this.config.strictMcpConfig,
|
|
1657
|
+
disallowedTools: this.config.webSearch === "disabled" ? ["WebSearch"] : void 0
|
|
1495
1658
|
});
|
|
1496
1659
|
log.info("doGenerate starting", {
|
|
1497
1660
|
cwd,
|
|
@@ -1512,7 +1675,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
1512
1675
|
let thinkingText = "";
|
|
1513
1676
|
let resultMeta = {};
|
|
1514
1677
|
const toolCalls = [];
|
|
1515
|
-
const result = await new Promise((
|
|
1678
|
+
const result = await new Promise((resolve3, reject) => {
|
|
1516
1679
|
rl.on("line", (line) => {
|
|
1517
1680
|
if (!line.trim()) return;
|
|
1518
1681
|
try {
|
|
@@ -1603,7 +1766,7 @@ ${plan}
|
|
|
1603
1766
|
durationMs: msg.duration_ms,
|
|
1604
1767
|
usage: msg.usage
|
|
1605
1768
|
};
|
|
1606
|
-
|
|
1769
|
+
resolve3({
|
|
1607
1770
|
...resultMeta,
|
|
1608
1771
|
text: responseText,
|
|
1609
1772
|
thinking: thinkingText,
|
|
@@ -1614,7 +1777,7 @@ ${plan}
|
|
|
1614
1777
|
}
|
|
1615
1778
|
});
|
|
1616
1779
|
rl.on("close", () => {
|
|
1617
|
-
|
|
1780
|
+
resolve3({
|
|
1618
1781
|
...resultMeta,
|
|
1619
1782
|
text: responseText,
|
|
1620
1783
|
thinking: thinkingText,
|
|
@@ -1661,7 +1824,7 @@ ${plan}
|
|
|
1661
1824
|
input: mappedInput,
|
|
1662
1825
|
executed,
|
|
1663
1826
|
skip
|
|
1664
|
-
} = mapTool(tc.name, tc.args);
|
|
1827
|
+
} = mapTool(tc.name, tc.args, { webSearch: this.config.webSearch });
|
|
1665
1828
|
if (skip) continue;
|
|
1666
1829
|
content.push({
|
|
1667
1830
|
type: "tool-call",
|
|
@@ -1762,6 +1925,7 @@ ${plan}
|
|
|
1762
1925
|
const self = this;
|
|
1763
1926
|
const pendingProxyCall = getPendingProxyCall(sk);
|
|
1764
1927
|
const pendingProxyResult = pendingProxyCall ? this.extractPendingProxyResult(options.prompt, pendingProxyCall.toolCallId) : null;
|
|
1928
|
+
const runtimeStatus = await getRuntimeMcpStatus();
|
|
1765
1929
|
log.info("doStream starting", {
|
|
1766
1930
|
cwd,
|
|
1767
1931
|
model: this.modelId,
|
|
@@ -1777,25 +1941,55 @@ ${plan}
|
|
|
1777
1941
|
let proc;
|
|
1778
1942
|
let lineEmitter;
|
|
1779
1943
|
let proxyServer = activeProcess?.proxyServer ?? null;
|
|
1944
|
+
if (activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
1945
|
+
const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
1946
|
+
const previousHash = activeProcess.mcpHash ?? null;
|
|
1947
|
+
if (previousHash !== probe.bridgedHash) {
|
|
1948
|
+
log.info("opencode MCP config changed, respawning claude", {
|
|
1949
|
+
sk,
|
|
1950
|
+
previousHash,
|
|
1951
|
+
currentHash: probe.bridgedHash
|
|
1952
|
+
});
|
|
1953
|
+
deleteActiveProcess(sk);
|
|
1954
|
+
activeProcess = void 0;
|
|
1955
|
+
proxyServer = null;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1780
1958
|
const setup = async () => {
|
|
1781
1959
|
if (!proxyServer && resolvedProxy) {
|
|
1782
1960
|
proxyServer = await self.ensureProxyServer(resolvedProxy, sk);
|
|
1783
1961
|
}
|
|
1962
|
+
const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
|
|
1963
|
+
const extraDisallowed = [];
|
|
1964
|
+
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
1965
|
+
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
1966
|
+
const mcp = self.effectiveMcpConfig(
|
|
1967
|
+
cwd,
|
|
1968
|
+
proxyServer?.configPath(),
|
|
1969
|
+
runtimeStatus
|
|
1970
|
+
);
|
|
1784
1971
|
const cliArgs = buildCliArgs({
|
|
1785
1972
|
sessionKey: sk,
|
|
1786
1973
|
skipPermissions,
|
|
1787
1974
|
model: self.modelId,
|
|
1788
1975
|
permissionMode: self.config.permissionMode,
|
|
1789
|
-
mcpConfig:
|
|
1976
|
+
mcpConfig: mcp.paths,
|
|
1790
1977
|
strictMcpConfig: self.config.strictMcpConfig,
|
|
1791
|
-
disallowedTools:
|
|
1978
|
+
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0
|
|
1792
1979
|
});
|
|
1793
1980
|
if (activeProcess) {
|
|
1794
1981
|
proc = activeProcess.proc;
|
|
1795
1982
|
lineEmitter = activeProcess.lineEmitter;
|
|
1796
1983
|
log.debug("reusing active process", { sk });
|
|
1797
1984
|
} else {
|
|
1798
|
-
const ap = spawnClaudeProcess(
|
|
1985
|
+
const ap = spawnClaudeProcess(
|
|
1986
|
+
cliPath,
|
|
1987
|
+
cliArgs,
|
|
1988
|
+
cwd,
|
|
1989
|
+
sk,
|
|
1990
|
+
proxyServer,
|
|
1991
|
+
mcp.bridgedHash
|
|
1992
|
+
);
|
|
1799
1993
|
proc = ap.proc;
|
|
1800
1994
|
lineEmitter = ap.lineEmitter;
|
|
1801
1995
|
activeProcess = ap;
|
|
@@ -1868,10 +2062,7 @@ ${plan}
|
|
|
1868
2062
|
}
|
|
1869
2063
|
});
|
|
1870
2064
|
controllerClosed = true;
|
|
1871
|
-
|
|
1872
|
-
lineEmitter.off("close", closeHandler);
|
|
1873
|
-
pendingProxyUnsubscribe?.();
|
|
1874
|
-
pendingProxyUnsubscribe = null;
|
|
2065
|
+
cleanupTurn();
|
|
1875
2066
|
try {
|
|
1876
2067
|
controller.close();
|
|
1877
2068
|
} catch {
|
|
@@ -1930,7 +2121,11 @@ ${plan}
|
|
|
1930
2121
|
inputJson: ""
|
|
1931
2122
|
});
|
|
1932
2123
|
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(
|
|
2124
|
+
const { name: mappedName, skip, executed } = mapTool(
|
|
2125
|
+
block.name,
|
|
2126
|
+
void 0,
|
|
2127
|
+
{ webSearch: self.config.webSearch }
|
|
2128
|
+
);
|
|
1934
2129
|
if (!skip) {
|
|
1935
2130
|
controller.enqueue({
|
|
1936
2131
|
type: "tool-input-start",
|
|
@@ -2047,7 +2242,7 @@ ${plan}
|
|
|
2047
2242
|
input: mappedInput,
|
|
2048
2243
|
executed,
|
|
2049
2244
|
skip
|
|
2050
|
-
} = mapTool(tc.name, parsedInput);
|
|
2245
|
+
} = mapTool(tc.name, parsedInput, { webSearch: self.config.webSearch });
|
|
2051
2246
|
if (!skip) {
|
|
2052
2247
|
toolCallsById.set(tc.id, {
|
|
2053
2248
|
id: tc.id,
|
|
@@ -2167,7 +2362,7 @@ ${plan}
|
|
|
2167
2362
|
input: mappedInput,
|
|
2168
2363
|
executed,
|
|
2169
2364
|
skip
|
|
2170
|
-
} = mapTool(block.name, parsedInput);
|
|
2365
|
+
} = mapTool(block.name, parsedInput, { webSearch: self.config.webSearch });
|
|
2171
2366
|
if (!skip) {
|
|
2172
2367
|
if (!executed) skipResultForIds.add(block.id);
|
|
2173
2368
|
controller.enqueue({
|
|
@@ -2287,8 +2482,7 @@ ${plan}
|
|
|
2287
2482
|
}
|
|
2288
2483
|
});
|
|
2289
2484
|
controllerClosed = true;
|
|
2290
|
-
|
|
2291
|
-
lineEmitter.off("close", closeHandler);
|
|
2485
|
+
cleanupTurn();
|
|
2292
2486
|
try {
|
|
2293
2487
|
controller.close();
|
|
2294
2488
|
} catch {
|
|
@@ -2303,12 +2497,8 @@ ${plan}
|
|
|
2303
2497
|
const closeHandler = () => {
|
|
2304
2498
|
log.debug("readline closed");
|
|
2305
2499
|
if (controllerClosed) return;
|
|
2306
|
-
clearFallbackTimer();
|
|
2307
2500
|
controllerClosed = true;
|
|
2308
|
-
|
|
2309
|
-
lineEmitter.off("close", closeHandler);
|
|
2310
|
-
pendingProxyUnsubscribe?.();
|
|
2311
|
-
pendingProxyUnsubscribe = null;
|
|
2501
|
+
cleanupTurn();
|
|
2312
2502
|
endTextBlock();
|
|
2313
2503
|
controller.enqueue({
|
|
2314
2504
|
type: "finish",
|
|
@@ -2323,6 +2513,28 @@ ${plan}
|
|
|
2323
2513
|
} catch {
|
|
2324
2514
|
}
|
|
2325
2515
|
};
|
|
2516
|
+
let cleanedUp = false;
|
|
2517
|
+
const cleanupTurn = () => {
|
|
2518
|
+
if (cleanedUp) return;
|
|
2519
|
+
cleanedUp = true;
|
|
2520
|
+
clearFallbackTimer();
|
|
2521
|
+
lineEmitter.off("line", lineHandler);
|
|
2522
|
+
lineEmitter.off("close", closeHandler);
|
|
2523
|
+
pendingProxyUnsubscribe?.();
|
|
2524
|
+
pendingProxyUnsubscribe = null;
|
|
2525
|
+
proc.off("error", procErrorHandler);
|
|
2526
|
+
};
|
|
2527
|
+
const procErrorHandler = (err) => {
|
|
2528
|
+
log.error("process error", { error: err.message });
|
|
2529
|
+
if (controllerClosed) return;
|
|
2530
|
+
controllerClosed = true;
|
|
2531
|
+
cleanupTurn();
|
|
2532
|
+
controller.enqueue({ type: "error", error: err });
|
|
2533
|
+
try {
|
|
2534
|
+
controller.close();
|
|
2535
|
+
} catch {
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2326
2538
|
lineEmitter.on("line", lineHandler);
|
|
2327
2539
|
lineEmitter.on("close", closeHandler);
|
|
2328
2540
|
pendingProxyUnsubscribe = onPendingProxyCall(sk, (call) => {
|
|
@@ -2333,19 +2545,7 @@ ${plan}
|
|
|
2333
2545
|
});
|
|
2334
2546
|
finishWithToolCall(call);
|
|
2335
2547
|
});
|
|
2336
|
-
proc.on("error",
|
|
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
|
-
});
|
|
2548
|
+
proc.on("error", procErrorHandler);
|
|
2349
2549
|
if (options.abortSignal) {
|
|
2350
2550
|
options.abortSignal.addEventListener("abort", () => {
|
|
2351
2551
|
if (turnCompleted || controllerClosed) return;
|
|
@@ -2355,10 +2555,7 @@ ${plan}
|
|
|
2355
2555
|
{ cwd }
|
|
2356
2556
|
);
|
|
2357
2557
|
controllerClosed = true;
|
|
2358
|
-
|
|
2359
|
-
lineEmitter.off("close", closeHandler);
|
|
2360
|
-
pendingProxyUnsubscribe?.();
|
|
2361
|
-
pendingProxyUnsubscribe = null;
|
|
2558
|
+
cleanupTurn();
|
|
2362
2559
|
try {
|
|
2363
2560
|
controller.close();
|
|
2364
2561
|
} catch {
|
|
@@ -2695,6 +2892,117 @@ function titleizeAccount(account) {
|
|
|
2695
2892
|
return normalizeAccountName(account).split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
2696
2893
|
}
|
|
2697
2894
|
|
|
2895
|
+
// src/cleanup-stale.ts
|
|
2896
|
+
import {
|
|
2897
|
+
existsSync as existsSync2,
|
|
2898
|
+
readFileSync as readFileSync2,
|
|
2899
|
+
realpathSync,
|
|
2900
|
+
rmSync,
|
|
2901
|
+
writeFileSync as writeFileSync3
|
|
2902
|
+
} from "fs";
|
|
2903
|
+
import { homedir as homedir2 } from "os";
|
|
2904
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
2905
|
+
import { fileURLToPath } from "url";
|
|
2906
|
+
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
2907
|
+
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
2908
|
+
var alreadyRan = false;
|
|
2909
|
+
function candidateCacheRoots() {
|
|
2910
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
2911
|
+
return [
|
|
2912
|
+
xdg ? join3(xdg, "opencode") : null,
|
|
2913
|
+
join3(homedir2(), ".cache", "opencode"),
|
|
2914
|
+
join3(homedir2(), "Library", "Caches", "opencode")
|
|
2915
|
+
].filter((p) => Boolean(p));
|
|
2916
|
+
}
|
|
2917
|
+
function userOpencodeJsonPath() {
|
|
2918
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join3(homedir2(), ".config");
|
|
2919
|
+
return join3(xdgConfig, "opencode", "opencode.json");
|
|
2920
|
+
}
|
|
2921
|
+
function userIntendsToUseUnscoped() {
|
|
2922
|
+
const cfg = userOpencodeJsonPath();
|
|
2923
|
+
if (!existsSync2(cfg)) return false;
|
|
2924
|
+
try {
|
|
2925
|
+
const json = JSON.parse(readFileSync2(cfg, "utf8"));
|
|
2926
|
+
const plugins = json.plugin;
|
|
2927
|
+
if (!Array.isArray(plugins)) return false;
|
|
2928
|
+
return plugins.some(
|
|
2929
|
+
(entry) => typeof entry === "string" && /^opencode-claude-code-plugin(@[^/]+)?$/.test(entry)
|
|
2930
|
+
);
|
|
2931
|
+
} catch {
|
|
2932
|
+
return false;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
function ourLoadedDir() {
|
|
2936
|
+
try {
|
|
2937
|
+
const filePath = fileURLToPath(import.meta.url);
|
|
2938
|
+
return realpathSync(resolve2(filePath, "..", ".."));
|
|
2939
|
+
} catch {
|
|
2940
|
+
return null;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
function cleanupStaleUnscopedInstall() {
|
|
2944
|
+
if (alreadyRan) return;
|
|
2945
|
+
alreadyRan = true;
|
|
2946
|
+
if (process.env.OPENCODE_CLAUDE_CODE_PLUGIN_NO_CLEANUP === "1") return;
|
|
2947
|
+
if (userIntendsToUseUnscoped()) return;
|
|
2948
|
+
const ourDir = ourLoadedDir();
|
|
2949
|
+
for (const cacheRoot of candidateCacheRoots()) {
|
|
2950
|
+
try {
|
|
2951
|
+
cleanupOne(cacheRoot, ourDir);
|
|
2952
|
+
} catch (err) {
|
|
2953
|
+
log.warn("cleanup-stale: error processing cache root", {
|
|
2954
|
+
cacheRoot,
|
|
2955
|
+
error: String(err)
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
function cleanupOne(cacheRoot, ourDir) {
|
|
2961
|
+
if (!existsSync2(cacheRoot)) return;
|
|
2962
|
+
const stalePath = join3(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
2963
|
+
if (!existsSync2(stalePath)) return;
|
|
2964
|
+
let realStalePath = stalePath;
|
|
2965
|
+
try {
|
|
2966
|
+
realStalePath = realpathSync(stalePath);
|
|
2967
|
+
} catch {
|
|
2968
|
+
}
|
|
2969
|
+
if (ourDir && realStalePath === ourDir) return;
|
|
2970
|
+
const pkgJsonPath = join3(stalePath, "package.json");
|
|
2971
|
+
if (!existsSync2(pkgJsonPath)) return;
|
|
2972
|
+
let pkg = {};
|
|
2973
|
+
try {
|
|
2974
|
+
pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
|
|
2975
|
+
} catch {
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (pkg.name !== STALE_PACKAGE_NAME) return;
|
|
2979
|
+
if (!pkg.description?.includes(SUSPECT_DESCRIPTION_TOKEN)) return;
|
|
2980
|
+
log.info("cleanup-stale: removing unscoped install", { stalePath });
|
|
2981
|
+
try {
|
|
2982
|
+
rmSync(stalePath, { recursive: true, force: true });
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
log.warn("cleanup-stale: rmSync failed", {
|
|
2985
|
+
stalePath,
|
|
2986
|
+
error: String(err)
|
|
2987
|
+
});
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
const cachePkgJson = join3(cacheRoot, "package.json");
|
|
2991
|
+
if (!existsSync2(cachePkgJson)) return;
|
|
2992
|
+
try {
|
|
2993
|
+
const cfg = JSON.parse(readFileSync2(cachePkgJson, "utf8"));
|
|
2994
|
+
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
2995
|
+
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
2996
|
+
writeFileSync3(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
2997
|
+
log.info("cleanup-stale: pruned dep from cache package.json");
|
|
2998
|
+
}
|
|
2999
|
+
} catch (err) {
|
|
3000
|
+
log.warn("cleanup-stale: cache package.json update failed", {
|
|
3001
|
+
error: String(err)
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2698
3006
|
// src/index.ts
|
|
2699
3007
|
function createClaudeCode(settings = {}) {
|
|
2700
3008
|
const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
|
|
@@ -2716,7 +3024,9 @@ function createClaudeCode(settings = {}) {
|
|
|
2716
3024
|
controlRequestBehavior: settings.controlRequestBehavior ?? "allow",
|
|
2717
3025
|
controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
|
|
2718
3026
|
controlRequestDenyMessage: settings.controlRequestDenyMessage,
|
|
2719
|
-
proxyTools
|
|
3027
|
+
proxyTools,
|
|
3028
|
+
webSearch: settings.webSearch,
|
|
3029
|
+
hotReloadMcp: settings.hotReloadMcp ?? true
|
|
2720
3030
|
});
|
|
2721
3031
|
};
|
|
2722
3032
|
const provider = function(modelId) {
|
|
@@ -2872,22 +3182,32 @@ async function expandAccountProviders(config) {
|
|
|
2872
3182
|
}
|
|
2873
3183
|
return expandedCount > 0;
|
|
2874
3184
|
}
|
|
2875
|
-
var server = async () =>
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
if (expanded) return;
|
|
2880
|
-
const existing = config.provider[PROVIDER_ID2];
|
|
2881
|
-
config.provider[PROVIDER_ID2] = {
|
|
2882
|
-
...existing,
|
|
2883
|
-
...await providerConfig(existing)
|
|
2884
|
-
};
|
|
2885
|
-
},
|
|
2886
|
-
provider: {
|
|
2887
|
-
id: PROVIDER_ID2,
|
|
2888
|
-
models: async (provider) => defaultModelsForProvider(provider.models)
|
|
3185
|
+
var server = async (input) => {
|
|
3186
|
+
cleanupStaleUnscopedInstall();
|
|
3187
|
+
if (input && typeof input === "object" && "client" in input) {
|
|
3188
|
+
setOpencodeClient(input.client);
|
|
2889
3189
|
}
|
|
2890
|
-
|
|
3190
|
+
return {
|
|
3191
|
+
config: async (config) => {
|
|
3192
|
+
config.provider ??= {};
|
|
3193
|
+
const expanded = await expandAccountProviders(config);
|
|
3194
|
+
if (expanded) return;
|
|
3195
|
+
const existing = config.provider[PROVIDER_ID2];
|
|
3196
|
+
config.provider[PROVIDER_ID2] = {
|
|
3197
|
+
...existing,
|
|
3198
|
+
...await providerConfig(existing)
|
|
3199
|
+
};
|
|
3200
|
+
},
|
|
3201
|
+
// No `event` hook: MCP config drift is detected at turn start by the
|
|
3202
|
+
// hot-reload check in `claude-code-language-model.ts`, which respawns
|
|
3203
|
+
// claude safely between turns. Eviction on `global.disposed` would kill
|
|
3204
|
+
// an in-flight stream and abort the user's current turn.
|
|
3205
|
+
provider: {
|
|
3206
|
+
id: PROVIDER_ID2,
|
|
3207
|
+
models: async (provider) => defaultModelsForProvider(provider.models)
|
|
3208
|
+
}
|
|
3209
|
+
};
|
|
3210
|
+
};
|
|
2891
3211
|
var index_default = {
|
|
2892
3212
|
id: "@khalilgharbaoui/opencode-claude-code-plugin",
|
|
2893
3213
|
server
|