@khalilgharbaoui/opencode-claude-code-plugin 0.20.0 → 0.22.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 +55 -5
- package/dist/index.d.ts +85 -15
- package/dist/index.js +286 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +37 -3
package/dist/index.js
CHANGED
|
@@ -1248,6 +1248,102 @@ function resolveDisallowedTools(options) {
|
|
|
1248
1248
|
if (options.disableWebSearch) push("WebSearch");
|
|
1249
1249
|
return out;
|
|
1250
1250
|
}
|
|
1251
|
+
function resolveProxyOpencodeToolDefs(options) {
|
|
1252
|
+
const requested = options.requested ?? [];
|
|
1253
|
+
if (requested.length === 0) return [];
|
|
1254
|
+
const items = options.items;
|
|
1255
|
+
if (!items) {
|
|
1256
|
+
log.warn(
|
|
1257
|
+
"proxyOpencodeTools is set but opencode's tool registry did not answer; forwarding nothing this spawn",
|
|
1258
|
+
{ requested: requested.map(String) }
|
|
1259
|
+
);
|
|
1260
|
+
return [];
|
|
1261
|
+
}
|
|
1262
|
+
const byLowerId = /* @__PURE__ */ new Map();
|
|
1263
|
+
for (const item of items) {
|
|
1264
|
+
const key = item.id.toLowerCase();
|
|
1265
|
+
if (!byLowerId.has(key)) byLowerId.set(key, item);
|
|
1266
|
+
}
|
|
1267
|
+
const taken = options.taken ?? /* @__PURE__ */ new Set();
|
|
1268
|
+
const out = [];
|
|
1269
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1270
|
+
const unknown = [];
|
|
1271
|
+
const collided = [];
|
|
1272
|
+
for (const raw of requested) {
|
|
1273
|
+
const name = String(raw).trim();
|
|
1274
|
+
if (!name) continue;
|
|
1275
|
+
const item = byLowerId.get(name.toLowerCase());
|
|
1276
|
+
if (!item) {
|
|
1277
|
+
unknown.push(name);
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (taken.has(item.id)) {
|
|
1281
|
+
collided.push(item.id);
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
if (seen.has(item.id)) continue;
|
|
1285
|
+
seen.add(item.id);
|
|
1286
|
+
out.push({
|
|
1287
|
+
name: item.id,
|
|
1288
|
+
description: typeof item.description === "string" ? item.description : "",
|
|
1289
|
+
inputSchema: item.parameters && typeof item.parameters === "object" ? item.parameters : { type: "object", properties: {} }
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
if (unknown.length > 0) {
|
|
1293
|
+
log.warn("ignoring unknown proxyOpencodeTools entries", {
|
|
1294
|
+
unknown,
|
|
1295
|
+
known: [...byLowerId.values()].map((item) => item.id).join(", ")
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (collided.length > 0) {
|
|
1299
|
+
log.warn(
|
|
1300
|
+
"proxyOpencodeTools entry dropped: a proxy tool already holds that name, and it keeps it",
|
|
1301
|
+
{ collided }
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
return out;
|
|
1305
|
+
}
|
|
1306
|
+
function resolveMcpProxyToolDefs(options) {
|
|
1307
|
+
const empty = { defs: [], coveredServers: /* @__PURE__ */ new Set() };
|
|
1308
|
+
const serverNames = options.serverNames ?? [];
|
|
1309
|
+
if (serverNames.length === 0) return empty;
|
|
1310
|
+
const tools = options.tools;
|
|
1311
|
+
if (!tools || tools.length === 0) return empty;
|
|
1312
|
+
const serversByLengthDesc = [...serverNames].sort((a, b) => b.length - a.length);
|
|
1313
|
+
const taken = options.taken ?? /* @__PURE__ */ new Set();
|
|
1314
|
+
const defs = [];
|
|
1315
|
+
const coveredServers = /* @__PURE__ */ new Set();
|
|
1316
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1317
|
+
const collided = [];
|
|
1318
|
+
for (const tool of tools) {
|
|
1319
|
+
if (tool?.type !== void 0 && tool.type !== "function") continue;
|
|
1320
|
+
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
1321
|
+
if (!name) continue;
|
|
1322
|
+
const matchedServer = serversByLengthDesc.find(
|
|
1323
|
+
(server2) => name === server2 || name.startsWith(`${server2}_`)
|
|
1324
|
+
);
|
|
1325
|
+
if (!matchedServer) continue;
|
|
1326
|
+
if (seen.has(name)) continue;
|
|
1327
|
+
if (taken.has(name)) {
|
|
1328
|
+
collided.push(name);
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
seen.add(name);
|
|
1332
|
+
coveredServers.add(matchedServer);
|
|
1333
|
+
defs.push({
|
|
1334
|
+
name,
|
|
1335
|
+
description: typeof tool.description === "string" ? tool.description : "",
|
|
1336
|
+
inputSchema: tool.inputSchema && typeof tool.inputSchema === "object" ? tool.inputSchema : { type: "object", properties: {} }
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
if (collided.length > 0) {
|
|
1340
|
+
log.warn(
|
|
1341
|
+
"MCP tool not routed through the proxy: another proxy tool already holds that name, and it keeps it",
|
|
1342
|
+
{ collided }
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
return { defs, coveredServers };
|
|
1346
|
+
}
|
|
1251
1347
|
function readBody(req) {
|
|
1252
1348
|
return new Promise((resolve5, reject) => {
|
|
1253
1349
|
const chunks = [];
|
|
@@ -1336,6 +1432,14 @@ function writeJson(res, body) {
|
|
|
1336
1432
|
|
|
1337
1433
|
// src/proxy-broker.ts
|
|
1338
1434
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
1435
|
+
var PROXY_STALL_WARNING_MS = 5 * 6e4;
|
|
1436
|
+
var PROXY_DEADLINE_WARNING_FRACTION = 0.6;
|
|
1437
|
+
var PROXY_DEADLINE_WARNING_MIN_MS = 6e4;
|
|
1438
|
+
function clearPendingTimers(pending) {
|
|
1439
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
1440
|
+
if (pending.stallTimer) clearInterval(pending.stallTimer);
|
|
1441
|
+
if (pending.deadlineWarnTimer) clearTimeout(pending.deadlineWarnTimer);
|
|
1442
|
+
}
|
|
1339
1443
|
var pendingByCallId = /* @__PURE__ */ new Map();
|
|
1340
1444
|
var callIdsBySession = /* @__PURE__ */ new Map();
|
|
1341
1445
|
var emitter = new EventEmitter2();
|
|
@@ -1361,10 +1465,10 @@ function onPendingProxyCall(sessionKey2, handler) {
|
|
|
1361
1465
|
emitter.on(name, handler);
|
|
1362
1466
|
return () => emitter.off(name, handler);
|
|
1363
1467
|
}
|
|
1364
|
-
function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
|
|
1468
|
+
function queuePendingProxyCall(sessionKey2, call, timeoutOverrides, stallWarningMs = PROXY_STALL_WARNING_MS, deadlineWarningMinMs = PROXY_DEADLINE_WARNING_MIN_MS) {
|
|
1365
1469
|
const previous = pendingByCallId.get(call.id);
|
|
1366
1470
|
if (previous) {
|
|
1367
|
-
|
|
1471
|
+
clearPendingTimers(previous);
|
|
1368
1472
|
previous.reject(
|
|
1369
1473
|
new Error(`Replaced pending proxy call ${call.id} with a fresh one`)
|
|
1370
1474
|
);
|
|
@@ -1381,6 +1485,7 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
|
|
|
1381
1485
|
if (!current) return;
|
|
1382
1486
|
pendingByCallId.delete(call.id);
|
|
1383
1487
|
indexRemove(current.sessionKey, call.id);
|
|
1488
|
+
clearPendingTimers(current);
|
|
1384
1489
|
current.reject(buildProxyTimeoutError(call.toolName, deadlineMs));
|
|
1385
1490
|
log.notice("timed out pending proxy call", {
|
|
1386
1491
|
sessionKey: current.sessionKey,
|
|
@@ -1389,6 +1494,38 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
|
|
|
1389
1494
|
deadlineMs
|
|
1390
1495
|
});
|
|
1391
1496
|
}, deadlineMs) : null;
|
|
1497
|
+
const stallTimer = deadlineMs === PROXY_NO_DEADLINE_MS && stallWarningMs > 0 ? setInterval(() => {
|
|
1498
|
+
const current = pendingByCallId.get(call.id);
|
|
1499
|
+
if (!current) return;
|
|
1500
|
+
log.warn("proxy call still waiting, no deadline", {
|
|
1501
|
+
sessionKey: current.sessionKey,
|
|
1502
|
+
toolCallId: current.toolCallId,
|
|
1503
|
+
toolName: current.toolName,
|
|
1504
|
+
waitedMs: Date.now() - current.createdAt,
|
|
1505
|
+
emitted: current.emitted === true,
|
|
1506
|
+
channelClosed: current.channel?.closed === true,
|
|
1507
|
+
note: "nothing will time this out; it ends when opencode returns a result, you abort, you send another message, or the claude process goes"
|
|
1508
|
+
});
|
|
1509
|
+
}, stallWarningMs) : null;
|
|
1510
|
+
stallTimer?.unref?.();
|
|
1511
|
+
const warnAtMs = Math.floor(deadlineMs * PROXY_DEADLINE_WARNING_FRACTION);
|
|
1512
|
+
const deadlineWarnTimer = deadlineMs >= deadlineWarningMinMs && deadlineMs > 0 && warnAtMs > 0 ? setTimeout(() => {
|
|
1513
|
+
const current = pendingByCallId.get(call.id);
|
|
1514
|
+
if (!current) return;
|
|
1515
|
+
const waitedMs = Date.now() - current.createdAt;
|
|
1516
|
+
log.warn("proxy call still waiting, deadline approaching", {
|
|
1517
|
+
sessionKey: current.sessionKey,
|
|
1518
|
+
toolCallId: current.toolCallId,
|
|
1519
|
+
toolName: current.toolName,
|
|
1520
|
+
waitedMs,
|
|
1521
|
+
deadlineMs,
|
|
1522
|
+
remainingMs: Math.max(0, deadlineMs - waitedMs),
|
|
1523
|
+
emitted: current.emitted === true,
|
|
1524
|
+
channelClosed: current.channel?.closed === true,
|
|
1525
|
+
note: "it will be rejected when the deadline passes; raise this tool's proxyToolTimeoutMs if the work is legitimately this long"
|
|
1526
|
+
});
|
|
1527
|
+
}, warnAtMs) : null;
|
|
1528
|
+
deadlineWarnTimer?.unref?.();
|
|
1392
1529
|
const pending = {
|
|
1393
1530
|
sessionKey: sessionKey2,
|
|
1394
1531
|
toolCallId: call.id,
|
|
@@ -1398,6 +1535,8 @@ function queuePendingProxyCall(sessionKey2, call, timeoutOverrides) {
|
|
|
1398
1535
|
createdAt: Date.now(),
|
|
1399
1536
|
deadlineMs,
|
|
1400
1537
|
timer,
|
|
1538
|
+
stallTimer,
|
|
1539
|
+
deadlineWarnTimer,
|
|
1401
1540
|
resolve: call.resolve,
|
|
1402
1541
|
reject: call.reject
|
|
1403
1542
|
};
|
|
@@ -1448,7 +1587,7 @@ function resolvePendingProxyCallById(toolCallId, result) {
|
|
|
1448
1587
|
if (!pending) return false;
|
|
1449
1588
|
pendingByCallId.delete(toolCallId);
|
|
1450
1589
|
indexRemove(pending.sessionKey, toolCallId);
|
|
1451
|
-
|
|
1590
|
+
clearPendingTimers(pending);
|
|
1452
1591
|
pending.resolve(result);
|
|
1453
1592
|
log.info("resolved pending proxy call", {
|
|
1454
1593
|
sessionKey: pending.sessionKey,
|
|
@@ -1462,7 +1601,7 @@ function rejectPendingProxyCallById(toolCallId, error) {
|
|
|
1462
1601
|
if (!pending) return false;
|
|
1463
1602
|
pendingByCallId.delete(toolCallId);
|
|
1464
1603
|
indexRemove(pending.sessionKey, toolCallId);
|
|
1465
|
-
|
|
1604
|
+
clearPendingTimers(pending);
|
|
1466
1605
|
pending.reject(error);
|
|
1467
1606
|
log.notice("rejected pending proxy call", {
|
|
1468
1607
|
sessionKey: pending.sessionKey,
|
|
@@ -3840,6 +3979,35 @@ function filterSideQuestionHistory(prompt) {
|
|
|
3840
3979
|
(message) => message.role === "assistant" ? { ...message, content: stripPluginNotes(message.content) } : message
|
|
3841
3980
|
);
|
|
3842
3981
|
}
|
|
3982
|
+
var DCP_REMINDER_BLOCK = /<dcp-system-reminder\b[^>]*>[\s\S]*?<\/dcp-system-reminder>/gi;
|
|
3983
|
+
function stripContextReminderBlocks(text) {
|
|
3984
|
+
if (!text.includes("<dcp-system-reminder")) return text;
|
|
3985
|
+
return text.replace(DCP_REMINDER_BLOCK, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
3986
|
+
}
|
|
3987
|
+
function shouldStripContextReminders(options) {
|
|
3988
|
+
if (options.enabled !== true) return false;
|
|
3989
|
+
const namesCompress = (list2) => (list2 ?? []).some((name) => String(name).trim().toLowerCase() === "compress");
|
|
3990
|
+
return !namesCompress(options.proxyTools) && !namesCompress(options.proxyOpencodeTools);
|
|
3991
|
+
}
|
|
3992
|
+
function stripContextReminders(prompt) {
|
|
3993
|
+
let removed = 0;
|
|
3994
|
+
const countIn = (text) => (text.match(DCP_REMINDER_BLOCK) ?? []).length;
|
|
3995
|
+
const out = prompt.map((message) => {
|
|
3996
|
+
if (message.role !== "user" && message.role !== "assistant") return message;
|
|
3997
|
+
if (!Array.isArray(message.content)) return message;
|
|
3998
|
+
let touched = false;
|
|
3999
|
+
const parts = message.content.map((part) => {
|
|
4000
|
+
if (!part || part.type !== "text" || typeof part.text !== "string") return part;
|
|
4001
|
+
const hits = countIn(part.text);
|
|
4002
|
+
if (hits === 0) return part;
|
|
4003
|
+
removed += hits;
|
|
4004
|
+
touched = true;
|
|
4005
|
+
return { ...part, text: stripContextReminderBlocks(part.text) };
|
|
4006
|
+
});
|
|
4007
|
+
return touched ? { ...message, content: parts } : message;
|
|
4008
|
+
});
|
|
4009
|
+
return removed > 0 ? { prompt: out, removed } : { prompt, removed: 0 };
|
|
4010
|
+
}
|
|
3843
4011
|
var SUPPORTED_IMAGE_TYPES = /* @__PURE__ */ new Set([
|
|
3844
4012
|
"image/jpeg",
|
|
3845
4013
|
"image/png",
|
|
@@ -4039,6 +4207,15 @@ function getClaudeUserMessage(prompt, includeHistoryContext = false, opts = {})
|
|
|
4039
4207
|
const compactionMode = opts.compactionMode === true;
|
|
4040
4208
|
const cliToolCallIds = opts.cliToolCallIds;
|
|
4041
4209
|
const content = [];
|
|
4210
|
+
if (opts.stripContextReminders) {
|
|
4211
|
+
const stripped = stripContextReminders(prompt);
|
|
4212
|
+
if (stripped.removed > 0) {
|
|
4213
|
+
log.info("stripped unsatisfiable context reminders", {
|
|
4214
|
+
blocks: stripped.removed
|
|
4215
|
+
});
|
|
4216
|
+
prompt = stripped.prompt;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4042
4219
|
const pushToolResult = (part) => {
|
|
4043
4220
|
const id = part.toolCallId;
|
|
4044
4221
|
const text = getToolResultText(part);
|
|
@@ -5592,6 +5769,14 @@ You are running via the Claude Code CLI (not a direct API call). This affects co
|
|
|
5592
5769
|
- Everything outside the summary is gone after the reset \u2014 tool output, files you read, and the earlier conversation are not replayed. Write the summary as the authoritative record.
|
|
5593
5770
|
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
5594
5771
|
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
5772
|
+
var CLAUDE_CLI_OPENCODE_COMPRESS_NOTE = `## Runtime environment: Claude Code CLI
|
|
5773
|
+
|
|
5774
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
5775
|
+
|
|
5776
|
+
- To compress context, call \`mcp__opencode_proxy__compress\`. Use that exact full name. It runs opencode's own \`compress\` tool, which is what a "MAX CONTEXT LIMIT REACHED" reminder is asking you to do.
|
|
5777
|
+
- It compresses opencode's stored conversation, NOT this Claude Code session. Your current session keeps the context it already has, so do not assume earlier detail is gone after the call.
|
|
5778
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
5779
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
5595
5780
|
function extractSystemMessages(prompt) {
|
|
5596
5781
|
const out = [];
|
|
5597
5782
|
for (const msg of prompt) {
|
|
@@ -5618,7 +5803,7 @@ ${options.compressionSummary.trim()}`
|
|
|
5618
5803
|
);
|
|
5619
5804
|
}
|
|
5620
5805
|
parts.push(
|
|
5621
|
-
options.compressEnabled ? CLAUDE_CLI_COMPRESS_NOTE : CLAUDE_CLI_CONTEXT_NOTE
|
|
5806
|
+
options.compressEnabled ? CLAUDE_CLI_COMPRESS_NOTE : options.opencodeCompressEnabled ? CLAUDE_CLI_OPENCODE_COMPRESS_NOTE : CLAUDE_CLI_CONTEXT_NOTE
|
|
5622
5807
|
);
|
|
5623
5808
|
for (const s of extraSystemContent) {
|
|
5624
5809
|
if (s.trim()) parts.push(s.trim());
|
|
@@ -5798,43 +5983,46 @@ var ClaudeCodeLanguageModel = class {
|
|
|
5798
5983
|
return picked.length > 0 ? picked : null;
|
|
5799
5984
|
}
|
|
5800
5985
|
/**
|
|
5801
|
-
* Resolve ProxyToolDef[] for opencode's MCP-
|
|
5986
|
+
* Resolve ProxyToolDef[] for opencode's MCP-backed tools so they go
|
|
5802
5987
|
* through the in-process proxy instead of being bridged into Claude CLI's
|
|
5803
|
-
* `--mcp-config`.
|
|
5804
|
-
*
|
|
5805
|
-
*
|
|
5806
|
-
*
|
|
5807
|
-
*
|
|
5988
|
+
* `--mcp-config`. Routing through the proxy keeps a single execution site
|
|
5989
|
+
* (opencode), so the call is permission-prompted and rendered as an
|
|
5990
|
+
* opencode tool call.
|
|
5991
|
+
*
|
|
5992
|
+
* Opt-in (`proxyOpencodeMcpTools: true`) and off by default. It used to
|
|
5993
|
+
* default to true while finding nothing, because it discovered tools via
|
|
5994
|
+
* `client.tool.list()`, which enumerates opencode's `ToolRegistry` and not
|
|
5995
|
+
* the MCP tools merged into the model's tool set afterwards. Discovery now
|
|
5996
|
+
* reads that merged set, the `tools` array opencode passes `doStream`, so
|
|
5997
|
+
* the option does what it says. Turning it on by default at the same time
|
|
5998
|
+
* would have silently moved every existing user's MCP traffic off the
|
|
5999
|
+
* working direct bridge, so the default went to false instead: today's
|
|
6000
|
+
* behaviour is preserved exactly and crossing over is the operator's call.
|
|
6001
|
+
*
|
|
6002
|
+
* Returns null when the feature is off or nothing matched, which leaves
|
|
6003
|
+
* every server on the direct bridge.
|
|
5808
6004
|
*/
|
|
5809
|
-
|
|
5810
|
-
if (this.config.proxyOpencodeMcpTools
|
|
6005
|
+
resolvedProxyMcpTools(allEnabledServerNames, modelTools, taken) {
|
|
6006
|
+
if (this.config.proxyOpencodeMcpTools !== true) return null;
|
|
5811
6007
|
if (this.config.bridgeOpencodeMcp === false) return null;
|
|
5812
6008
|
if (allEnabledServerNames.length === 0) return null;
|
|
5813
|
-
const
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
);
|
|
5818
|
-
if (
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
const out = [];
|
|
5823
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5824
|
-
for (const item of items) {
|
|
5825
|
-
const matchedServer = serversByLengthDesc.find(
|
|
5826
|
-
(name) => item.id === name || item.id.startsWith(`${name}_`)
|
|
6009
|
+
const resolution = resolveMcpProxyToolDefs({
|
|
6010
|
+
serverNames: allEnabledServerNames,
|
|
6011
|
+
tools: modelTools,
|
|
6012
|
+
taken
|
|
6013
|
+
});
|
|
6014
|
+
if (resolution.defs.length === 0) {
|
|
6015
|
+
log.warn(
|
|
6016
|
+
"proxyOpencodeMcpTools is on but no MCP tool was found in opencode's tool set; those servers stay on the direct bridge this spawn",
|
|
6017
|
+
{ servers: allEnabledServerNames, modelTools: modelTools?.length ?? 0 }
|
|
5827
6018
|
);
|
|
5828
|
-
|
|
5829
|
-
if (seen.has(item.id)) continue;
|
|
5830
|
-
seen.add(item.id);
|
|
5831
|
-
out.push({
|
|
5832
|
-
name: item.id,
|
|
5833
|
-
description: item.description ?? "",
|
|
5834
|
-
inputSchema: item.parameters && typeof item.parameters === "object" ? item.parameters : { type: "object", properties: {} }
|
|
5835
|
-
});
|
|
6019
|
+
return null;
|
|
5836
6020
|
}
|
|
5837
|
-
|
|
6021
|
+
log.debug("routing opencode MCP tools through the proxy", {
|
|
6022
|
+
servers: [...resolution.coveredServers],
|
|
6023
|
+
tools: resolution.defs.map((def) => def.name)
|
|
6024
|
+
});
|
|
6025
|
+
return resolution;
|
|
5838
6026
|
}
|
|
5839
6027
|
/**
|
|
5840
6028
|
* Live tool info derived from a single `client.tool.list()` fetch:
|
|
@@ -5865,9 +6053,22 @@ var ClaudeCodeLanguageModel = class {
|
|
|
5865
6053
|
resolved: items !== void 0,
|
|
5866
6054
|
taskDescription: items?.find((item) => item.id === "task")?.description,
|
|
5867
6055
|
questionDescription: question?.description,
|
|
5868
|
-
hasQuestion: !!question
|
|
6056
|
+
hasQuestion: !!question,
|
|
6057
|
+
items
|
|
5869
6058
|
};
|
|
5870
6059
|
}
|
|
6060
|
+
/**
|
|
6061
|
+
* Whether dcp-style context reminders should be stripped from this turn's
|
|
6062
|
+
* messages. Config-only and synchronous, so it can be answered before the
|
|
6063
|
+
* spawn block resolves anything: `userMsg` is built well ahead of it.
|
|
6064
|
+
*/
|
|
6065
|
+
stripContextRemindersEnabled() {
|
|
6066
|
+
return shouldStripContextReminders({
|
|
6067
|
+
enabled: this.config.stripContextReminders,
|
|
6068
|
+
proxyTools: this.config.proxyTools,
|
|
6069
|
+
proxyOpencodeTools: this.config.proxyOpencodeTools
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
5871
6072
|
/** Share one lazy registry request within a turn without making it stale. */
|
|
5872
6073
|
createLiveToolInfoLoader() {
|
|
5873
6074
|
let pending;
|
|
@@ -5903,10 +6104,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
5903
6104
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
5904
6105
|
* The process lifecycle owns the server lifecycle via session-manager.
|
|
5905
6106
|
*/
|
|
5906
|
-
async ensureProxyServer(tools, sessionKeyForCalls) {
|
|
6107
|
+
async ensureProxyServer(tools, sessionKeyForCalls, interceptCompress) {
|
|
5907
6108
|
const timeoutOverrides = this.config.proxyToolTimeoutMs;
|
|
5908
6109
|
const interceptors = /* @__PURE__ */ new Map();
|
|
5909
|
-
if (tools.some((t) => t.name === "compress")) {
|
|
6110
|
+
if (interceptCompress && tools.some((t) => t.name === "compress")) {
|
|
5910
6111
|
interceptors.set("compress", (input) => {
|
|
5911
6112
|
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
5912
6113
|
if (!summary) {
|
|
@@ -6284,7 +6485,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6284
6485
|
);
|
|
6285
6486
|
const sk = effortSessionKey(baseKey, reasoningEffort);
|
|
6286
6487
|
const compactionMode = this.isCompactionCall(options);
|
|
6287
|
-
if (scope === "tools" && (this.resolvedProxyTools() || this.config.proxyOpencodeMcpTools
|
|
6488
|
+
if (scope === "tools" && (this.resolvedProxyTools() || this.config.proxyOpencodeMcpTools === true && this.config.bridgeOpencodeMcp !== false)) {
|
|
6288
6489
|
return this.doGenerateViaStream(options);
|
|
6289
6490
|
}
|
|
6290
6491
|
if (compactionMode) {
|
|
@@ -6347,7 +6548,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6347
6548
|
// at all: every tool result reaching it belongs to opencode and must be
|
|
6348
6549
|
// rendered as text rather than an orphaned `tool_result` (issue #29).
|
|
6349
6550
|
getClaudeUserMessage(options.prompt, includeHistoryContext, {
|
|
6350
|
-
cliToolCallIds: /* @__PURE__ */ new Set()
|
|
6551
|
+
cliToolCallIds: /* @__PURE__ */ new Set(),
|
|
6552
|
+
stripContextReminders: this.stripContextRemindersEnabled()
|
|
6351
6553
|
});
|
|
6352
6554
|
const [runtimeStatus, cliVersion, planModeQuestionActive] = await Promise.all([
|
|
6353
6555
|
getRuntimeMcpStatus(),
|
|
@@ -6866,7 +7068,8 @@ ${plan}
|
|
|
6866
7068
|
const previousPendingProxyCalls = compactionMode ? [] : getPendingProxyCalls(sk);
|
|
6867
7069
|
const userMsg = exitPlanModeQuestionResult ?? getClaudeUserMessage(options.prompt, includeHistoryContext, {
|
|
6868
7070
|
compactionMode,
|
|
6869
|
-
cliToolCallIds: new Set(previousPendingProxyCalls.map((c) => c.toolCallId))
|
|
7071
|
+
cliToolCallIds: new Set(previousPendingProxyCalls.map((c) => c.toolCallId)),
|
|
7072
|
+
stripContextReminders: this.stripContextRemindersEnabled()
|
|
6870
7073
|
});
|
|
6871
7074
|
const resolvedProxy = compactionMode ? null : this.resolvedProxyTools();
|
|
6872
7075
|
const loadLiveToolInfo = this.createLiveToolInfoLoader();
|
|
@@ -7028,13 +7231,21 @@ ${plan}
|
|
|
7028
7231
|
void 0,
|
|
7029
7232
|
runtimeStatus
|
|
7030
7233
|
);
|
|
7031
|
-
const
|
|
7032
|
-
discovery.allEnabledServerNames
|
|
7234
|
+
const mcpResolution = self.resolvedProxyMcpTools(
|
|
7235
|
+
discovery.allEnabledServerNames,
|
|
7236
|
+
options.tools,
|
|
7237
|
+
new Set((resolvedProxy ?? []).map((def) => def.name))
|
|
7033
7238
|
);
|
|
7034
|
-
const
|
|
7239
|
+
const proxyMcpTools = mcpResolution?.defs ?? null;
|
|
7240
|
+
const excludeServers = mcpResolution ? mcpResolution.coveredServers : void 0;
|
|
7035
7241
|
const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
|
|
7036
7242
|
const questionProxyEnabled = resolvedProxy?.some((t) => t.name === "question") ?? false;
|
|
7037
|
-
const
|
|
7243
|
+
const opencodeToolsRequested = (self.config.proxyOpencodeTools?.length ?? 0) > 0;
|
|
7244
|
+
log.debug("opencode tool forwarding gate", {
|
|
7245
|
+
requested: self.config.proxyOpencodeTools ?? null,
|
|
7246
|
+
opencodeToolsRequested
|
|
7247
|
+
});
|
|
7248
|
+
const liveToolInfo = taskProxyEnabled || questionProxyEnabled || opencodeToolsRequested ? await loadLiveToolInfo() : {
|
|
7038
7249
|
resolved: false,
|
|
7039
7250
|
taskDescription: void 0,
|
|
7040
7251
|
questionDescription: void 0,
|
|
@@ -7070,13 +7281,33 @@ ${plan}
|
|
|
7070
7281
|
kept: liveToolInfo.hasQuestion
|
|
7071
7282
|
});
|
|
7072
7283
|
}
|
|
7284
|
+
const opencodeToolDefs = resolveProxyOpencodeToolDefs({
|
|
7285
|
+
requested: self.config.proxyOpencodeTools,
|
|
7286
|
+
items: liveToolInfo.items,
|
|
7287
|
+
taken: new Set(
|
|
7288
|
+
[...enrichedProxy ?? [], ...proxyMcpTools ?? []].map(
|
|
7289
|
+
(t) => t.name
|
|
7290
|
+
)
|
|
7291
|
+
)
|
|
7292
|
+
});
|
|
7293
|
+
if (opencodeToolDefs.length > 0) {
|
|
7294
|
+
log.info("forwarding opencode tools through the proxy", {
|
|
7295
|
+
tools: opencodeToolDefs.map((t) => t.name)
|
|
7296
|
+
});
|
|
7297
|
+
}
|
|
7073
7298
|
const combinedList = [
|
|
7074
7299
|
...enrichedProxy ?? [],
|
|
7075
|
-
...proxyMcpTools ?? []
|
|
7300
|
+
...proxyMcpTools ?? [],
|
|
7301
|
+
...opencodeToolDefs
|
|
7076
7302
|
];
|
|
7077
7303
|
const combinedProxyTools = combinedList.length > 0 ? combinedList : null;
|
|
7304
|
+
const pluginCompressEnabled = enrichedProxy?.some((t) => t.name === "compress") ?? false;
|
|
7078
7305
|
if (!proxyServer && combinedProxyTools) {
|
|
7079
|
-
proxyServer = await self.ensureProxyServer(
|
|
7306
|
+
proxyServer = await self.ensureProxyServer(
|
|
7307
|
+
combinedProxyTools,
|
|
7308
|
+
sk,
|
|
7309
|
+
pluginCompressEnabled
|
|
7310
|
+
);
|
|
7080
7311
|
}
|
|
7081
7312
|
const questionProxyActive = enrichedProxy?.some((t) => t.name === "question") ?? false;
|
|
7082
7313
|
const allDisallowed = resolveDisallowedTools({
|
|
@@ -7099,7 +7330,10 @@ ${plan}
|
|
|
7099
7330
|
...questionProxyActive ? [QUESTION_PROXY_HINT] : []
|
|
7100
7331
|
],
|
|
7101
7332
|
{
|
|
7102
|
-
compressEnabled:
|
|
7333
|
+
compressEnabled: pluginCompressEnabled,
|
|
7334
|
+
opencodeCompressEnabled: opencodeToolDefs.some(
|
|
7335
|
+
(t) => t.name === "compress"
|
|
7336
|
+
),
|
|
7103
7337
|
compressionSummary: getCompressionSummary(sk)
|
|
7104
7338
|
}
|
|
7105
7339
|
);
|
|
@@ -8923,12 +9157,14 @@ function createClaudeCode(settings = {}) {
|
|
|
8923
9157
|
controlRequestToolBehaviors: settings.controlRequestToolBehaviors,
|
|
8924
9158
|
controlRequestDenyMessage: settings.controlRequestDenyMessage,
|
|
8925
9159
|
proxyTools,
|
|
9160
|
+
proxyOpencodeTools: settings.proxyOpencodeTools,
|
|
9161
|
+
stripContextReminders: settings.stripContextReminders === true,
|
|
8926
9162
|
extraDisallowedTools: settings.extraDisallowedTools,
|
|
8927
9163
|
proxyToolTimeoutMs: settings.proxyToolTimeoutMs,
|
|
8928
9164
|
planModeQuestion: settings.planModeQuestion ?? false,
|
|
8929
9165
|
webSearch: settings.webSearch,
|
|
8930
9166
|
hotReloadMcp: settings.hotReloadMcp ?? true,
|
|
8931
|
-
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools
|
|
9167
|
+
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools === true,
|
|
8932
9168
|
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
8933
9169
|
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart",
|
|
8934
9170
|
compactionModel: settings.compactionModel,
|