@khalilgharbaoui/opencode-claude-code-plugin 0.4.18 → 0.4.20
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 +49 -2
- package/dist/index.d.ts +48 -0
- package/dist/index.js +457 -78
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -198,7 +198,12 @@ var OPENCODE_HANDLED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
198
198
|
var CLAUDE_INTERNAL_TOOLS = /* @__PURE__ */ new Set([
|
|
199
199
|
"ToolSearch",
|
|
200
200
|
"Agent",
|
|
201
|
-
"AskFollowupQuestion"
|
|
201
|
+
"AskFollowupQuestion",
|
|
202
|
+
"TaskCreate",
|
|
203
|
+
"TaskUpdate",
|
|
204
|
+
"TaskList",
|
|
205
|
+
"TaskGet",
|
|
206
|
+
"TaskStop"
|
|
202
207
|
]);
|
|
203
208
|
function mapTool(name, input, opts) {
|
|
204
209
|
if (CLAUDE_INTERNAL_TOOLS.has(name)) {
|
|
@@ -340,7 +345,74 @@ function getToolResultText(part) {
|
|
|
340
345
|
return JSON.stringify(value);
|
|
341
346
|
}
|
|
342
347
|
}
|
|
343
|
-
|
|
348
|
+
var MAX_HISTORY_CHARS = 18e4;
|
|
349
|
+
var MAX_TOOL_RESULT_CHARS = 1e4;
|
|
350
|
+
var MAX_TOOL_INPUT_CHARS = 2e3;
|
|
351
|
+
function clipWithMarker(text, max) {
|
|
352
|
+
if (text.length <= max) return text;
|
|
353
|
+
return `${text.slice(0, max)}
|
|
354
|
+
\u2026[truncated ${text.length - max} chars]`;
|
|
355
|
+
}
|
|
356
|
+
function renderToolInput(input) {
|
|
357
|
+
let raw;
|
|
358
|
+
try {
|
|
359
|
+
raw = typeof input === "string" ? input : JSON.stringify(input);
|
|
360
|
+
} catch {
|
|
361
|
+
raw = String(input);
|
|
362
|
+
}
|
|
363
|
+
return clipWithMarker(raw, MAX_TOOL_INPUT_CHARS);
|
|
364
|
+
}
|
|
365
|
+
function renderMessageContentForCompaction(msg) {
|
|
366
|
+
const lines = [];
|
|
367
|
+
let toolResultCount = 0;
|
|
368
|
+
if (typeof msg.content === "string") {
|
|
369
|
+
return { text: msg.content, toolResultCount: 0 };
|
|
370
|
+
}
|
|
371
|
+
if (!Array.isArray(msg.content)) {
|
|
372
|
+
return { text: "", toolResultCount: 0 };
|
|
373
|
+
}
|
|
374
|
+
for (const part of msg.content) {
|
|
375
|
+
if (!part) continue;
|
|
376
|
+
switch (part.type) {
|
|
377
|
+
case "text":
|
|
378
|
+
if (part.text) lines.push(part.text);
|
|
379
|
+
break;
|
|
380
|
+
case "tool-call":
|
|
381
|
+
lines.push(
|
|
382
|
+
`[tool_use:${part.toolName ?? "unknown"}(${renderToolInput(part.input)})]`
|
|
383
|
+
);
|
|
384
|
+
break;
|
|
385
|
+
case "tool-result":
|
|
386
|
+
toolResultCount++;
|
|
387
|
+
lines.push(
|
|
388
|
+
`[tool_result:${part.toolName ?? part.toolCallId ?? "unknown"}]
|
|
389
|
+
${clipWithMarker(
|
|
390
|
+
getToolResultText(part),
|
|
391
|
+
MAX_TOOL_RESULT_CHARS
|
|
392
|
+
)}`
|
|
393
|
+
);
|
|
394
|
+
break;
|
|
395
|
+
case "image":
|
|
396
|
+
lines.push(
|
|
397
|
+
`[image: ${part.mediaType ?? part.mimeType ?? "unknown"}]`
|
|
398
|
+
);
|
|
399
|
+
break;
|
|
400
|
+
case "file":
|
|
401
|
+
lines.push(
|
|
402
|
+
`[file: ${part.mediaType ?? part.mimeType ?? "unknown"}]`
|
|
403
|
+
);
|
|
404
|
+
break;
|
|
405
|
+
case "reasoning":
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { text: lines.join("\n"), toolResultCount };
|
|
410
|
+
}
|
|
411
|
+
function compactConversationHistory(prompt, opts = {}) {
|
|
412
|
+
const mode = opts.mode ?? "fresh-session";
|
|
413
|
+
if (mode === "compaction") {
|
|
414
|
+
return buildCompactionHistory(prompt);
|
|
415
|
+
}
|
|
344
416
|
const conversationMessages = prompt.filter(
|
|
345
417
|
(m) => m.role === "user" || m.role === "assistant"
|
|
346
418
|
);
|
|
@@ -382,9 +454,59 @@ function compactConversationHistory(prompt) {
|
|
|
382
454
|
}
|
|
383
455
|
return historyParts.join("\n\n");
|
|
384
456
|
}
|
|
385
|
-
function
|
|
457
|
+
function buildCompactionHistory(prompt) {
|
|
458
|
+
const entries = [];
|
|
459
|
+
let total = 0;
|
|
460
|
+
let totalToolResults = 0;
|
|
461
|
+
let droppedOldest = 0;
|
|
462
|
+
const end = prompt.length > 0 && prompt[prompt.length - 1].role === "user" ? prompt.length - 1 : prompt.length;
|
|
463
|
+
for (let i = end - 1; i >= 0; i--) {
|
|
464
|
+
const msg = prompt[i];
|
|
465
|
+
const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : msg.role === "tool" ? "Tool" : msg.role;
|
|
466
|
+
const { text, toolResultCount } = renderMessageContentForCompaction(msg);
|
|
467
|
+
if (!text.trim()) continue;
|
|
468
|
+
const entry = `${roleLabel}: ${text}`;
|
|
469
|
+
if (total + entry.length > MAX_HISTORY_CHARS) {
|
|
470
|
+
droppedOldest = i + 1;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
entries.push(entry);
|
|
474
|
+
total += entry.length + 2;
|
|
475
|
+
totalToolResults += toolResultCount;
|
|
476
|
+
}
|
|
477
|
+
if (entries.length === 0) return null;
|
|
478
|
+
entries.reverse();
|
|
479
|
+
log.info("built compaction history", {
|
|
480
|
+
entries: entries.length,
|
|
481
|
+
chars: total,
|
|
482
|
+
toolResults: totalToolResults,
|
|
483
|
+
droppedOldestBefore: droppedOldest
|
|
484
|
+
});
|
|
485
|
+
return entries.join("\n\n");
|
|
486
|
+
}
|
|
487
|
+
function getClaudeUserMessage(prompt, includeHistoryContext = false, reasoningEffort, opts = {}) {
|
|
488
|
+
const compactionMode = opts.compactionMode === true;
|
|
386
489
|
const content = [];
|
|
387
|
-
if (
|
|
490
|
+
if (compactionMode) {
|
|
491
|
+
const transcript = compactConversationHistory(prompt, {
|
|
492
|
+
mode: "compaction"
|
|
493
|
+
});
|
|
494
|
+
if (transcript) {
|
|
495
|
+
log.info("including compaction transcript", {
|
|
496
|
+
historyLength: transcript.length
|
|
497
|
+
});
|
|
498
|
+
content.push({
|
|
499
|
+
type: "text",
|
|
500
|
+
text: `<conversation_transcript>
|
|
501
|
+
${transcript}
|
|
502
|
+
</conversation_transcript>
|
|
503
|
+
|
|
504
|
+
The complete prior conversation appears above. The synthesis instructions follow below.
|
|
505
|
+
|
|
506
|
+
`
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
} else if (includeHistoryContext) {
|
|
388
510
|
const historyContext = compactConversationHistory(prompt);
|
|
389
511
|
if (historyContext) {
|
|
390
512
|
log.info("including conversation history context", {
|
|
@@ -467,17 +589,19 @@ Now continuing with the current message:
|
|
|
467
589
|
}
|
|
468
590
|
});
|
|
469
591
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
592
|
+
if (!compactionMode) {
|
|
593
|
+
const keyword = reasoningKeyword(reasoningEffort);
|
|
594
|
+
if (keyword) {
|
|
595
|
+
const lastTextPart = [...content].reverse().find((p) => p.type === "text");
|
|
596
|
+
if (lastTextPart) {
|
|
597
|
+
lastTextPart.text = lastTextPart.text ? `${lastTextPart.text}
|
|
475
598
|
|
|
476
599
|
(${keyword})` : `(${keyword})`;
|
|
477
|
-
|
|
478
|
-
|
|
600
|
+
} else {
|
|
601
|
+
content.push({ type: "text", text: `(${keyword})` });
|
|
602
|
+
}
|
|
603
|
+
log.debug("injected reasoning keyword", { effort: reasoningEffort, keyword });
|
|
479
604
|
}
|
|
480
|
-
log.debug("injected reasoning keyword", { effort: reasoningEffort, keyword });
|
|
481
605
|
}
|
|
482
606
|
return JSON.stringify({
|
|
483
607
|
type: "user",
|
|
@@ -922,9 +1046,87 @@ import { spawn } from "child_process";
|
|
|
922
1046
|
import { createInterface } from "readline";
|
|
923
1047
|
import { EventEmitter } from "events";
|
|
924
1048
|
import { unlink } from "fs/promises";
|
|
1049
|
+
|
|
1050
|
+
// src/cli-version.ts
|
|
1051
|
+
import { execFile } from "child_process";
|
|
1052
|
+
import { promisify } from "util";
|
|
1053
|
+
var execFileAsync = promisify(execFile);
|
|
1054
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1055
|
+
function detectCliVersion(cliPath) {
|
|
1056
|
+
const cached = cache.get(cliPath);
|
|
1057
|
+
if (cached) return cached;
|
|
1058
|
+
const promise = (async () => {
|
|
1059
|
+
try {
|
|
1060
|
+
const { stdout } = await execFileAsync(cliPath, ["--version"], {
|
|
1061
|
+
timeout: 5e3
|
|
1062
|
+
});
|
|
1063
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(stdout.trim());
|
|
1064
|
+
if (!match) {
|
|
1065
|
+
log.warn("claude --version output unparseable", { stdout: stdout.trim() });
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
const v = {
|
|
1069
|
+
major: Number(match[1]),
|
|
1070
|
+
minor: Number(match[2]),
|
|
1071
|
+
patch: Number(match[3]),
|
|
1072
|
+
raw: stdout.trim()
|
|
1073
|
+
};
|
|
1074
|
+
log.info("detected claude cli version", { cliPath, version: v.raw });
|
|
1075
|
+
if (!cliSupportsThinkingDisplay(v)) {
|
|
1076
|
+
log.notice(
|
|
1077
|
+
"claude cli < 2.1.142 detected; Opus 4.7 thinking summaries unavailable. Run `npm i -g @anthropic-ai/claude-code` to upgrade.",
|
|
1078
|
+
{ version: v.raw }
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return v;
|
|
1082
|
+
} catch (err) {
|
|
1083
|
+
log.warn("failed to detect claude cli version", {
|
|
1084
|
+
cliPath,
|
|
1085
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1086
|
+
});
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
})();
|
|
1090
|
+
cache.set(cliPath, promise);
|
|
1091
|
+
return promise;
|
|
1092
|
+
}
|
|
1093
|
+
function gte(v, target) {
|
|
1094
|
+
if (v.major !== target.major) return v.major > target.major;
|
|
1095
|
+
if (v.minor !== target.minor) return v.minor > target.minor;
|
|
1096
|
+
return v.patch >= target.patch;
|
|
1097
|
+
}
|
|
1098
|
+
function cliSupportsThinkingDisplay(v) {
|
|
1099
|
+
if (!v) return false;
|
|
1100
|
+
return gte(v, { major: 2, minor: 1, patch: 142 });
|
|
1101
|
+
}
|
|
1102
|
+
function cliSupportsThinking(v) {
|
|
1103
|
+
if (!v) return false;
|
|
1104
|
+
return gte(v, { major: 2, minor: 0, patch: 0 });
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/session-manager.ts
|
|
925
1108
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
926
1109
|
var claudeSessions = /* @__PURE__ */ new Map();
|
|
927
1110
|
var MAX_ACTIVE_PROCESSES = 16;
|
|
1111
|
+
function envFlagEnabled(value) {
|
|
1112
|
+
if (value === void 0) return false;
|
|
1113
|
+
const normalized = value.trim().toLowerCase();
|
|
1114
|
+
if (!normalized) return false;
|
|
1115
|
+
return !["0", "false", "no", "off"].includes(normalized);
|
|
1116
|
+
}
|
|
1117
|
+
function isClaudeThinkingDisabled() {
|
|
1118
|
+
return envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_THINKING) || envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING);
|
|
1119
|
+
}
|
|
1120
|
+
function claudeSpawnEnv() {
|
|
1121
|
+
const env = {
|
|
1122
|
+
...process.env,
|
|
1123
|
+
TERM: "xterm-256color"
|
|
1124
|
+
};
|
|
1125
|
+
if (!isClaudeThinkingDisabled() && process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0) {
|
|
1126
|
+
env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES = "1";
|
|
1127
|
+
}
|
|
1128
|
+
return env;
|
|
1129
|
+
}
|
|
928
1130
|
function touch(key) {
|
|
929
1131
|
const existing = activeProcesses.get(key);
|
|
930
1132
|
if (existing) {
|
|
@@ -968,7 +1170,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
968
1170
|
const proc = spawn(cliPath, cliArgs, {
|
|
969
1171
|
cwd,
|
|
970
1172
|
stdio: ["pipe", "pipe", "pipe"],
|
|
971
|
-
env:
|
|
1173
|
+
env: claudeSpawnEnv(),
|
|
972
1174
|
shell: process.platform === "win32"
|
|
973
1175
|
});
|
|
974
1176
|
const lineEmitter = new EventEmitter();
|
|
@@ -1029,7 +1231,10 @@ function buildCliArgs(opts) {
|
|
|
1029
1231
|
mcpConfig,
|
|
1030
1232
|
strictMcpConfig,
|
|
1031
1233
|
disallowedTools,
|
|
1032
|
-
appendSystemPromptFile
|
|
1234
|
+
appendSystemPromptFile,
|
|
1235
|
+
thinking,
|
|
1236
|
+
thinkingDisplay,
|
|
1237
|
+
cliVersion
|
|
1033
1238
|
} = opts;
|
|
1034
1239
|
const args = [
|
|
1035
1240
|
"--print",
|
|
@@ -1065,6 +1270,12 @@ function buildCliArgs(opts) {
|
|
|
1065
1270
|
if (disallowedTools && disallowedTools.length > 0) {
|
|
1066
1271
|
args.push("--disallowedTools", ...disallowedTools);
|
|
1067
1272
|
}
|
|
1273
|
+
if (thinking && cliSupportsThinking(cliVersion ?? null)) {
|
|
1274
|
+
args.push("--thinking", thinking);
|
|
1275
|
+
}
|
|
1276
|
+
if (thinkingDisplay && cliSupportsThinkingDisplay(cliVersion ?? null)) {
|
|
1277
|
+
args.push("--thinking-display", thinkingDisplay);
|
|
1278
|
+
}
|
|
1068
1279
|
if (appendSystemPromptFile) {
|
|
1069
1280
|
args.push("--append-system-prompt-file", appendSystemPromptFile);
|
|
1070
1281
|
}
|
|
@@ -1316,8 +1527,8 @@ async function createProxyMcpServer(tools = DEFAULT_PROXY_TOOLS) {
|
|
|
1316
1527
|
});
|
|
1317
1528
|
} catch (error) {
|
|
1318
1529
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1319
|
-
const
|
|
1320
|
-
const logFn =
|
|
1530
|
+
const isExpectedCleanup = errorMessage.includes("timed out after") && errorMessage.includes("waiting for opencode to resolve") || errorMessage.includes("rejecting as orphaned") || errorMessage.includes("was orphaned by a new user turn");
|
|
1531
|
+
const logFn = isExpectedCleanup ? log.notice : log.warn;
|
|
1321
1532
|
logFn("proxy-mcp error handling request", {
|
|
1322
1533
|
error: errorMessage
|
|
1323
1534
|
});
|
|
@@ -1549,7 +1760,7 @@ function rejectPendingProxyCallById(toolCallId, error) {
|
|
|
1549
1760
|
indexRemove(pending.sessionKey, toolCallId);
|
|
1550
1761
|
clearTimeout(pending.timer);
|
|
1551
1762
|
pending.reject(error);
|
|
1552
|
-
log.
|
|
1763
|
+
log.notice("rejected pending proxy call", {
|
|
1553
1764
|
sessionKey: pending.sessionKey,
|
|
1554
1765
|
toolCallId: pending.toolCallId,
|
|
1555
1766
|
toolName: pending.toolName,
|
|
@@ -1574,6 +1785,20 @@ import { unlink as unlink2 } from "fs/promises";
|
|
|
1574
1785
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
1575
1786
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1576
1787
|
import { dirname as dirname3, join as join5 } from "path";
|
|
1788
|
+
var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
|
|
1789
|
+
function resolveCompactionModel(configured) {
|
|
1790
|
+
const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
|
|
1791
|
+
if (env) return env;
|
|
1792
|
+
const trimmed = configured?.trim();
|
|
1793
|
+
if (trimmed) return trimmed;
|
|
1794
|
+
return DEFAULT_COMPACTION_MODEL;
|
|
1795
|
+
}
|
|
1796
|
+
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
1797
|
+
"thinking_delta",
|
|
1798
|
+
"text_delta",
|
|
1799
|
+
"input_json_delta",
|
|
1800
|
+
"signature_delta"
|
|
1801
|
+
]);
|
|
1577
1802
|
function hasNewUserContent(prompt) {
|
|
1578
1803
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1579
1804
|
const msg = prompt[i];
|
|
@@ -2015,6 +2240,32 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2015
2240
|
];
|
|
2016
2241
|
return valid.includes(effort) ? effort : void 0;
|
|
2017
2242
|
}
|
|
2243
|
+
getOpencodeAgent(providerOptions) {
|
|
2244
|
+
if (!providerOptions) return void 0;
|
|
2245
|
+
const ownKey = this.config.provider;
|
|
2246
|
+
const bag = providerOptions[ownKey] ?? providerOptions["claude-code"];
|
|
2247
|
+
const agent = bag?.opencodeAgent;
|
|
2248
|
+
return typeof agent === "string" ? agent : void 0;
|
|
2249
|
+
}
|
|
2250
|
+
isCompactionCall(options) {
|
|
2251
|
+
return this.getOpencodeAgent(options.providerOptions) === "compaction";
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Pick the model used to handle /compact. Precedence:
|
|
2255
|
+
* 1. `CLAUDE_CODE_COMPACTION_MODEL` env var (per-process override)
|
|
2256
|
+
* 2. `compactionModel` provider setting (opencode.json / .jsonc)
|
|
2257
|
+
* 3. Built-in default (claude-haiku-4-5)
|
|
2258
|
+
*/
|
|
2259
|
+
resolveCompactionModel() {
|
|
2260
|
+
return resolveCompactionModel(this.config.compactionModel);
|
|
2261
|
+
}
|
|
2262
|
+
thinkingCliOptions() {
|
|
2263
|
+
if (isClaudeThinkingDisabled()) return {};
|
|
2264
|
+
return {
|
|
2265
|
+
thinking: "enabled",
|
|
2266
|
+
thinkingDisplay: process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0 ? "summarized" : void 0
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2018
2269
|
latestUserText(prompt) {
|
|
2019
2270
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
2020
2271
|
const msg = prompt[i];
|
|
@@ -2138,10 +2389,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2138
2389
|
const scope = this.requestScope(options);
|
|
2139
2390
|
const affinity = this.sessionAffinity(options);
|
|
2140
2391
|
const sk = sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
|
|
2392
|
+
const compactionMode = this.isCompactionCall(options);
|
|
2141
2393
|
if (scope === "tools" && (this.resolvedProxyTools() || this.config.proxyOpencodeMcpTools !== false && this.config.bridgeOpencodeMcp !== false)) {
|
|
2142
2394
|
return this.doGenerateViaStream(options);
|
|
2143
2395
|
}
|
|
2396
|
+
if (compactionMode) {
|
|
2397
|
+
return this.doGenerateViaStream(options);
|
|
2398
|
+
}
|
|
2144
2399
|
if (scope === "no-tools") {
|
|
2400
|
+
log.info("doGenerate no-tools title stub", {
|
|
2401
|
+
compactionMode,
|
|
2402
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2403
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2404
|
+
});
|
|
2145
2405
|
const text = this.synthesizeTitle(options.prompt);
|
|
2146
2406
|
return {
|
|
2147
2407
|
content: [{ type: "text", text }],
|
|
@@ -2193,7 +2453,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2193
2453
|
includeHistoryContext,
|
|
2194
2454
|
reasoningEffort
|
|
2195
2455
|
);
|
|
2196
|
-
const runtimeStatus = await
|
|
2456
|
+
const [runtimeStatus, cliVersion] = await Promise.all([
|
|
2457
|
+
getRuntimeMcpStatus(),
|
|
2458
|
+
detectCliVersion(this.config.cliPath)
|
|
2459
|
+
]);
|
|
2197
2460
|
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2198
2461
|
cwd,
|
|
2199
2462
|
this.config.multiStepContinuation !== false
|
|
@@ -2207,7 +2470,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2207
2470
|
mcpConfig: this.effectiveMcpConfig(cwd, void 0, runtimeStatus).paths,
|
|
2208
2471
|
strictMcpConfig: this.config.strictMcpConfig,
|
|
2209
2472
|
disallowedTools: this.config.webSearch === "disabled" ? ["WebSearch"] : void 0,
|
|
2210
|
-
appendSystemPromptFile: systemPromptFile
|
|
2473
|
+
appendSystemPromptFile: systemPromptFile,
|
|
2474
|
+
...this.thinkingCliOptions(),
|
|
2475
|
+
cliVersion
|
|
2211
2476
|
});
|
|
2212
2477
|
log.info("doGenerate starting", {
|
|
2213
2478
|
cwd,
|
|
@@ -2220,7 +2485,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2220
2485
|
const proc = spawn2(this.config.cliPath, cliArgs, {
|
|
2221
2486
|
cwd,
|
|
2222
2487
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2223
|
-
env:
|
|
2488
|
+
env: claudeSpawnEnv(),
|
|
2224
2489
|
shell: process.platform === "win32"
|
|
2225
2490
|
});
|
|
2226
2491
|
if (systemPromptFile) {
|
|
@@ -2435,11 +2700,18 @@ ${plan}
|
|
|
2435
2700
|
const skipPermissions = this.config.skipPermissions !== false;
|
|
2436
2701
|
const scope = this.requestScope(options);
|
|
2437
2702
|
const affinity = this.sessionAffinity(options);
|
|
2438
|
-
const
|
|
2703
|
+
const compactionMode = this.isCompactionCall(options);
|
|
2704
|
+
const effectiveModelId = compactionMode ? this.resolveCompactionModel() : this.modelId;
|
|
2705
|
+
const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
|
|
2439
2706
|
const toUsage = this.toUsage.bind(this);
|
|
2440
2707
|
const toFinishReason = this.toFinishReason.bind(this);
|
|
2441
2708
|
const handleControlRequest = this.handleControlRequest.bind(this);
|
|
2442
|
-
if (scope === "no-tools") {
|
|
2709
|
+
if (scope === "no-tools" && !compactionMode) {
|
|
2710
|
+
log.info("doStream no-tools title stub", {
|
|
2711
|
+
compactionMode,
|
|
2712
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2713
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2714
|
+
});
|
|
2443
2715
|
const text = this.synthesizeTitle(options.prompt);
|
|
2444
2716
|
const textId = generateId();
|
|
2445
2717
|
const stream2 = new ReadableStream({
|
|
@@ -2501,11 +2773,12 @@ ${plan}
|
|
|
2501
2773
|
const userMsg = getClaudeUserMessage(
|
|
2502
2774
|
options.prompt,
|
|
2503
2775
|
includeHistoryContext,
|
|
2504
|
-
reasoningEffort
|
|
2776
|
+
reasoningEffort,
|
|
2777
|
+
{ compactionMode }
|
|
2505
2778
|
);
|
|
2506
|
-
const resolvedProxy = this.resolvedProxyTools();
|
|
2779
|
+
const resolvedProxy = compactionMode ? null : this.resolvedProxyTools();
|
|
2507
2780
|
const self = this;
|
|
2508
|
-
const previousPendingProxyCalls = getPendingProxyCalls(sk);
|
|
2781
|
+
const previousPendingProxyCalls = compactionMode ? [] : getPendingProxyCalls(sk);
|
|
2509
2782
|
const previousPendingProxyMatches = previousPendingProxyCalls.map((call) => ({
|
|
2510
2783
|
call,
|
|
2511
2784
|
result: this.extractPendingProxyResult(options.prompt, call.toolCallId)
|
|
@@ -2513,23 +2786,34 @@ ${plan}
|
|
|
2513
2786
|
const hasMatchedPendingResults = previousPendingProxyMatches.some(
|
|
2514
2787
|
(m) => m.result !== null
|
|
2515
2788
|
);
|
|
2516
|
-
const runtimeStatus = await
|
|
2789
|
+
const [runtimeStatus, cliVersion] = await Promise.all([
|
|
2790
|
+
compactionMode ? Promise.resolve(void 0) : getRuntimeMcpStatus(),
|
|
2791
|
+
detectCliVersion(this.config.cliPath)
|
|
2792
|
+
]);
|
|
2517
2793
|
log.info("doStream starting", {
|
|
2518
2794
|
cwd,
|
|
2519
|
-
model:
|
|
2795
|
+
model: effectiveModelId,
|
|
2520
2796
|
textLength: userMsg.length,
|
|
2521
2797
|
includeHistoryContext,
|
|
2522
2798
|
hasActiveProcess,
|
|
2523
2799
|
reasoningEffort,
|
|
2524
|
-
proxyTools: resolvedProxy?.map((t) => t.name) ?? null
|
|
2800
|
+
proxyTools: resolvedProxy?.map((t) => t.name) ?? null,
|
|
2801
|
+
compactionMode,
|
|
2802
|
+
scope,
|
|
2803
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2804
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2525
2805
|
});
|
|
2526
2806
|
const stream = new ReadableStream({
|
|
2527
2807
|
start(controller) {
|
|
2808
|
+
if (compactionMode) {
|
|
2809
|
+
deleteActiveProcess(sk);
|
|
2810
|
+
deleteClaudeSessionId(sk);
|
|
2811
|
+
}
|
|
2528
2812
|
let activeProcess = getActiveProcess(sk);
|
|
2529
2813
|
let proc;
|
|
2530
2814
|
let lineEmitter;
|
|
2531
2815
|
let proxyServer = activeProcess?.proxyServer ?? null;
|
|
2532
|
-
if (activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
2816
|
+
if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
2533
2817
|
const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
2534
2818
|
const previousHash = activeProcess.mcpHash ?? null;
|
|
2535
2819
|
if (previousHash !== probe.bridgedHash) {
|
|
@@ -2544,44 +2828,64 @@ ${plan}
|
|
|
2544
2828
|
}
|
|
2545
2829
|
}
|
|
2546
2830
|
const setup = async () => {
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
)
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2831
|
+
let cliArgs;
|
|
2832
|
+
let spawnSystemPromptFile;
|
|
2833
|
+
let spawnProxyServer = null;
|
|
2834
|
+
let spawnMcpHash = null;
|
|
2835
|
+
if (compactionMode) {
|
|
2836
|
+
cliArgs = buildCliArgs({
|
|
2837
|
+
sessionKey: sk,
|
|
2838
|
+
skipPermissions,
|
|
2839
|
+
includeSessionId: false,
|
|
2840
|
+
model: effectiveModelId,
|
|
2841
|
+
permissionMode: self.config.permissionMode,
|
|
2842
|
+
cliVersion
|
|
2843
|
+
});
|
|
2844
|
+
} else {
|
|
2845
|
+
const discovery = self.effectiveMcpConfig(
|
|
2846
|
+
cwd,
|
|
2847
|
+
void 0,
|
|
2848
|
+
runtimeStatus
|
|
2849
|
+
);
|
|
2850
|
+
const proxyMcpTools = await self.resolvedProxyMcpTools(
|
|
2851
|
+
discovery.allEnabledServerNames
|
|
2852
|
+
);
|
|
2853
|
+
const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
|
|
2854
|
+
const combinedProxyTools = resolvedProxy || proxyMcpTools ? [...resolvedProxy ?? [], ...proxyMcpTools ?? []] : null;
|
|
2855
|
+
if (!proxyServer && combinedProxyTools) {
|
|
2856
|
+
proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
|
|
2857
|
+
}
|
|
2858
|
+
const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
|
|
2859
|
+
const extraDisallowed = [];
|
|
2860
|
+
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
2861
|
+
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
2862
|
+
const mcp = self.effectiveMcpConfig(
|
|
2863
|
+
cwd,
|
|
2864
|
+
proxyServer?.configPath(),
|
|
2865
|
+
runtimeStatus,
|
|
2866
|
+
excludeServers
|
|
2867
|
+
);
|
|
2868
|
+
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
2869
|
+
cwd,
|
|
2870
|
+
self.config.multiStepContinuation !== false
|
|
2871
|
+
);
|
|
2872
|
+
cliArgs = buildCliArgs({
|
|
2873
|
+
sessionKey: sk,
|
|
2874
|
+
skipPermissions,
|
|
2875
|
+
model: self.modelId,
|
|
2876
|
+
permissionMode: self.config.permissionMode,
|
|
2877
|
+
mcpConfig: mcp.paths,
|
|
2878
|
+
strictMcpConfig: self.config.strictMcpConfig,
|
|
2879
|
+
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0,
|
|
2880
|
+
appendSystemPromptFile: systemPromptFile,
|
|
2881
|
+
...self.thinkingCliOptions(),
|
|
2882
|
+
cliVersion
|
|
2883
|
+
});
|
|
2884
|
+
spawnSystemPromptFile = systemPromptFile;
|
|
2885
|
+
spawnProxyServer = proxyServer;
|
|
2886
|
+
spawnMcpHash = mcp.bridgedHash;
|
|
2559
2887
|
}
|
|
2560
|
-
|
|
2561
|
-
const extraDisallowed = [];
|
|
2562
|
-
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
2563
|
-
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
2564
|
-
const mcp = self.effectiveMcpConfig(
|
|
2565
|
-
cwd,
|
|
2566
|
-
proxyServer?.configPath(),
|
|
2567
|
-
runtimeStatus,
|
|
2568
|
-
excludeServers
|
|
2569
|
-
);
|
|
2570
|
-
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
2571
|
-
cwd,
|
|
2572
|
-
self.config.multiStepContinuation !== false
|
|
2573
|
-
);
|
|
2574
|
-
const cliArgs = buildCliArgs({
|
|
2575
|
-
sessionKey: sk,
|
|
2576
|
-
skipPermissions,
|
|
2577
|
-
model: self.modelId,
|
|
2578
|
-
permissionMode: self.config.permissionMode,
|
|
2579
|
-
mcpConfig: mcp.paths,
|
|
2580
|
-
strictMcpConfig: self.config.strictMcpConfig,
|
|
2581
|
-
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0,
|
|
2582
|
-
appendSystemPromptFile: systemPromptFile
|
|
2583
|
-
});
|
|
2584
|
-
if (activeProcess) {
|
|
2888
|
+
if (activeProcess && !compactionMode) {
|
|
2585
2889
|
proc = activeProcess.proc;
|
|
2586
2890
|
lineEmitter = activeProcess.lineEmitter;
|
|
2587
2891
|
log.debug("reusing active process", { sk });
|
|
@@ -2591,9 +2895,9 @@ ${plan}
|
|
|
2591
2895
|
cliArgs,
|
|
2592
2896
|
cwd,
|
|
2593
2897
|
sk,
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2898
|
+
spawnProxyServer,
|
|
2899
|
+
spawnMcpHash,
|
|
2900
|
+
spawnSystemPromptFile
|
|
2597
2901
|
);
|
|
2598
2902
|
proc = ap.proc;
|
|
2599
2903
|
lineEmitter = ap.lineEmitter;
|
|
@@ -2619,6 +2923,7 @@ ${plan}
|
|
|
2619
2923
|
};
|
|
2620
2924
|
const reasoningIds = /* @__PURE__ */ new Map();
|
|
2621
2925
|
const reasoningStarted = /* @__PURE__ */ new Map();
|
|
2926
|
+
let hadThinkingTextFromStream = false;
|
|
2622
2927
|
let turnCompleted = false;
|
|
2623
2928
|
let controllerClosed = false;
|
|
2624
2929
|
let pendingProxyUnsubscribe = null;
|
|
@@ -2765,11 +3070,6 @@ ${plan}
|
|
|
2765
3070
|
noteReasoning();
|
|
2766
3071
|
const reasoningId = generateId();
|
|
2767
3072
|
reasoningIds.set(idx, reasoningId);
|
|
2768
|
-
controller.enqueue({
|
|
2769
|
-
type: "reasoning-start",
|
|
2770
|
-
id: reasoningId
|
|
2771
|
-
});
|
|
2772
|
-
reasoningStarted.set(idx, true);
|
|
2773
3073
|
}
|
|
2774
3074
|
if (block.type === "text") {
|
|
2775
3075
|
textBlockIndices.add(idx);
|
|
@@ -2819,8 +3119,16 @@ ${plan}
|
|
|
2819
3119
|
const idx = msg.index;
|
|
2820
3120
|
if (delta.type === "thinking_delta" && delta.thinking) {
|
|
2821
3121
|
noteReasoning();
|
|
3122
|
+
hadThinkingTextFromStream = true;
|
|
2822
3123
|
const reasoningId = reasoningIds.get(idx);
|
|
2823
3124
|
if (reasoningId) {
|
|
3125
|
+
if (!reasoningStarted.get(idx)) {
|
|
3126
|
+
controller.enqueue({
|
|
3127
|
+
type: "reasoning-start",
|
|
3128
|
+
id: reasoningId
|
|
3129
|
+
});
|
|
3130
|
+
reasoningStarted.set(idx, true);
|
|
3131
|
+
}
|
|
2824
3132
|
controller.enqueue({
|
|
2825
3133
|
type: "reasoning-delta",
|
|
2826
3134
|
id: reasoningId,
|
|
@@ -2849,6 +3157,13 @@ ${plan}
|
|
|
2849
3157
|
});
|
|
2850
3158
|
}
|
|
2851
3159
|
}
|
|
3160
|
+
if (!KNOWN_DELTA_TYPES.has(delta.type)) {
|
|
3161
|
+
log.debug("unrecognized content_block_delta type", {
|
|
3162
|
+
type: delta.type,
|
|
3163
|
+
idx,
|
|
3164
|
+
keys: Object.keys(delta)
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
2852
3167
|
}
|
|
2853
3168
|
if (msg.type === "content_block_stop" && msg.index !== void 0) {
|
|
2854
3169
|
const idx = msg.index;
|
|
@@ -2946,6 +3261,42 @@ ${plan}
|
|
|
2946
3261
|
if (msg.type === "assistant" && msg.message && typeof msg.message.stop_reason === "string") {
|
|
2947
3262
|
lastStopReason = msg.message.stop_reason;
|
|
2948
3263
|
}
|
|
3264
|
+
if (msg.type === "assistant" && msg.message?.content && gotPartialEvents) {
|
|
3265
|
+
const thinkingBlocks = msg.message.content.filter(
|
|
3266
|
+
(b) => b.type === "thinking"
|
|
3267
|
+
);
|
|
3268
|
+
if (thinkingBlocks.length > 0) {
|
|
3269
|
+
log.info("assistant message thinking blocks", {
|
|
3270
|
+
count: thinkingBlocks.length,
|
|
3271
|
+
hasText: thinkingBlocks.some(
|
|
3272
|
+
(b) => typeof b.thinking === "string" && b.thinking.length > 0
|
|
3273
|
+
),
|
|
3274
|
+
hadStreamThinking: hadThinkingTextFromStream
|
|
3275
|
+
});
|
|
3276
|
+
if (!hadThinkingTextFromStream) {
|
|
3277
|
+
for (const block of thinkingBlocks) {
|
|
3278
|
+
if (block.thinking && block.thinking.length > 0) {
|
|
3279
|
+
noteReasoning();
|
|
3280
|
+
hadThinkingTextFromStream = true;
|
|
3281
|
+
const thinkingId = generateId();
|
|
3282
|
+
controller.enqueue({
|
|
3283
|
+
type: "reasoning-start",
|
|
3284
|
+
id: thinkingId
|
|
3285
|
+
});
|
|
3286
|
+
controller.enqueue({
|
|
3287
|
+
type: "reasoning-delta",
|
|
3288
|
+
id: thinkingId,
|
|
3289
|
+
delta: block.thinking
|
|
3290
|
+
});
|
|
3291
|
+
controller.enqueue({
|
|
3292
|
+
type: "reasoning-end",
|
|
3293
|
+
id: thinkingId
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
2949
3300
|
if (msg.type === "assistant" && msg.message?.content && !gotPartialEvents) {
|
|
2950
3301
|
const hasText = msg.message.content.some(
|
|
2951
3302
|
(b) => b.type === "text" && b.text
|
|
@@ -3234,7 +3585,10 @@ ${plan}
|
|
|
3234
3585
|
finishReason: toFinishReason("stop"),
|
|
3235
3586
|
usage: toUsage(msg.usage),
|
|
3236
3587
|
providerMetadata: {
|
|
3237
|
-
"claude-code":
|
|
3588
|
+
"claude-code": {
|
|
3589
|
+
...resultMeta,
|
|
3590
|
+
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
3591
|
+
},
|
|
3238
3592
|
...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
|
|
3239
3593
|
anthropic: {
|
|
3240
3594
|
cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
|
|
@@ -3275,7 +3629,10 @@ ${plan}
|
|
|
3275
3629
|
finishReason: toFinishReason("stop"),
|
|
3276
3630
|
usage: toUsage(),
|
|
3277
3631
|
providerMetadata: {
|
|
3278
|
-
"claude-code":
|
|
3632
|
+
"claude-code": {
|
|
3633
|
+
...resultMeta,
|
|
3634
|
+
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
3635
|
+
}
|
|
3279
3636
|
}
|
|
3280
3637
|
});
|
|
3281
3638
|
try {
|
|
@@ -3384,7 +3741,7 @@ ${plan}
|
|
|
3384
3741
|
});
|
|
3385
3742
|
resolvePendingProxyCallById(call.toolCallId, result);
|
|
3386
3743
|
} else {
|
|
3387
|
-
log.
|
|
3744
|
+
log.notice(
|
|
3388
3745
|
"pending proxy call had no matching tool-result; rejecting as orphan",
|
|
3389
3746
|
{
|
|
3390
3747
|
sessionKey: sk,
|
|
@@ -3875,7 +4232,8 @@ function createClaudeCode(settings = {}) {
|
|
|
3875
4232
|
hotReloadMcp: settings.hotReloadMcp ?? true,
|
|
3876
4233
|
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
|
|
3877
4234
|
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
3878
|
-
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart"
|
|
4235
|
+
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart",
|
|
4236
|
+
compactionModel: settings.compactionModel
|
|
3879
4237
|
});
|
|
3880
4238
|
};
|
|
3881
4239
|
const provider = function(modelId) {
|
|
@@ -4069,6 +4427,27 @@ var server = async (input) => {
|
|
|
4069
4427
|
provider: {
|
|
4070
4428
|
id: PROVIDER_ID2,
|
|
4071
4429
|
models: async (provider) => defaultModelsForProvider(provider.models)
|
|
4430
|
+
},
|
|
4431
|
+
// Inject opencode's agent name into providerOptions so the language
|
|
4432
|
+
// model can distinguish /compact (and title) calls from normal turns.
|
|
4433
|
+
// Without this, every no-tools call looks like a title request and
|
|
4434
|
+
// gets short-circuited to a synthetic stub.
|
|
4435
|
+
"chat.params": async (input2, output) => {
|
|
4436
|
+
const providerID = input2.model?.providerID ?? input2.provider?.info?.id;
|
|
4437
|
+
log.debug("chat.params hook fired", {
|
|
4438
|
+
agent: input2.agent,
|
|
4439
|
+
providerID,
|
|
4440
|
+
sessionID: input2.sessionID
|
|
4441
|
+
});
|
|
4442
|
+
if (typeof providerID !== "string") return;
|
|
4443
|
+
if (providerID !== PROVIDER_ID2 && !providerID.startsWith(`${PROVIDER_ID2}-`)) return;
|
|
4444
|
+
if (!input2.agent) return;
|
|
4445
|
+
output.options ??= {};
|
|
4446
|
+
output.options.opencodeAgent = input2.agent;
|
|
4447
|
+
log.debug("chat.params tagged providerOptions", {
|
|
4448
|
+
agent: input2.agent,
|
|
4449
|
+
providerID
|
|
4450
|
+
});
|
|
4072
4451
|
}
|
|
4073
4452
|
};
|
|
4074
4453
|
};
|