@khalilgharbaoui/opencode-claude-code-plugin 0.9.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -5
- package/dist/index.d.ts +30 -1
- package/dist/index.js +664 -240
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -733,6 +733,10 @@ import * as fs2 from "fs";
|
|
|
733
733
|
import * as path2 from "path";
|
|
734
734
|
import * as os2 from "os";
|
|
735
735
|
import * as crypto from "crypto";
|
|
736
|
+
import {
|
|
737
|
+
parse as parseJsonc,
|
|
738
|
+
printParseErrorCode
|
|
739
|
+
} from "jsonc-parser";
|
|
736
740
|
|
|
737
741
|
// src/tmp.ts
|
|
738
742
|
import * as fs from "fs";
|
|
@@ -776,49 +780,18 @@ function dirExists(p) {
|
|
|
776
780
|
return false;
|
|
777
781
|
}
|
|
778
782
|
}
|
|
779
|
-
function stripJsonComments(text) {
|
|
780
|
-
let out = "";
|
|
781
|
-
let i = 0;
|
|
782
|
-
let inString = null;
|
|
783
|
-
while (i < text.length) {
|
|
784
|
-
const c = text[i];
|
|
785
|
-
if (inString) {
|
|
786
|
-
out += c;
|
|
787
|
-
if (c === "\\" && i + 1 < text.length) {
|
|
788
|
-
out += text[i + 1];
|
|
789
|
-
i += 2;
|
|
790
|
-
continue;
|
|
791
|
-
}
|
|
792
|
-
if (c === inString) inString = null;
|
|
793
|
-
i++;
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
if (c === '"' || c === "'") {
|
|
797
|
-
inString = c;
|
|
798
|
-
out += c;
|
|
799
|
-
i++;
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
if (c === "/" && text[i + 1] === "/") {
|
|
803
|
-
while (i < text.length && text[i] !== "\n") i++;
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
if (c === "/" && text[i + 1] === "*") {
|
|
807
|
-
i += 2;
|
|
808
|
-
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
809
|
-
i++;
|
|
810
|
-
i += 2;
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
out += c;
|
|
814
|
-
i++;
|
|
815
|
-
}
|
|
816
|
-
return out;
|
|
817
|
-
}
|
|
818
783
|
function readAndParse(file) {
|
|
819
784
|
try {
|
|
820
785
|
const raw = fs2.readFileSync(file, "utf8");
|
|
821
|
-
|
|
786
|
+
const errors = [];
|
|
787
|
+
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
788
|
+
if (errors.length > 0) {
|
|
789
|
+
const first = errors[0];
|
|
790
|
+
throw new Error(
|
|
791
|
+
`${printParseErrorCode(first.error)} at offset ${first.offset}`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
return parsed;
|
|
822
795
|
} catch (e) {
|
|
823
796
|
log.warn("failed to parse opencode config", {
|
|
824
797
|
file,
|
|
@@ -1001,6 +974,31 @@ function mergeMcp(target, source) {
|
|
|
1001
974
|
return out;
|
|
1002
975
|
}
|
|
1003
976
|
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
977
|
+
const {
|
|
978
|
+
servers: merged,
|
|
979
|
+
enabledServerNames: allEnabledServerNames,
|
|
980
|
+
hash
|
|
981
|
+
} = mergeOpencodeMcp(cwd, runtimeStatus);
|
|
982
|
+
const servers = {};
|
|
983
|
+
const bridgedServerNames = [];
|
|
984
|
+
for (const [name, spec] of Object.entries(merged)) {
|
|
985
|
+
if (!spec || typeof spec !== "object") continue;
|
|
986
|
+
if (excludeServers?.has(name)) continue;
|
|
987
|
+
const translated = translateServer(name, spec);
|
|
988
|
+
if (translated) {
|
|
989
|
+
servers[name] = translated;
|
|
990
|
+
bridgedServerNames.push(name);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return finishBridge({
|
|
994
|
+
servers,
|
|
995
|
+
bridgedServerNames,
|
|
996
|
+
allEnabledServerNames,
|
|
997
|
+
hash,
|
|
998
|
+
excludeServers
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
function mergeOpencodeMcp(cwd, runtimeStatus) {
|
|
1004
1002
|
const worktree = detectWorktree(cwd);
|
|
1005
1003
|
let merged = {};
|
|
1006
1004
|
merged = mergeMcp(merged, extractMcpBlock(loadGlobalConfig()));
|
|
@@ -1039,26 +1037,19 @@ function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
|
1039
1037
|
merged[name] = { ...base, enabled: status === "connected" };
|
|
1040
1038
|
}
|
|
1041
1039
|
}
|
|
1042
|
-
const
|
|
1040
|
+
const enabledServerNames = [];
|
|
1043
1041
|
for (const [name, spec] of Object.entries(merged)) {
|
|
1044
1042
|
if (!spec || typeof spec !== "object") continue;
|
|
1045
1043
|
const enabled = spec.enabled;
|
|
1046
1044
|
if (enabled === false) continue;
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
const servers = {};
|
|
1050
|
-
const bridgedServerNames = [];
|
|
1051
|
-
for (const [name, spec] of Object.entries(merged)) {
|
|
1052
|
-
if (!spec || typeof spec !== "object") continue;
|
|
1053
|
-
if (excludeServers?.has(name)) continue;
|
|
1054
|
-
const translated = translateServer(name, spec);
|
|
1055
|
-
if (translated) {
|
|
1056
|
-
servers[name] = translated;
|
|
1057
|
-
bridgedServerNames.push(name);
|
|
1058
|
-
}
|
|
1045
|
+
enabledServerNames.push(name);
|
|
1059
1046
|
}
|
|
1060
1047
|
const mergedBody = JSON.stringify({ mcpServers: merged }, null, 2);
|
|
1061
1048
|
const hash = crypto.createHash("sha256").update(mergedBody).digest("hex").slice(0, 12);
|
|
1049
|
+
return { servers: merged, enabledServerNames, hash };
|
|
1050
|
+
}
|
|
1051
|
+
function finishBridge(input) {
|
|
1052
|
+
const { servers, bridgedServerNames, allEnabledServerNames, hash, excludeServers } = input;
|
|
1062
1053
|
if (Object.keys(servers).length === 0) {
|
|
1063
1054
|
const allEnabledServersExcluded = excludeServers && allEnabledServerNames.length > 0 && allEnabledServerNames.every((name) => excludeServers.has(name));
|
|
1064
1055
|
if (!allEnabledServersExcluded) return null;
|
|
@@ -1109,6 +1100,9 @@ var opencodeProjectDirectory;
|
|
|
1109
1100
|
function setOpencodeProjectDirectory(dir) {
|
|
1110
1101
|
opencodeProjectDirectory = dir;
|
|
1111
1102
|
}
|
|
1103
|
+
function getOpencodeProjectDirectory() {
|
|
1104
|
+
return opencodeProjectDirectory;
|
|
1105
|
+
}
|
|
1112
1106
|
function isUsableDirectory(d) {
|
|
1113
1107
|
return typeof d === "string" && d.length > 1 && d !== "/";
|
|
1114
1108
|
}
|
|
@@ -1243,6 +1237,8 @@ function cliSupportsThinking(v) {
|
|
|
1243
1237
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
1244
1238
|
var claudeSessions = /* @__PURE__ */ new Map();
|
|
1245
1239
|
var MAX_ACTIVE_PROCESSES = 16;
|
|
1240
|
+
var PROCESS_EXIT_TIMEOUT_MS = 1500;
|
|
1241
|
+
var PROCESS_FORCE_EXIT_TIMEOUT_MS = 500;
|
|
1246
1242
|
function envFlagEnabled(value) {
|
|
1247
1243
|
if (value === void 0) return false;
|
|
1248
1244
|
const normalized = value.trim().toLowerCase();
|
|
@@ -1289,13 +1285,54 @@ function getActiveProcess(key) {
|
|
|
1289
1285
|
function setActiveProcess(key, ap) {
|
|
1290
1286
|
activeProcesses.set(key, ap);
|
|
1291
1287
|
}
|
|
1292
|
-
function
|
|
1288
|
+
function detachActiveProcess(key) {
|
|
1293
1289
|
const ap = activeProcesses.get(key);
|
|
1294
|
-
if (ap)
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1290
|
+
if (!ap) return void 0;
|
|
1291
|
+
activeProcesses.delete(key);
|
|
1292
|
+
void ap.proxyServer?.close();
|
|
1293
|
+
return ap;
|
|
1294
|
+
}
|
|
1295
|
+
function deleteActiveProcess(key) {
|
|
1296
|
+
const ap = detachActiveProcess(key);
|
|
1297
|
+
ap?.proc.kill();
|
|
1298
|
+
}
|
|
1299
|
+
function hasProcessExited(proc) {
|
|
1300
|
+
return proc.exitCode !== null || proc.signalCode !== null;
|
|
1301
|
+
}
|
|
1302
|
+
function waitForProcessExit(proc, timeoutMs) {
|
|
1303
|
+
if (hasProcessExited(proc)) return Promise.resolve(true);
|
|
1304
|
+
return new Promise((resolve4) => {
|
|
1305
|
+
const onExit = () => {
|
|
1306
|
+
clearTimeout(timer);
|
|
1307
|
+
resolve4(true);
|
|
1308
|
+
};
|
|
1309
|
+
const timer = setTimeout(() => {
|
|
1310
|
+
proc.off("exit", onExit);
|
|
1311
|
+
resolve4(hasProcessExited(proc));
|
|
1312
|
+
}, timeoutMs);
|
|
1313
|
+
proc.once("exit", onExit);
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
async function deleteActiveProcessAndWait(key, options = {}) {
|
|
1317
|
+
const ap = detachActiveProcess(key);
|
|
1318
|
+
if (!ap || hasProcessExited(ap.proc)) return true;
|
|
1319
|
+
const gracefulExit = waitForProcessExit(
|
|
1320
|
+
ap.proc,
|
|
1321
|
+
options.exitTimeoutMs ?? PROCESS_EXIT_TIMEOUT_MS
|
|
1322
|
+
);
|
|
1323
|
+
ap.proc.kill();
|
|
1324
|
+
if (await gracefulExit) return true;
|
|
1325
|
+
const forcedExit = waitForProcessExit(
|
|
1326
|
+
ap.proc,
|
|
1327
|
+
options.forceExitTimeoutMs ?? PROCESS_FORCE_EXIT_TIMEOUT_MS
|
|
1328
|
+
);
|
|
1329
|
+
ap.proc.kill("SIGKILL");
|
|
1330
|
+
if (await forcedExit) return true;
|
|
1331
|
+
log.warn("claude process did not exit; starting a fresh session", {
|
|
1332
|
+
sessionKey: key
|
|
1333
|
+
});
|
|
1334
|
+
deleteClaudeSessionId(key);
|
|
1335
|
+
return false;
|
|
1299
1336
|
}
|
|
1300
1337
|
function getClaudeSessionId(key) {
|
|
1301
1338
|
return claudeSessions.get(key);
|
|
@@ -1343,8 +1380,9 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
1343
1380
|
void unlink(systemPromptFile).catch(() => {
|
|
1344
1381
|
});
|
|
1345
1382
|
}
|
|
1346
|
-
activeProcesses.
|
|
1347
|
-
if (
|
|
1383
|
+
const ownsSessionKey = activeProcesses.get(sessionKey2) === ap;
|
|
1384
|
+
if (ownsSessionKey) activeProcesses.delete(sessionKey2);
|
|
1385
|
+
if (ownsSessionKey && code !== 0 && code !== null) {
|
|
1348
1386
|
log.info("process exited with error, clearing session", {
|
|
1349
1387
|
code,
|
|
1350
1388
|
sessionKey: sessionKey2
|
|
@@ -1355,16 +1393,50 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
1355
1393
|
proc.stderr?.on("data", (data) => {
|
|
1356
1394
|
const stderr = data.toString();
|
|
1357
1395
|
log.debug("stderr", { data: stderr.slice(0, 200) });
|
|
1358
|
-
if (stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1396
|
+
if (stderr.includes("No conversation found") || stderr.includes("Session ID") && (stderr.includes("already in use") || stderr.includes("not found") || stderr.includes("invalid"))) {
|
|
1397
|
+
if (activeProcesses.get(sessionKey2) === ap) {
|
|
1398
|
+
log.warn("claude session ID error, clearing session", {
|
|
1399
|
+
sessionKey: sessionKey2,
|
|
1400
|
+
error: stderr.slice(0, 200)
|
|
1401
|
+
});
|
|
1402
|
+
claudeSessions.delete(sessionKey2);
|
|
1403
|
+
} else {
|
|
1404
|
+
log.debug("ignoring session ID error from stale claude process", {
|
|
1405
|
+
sessionKey: sessionKey2
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1364
1408
|
}
|
|
1365
1409
|
});
|
|
1366
1410
|
return ap;
|
|
1367
1411
|
}
|
|
1412
|
+
function appendResumeIfNeeded(sessionKey2, cliArgs) {
|
|
1413
|
+
if (cliArgs.includes("--resume") || cliArgs.includes("--session-id")) {
|
|
1414
|
+
return cliArgs;
|
|
1415
|
+
}
|
|
1416
|
+
const sid = claudeSessions.get(sessionKey2);
|
|
1417
|
+
if (!sid) return cliArgs;
|
|
1418
|
+
return [...cliArgs, "--resume", sid];
|
|
1419
|
+
}
|
|
1420
|
+
function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropicApiKey) {
|
|
1421
|
+
const old = activeProcesses.get(sessionKey2);
|
|
1422
|
+
if (!old) return void 0;
|
|
1423
|
+
activeProcesses.delete(sessionKey2);
|
|
1424
|
+
old.proc.removeAllListeners("exit");
|
|
1425
|
+
try {
|
|
1426
|
+
old.proc.kill();
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
return spawnClaudeProcess(
|
|
1430
|
+
cliPath,
|
|
1431
|
+
appendResumeIfNeeded(sessionKey2, cliArgs),
|
|
1432
|
+
cwd,
|
|
1433
|
+
sessionKey2,
|
|
1434
|
+
old.proxyServer,
|
|
1435
|
+
old.mcpHash,
|
|
1436
|
+
old.systemPromptFile,
|
|
1437
|
+
ignoreAnthropicApiKey
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1368
1440
|
function buildCliArgs(opts) {
|
|
1369
1441
|
const {
|
|
1370
1442
|
sessionKey: sessionKey2,
|
|
@@ -1398,7 +1470,7 @@ function buildCliArgs(opts) {
|
|
|
1398
1470
|
if (includeSessionId) {
|
|
1399
1471
|
const sessionId = claudeSessions.get(sessionKey2);
|
|
1400
1472
|
if (sessionId && !activeProcesses.has(sessionKey2)) {
|
|
1401
|
-
args.push("--
|
|
1473
|
+
args.push("--resume", sessionId);
|
|
1402
1474
|
}
|
|
1403
1475
|
}
|
|
1404
1476
|
if (mcpConfig) {
|
|
@@ -1990,10 +2062,61 @@ import * as fs4 from "fs";
|
|
|
1990
2062
|
import * as path4 from "path";
|
|
1991
2063
|
import * as crypto2 from "crypto";
|
|
1992
2064
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
2065
|
+
var SERVER_CLOSED_MESSAGE = "proxy MCP server closed";
|
|
2066
|
+
function isExpectedCleanupError(message) {
|
|
2067
|
+
return message.includes("timed out after") && message.includes("waiting for opencode to resolve") || message.includes("rejecting as orphaned") || message.includes("was orphaned by a new user turn") || message.includes("stream was aborted") || message.includes(SERVER_CLOSED_MESSAGE);
|
|
2068
|
+
}
|
|
1993
2069
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
1994
2070
|
var SERVER_NAME = "opencode_proxy";
|
|
1995
2071
|
var PROXY_TOOL_PREFIX = `mcp__${SERVER_NAME}__`;
|
|
1996
|
-
var
|
|
2072
|
+
var PROXY_DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2073
|
+
var PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS = {
|
|
2074
|
+
task: 60 * 60 * 1e3
|
|
2075
|
+
// 60 min
|
|
2076
|
+
};
|
|
2077
|
+
var MAX_PROXY_TIMEOUT_MS = 2 ** 31 - 1;
|
|
2078
|
+
function resolveProxyCallTimeoutMs(toolName, input, overrides) {
|
|
2079
|
+
const key = toolName.toLowerCase();
|
|
2080
|
+
let ms = PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS[key] ?? PROXY_DEFAULT_TIMEOUT_MS;
|
|
2081
|
+
if (overrides) {
|
|
2082
|
+
const ov = lookupCaseInsensitive(overrides, key);
|
|
2083
|
+
if (typeof ov === "number" && ov > 0) ms = ov;
|
|
2084
|
+
}
|
|
2085
|
+
if (key === "bash") {
|
|
2086
|
+
const requested = input?.timeout;
|
|
2087
|
+
if (typeof requested === "number" && requested > ms) ms = requested;
|
|
2088
|
+
}
|
|
2089
|
+
return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
|
|
2090
|
+
}
|
|
2091
|
+
function lookupCaseInsensitive(map, key) {
|
|
2092
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
|
|
2093
|
+
for (const k of Object.keys(map)) {
|
|
2094
|
+
if (k.toLowerCase() === key) return map[k];
|
|
2095
|
+
}
|
|
2096
|
+
return void 0;
|
|
2097
|
+
}
|
|
2098
|
+
function resolveProxyClientCeilingMs(overrides) {
|
|
2099
|
+
let ms = PROXY_DEFAULT_TIMEOUT_MS;
|
|
2100
|
+
for (const v of Object.values(PROXY_PER_TOOL_DEFAULT_TIMEOUT_MS)) {
|
|
2101
|
+
if (v > ms) ms = v;
|
|
2102
|
+
}
|
|
2103
|
+
if (overrides) {
|
|
2104
|
+
for (const v of Object.values(overrides)) {
|
|
2105
|
+
if (typeof v === "number" && v > ms) ms = v;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return Math.min(ms, MAX_PROXY_TIMEOUT_MS);
|
|
2109
|
+
}
|
|
2110
|
+
function buildProxyTimeoutError(toolName, ms) {
|
|
2111
|
+
const key = toolName.toLowerCase();
|
|
2112
|
+
const base = `Proxy tool '${toolName}' timed out after ${ms}ms waiting for opencode to resolve the call`;
|
|
2113
|
+
if (key === "task") {
|
|
2114
|
+
return new Error(
|
|
2115
|
+
base + " (the subagent). The subagent may still be running but its result is no longer reachable in this session. Do not declare the dispatch failed, and do not 'schedule a wake-up' or defer -- that mechanism does not apply here. If the result is required, re-dispatch or verify it directly now."
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
return new Error(base);
|
|
2119
|
+
}
|
|
1997
2120
|
var DEFAULT_PROXY_TOOLS = [
|
|
1998
2121
|
{
|
|
1999
2122
|
name: "bash",
|
|
@@ -2086,7 +2209,7 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
2086
2209
|
},
|
|
2087
2210
|
{
|
|
2088
2211
|
name: "task",
|
|
2089
|
-
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json).
|
|
2212
|
+
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json). Foreground calls block until the subagent finishes; set `background` to request opencode's background execution mode. Task calls get a 60-minute proxy deadline by default (configurable via proxyToolTimeoutMs).",
|
|
2090
2213
|
inputSchema: {
|
|
2091
2214
|
type: "object",
|
|
2092
2215
|
properties: {
|
|
@@ -2109,13 +2232,17 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
2109
2232
|
command: {
|
|
2110
2233
|
type: "string",
|
|
2111
2234
|
description: "The command that triggered this task"
|
|
2235
|
+
},
|
|
2236
|
+
background: {
|
|
2237
|
+
type: "boolean",
|
|
2238
|
+
description: "Run the task in the background when supported by opencode"
|
|
2112
2239
|
}
|
|
2113
2240
|
},
|
|
2114
2241
|
required: ["description", "prompt", "subagent_type"]
|
|
2115
2242
|
}
|
|
2116
2243
|
}
|
|
2117
2244
|
];
|
|
2118
|
-
async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
2245
|
+
async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS, timeoutOverrides) {
|
|
2119
2246
|
const calls = new EventEmitter3();
|
|
2120
2247
|
const pending = /* @__PURE__ */ new Map();
|
|
2121
2248
|
const server2 = createServer(async (req, res) => {
|
|
@@ -2124,13 +2251,17 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2124
2251
|
res.end();
|
|
2125
2252
|
return;
|
|
2126
2253
|
}
|
|
2254
|
+
let requestId = null;
|
|
2255
|
+
let requestMethod = null;
|
|
2127
2256
|
try {
|
|
2128
2257
|
const body = await readBody(req);
|
|
2129
2258
|
const request = JSON.parse(body);
|
|
2259
|
+
requestId = request?.id ?? null;
|
|
2260
|
+
requestMethod = typeof request?.method === "string" ? request.method : null;
|
|
2130
2261
|
if (request?.jsonrpc !== "2.0" || typeof request.method !== "string") {
|
|
2131
2262
|
writeJson(res, {
|
|
2132
2263
|
jsonrpc: "2.0",
|
|
2133
|
-
id:
|
|
2264
|
+
id: requestId,
|
|
2134
2265
|
error: { code: -32600, message: "Invalid request" }
|
|
2135
2266
|
});
|
|
2136
2267
|
return;
|
|
@@ -2142,7 +2273,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2142
2273
|
if (request.method === "initialize") {
|
|
2143
2274
|
writeJson(res, {
|
|
2144
2275
|
jsonrpc: "2.0",
|
|
2145
|
-
id:
|
|
2276
|
+
id: requestId,
|
|
2146
2277
|
result: {
|
|
2147
2278
|
protocolVersion: PROTOCOL_VERSION,
|
|
2148
2279
|
capabilities: { tools: {} },
|
|
@@ -2162,7 +2293,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2162
2293
|
if (request.method === "tools/list") {
|
|
2163
2294
|
writeJson(res, {
|
|
2164
2295
|
jsonrpc: "2.0",
|
|
2165
|
-
id:
|
|
2296
|
+
id: requestId,
|
|
2166
2297
|
result: {
|
|
2167
2298
|
tools: tools.map((t) => ({
|
|
2168
2299
|
name: t.name,
|
|
@@ -2180,10 +2311,10 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2180
2311
|
if (!tools.some((t) => t.name === toolName)) {
|
|
2181
2312
|
writeJson(res, {
|
|
2182
2313
|
jsonrpc: "2.0",
|
|
2183
|
-
id:
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2314
|
+
id: requestId,
|
|
2315
|
+
result: {
|
|
2316
|
+
content: [{ type: "text", text: `Unknown proxy tool: ${toolName}` }],
|
|
2317
|
+
isError: true
|
|
2187
2318
|
}
|
|
2188
2319
|
});
|
|
2189
2320
|
return;
|
|
@@ -2205,20 +2336,21 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2205
2336
|
reject
|
|
2206
2337
|
};
|
|
2207
2338
|
pending.set(callId, entry);
|
|
2339
|
+
const deadlineMs = resolveProxyCallTimeoutMs(
|
|
2340
|
+
toolName,
|
|
2341
|
+
input,
|
|
2342
|
+
timeoutOverrides
|
|
2343
|
+
);
|
|
2208
2344
|
timer = setTimeout(() => {
|
|
2209
2345
|
if (!pending.has(callId)) return;
|
|
2210
2346
|
pending.delete(callId);
|
|
2211
2347
|
log.notice("proxy-mcp tool call timed out", {
|
|
2212
2348
|
callId,
|
|
2213
2349
|
toolName,
|
|
2214
|
-
|
|
2350
|
+
deadlineMs
|
|
2215
2351
|
});
|
|
2216
|
-
reject(
|
|
2217
|
-
|
|
2218
|
-
`Proxy tool '${toolName}' timed out after ${PROXY_CALL_TIMEOUT_MS}ms waiting for opencode to resolve the call`
|
|
2219
|
-
)
|
|
2220
|
-
);
|
|
2221
|
-
}, PROXY_CALL_TIMEOUT_MS);
|
|
2352
|
+
reject(buildProxyTimeoutError(toolName, deadlineMs));
|
|
2353
|
+
}, deadlineMs);
|
|
2222
2354
|
calls.emit("call", entry);
|
|
2223
2355
|
}
|
|
2224
2356
|
).finally(() => {
|
|
@@ -2228,17 +2360,17 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2228
2360
|
if (result.kind === "error") {
|
|
2229
2361
|
writeJson(res, {
|
|
2230
2362
|
jsonrpc: "2.0",
|
|
2231
|
-
id:
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2363
|
+
id: requestId,
|
|
2364
|
+
result: {
|
|
2365
|
+
content: [{ type: "text", text: result.message }],
|
|
2366
|
+
isError: true
|
|
2235
2367
|
}
|
|
2236
2368
|
});
|
|
2237
2369
|
return;
|
|
2238
2370
|
}
|
|
2239
2371
|
writeJson(res, {
|
|
2240
2372
|
jsonrpc: "2.0",
|
|
2241
|
-
id:
|
|
2373
|
+
id: requestId,
|
|
2242
2374
|
result: {
|
|
2243
2375
|
content: [{ type: "text", text: result.text }],
|
|
2244
2376
|
isError: result.isError === true
|
|
@@ -2248,20 +2380,38 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2248
2380
|
}
|
|
2249
2381
|
writeJson(res, {
|
|
2250
2382
|
jsonrpc: "2.0",
|
|
2251
|
-
id:
|
|
2383
|
+
id: requestId,
|
|
2252
2384
|
error: { code: -32601, message: `Unknown method: ${request.method}` }
|
|
2253
2385
|
});
|
|
2254
2386
|
} catch (error) {
|
|
2255
2387
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2256
|
-
const
|
|
2257
|
-
const logFn = isExpectedCleanup ? log.notice : log.warn;
|
|
2388
|
+
const logFn = isExpectedCleanupError(errorMessage) ? log.notice : log.warn;
|
|
2258
2389
|
logFn("proxy-mcp error handling request", {
|
|
2259
2390
|
error: errorMessage
|
|
2260
2391
|
});
|
|
2392
|
+
if (requestMethod === "tools/call") {
|
|
2393
|
+
try {
|
|
2394
|
+
writeJson(res, {
|
|
2395
|
+
jsonrpc: "2.0",
|
|
2396
|
+
id: requestId,
|
|
2397
|
+
result: {
|
|
2398
|
+
content: [{ type: "text", text: errorMessage }],
|
|
2399
|
+
isError: true
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
} catch {
|
|
2403
|
+
try {
|
|
2404
|
+
res.statusCode = 500;
|
|
2405
|
+
res.end();
|
|
2406
|
+
} catch {
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2261
2411
|
try {
|
|
2262
2412
|
writeJson(res, {
|
|
2263
2413
|
jsonrpc: "2.0",
|
|
2264
|
-
id:
|
|
2414
|
+
id: requestId,
|
|
2265
2415
|
error: {
|
|
2266
2416
|
code: -32603,
|
|
2267
2417
|
message: error instanceof Error ? error.message : "Internal error"
|
|
@@ -2306,7 +2456,8 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2306
2456
|
mcpServers: {
|
|
2307
2457
|
[SERVER_NAME]: {
|
|
2308
2458
|
type: "http",
|
|
2309
|
-
url
|
|
2459
|
+
url,
|
|
2460
|
+
timeout: resolveProxyClientCeilingMs(timeoutOverrides)
|
|
2310
2461
|
}
|
|
2311
2462
|
}
|
|
2312
2463
|
},
|
|
@@ -2324,7 +2475,7 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
2324
2475
|
},
|
|
2325
2476
|
async close() {
|
|
2326
2477
|
for (const entry of pending.values()) {
|
|
2327
|
-
entry.reject(new Error(
|
|
2478
|
+
entry.reject(new Error(SERVER_CLOSED_MESSAGE));
|
|
2328
2479
|
}
|
|
2329
2480
|
pending.clear();
|
|
2330
2481
|
await new Promise((resolve4) => {
|
|
@@ -2386,7 +2537,6 @@ import { EventEmitter as EventEmitter4 } from "events";
|
|
|
2386
2537
|
var pendingByCallId = /* @__PURE__ */ new Map();
|
|
2387
2538
|
var callIdsBySession = /* @__PURE__ */ new Map();
|
|
2388
2539
|
var emitter = new EventEmitter4();
|
|
2389
|
-
var PENDING_PROXY_CALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
2390
2540
|
function eventName(sessionKey2) {
|
|
2391
2541
|
return `pending:${sessionKey2}`;
|
|
2392
2542
|
}
|
|
@@ -2409,7 +2559,7 @@ function onPendingProxyCall(sessionKey2, handler) {
|
|
|
2409
2559
|
emitter.on(name, handler);
|
|
2410
2560
|
return () => emitter.off(name, handler);
|
|
2411
2561
|
}
|
|
2412
|
-
function queuePendingProxyCall(sessionKey2, call) {
|
|
2562
|
+
function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
|
|
2413
2563
|
const previous = pendingByCallId.get(call.id);
|
|
2414
2564
|
if (previous) {
|
|
2415
2565
|
clearTimeout(previous.timer);
|
|
@@ -2419,23 +2569,24 @@ function queuePendingProxyCall(sessionKey2, call) {
|
|
|
2419
2569
|
pendingByCallId.delete(call.id);
|
|
2420
2570
|
indexRemove(previous.sessionKey, call.id);
|
|
2421
2571
|
}
|
|
2572
|
+
const deadlineMs = resolveProxyCallTimeoutMs(
|
|
2573
|
+
call.toolName,
|
|
2574
|
+
call.input,
|
|
2575
|
+
timeoutOverrides
|
|
2576
|
+
);
|
|
2422
2577
|
const timer = setTimeout(() => {
|
|
2423
2578
|
const current = pendingByCallId.get(call.id);
|
|
2424
2579
|
if (!current) return;
|
|
2425
2580
|
pendingByCallId.delete(call.id);
|
|
2426
2581
|
indexRemove(current.sessionKey, call.id);
|
|
2427
|
-
current.reject(
|
|
2428
|
-
new Error(
|
|
2429
|
-
`Proxy tool call '${call.toolName}' timed out after ${PENDING_PROXY_CALL_TIMEOUT_MS}ms waiting for opencode to resolve the call`
|
|
2430
|
-
)
|
|
2431
|
-
);
|
|
2582
|
+
current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
|
|
2432
2583
|
log.notice("timed out pending proxy call", {
|
|
2433
2584
|
sessionKey: current.sessionKey,
|
|
2434
2585
|
toolCallId: call.id,
|
|
2435
2586
|
toolName: call.toolName,
|
|
2436
|
-
|
|
2587
|
+
deadlineMs
|
|
2437
2588
|
});
|
|
2438
|
-
},
|
|
2589
|
+
}, deadlineMs);
|
|
2439
2590
|
const pending = {
|
|
2440
2591
|
sessionKey: sessionKey2,
|
|
2441
2592
|
toolCallId: call.id,
|
|
@@ -2574,6 +2725,7 @@ function hasNewUserContent(prompt) {
|
|
|
2574
2725
|
var AUTO_CONTINUE_MAX_ATTEMPTS = 8;
|
|
2575
2726
|
var AUTO_CONTINUE_MAX_ELAPSED_MS = 10 * 60 * 1e3;
|
|
2576
2727
|
var AUTO_CONTINUE_NO_PROGRESS_LIMIT = 2;
|
|
2728
|
+
var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
|
|
2577
2729
|
var AUTO_CONTINUE_PROMPT = "Continue the task from where you stopped. Do not summarize; keep working until the requested task is complete, you need clarification, or you hit a real blocker.";
|
|
2578
2730
|
function normalizeVisibleText(text) {
|
|
2579
2731
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -2701,9 +2853,9 @@ function makeAutoContinueMessage() {
|
|
|
2701
2853
|
}
|
|
2702
2854
|
});
|
|
2703
2855
|
}
|
|
2704
|
-
function readPromptFileIfPresent(
|
|
2856
|
+
function readPromptFileIfPresent(path7) {
|
|
2705
2857
|
try {
|
|
2706
|
-
const content = readFileSync3(
|
|
2858
|
+
const content = readFileSync3(path7, "utf8").trim();
|
|
2707
2859
|
return content || void 0;
|
|
2708
2860
|
} catch {
|
|
2709
2861
|
return void 0;
|
|
@@ -2772,10 +2924,10 @@ function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystem
|
|
|
2772
2924
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
2773
2925
|
const content = parts.join("\n\n");
|
|
2774
2926
|
if (!content) return void 0;
|
|
2775
|
-
const
|
|
2927
|
+
const path7 = join6(tmpdir2(), `opencode-cc-sys-${randomUUID3()}.md`);
|
|
2776
2928
|
try {
|
|
2777
|
-
writeFileSync3(
|
|
2778
|
-
return
|
|
2929
|
+
writeFileSync3(path7, content, "utf8");
|
|
2930
|
+
return path7;
|
|
2779
2931
|
} catch (err) {
|
|
2780
2932
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
2781
2933
|
return void 0;
|
|
@@ -2911,9 +3063,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2911
3063
|
* The process lifecycle owns the server lifecycle via session-manager.
|
|
2912
3064
|
*/
|
|
2913
3065
|
async ensureProxyServer(tools, sessionKeyForCalls) {
|
|
2914
|
-
const
|
|
3066
|
+
const timeoutOverrides = this.config.proxyToolTimeoutMs;
|
|
3067
|
+
const srv = await createProxyMcpServer(tools, timeoutOverrides);
|
|
2915
3068
|
srv.calls.on("call", (call) => {
|
|
2916
|
-
queuePendingProxyCall(sessionKeyForCalls, call);
|
|
3069
|
+
queuePendingProxyCall(sessionKeyForCalls, call, timeoutOverrides);
|
|
2917
3070
|
});
|
|
2918
3071
|
return srv;
|
|
2919
3072
|
}
|
|
@@ -3668,22 +3821,32 @@ ${plan}
|
|
|
3668
3821
|
let activeProcess = getActiveProcess(sk);
|
|
3669
3822
|
let proc;
|
|
3670
3823
|
let lineEmitter;
|
|
3824
|
+
let cliArgs;
|
|
3671
3825
|
let proxyServer = activeProcess?.proxyServer ?? null;
|
|
3672
|
-
if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
3673
|
-
const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
3674
|
-
const previousHash = activeProcess.mcpHash ?? null;
|
|
3675
|
-
if (previousHash !== probe.bridgedHash) {
|
|
3676
|
-
log.info("opencode MCP config changed, respawning claude", {
|
|
3677
|
-
sk,
|
|
3678
|
-
previousHash,
|
|
3679
|
-
currentHash: probe.bridgedHash
|
|
3680
|
-
});
|
|
3681
|
-
deleteActiveProcess(sk);
|
|
3682
|
-
activeProcess = void 0;
|
|
3683
|
-
proxyServer = null;
|
|
3684
|
-
}
|
|
3685
|
-
}
|
|
3686
3826
|
const setup = async () => {
|
|
3827
|
+
if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
3828
|
+
const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
3829
|
+
const previousHash = activeProcess.mcpHash ?? null;
|
|
3830
|
+
if (previousHash !== probe.bridgedHash) {
|
|
3831
|
+
if (previousPendingProxyCalls.length > 0) {
|
|
3832
|
+
log.info("deferring MCP hot reload until proxy calls resolve", {
|
|
3833
|
+
sk,
|
|
3834
|
+
previousHash,
|
|
3835
|
+
currentHash: probe.bridgedHash,
|
|
3836
|
+
pendingCalls: previousPendingProxyCalls.length
|
|
3837
|
+
});
|
|
3838
|
+
} else {
|
|
3839
|
+
log.info("opencode MCP config changed, respawning claude", {
|
|
3840
|
+
sk,
|
|
3841
|
+
previousHash,
|
|
3842
|
+
currentHash: probe.bridgedHash
|
|
3843
|
+
});
|
|
3844
|
+
await deleteActiveProcessAndWait(sk);
|
|
3845
|
+
activeProcess = void 0;
|
|
3846
|
+
proxyServer = null;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3687
3850
|
if (useInteractive && !compactionMode) {
|
|
3688
3851
|
const mcp = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
3689
3852
|
if (activeProcess) {
|
|
@@ -3744,7 +3907,6 @@ ${plan}
|
|
|
3744
3907
|
});
|
|
3745
3908
|
}
|
|
3746
3909
|
} else {
|
|
3747
|
-
let cliArgs;
|
|
3748
3910
|
let spawnSystemPromptFile;
|
|
3749
3911
|
let spawnProxyServer = null;
|
|
3750
3912
|
let spawnMcpHash = null;
|
|
@@ -3847,6 +4009,7 @@ ${plan}
|
|
|
3847
4009
|
let controllerClosed = false;
|
|
3848
4010
|
let pendingProxyUnsubscribe = null;
|
|
3849
4011
|
let resultFallbackTimer = null;
|
|
4012
|
+
let pendingResultCompletion = null;
|
|
3850
4013
|
let hasReceivedContent = false;
|
|
3851
4014
|
let visibleTextSinceContinue = "";
|
|
3852
4015
|
let lastVisibleTextSinceContinue = "";
|
|
@@ -3877,6 +4040,103 @@ ${plan}
|
|
|
3877
4040
|
closeHandler();
|
|
3878
4041
|
}, delayMs);
|
|
3879
4042
|
};
|
|
4043
|
+
const START_WATCHDOG_MS = (() => {
|
|
4044
|
+
const env = process.env.CLAUDE_CODE_START_WATCHDOG_MS;
|
|
4045
|
+
const parsed = env ? Number.parseInt(env, 10) : NaN;
|
|
4046
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 9e4;
|
|
4047
|
+
})();
|
|
4048
|
+
let startWatchdog = null;
|
|
4049
|
+
let respawnAttempted = false;
|
|
4050
|
+
const clearStartWatchdog = () => {
|
|
4051
|
+
if (startWatchdog) {
|
|
4052
|
+
clearTimeout(startWatchdog);
|
|
4053
|
+
startWatchdog = null;
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
const onStartWatchdogFire = () => {
|
|
4057
|
+
startWatchdog = null;
|
|
4058
|
+
if (controllerClosed || hasReceivedContent) return;
|
|
4059
|
+
if (respawnAttempted) {
|
|
4060
|
+
log.error(
|
|
4061
|
+
"claude process still silent after respawn; ending turn",
|
|
4062
|
+
{ sessionKey: sk }
|
|
4063
|
+
);
|
|
4064
|
+
deleteActiveProcess(sk);
|
|
4065
|
+
deleteClaudeSessionId(sk);
|
|
4066
|
+
controllerClosed = true;
|
|
4067
|
+
cleanupTurn();
|
|
4068
|
+
controller.enqueue({
|
|
4069
|
+
type: "error",
|
|
4070
|
+
error: new Error(
|
|
4071
|
+
"Claude process produced no output after the envelope write (start watchdog timeout)."
|
|
4072
|
+
)
|
|
4073
|
+
});
|
|
4074
|
+
try {
|
|
4075
|
+
controller.close();
|
|
4076
|
+
} catch {
|
|
4077
|
+
}
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
4080
|
+
respawnAttempted = true;
|
|
4081
|
+
log.warn(
|
|
4082
|
+
"no stdout after envelope write; respawning claude process to resume conversation",
|
|
4083
|
+
{ sessionKey: sk, startWatchdogMs: START_WATCHDOG_MS }
|
|
4084
|
+
);
|
|
4085
|
+
lineEmitter.off("line", lineHandler);
|
|
4086
|
+
lineEmitter.off("close", closeHandler);
|
|
4087
|
+
proc.off("error", procErrorHandler);
|
|
4088
|
+
const newAp = respawnActiveProcess(
|
|
4089
|
+
sk,
|
|
4090
|
+
cliPath,
|
|
4091
|
+
cliArgs,
|
|
4092
|
+
cwd,
|
|
4093
|
+
self.config.ignoreAnthropicApiKey
|
|
4094
|
+
);
|
|
4095
|
+
if (!newAp) {
|
|
4096
|
+
log.error(
|
|
4097
|
+
"no active process to respawn (start watchdog); ending turn",
|
|
4098
|
+
{ sessionKey: sk }
|
|
4099
|
+
);
|
|
4100
|
+
controllerClosed = true;
|
|
4101
|
+
cleanupTurn();
|
|
4102
|
+
controller.enqueue({
|
|
4103
|
+
type: "error",
|
|
4104
|
+
error: new Error(
|
|
4105
|
+
"No active claude process to respawn after start watchdog timeout."
|
|
4106
|
+
)
|
|
4107
|
+
});
|
|
4108
|
+
try {
|
|
4109
|
+
controller.close();
|
|
4110
|
+
} catch {
|
|
4111
|
+
}
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
proc = newAp.proc;
|
|
4115
|
+
lineEmitter = newAp.lineEmitter;
|
|
4116
|
+
activeProcess = newAp;
|
|
4117
|
+
lineEmitter.on("line", lineHandler);
|
|
4118
|
+
lineEmitter.on("close", closeHandler);
|
|
4119
|
+
proc.on("error", procErrorHandler);
|
|
4120
|
+
try {
|
|
4121
|
+
proc.stdin?.write(userMsg + "\n");
|
|
4122
|
+
log.debug("re-sent user message after respawn", {
|
|
4123
|
+
textLength: userMsg.length
|
|
4124
|
+
});
|
|
4125
|
+
} catch (err) {
|
|
4126
|
+
log.error("failed to re-send envelope after respawn", {
|
|
4127
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
startWatchdog = setTimeout(
|
|
4131
|
+
onStartWatchdogFire,
|
|
4132
|
+
START_WATCHDOG_MS
|
|
4133
|
+
);
|
|
4134
|
+
};
|
|
4135
|
+
const armStartWatchdog = () => {
|
|
4136
|
+
clearStartWatchdog();
|
|
4137
|
+
if (controllerClosed) return;
|
|
4138
|
+
startWatchdog = setTimeout(onStartWatchdogFire, START_WATCHDOG_MS);
|
|
4139
|
+
};
|
|
3880
4140
|
const toolCallMap = /* @__PURE__ */ new Map();
|
|
3881
4141
|
const skipResultForIds = /* @__PURE__ */ new Set();
|
|
3882
4142
|
const toolCallsById = /* @__PURE__ */ new Map();
|
|
@@ -3932,6 +4192,28 @@ ${plan}
|
|
|
3932
4192
|
});
|
|
3933
4193
|
finishWithToolCalls(batch);
|
|
3934
4194
|
};
|
|
4195
|
+
const settleResultBoundary = () => {
|
|
4196
|
+
drainTimer = null;
|
|
4197
|
+
const completeResult2 = pendingResultCompletion;
|
|
4198
|
+
pendingResultCompletion = null;
|
|
4199
|
+
if (!completeResult2 || controllerClosed) return;
|
|
4200
|
+
if (drainBuffer.length > 0) {
|
|
4201
|
+
drainNow();
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
completeResult2();
|
|
4205
|
+
};
|
|
4206
|
+
const scheduleResultBoundary = (completeResult2, delayMs) => {
|
|
4207
|
+
pendingResultCompletion = completeResult2;
|
|
4208
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
4209
|
+
drainTimer = setTimeout(settleResultBoundary, delayMs);
|
|
4210
|
+
};
|
|
4211
|
+
const noteResultBoundaryCall = () => {
|
|
4212
|
+
if (!pendingResultCompletion) return false;
|
|
4213
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
4214
|
+
drainTimer = setTimeout(settleResultBoundary, DRAIN_QUIET_MS);
|
|
4215
|
+
return true;
|
|
4216
|
+
};
|
|
3935
4217
|
const noteVisibleText = (text) => {
|
|
3936
4218
|
visibleTextSinceContinue += text;
|
|
3937
4219
|
lastVisibleTextSinceContinue += text;
|
|
@@ -3956,11 +4238,106 @@ ${plan}
|
|
|
3956
4238
|
hadProxyActivitySinceContinue = false;
|
|
3957
4239
|
lastStopReason = null;
|
|
3958
4240
|
};
|
|
4241
|
+
const completeResult = (msg) => {
|
|
4242
|
+
if (controllerClosed) return;
|
|
4243
|
+
if (drainBuffer.length > 0) {
|
|
4244
|
+
drainNow();
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
const pendingSiblings = getPendingProxyCalls(sk);
|
|
4248
|
+
if (pendingSiblings.length > 0) {
|
|
4249
|
+
log.info("leaving parallel proxy calls pending at result boundary", {
|
|
4250
|
+
sessionKey: sk,
|
|
4251
|
+
count: pendingSiblings.length
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4254
|
+
const autoDecision = shouldAutoContinueIncompleteTurn(
|
|
4255
|
+
autoContinueState,
|
|
4256
|
+
{
|
|
4257
|
+
text: visibleTextSinceContinue,
|
|
4258
|
+
lastVisibleText: lastVisibleTextSinceContinue,
|
|
4259
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
4260
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
4261
|
+
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
4262
|
+
isError: msg.is_error,
|
|
4263
|
+
stopReason: lastStopReason
|
|
4264
|
+
}
|
|
4265
|
+
);
|
|
4266
|
+
if (autoDecision.continue) {
|
|
4267
|
+
const signature = continuationSignature({
|
|
4268
|
+
text: visibleTextSinceContinue,
|
|
4269
|
+
lastVisibleText: lastVisibleTextSinceContinue,
|
|
4270
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
4271
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
4272
|
+
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
4273
|
+
isError: msg.is_error
|
|
4274
|
+
});
|
|
4275
|
+
autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
|
|
4276
|
+
autoContinueState.lastSignature = signature;
|
|
4277
|
+
autoContinueState.attempts++;
|
|
4278
|
+
log.notice("auto-continuing incomplete claude result", {
|
|
4279
|
+
sessionKey: sk,
|
|
4280
|
+
reason: autoDecision.reason,
|
|
4281
|
+
attempts: autoContinueState.attempts,
|
|
4282
|
+
textLength: visibleTextSinceContinue.length,
|
|
4283
|
+
lastTextLength: lastVisibleTextSinceContinue.length,
|
|
4284
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
4285
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
4286
|
+
hadProxyActivity: hadProxyActivitySinceContinue
|
|
4287
|
+
});
|
|
4288
|
+
turnCompleted = false;
|
|
4289
|
+
resetAutoContinueWindow();
|
|
4290
|
+
proc.stdin?.write(makeAutoContinueMessage() + "\n");
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
log.notice("auto-continuation stopped", {
|
|
4294
|
+
sessionKey: sk,
|
|
4295
|
+
reason: autoDecision.reason,
|
|
4296
|
+
stopReason: lastStopReason,
|
|
4297
|
+
attempts: autoContinueState.attempts,
|
|
4298
|
+
textLength: visibleTextSinceContinue.length,
|
|
4299
|
+
lastTextLength: lastVisibleTextSinceContinue.length,
|
|
4300
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
4301
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
4302
|
+
hadProxyActivity: hadProxyActivitySinceContinue
|
|
4303
|
+
});
|
|
4304
|
+
for (const [idx, reasoningId] of reasoningIds) {
|
|
4305
|
+
if (reasoningStarted.get(idx)) {
|
|
4306
|
+
controller.enqueue({
|
|
4307
|
+
type: "reasoning-end",
|
|
4308
|
+
id: reasoningId
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
controller.enqueue({
|
|
4313
|
+
type: "finish",
|
|
4314
|
+
finishReason: toFinishReason("stop"),
|
|
4315
|
+
usage: toUsage(msg.usage),
|
|
4316
|
+
providerMetadata: {
|
|
4317
|
+
"claude-code": {
|
|
4318
|
+
...resultMeta,
|
|
4319
|
+
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
4320
|
+
},
|
|
4321
|
+
...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
|
|
4322
|
+
anthropic: {
|
|
4323
|
+
cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
|
|
4324
|
+
}
|
|
4325
|
+
} : {}
|
|
4326
|
+
}
|
|
4327
|
+
});
|
|
4328
|
+
controllerClosed = true;
|
|
4329
|
+
cleanupTurn();
|
|
4330
|
+
try {
|
|
4331
|
+
controller.close();
|
|
4332
|
+
} catch {
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
3959
4335
|
let gotPartialEvents = false;
|
|
3960
4336
|
const lineHandler = (line) => {
|
|
3961
4337
|
if (!line.trim()) return;
|
|
3962
4338
|
if (controllerClosed) return;
|
|
3963
4339
|
startResultFallback();
|
|
4340
|
+
clearStartWatchdog();
|
|
3964
4341
|
try {
|
|
3965
4342
|
const outer = JSON.parse(line);
|
|
3966
4343
|
const msg = outer.type === "stream_event" && outer.event ? { ...outer.event, session_id: outer.session_id } : outer;
|
|
@@ -4150,6 +4527,7 @@ ${plan}
|
|
|
4150
4527
|
});
|
|
4151
4528
|
endTextBlock();
|
|
4152
4529
|
} else if (tc.name.startsWith(PROXY_TOOL_PREFIX)) {
|
|
4530
|
+
noteProxyActivity();
|
|
4153
4531
|
log.debug("ignoring proxy tool_use block; broker handles it", {
|
|
4154
4532
|
name: tc.name,
|
|
4155
4533
|
id: tc.id
|
|
@@ -4316,6 +4694,7 @@ ${plan}
|
|
|
4316
4694
|
});
|
|
4317
4695
|
endTextBlock();
|
|
4318
4696
|
} else if (block.name.startsWith(PROXY_TOOL_PREFIX)) {
|
|
4697
|
+
noteProxyActivity();
|
|
4319
4698
|
log.debug("ignoring proxy tool_use from assistant message", {
|
|
4320
4699
|
name: block.name,
|
|
4321
4700
|
id: block.id
|
|
@@ -4466,113 +4845,36 @@ ${plan}
|
|
|
4466
4845
|
});
|
|
4467
4846
|
turnCompleted = true;
|
|
4468
4847
|
endTextBlock();
|
|
4469
|
-
|
|
4848
|
+
const shouldDeferResult = !msg.is_error && !autoContinueState.aborted && !autoContinueState.sawAskUserQuestion;
|
|
4849
|
+
if (drainBuffer.length > 0 && shouldDeferResult) {
|
|
4470
4850
|
log.info(
|
|
4471
|
-
"
|
|
4851
|
+
"waiting for parallel proxy calls at turn-result boundary",
|
|
4472
4852
|
{
|
|
4473
4853
|
sessionKey: sk,
|
|
4474
4854
|
count: drainBuffer.length
|
|
4475
4855
|
}
|
|
4476
4856
|
);
|
|
4477
|
-
|
|
4857
|
+
scheduleResultBoundary(
|
|
4858
|
+
() => completeResult(msg),
|
|
4859
|
+
DRAIN_QUIET_MS
|
|
4860
|
+
);
|
|
4478
4861
|
return;
|
|
4479
4862
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
"rejecting orphan pending proxy calls at turn-result boundary",
|
|
4863
|
+
if (drainBuffer.length === 0 && hadProxyActivitySinceContinue && shouldDeferResult) {
|
|
4864
|
+
log.info(
|
|
4865
|
+
"waiting for delayed proxy call at turn-result boundary",
|
|
4484
4866
|
{
|
|
4485
4867
|
sessionKey: sk,
|
|
4486
|
-
|
|
4868
|
+
graceMs: PROXY_RESULT_BOUNDARY_GRACE_MS
|
|
4487
4869
|
}
|
|
4488
4870
|
);
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
"Claude CLI emitted result with pending proxy calls not in drain buffer"
|
|
4493
|
-
)
|
|
4871
|
+
scheduleResultBoundary(
|
|
4872
|
+
() => completeResult(msg),
|
|
4873
|
+
PROXY_RESULT_BOUNDARY_GRACE_MS
|
|
4494
4874
|
);
|
|
4495
|
-
}
|
|
4496
|
-
const autoDecision = shouldAutoContinueIncompleteTurn(
|
|
4497
|
-
autoContinueState,
|
|
4498
|
-
{
|
|
4499
|
-
text: visibleTextSinceContinue,
|
|
4500
|
-
lastVisibleText: lastVisibleTextSinceContinue,
|
|
4501
|
-
hadReasoning: hadReasoningSinceContinue,
|
|
4502
|
-
hadToolActivity: hadToolActivitySinceContinue,
|
|
4503
|
-
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
4504
|
-
isError: msg.is_error,
|
|
4505
|
-
stopReason: lastStopReason
|
|
4506
|
-
}
|
|
4507
|
-
);
|
|
4508
|
-
if (autoDecision.continue) {
|
|
4509
|
-
const signature = continuationSignature({
|
|
4510
|
-
text: visibleTextSinceContinue,
|
|
4511
|
-
lastVisibleText: lastVisibleTextSinceContinue,
|
|
4512
|
-
hadReasoning: hadReasoningSinceContinue,
|
|
4513
|
-
hadToolActivity: hadToolActivitySinceContinue,
|
|
4514
|
-
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
4515
|
-
isError: msg.is_error
|
|
4516
|
-
});
|
|
4517
|
-
autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
|
|
4518
|
-
autoContinueState.lastSignature = signature;
|
|
4519
|
-
autoContinueState.attempts++;
|
|
4520
|
-
log.notice("auto-continuing incomplete claude result", {
|
|
4521
|
-
sessionKey: sk,
|
|
4522
|
-
reason: autoDecision.reason,
|
|
4523
|
-
attempts: autoContinueState.attempts,
|
|
4524
|
-
textLength: visibleTextSinceContinue.length,
|
|
4525
|
-
lastTextLength: lastVisibleTextSinceContinue.length,
|
|
4526
|
-
hadReasoning: hadReasoningSinceContinue,
|
|
4527
|
-
hadToolActivity: hadToolActivitySinceContinue,
|
|
4528
|
-
hadProxyActivity: hadProxyActivitySinceContinue
|
|
4529
|
-
});
|
|
4530
|
-
turnCompleted = false;
|
|
4531
|
-
resetAutoContinueWindow();
|
|
4532
|
-
proc.stdin?.write(makeAutoContinueMessage() + "\n");
|
|
4533
4875
|
return;
|
|
4534
4876
|
}
|
|
4535
|
-
|
|
4536
|
-
sessionKey: sk,
|
|
4537
|
-
reason: autoDecision.reason,
|
|
4538
|
-
stopReason: lastStopReason,
|
|
4539
|
-
attempts: autoContinueState.attempts,
|
|
4540
|
-
textLength: visibleTextSinceContinue.length,
|
|
4541
|
-
lastTextLength: lastVisibleTextSinceContinue.length,
|
|
4542
|
-
hadReasoning: hadReasoningSinceContinue,
|
|
4543
|
-
hadToolActivity: hadToolActivitySinceContinue,
|
|
4544
|
-
hadProxyActivity: hadProxyActivitySinceContinue
|
|
4545
|
-
});
|
|
4546
|
-
for (const [idx, reasoningId] of reasoningIds) {
|
|
4547
|
-
if (reasoningStarted.get(idx)) {
|
|
4548
|
-
controller.enqueue({
|
|
4549
|
-
type: "reasoning-end",
|
|
4550
|
-
id: reasoningId
|
|
4551
|
-
});
|
|
4552
|
-
}
|
|
4553
|
-
}
|
|
4554
|
-
controller.enqueue({
|
|
4555
|
-
type: "finish",
|
|
4556
|
-
finishReason: toFinishReason("stop"),
|
|
4557
|
-
usage: toUsage(msg.usage),
|
|
4558
|
-
providerMetadata: {
|
|
4559
|
-
"claude-code": {
|
|
4560
|
-
...resultMeta,
|
|
4561
|
-
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
4562
|
-
},
|
|
4563
|
-
...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
|
|
4564
|
-
anthropic: {
|
|
4565
|
-
cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
|
|
4566
|
-
}
|
|
4567
|
-
} : {}
|
|
4568
|
-
}
|
|
4569
|
-
});
|
|
4570
|
-
controllerClosed = true;
|
|
4571
|
-
cleanupTurn();
|
|
4572
|
-
try {
|
|
4573
|
-
controller.close();
|
|
4574
|
-
} catch {
|
|
4575
|
-
}
|
|
4877
|
+
completeResult(msg);
|
|
4576
4878
|
}
|
|
4577
4879
|
} catch (e) {
|
|
4578
4880
|
log.debug("failed to parse line", {
|
|
@@ -4616,6 +4918,8 @@ ${plan}
|
|
|
4616
4918
|
if (cleanedUp) return;
|
|
4617
4919
|
cleanedUp = true;
|
|
4618
4920
|
clearFallbackTimer();
|
|
4921
|
+
pendingResultCompletion = null;
|
|
4922
|
+
clearStartWatchdog();
|
|
4619
4923
|
if (drainTimer) {
|
|
4620
4924
|
clearTimeout(drainTimer);
|
|
4621
4925
|
drainTimer = null;
|
|
@@ -4676,6 +4980,7 @@ ${plan}
|
|
|
4676
4980
|
noteProxyActivity();
|
|
4677
4981
|
noteToolActivity();
|
|
4678
4982
|
drainBuffer.push(call);
|
|
4983
|
+
if (noteResultBoundaryCall()) return;
|
|
4679
4984
|
if (drainTimer) clearTimeout(drainTimer);
|
|
4680
4985
|
drainTimer = setTimeout(drainNow, DRAIN_QUIET_MS);
|
|
4681
4986
|
});
|
|
@@ -4689,6 +4994,15 @@ ${plan}
|
|
|
4689
4994
|
"abort signal received before content, closing stream immediately",
|
|
4690
4995
|
{ cwd }
|
|
4691
4996
|
);
|
|
4997
|
+
if (drainBuffer.length > 0 || getPendingProxyCalls(sk).length > 0) {
|
|
4998
|
+
rejectAllPendingProxyCallsForSession(
|
|
4999
|
+
sk,
|
|
5000
|
+
new Error(
|
|
5001
|
+
"Provider stream was aborted before pending proxy calls were emitted"
|
|
5002
|
+
)
|
|
5003
|
+
);
|
|
5004
|
+
drainBuffer.length = 0;
|
|
5005
|
+
}
|
|
4692
5006
|
controllerClosed = true;
|
|
4693
5007
|
cleanupTurn();
|
|
4694
5008
|
try {
|
|
@@ -4714,20 +5028,14 @@ ${plan}
|
|
|
4714
5028
|
});
|
|
4715
5029
|
resolvePendingProxyCallById(call.toolCallId, result);
|
|
4716
5030
|
} else {
|
|
4717
|
-
log.
|
|
4718
|
-
"
|
|
5031
|
+
log.info(
|
|
5032
|
+
"leaving unmatched parallel proxy call pending",
|
|
4719
5033
|
{
|
|
4720
5034
|
sessionKey: sk,
|
|
4721
5035
|
toolCallId: call.toolCallId,
|
|
4722
5036
|
toolName: call.toolName
|
|
4723
5037
|
}
|
|
4724
5038
|
);
|
|
4725
|
-
rejectPendingProxyCallById(
|
|
4726
|
-
call.toolCallId,
|
|
4727
|
-
new Error(
|
|
4728
|
-
`Pending proxy call '${call.toolName}' (${call.toolCallId}) was not matched in tool-result turn; rejecting as orphaned`
|
|
4729
|
-
)
|
|
4730
|
-
);
|
|
4731
5039
|
}
|
|
4732
5040
|
}
|
|
4733
5041
|
return;
|
|
@@ -4744,6 +5052,7 @@ ${plan}
|
|
|
4744
5052
|
}
|
|
4745
5053
|
proc.stdin?.write(userMsg + "\n");
|
|
4746
5054
|
log.debug("sent user message", { textLength: userMsg.length });
|
|
5055
|
+
armStartWatchdog();
|
|
4747
5056
|
};
|
|
4748
5057
|
void setup().catch((err) => {
|
|
4749
5058
|
log.error("failed to set up doStream", {
|
|
@@ -5227,6 +5536,107 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
5227
5536
|
}
|
|
5228
5537
|
}
|
|
5229
5538
|
|
|
5539
|
+
// src/startup-diagnostics.ts
|
|
5540
|
+
import * as fs5 from "fs";
|
|
5541
|
+
import * as path6 from "path";
|
|
5542
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5543
|
+
var cachedPluginVersion;
|
|
5544
|
+
function pluginVersion() {
|
|
5545
|
+
if (cachedPluginVersion) return cachedPluginVersion;
|
|
5546
|
+
try {
|
|
5547
|
+
const here = path6.dirname(fileURLToPath2(import.meta.url));
|
|
5548
|
+
const raw = fs5.readFileSync(path6.join(here, "..", "package.json"), "utf8");
|
|
5549
|
+
const version = JSON.parse(raw).version;
|
|
5550
|
+
cachedPluginVersion = typeof version === "string" ? version : "unknown";
|
|
5551
|
+
} catch {
|
|
5552
|
+
cachedPluginVersion = "unknown";
|
|
5553
|
+
}
|
|
5554
|
+
return cachedPluginVersion;
|
|
5555
|
+
}
|
|
5556
|
+
function pickOpencodeVersion(input) {
|
|
5557
|
+
if (!input || typeof input !== "object") return void 0;
|
|
5558
|
+
const app = input.app;
|
|
5559
|
+
if (app && typeof app === "object") {
|
|
5560
|
+
const version = app.version;
|
|
5561
|
+
if (typeof version === "string" && version.length > 0) return version;
|
|
5562
|
+
}
|
|
5563
|
+
const direct = input.version;
|
|
5564
|
+
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
5565
|
+
return void 0;
|
|
5566
|
+
}
|
|
5567
|
+
function describeSpawnCwd(configured, live = process.cwd(), captured = getOpencodeProjectDirectory()) {
|
|
5568
|
+
if (typeof configured === "string" && configured.length > 0) {
|
|
5569
|
+
return { resolved: configured, source: "configured" };
|
|
5570
|
+
}
|
|
5571
|
+
if (isUsableDirectory(live)) return { resolved: live, source: "process" };
|
|
5572
|
+
if (isUsableDirectory(captured)) return { resolved: captured, source: "captured" };
|
|
5573
|
+
return { resolved: live, source: "unresolved" };
|
|
5574
|
+
}
|
|
5575
|
+
function stringList(value) {
|
|
5576
|
+
if (!Array.isArray(value)) return [];
|
|
5577
|
+
return value.filter((entry) => typeof entry === "string");
|
|
5578
|
+
}
|
|
5579
|
+
function firstOption(providers, key) {
|
|
5580
|
+
for (const entry of Object.values(providers)) {
|
|
5581
|
+
const value = entry?.options?.[key];
|
|
5582
|
+
if (value !== void 0) return value;
|
|
5583
|
+
}
|
|
5584
|
+
return void 0;
|
|
5585
|
+
}
|
|
5586
|
+
function collectStartupDiagnostics(providers, opencodeVersion) {
|
|
5587
|
+
const accounts = [];
|
|
5588
|
+
for (const entry of Object.values(providers)) {
|
|
5589
|
+
const account = entry?.options?.account;
|
|
5590
|
+
if (typeof account === "string" && account.length > 0) accounts.push(account);
|
|
5591
|
+
}
|
|
5592
|
+
const cwd = describeSpawnCwd(firstOption(providers, "cwd"));
|
|
5593
|
+
let mcpServers = [];
|
|
5594
|
+
try {
|
|
5595
|
+
mcpServers = mergeOpencodeMcp(cwd.resolved).enabledServerNames;
|
|
5596
|
+
} catch (err) {
|
|
5597
|
+
log.debug("startup diagnostics could not read MCP config", {
|
|
5598
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
return {
|
|
5602
|
+
plugin: pluginVersion(),
|
|
5603
|
+
opencode: opencodeVersion ?? process.env.OPENCODE_VERSION ?? "unknown",
|
|
5604
|
+
claudeCliPath: String(firstOption(providers, "cliPath") ?? "claude"),
|
|
5605
|
+
cwd,
|
|
5606
|
+
providers: Object.keys(providers),
|
|
5607
|
+
accounts,
|
|
5608
|
+
proxyTools: stringList(firstOption(providers, "proxyTools")),
|
|
5609
|
+
mcpServers,
|
|
5610
|
+
interactiveTransport: firstOption(providers, "interactive") === true || process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT === "1",
|
|
5611
|
+
anthropicApiKeyInEnv: Boolean(
|
|
5612
|
+
process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN
|
|
5613
|
+
)
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
5616
|
+
var logged = false;
|
|
5617
|
+
function logStartupDiagnostics(providers, opencodeVersion) {
|
|
5618
|
+
if (logged) return;
|
|
5619
|
+
logged = true;
|
|
5620
|
+
void (async () => {
|
|
5621
|
+
try {
|
|
5622
|
+
const { claudeCliPath, ...rest } = collectStartupDiagnostics(
|
|
5623
|
+
providers,
|
|
5624
|
+
opencodeVersion
|
|
5625
|
+
);
|
|
5626
|
+
const cli = await detectCliVersion(claudeCliPath);
|
|
5627
|
+
const diagnostics = {
|
|
5628
|
+
...rest,
|
|
5629
|
+
claudeCli: { path: claudeCliPath, version: cli?.raw ?? "not detected" }
|
|
5630
|
+
};
|
|
5631
|
+
log.notice("claude-code plugin ready", { ...diagnostics });
|
|
5632
|
+
} catch (err) {
|
|
5633
|
+
log.debug("startup diagnostics failed", {
|
|
5634
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
})();
|
|
5638
|
+
}
|
|
5639
|
+
|
|
5230
5640
|
// src/index.ts
|
|
5231
5641
|
function pickOpencodeDirectory(input) {
|
|
5232
5642
|
if (!input || typeof input !== "object") return void 0;
|
|
@@ -5236,6 +5646,13 @@ function pickOpencodeDirectory(input) {
|
|
|
5236
5646
|
return void 0;
|
|
5237
5647
|
}
|
|
5238
5648
|
var warnedAnthropicApiKey = false;
|
|
5649
|
+
var DEFAULT_PROXY_TOOL_NAMES = [
|
|
5650
|
+
"Bash",
|
|
5651
|
+
"Edit",
|
|
5652
|
+
"Write",
|
|
5653
|
+
"WebFetch",
|
|
5654
|
+
"Task"
|
|
5655
|
+
];
|
|
5239
5656
|
function warnIfAnthropicApiKey(ignore) {
|
|
5240
5657
|
if (warnedAnthropicApiKey) return;
|
|
5241
5658
|
if (!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_AUTH_TOKEN) return;
|
|
@@ -5262,7 +5679,7 @@ function createClaudeCode(settings = {}) {
|
|
|
5262
5679
|
warnIfAnthropicApiKey(settings.ignoreAnthropicApiKey);
|
|
5263
5680
|
const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
|
|
5264
5681
|
const providerName = settings.providerID ?? settings.name ?? "claude-code";
|
|
5265
|
-
const proxyTools = settings.proxyTools ?? [
|
|
5682
|
+
const proxyTools = settings.proxyTools ?? [...DEFAULT_PROXY_TOOL_NAMES];
|
|
5266
5683
|
const createModel = (modelId) => {
|
|
5267
5684
|
return new ClaudeCodeLanguageModel(modelId, {
|
|
5268
5685
|
provider: providerName,
|
|
@@ -5280,6 +5697,7 @@ function createClaudeCode(settings = {}) {
|
|
|
5280
5697
|
controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
|
|
5281
5698
|
controlRequestDenyMessage: settings.controlRequestDenyMessage,
|
|
5282
5699
|
proxyTools,
|
|
5700
|
+
proxyToolTimeoutMs: settings.proxyToolTimeoutMs,
|
|
5283
5701
|
webSearch: settings.webSearch,
|
|
5284
5702
|
hotReloadMcp: settings.hotReloadMcp ?? true,
|
|
5285
5703
|
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
|
|
@@ -5374,7 +5792,7 @@ function configModelsForProvider(providerModels, providerID, modelSuffix) {
|
|
|
5374
5792
|
async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefaults = {}, displayName) {
|
|
5375
5793
|
const mergedOptions = {
|
|
5376
5794
|
cliPath: "claude",
|
|
5377
|
-
proxyTools: [
|
|
5795
|
+
proxyTools: [...DEFAULT_PROXY_TOOL_NAMES],
|
|
5378
5796
|
...optionDefaults,
|
|
5379
5797
|
...cleanProviderOptions(existing?.options),
|
|
5380
5798
|
providerID
|
|
@@ -5394,6 +5812,13 @@ async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefault
|
|
|
5394
5812
|
// opencode's config-path loader parses (and merges user variants).
|
|
5395
5813
|
};
|
|
5396
5814
|
}
|
|
5815
|
+
function claudeCodeProviders(providers) {
|
|
5816
|
+
const out = {};
|
|
5817
|
+
for (const [id, entry] of Object.entries(providers ?? {})) {
|
|
5818
|
+
if (id === PROVIDER_ID2 || id.startsWith(`${PROVIDER_ID2}-`)) out[id] = entry;
|
|
5819
|
+
}
|
|
5820
|
+
return out;
|
|
5821
|
+
}
|
|
5397
5822
|
async function expandAccountProviders(config) {
|
|
5398
5823
|
const seed = config.provider?.[PROVIDER_ID2];
|
|
5399
5824
|
const accounts = resolveAccounts(seed?.options?.accounts);
|
|
@@ -5439,6 +5864,7 @@ async function expandAccountProviders(config) {
|
|
|
5439
5864
|
}
|
|
5440
5865
|
var server = async (input) => {
|
|
5441
5866
|
cleanupStaleUnscopedInstall();
|
|
5867
|
+
const opencodeVersion = pickOpencodeVersion(input);
|
|
5442
5868
|
if (input && typeof input === "object" && "client" in input) {
|
|
5443
5869
|
setOpencodeClient(input.client);
|
|
5444
5870
|
}
|
|
@@ -5448,12 +5874,10 @@ var server = async (input) => {
|
|
|
5448
5874
|
config.provider ??= {};
|
|
5449
5875
|
const expanded = await expandAccountProviders(config);
|
|
5450
5876
|
if (expanded) {
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
}));
|
|
5456
|
-
log.notice("registered claude-code providers", { providers: registered2 });
|
|
5877
|
+
logStartupDiagnostics(
|
|
5878
|
+
claudeCodeProviders(config.provider),
|
|
5879
|
+
opencodeVersion
|
|
5880
|
+
);
|
|
5457
5881
|
return;
|
|
5458
5882
|
}
|
|
5459
5883
|
const existing = config.provider[PROVIDER_ID2];
|
|
@@ -5465,11 +5889,10 @@ var server = async (input) => {
|
|
|
5465
5889
|
PROVIDER_ID2
|
|
5466
5890
|
)
|
|
5467
5891
|
};
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
});
|
|
5892
|
+
logStartupDiagnostics(
|
|
5893
|
+
claudeCodeProviders(config.provider),
|
|
5894
|
+
opencodeVersion
|
|
5895
|
+
);
|
|
5473
5896
|
},
|
|
5474
5897
|
// No `event` hook: MCP config drift is detected at turn start by the
|
|
5475
5898
|
// hot-reload check in `claude-code-language-model.ts`, which respawns
|
|
@@ -5514,6 +5937,7 @@ var index_default = {
|
|
|
5514
5937
|
export {
|
|
5515
5938
|
ClaudeCodeLanguageModel,
|
|
5516
5939
|
bridgeOpencodeMcp,
|
|
5940
|
+
claudeCodeProviders,
|
|
5517
5941
|
configModelsForProvider,
|
|
5518
5942
|
createClaudeCode,
|
|
5519
5943
|
index_default as default,
|