@khalilgharbaoui/opencode-claude-code-plugin 0.4.19 → 0.4.21
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 +58 -2
- package/dist/index.d.ts +48 -0
- package/dist/index.js +475 -82
- 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",
|
|
@@ -865,6 +989,25 @@ function setOpencodeClient(client) {
|
|
|
865
989
|
opencodeClient = client;
|
|
866
990
|
}
|
|
867
991
|
}
|
|
992
|
+
var opencodeProjectDirectory;
|
|
993
|
+
function setOpencodeProjectDirectory(dir) {
|
|
994
|
+
opencodeProjectDirectory = dir;
|
|
995
|
+
}
|
|
996
|
+
function isUsableDirectory(d) {
|
|
997
|
+
return typeof d === "string" && d.length > 1 && d !== "/";
|
|
998
|
+
}
|
|
999
|
+
function resolveSpawnCwd(configured) {
|
|
1000
|
+
return resolveSpawnCwdFrom(
|
|
1001
|
+
configured,
|
|
1002
|
+
process.cwd(),
|
|
1003
|
+
opencodeProjectDirectory
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
function resolveSpawnCwdFrom(configured, live, captured) {
|
|
1007
|
+
if (configured) return configured;
|
|
1008
|
+
if (isUsableDirectory(live)) return live;
|
|
1009
|
+
return captured ?? live;
|
|
1010
|
+
}
|
|
868
1011
|
async function getRuntimeMcpStatus() {
|
|
869
1012
|
const client = opencodeClient;
|
|
870
1013
|
if (!client?.mcp?.status) return void 0;
|
|
@@ -922,9 +1065,87 @@ import { spawn } from "child_process";
|
|
|
922
1065
|
import { createInterface } from "readline";
|
|
923
1066
|
import { EventEmitter } from "events";
|
|
924
1067
|
import { unlink } from "fs/promises";
|
|
1068
|
+
|
|
1069
|
+
// src/cli-version.ts
|
|
1070
|
+
import { execFile } from "child_process";
|
|
1071
|
+
import { promisify } from "util";
|
|
1072
|
+
var execFileAsync = promisify(execFile);
|
|
1073
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1074
|
+
function detectCliVersion(cliPath) {
|
|
1075
|
+
const cached = cache.get(cliPath);
|
|
1076
|
+
if (cached) return cached;
|
|
1077
|
+
const promise = (async () => {
|
|
1078
|
+
try {
|
|
1079
|
+
const { stdout } = await execFileAsync(cliPath, ["--version"], {
|
|
1080
|
+
timeout: 5e3
|
|
1081
|
+
});
|
|
1082
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(stdout.trim());
|
|
1083
|
+
if (!match) {
|
|
1084
|
+
log.warn("claude --version output unparseable", { stdout: stdout.trim() });
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
const v = {
|
|
1088
|
+
major: Number(match[1]),
|
|
1089
|
+
minor: Number(match[2]),
|
|
1090
|
+
patch: Number(match[3]),
|
|
1091
|
+
raw: stdout.trim()
|
|
1092
|
+
};
|
|
1093
|
+
log.info("detected claude cli version", { cliPath, version: v.raw });
|
|
1094
|
+
if (!cliSupportsThinkingDisplay(v)) {
|
|
1095
|
+
log.notice(
|
|
1096
|
+
"claude cli < 2.1.142 detected; Opus 4.7 thinking summaries unavailable. Run `npm i -g @anthropic-ai/claude-code` to upgrade.",
|
|
1097
|
+
{ version: v.raw }
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return v;
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
log.warn("failed to detect claude cli version", {
|
|
1103
|
+
cliPath,
|
|
1104
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1105
|
+
});
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
})();
|
|
1109
|
+
cache.set(cliPath, promise);
|
|
1110
|
+
return promise;
|
|
1111
|
+
}
|
|
1112
|
+
function gte(v, target) {
|
|
1113
|
+
if (v.major !== target.major) return v.major > target.major;
|
|
1114
|
+
if (v.minor !== target.minor) return v.minor > target.minor;
|
|
1115
|
+
return v.patch >= target.patch;
|
|
1116
|
+
}
|
|
1117
|
+
function cliSupportsThinkingDisplay(v) {
|
|
1118
|
+
if (!v) return false;
|
|
1119
|
+
return gte(v, { major: 2, minor: 1, patch: 142 });
|
|
1120
|
+
}
|
|
1121
|
+
function cliSupportsThinking(v) {
|
|
1122
|
+
if (!v) return false;
|
|
1123
|
+
return gte(v, { major: 2, minor: 0, patch: 0 });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/session-manager.ts
|
|
925
1127
|
var activeProcesses = /* @__PURE__ */ new Map();
|
|
926
1128
|
var claudeSessions = /* @__PURE__ */ new Map();
|
|
927
1129
|
var MAX_ACTIVE_PROCESSES = 16;
|
|
1130
|
+
function envFlagEnabled(value) {
|
|
1131
|
+
if (value === void 0) return false;
|
|
1132
|
+
const normalized = value.trim().toLowerCase();
|
|
1133
|
+
if (!normalized) return false;
|
|
1134
|
+
return !["0", "false", "no", "off"].includes(normalized);
|
|
1135
|
+
}
|
|
1136
|
+
function isClaudeThinkingDisabled() {
|
|
1137
|
+
return envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_THINKING) || envFlagEnabled(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING);
|
|
1138
|
+
}
|
|
1139
|
+
function claudeSpawnEnv() {
|
|
1140
|
+
const env = {
|
|
1141
|
+
...process.env,
|
|
1142
|
+
TERM: "xterm-256color"
|
|
1143
|
+
};
|
|
1144
|
+
if (!isClaudeThinkingDisabled() && process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0) {
|
|
1145
|
+
env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES = "1";
|
|
1146
|
+
}
|
|
1147
|
+
return env;
|
|
1148
|
+
}
|
|
928
1149
|
function touch(key) {
|
|
929
1150
|
const existing = activeProcesses.get(key);
|
|
930
1151
|
if (existing) {
|
|
@@ -968,7 +1189,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
968
1189
|
const proc = spawn(cliPath, cliArgs, {
|
|
969
1190
|
cwd,
|
|
970
1191
|
stdio: ["pipe", "pipe", "pipe"],
|
|
971
|
-
env:
|
|
1192
|
+
env: claudeSpawnEnv(),
|
|
972
1193
|
shell: process.platform === "win32"
|
|
973
1194
|
});
|
|
974
1195
|
const lineEmitter = new EventEmitter();
|
|
@@ -1029,7 +1250,10 @@ function buildCliArgs(opts) {
|
|
|
1029
1250
|
mcpConfig,
|
|
1030
1251
|
strictMcpConfig,
|
|
1031
1252
|
disallowedTools,
|
|
1032
|
-
appendSystemPromptFile
|
|
1253
|
+
appendSystemPromptFile,
|
|
1254
|
+
thinking,
|
|
1255
|
+
thinkingDisplay,
|
|
1256
|
+
cliVersion
|
|
1033
1257
|
} = opts;
|
|
1034
1258
|
const args = [
|
|
1035
1259
|
"--print",
|
|
@@ -1065,6 +1289,12 @@ function buildCliArgs(opts) {
|
|
|
1065
1289
|
if (disallowedTools && disallowedTools.length > 0) {
|
|
1066
1290
|
args.push("--disallowedTools", ...disallowedTools);
|
|
1067
1291
|
}
|
|
1292
|
+
if (thinking && cliSupportsThinking(cliVersion ?? null)) {
|
|
1293
|
+
args.push("--thinking", thinking);
|
|
1294
|
+
}
|
|
1295
|
+
if (thinkingDisplay && cliSupportsThinkingDisplay(cliVersion ?? null)) {
|
|
1296
|
+
args.push("--thinking-display", thinkingDisplay);
|
|
1297
|
+
}
|
|
1068
1298
|
if (appendSystemPromptFile) {
|
|
1069
1299
|
args.push("--append-system-prompt-file", appendSystemPromptFile);
|
|
1070
1300
|
}
|
|
@@ -1574,6 +1804,20 @@ import { unlink as unlink2 } from "fs/promises";
|
|
|
1574
1804
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
1575
1805
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1576
1806
|
import { dirname as dirname3, join as join5 } from "path";
|
|
1807
|
+
var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
|
|
1808
|
+
function resolveCompactionModel(configured) {
|
|
1809
|
+
const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
|
|
1810
|
+
if (env) return env;
|
|
1811
|
+
const trimmed = configured?.trim();
|
|
1812
|
+
if (trimmed) return trimmed;
|
|
1813
|
+
return DEFAULT_COMPACTION_MODEL;
|
|
1814
|
+
}
|
|
1815
|
+
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
1816
|
+
"thinking_delta",
|
|
1817
|
+
"text_delta",
|
|
1818
|
+
"input_json_delta",
|
|
1819
|
+
"signature_delta"
|
|
1820
|
+
]);
|
|
1577
1821
|
function hasNewUserContent(prompt) {
|
|
1578
1822
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1579
1823
|
const msg = prompt[i];
|
|
@@ -2015,6 +2259,32 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2015
2259
|
];
|
|
2016
2260
|
return valid.includes(effort) ? effort : void 0;
|
|
2017
2261
|
}
|
|
2262
|
+
getOpencodeAgent(providerOptions) {
|
|
2263
|
+
if (!providerOptions) return void 0;
|
|
2264
|
+
const ownKey = this.config.provider;
|
|
2265
|
+
const bag = providerOptions[ownKey] ?? providerOptions["claude-code"];
|
|
2266
|
+
const agent = bag?.opencodeAgent;
|
|
2267
|
+
return typeof agent === "string" ? agent : void 0;
|
|
2268
|
+
}
|
|
2269
|
+
isCompactionCall(options) {
|
|
2270
|
+
return this.getOpencodeAgent(options.providerOptions) === "compaction";
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Pick the model used to handle /compact. Precedence:
|
|
2274
|
+
* 1. `CLAUDE_CODE_COMPACTION_MODEL` env var (per-process override)
|
|
2275
|
+
* 2. `compactionModel` provider setting (opencode.json / .jsonc)
|
|
2276
|
+
* 3. Built-in default (claude-haiku-4-5)
|
|
2277
|
+
*/
|
|
2278
|
+
resolveCompactionModel() {
|
|
2279
|
+
return resolveCompactionModel(this.config.compactionModel);
|
|
2280
|
+
}
|
|
2281
|
+
thinkingCliOptions() {
|
|
2282
|
+
if (isClaudeThinkingDisabled()) return {};
|
|
2283
|
+
return {
|
|
2284
|
+
thinking: "enabled",
|
|
2285
|
+
thinkingDisplay: process.env.CLAUDE_CODE_SHOW_THINKING_SUMMARIES === void 0 ? "summarized" : void 0
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2018
2288
|
latestUserText(prompt) {
|
|
2019
2289
|
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
2020
2290
|
const msg = prompt[i];
|
|
@@ -2134,14 +2404,23 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2134
2404
|
}
|
|
2135
2405
|
async doGenerate(options) {
|
|
2136
2406
|
const warnings = [];
|
|
2137
|
-
const cwd = this.config.cwd
|
|
2407
|
+
const cwd = resolveSpawnCwd(this.config.cwd);
|
|
2138
2408
|
const scope = this.requestScope(options);
|
|
2139
2409
|
const affinity = this.sessionAffinity(options);
|
|
2140
2410
|
const sk = sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
|
|
2411
|
+
const compactionMode = this.isCompactionCall(options);
|
|
2141
2412
|
if (scope === "tools" && (this.resolvedProxyTools() || this.config.proxyOpencodeMcpTools !== false && this.config.bridgeOpencodeMcp !== false)) {
|
|
2142
2413
|
return this.doGenerateViaStream(options);
|
|
2143
2414
|
}
|
|
2415
|
+
if (compactionMode) {
|
|
2416
|
+
return this.doGenerateViaStream(options);
|
|
2417
|
+
}
|
|
2144
2418
|
if (scope === "no-tools") {
|
|
2419
|
+
log.info("doGenerate no-tools title stub", {
|
|
2420
|
+
compactionMode,
|
|
2421
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2422
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2423
|
+
});
|
|
2145
2424
|
const text = this.synthesizeTitle(options.prompt);
|
|
2146
2425
|
return {
|
|
2147
2426
|
content: [{ type: "text", text }],
|
|
@@ -2193,7 +2472,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2193
2472
|
includeHistoryContext,
|
|
2194
2473
|
reasoningEffort
|
|
2195
2474
|
);
|
|
2196
|
-
const runtimeStatus = await
|
|
2475
|
+
const [runtimeStatus, cliVersion] = await Promise.all([
|
|
2476
|
+
getRuntimeMcpStatus(),
|
|
2477
|
+
detectCliVersion(this.config.cliPath)
|
|
2478
|
+
]);
|
|
2197
2479
|
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2198
2480
|
cwd,
|
|
2199
2481
|
this.config.multiStepContinuation !== false
|
|
@@ -2207,7 +2489,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2207
2489
|
mcpConfig: this.effectiveMcpConfig(cwd, void 0, runtimeStatus).paths,
|
|
2208
2490
|
strictMcpConfig: this.config.strictMcpConfig,
|
|
2209
2491
|
disallowedTools: this.config.webSearch === "disabled" ? ["WebSearch"] : void 0,
|
|
2210
|
-
appendSystemPromptFile: systemPromptFile
|
|
2492
|
+
appendSystemPromptFile: systemPromptFile,
|
|
2493
|
+
...this.thinkingCliOptions(),
|
|
2494
|
+
cliVersion
|
|
2211
2495
|
});
|
|
2212
2496
|
log.info("doGenerate starting", {
|
|
2213
2497
|
cwd,
|
|
@@ -2220,7 +2504,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2220
2504
|
const proc = spawn2(this.config.cliPath, cliArgs, {
|
|
2221
2505
|
cwd,
|
|
2222
2506
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2223
|
-
env:
|
|
2507
|
+
env: claudeSpawnEnv(),
|
|
2224
2508
|
shell: process.platform === "win32"
|
|
2225
2509
|
});
|
|
2226
2510
|
if (systemPromptFile) {
|
|
@@ -2430,16 +2714,23 @@ ${plan}
|
|
|
2430
2714
|
}
|
|
2431
2715
|
async doStream(options) {
|
|
2432
2716
|
const warnings = [];
|
|
2433
|
-
const cwd = this.config.cwd
|
|
2717
|
+
const cwd = resolveSpawnCwd(this.config.cwd);
|
|
2434
2718
|
const cliPath = this.config.cliPath;
|
|
2435
2719
|
const skipPermissions = this.config.skipPermissions !== false;
|
|
2436
2720
|
const scope = this.requestScope(options);
|
|
2437
2721
|
const affinity = this.sessionAffinity(options);
|
|
2438
|
-
const
|
|
2722
|
+
const compactionMode = this.isCompactionCall(options);
|
|
2723
|
+
const effectiveModelId = compactionMode ? this.resolveCompactionModel() : this.modelId;
|
|
2724
|
+
const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
|
|
2439
2725
|
const toUsage = this.toUsage.bind(this);
|
|
2440
2726
|
const toFinishReason = this.toFinishReason.bind(this);
|
|
2441
2727
|
const handleControlRequest = this.handleControlRequest.bind(this);
|
|
2442
|
-
if (scope === "no-tools") {
|
|
2728
|
+
if (scope === "no-tools" && !compactionMode) {
|
|
2729
|
+
log.info("doStream no-tools title stub", {
|
|
2730
|
+
compactionMode,
|
|
2731
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2732
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2733
|
+
});
|
|
2443
2734
|
const text = this.synthesizeTitle(options.prompt);
|
|
2444
2735
|
const textId = generateId();
|
|
2445
2736
|
const stream2 = new ReadableStream({
|
|
@@ -2501,11 +2792,12 @@ ${plan}
|
|
|
2501
2792
|
const userMsg = getClaudeUserMessage(
|
|
2502
2793
|
options.prompt,
|
|
2503
2794
|
includeHistoryContext,
|
|
2504
|
-
reasoningEffort
|
|
2795
|
+
reasoningEffort,
|
|
2796
|
+
{ compactionMode }
|
|
2505
2797
|
);
|
|
2506
|
-
const resolvedProxy = this.resolvedProxyTools();
|
|
2798
|
+
const resolvedProxy = compactionMode ? null : this.resolvedProxyTools();
|
|
2507
2799
|
const self = this;
|
|
2508
|
-
const previousPendingProxyCalls = getPendingProxyCalls(sk);
|
|
2800
|
+
const previousPendingProxyCalls = compactionMode ? [] : getPendingProxyCalls(sk);
|
|
2509
2801
|
const previousPendingProxyMatches = previousPendingProxyCalls.map((call) => ({
|
|
2510
2802
|
call,
|
|
2511
2803
|
result: this.extractPendingProxyResult(options.prompt, call.toolCallId)
|
|
@@ -2513,23 +2805,34 @@ ${plan}
|
|
|
2513
2805
|
const hasMatchedPendingResults = previousPendingProxyMatches.some(
|
|
2514
2806
|
(m) => m.result !== null
|
|
2515
2807
|
);
|
|
2516
|
-
const runtimeStatus = await
|
|
2808
|
+
const [runtimeStatus, cliVersion] = await Promise.all([
|
|
2809
|
+
compactionMode ? Promise.resolve(void 0) : getRuntimeMcpStatus(),
|
|
2810
|
+
detectCliVersion(this.config.cliPath)
|
|
2811
|
+
]);
|
|
2517
2812
|
log.info("doStream starting", {
|
|
2518
2813
|
cwd,
|
|
2519
|
-
model:
|
|
2814
|
+
model: effectiveModelId,
|
|
2520
2815
|
textLength: userMsg.length,
|
|
2521
2816
|
includeHistoryContext,
|
|
2522
2817
|
hasActiveProcess,
|
|
2523
2818
|
reasoningEffort,
|
|
2524
|
-
proxyTools: resolvedProxy?.map((t) => t.name) ?? null
|
|
2819
|
+
proxyTools: resolvedProxy?.map((t) => t.name) ?? null,
|
|
2820
|
+
compactionMode,
|
|
2821
|
+
scope,
|
|
2822
|
+
opencodeAgent: this.getOpencodeAgent(options.providerOptions),
|
|
2823
|
+
providerOptionsKeys: options.providerOptions ? Object.keys(options.providerOptions) : []
|
|
2525
2824
|
});
|
|
2526
2825
|
const stream = new ReadableStream({
|
|
2527
2826
|
start(controller) {
|
|
2827
|
+
if (compactionMode) {
|
|
2828
|
+
deleteActiveProcess(sk);
|
|
2829
|
+
deleteClaudeSessionId(sk);
|
|
2830
|
+
}
|
|
2528
2831
|
let activeProcess = getActiveProcess(sk);
|
|
2529
2832
|
let proc;
|
|
2530
2833
|
let lineEmitter;
|
|
2531
2834
|
let proxyServer = activeProcess?.proxyServer ?? null;
|
|
2532
|
-
if (activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
2835
|
+
if (!compactionMode && activeProcess && self.config.hotReloadMcp !== false && self.config.bridgeOpencodeMcp !== false) {
|
|
2533
2836
|
const probe = self.effectiveMcpConfig(cwd, void 0, runtimeStatus);
|
|
2534
2837
|
const previousHash = activeProcess.mcpHash ?? null;
|
|
2535
2838
|
if (previousHash !== probe.bridgedHash) {
|
|
@@ -2544,44 +2847,64 @@ ${plan}
|
|
|
2544
2847
|
}
|
|
2545
2848
|
}
|
|
2546
2849
|
const setup = async () => {
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
)
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2850
|
+
let cliArgs;
|
|
2851
|
+
let spawnSystemPromptFile;
|
|
2852
|
+
let spawnProxyServer = null;
|
|
2853
|
+
let spawnMcpHash = null;
|
|
2854
|
+
if (compactionMode) {
|
|
2855
|
+
cliArgs = buildCliArgs({
|
|
2856
|
+
sessionKey: sk,
|
|
2857
|
+
skipPermissions,
|
|
2858
|
+
includeSessionId: false,
|
|
2859
|
+
model: effectiveModelId,
|
|
2860
|
+
permissionMode: self.config.permissionMode,
|
|
2861
|
+
cliVersion
|
|
2862
|
+
});
|
|
2863
|
+
} else {
|
|
2864
|
+
const discovery = self.effectiveMcpConfig(
|
|
2865
|
+
cwd,
|
|
2866
|
+
void 0,
|
|
2867
|
+
runtimeStatus
|
|
2868
|
+
);
|
|
2869
|
+
const proxyMcpTools = await self.resolvedProxyMcpTools(
|
|
2870
|
+
discovery.allEnabledServerNames
|
|
2871
|
+
);
|
|
2872
|
+
const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
|
|
2873
|
+
const combinedProxyTools = resolvedProxy || proxyMcpTools ? [...resolvedProxy ?? [], ...proxyMcpTools ?? []] : null;
|
|
2874
|
+
if (!proxyServer && combinedProxyTools) {
|
|
2875
|
+
proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
|
|
2876
|
+
}
|
|
2877
|
+
const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
|
|
2878
|
+
const extraDisallowed = [];
|
|
2879
|
+
if (self.config.webSearch === "disabled") extraDisallowed.push("WebSearch");
|
|
2880
|
+
const allDisallowed = [...proxyDisallowed, ...extraDisallowed];
|
|
2881
|
+
const mcp = self.effectiveMcpConfig(
|
|
2882
|
+
cwd,
|
|
2883
|
+
proxyServer?.configPath(),
|
|
2884
|
+
runtimeStatus,
|
|
2885
|
+
excludeServers
|
|
2886
|
+
);
|
|
2887
|
+
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
2888
|
+
cwd,
|
|
2889
|
+
self.config.multiStepContinuation !== false
|
|
2890
|
+
);
|
|
2891
|
+
cliArgs = buildCliArgs({
|
|
2892
|
+
sessionKey: sk,
|
|
2893
|
+
skipPermissions,
|
|
2894
|
+
model: self.modelId,
|
|
2895
|
+
permissionMode: self.config.permissionMode,
|
|
2896
|
+
mcpConfig: mcp.paths,
|
|
2897
|
+
strictMcpConfig: self.config.strictMcpConfig,
|
|
2898
|
+
disallowedTools: allDisallowed.length > 0 ? allDisallowed : void 0,
|
|
2899
|
+
appendSystemPromptFile: systemPromptFile,
|
|
2900
|
+
...self.thinkingCliOptions(),
|
|
2901
|
+
cliVersion
|
|
2902
|
+
});
|
|
2903
|
+
spawnSystemPromptFile = systemPromptFile;
|
|
2904
|
+
spawnProxyServer = proxyServer;
|
|
2905
|
+
spawnMcpHash = mcp.bridgedHash;
|
|
2559
2906
|
}
|
|
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) {
|
|
2907
|
+
if (activeProcess && !compactionMode) {
|
|
2585
2908
|
proc = activeProcess.proc;
|
|
2586
2909
|
lineEmitter = activeProcess.lineEmitter;
|
|
2587
2910
|
log.debug("reusing active process", { sk });
|
|
@@ -2591,9 +2914,9 @@ ${plan}
|
|
|
2591
2914
|
cliArgs,
|
|
2592
2915
|
cwd,
|
|
2593
2916
|
sk,
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2917
|
+
spawnProxyServer,
|
|
2918
|
+
spawnMcpHash,
|
|
2919
|
+
spawnSystemPromptFile
|
|
2597
2920
|
);
|
|
2598
2921
|
proc = ap.proc;
|
|
2599
2922
|
lineEmitter = ap.lineEmitter;
|
|
@@ -2619,6 +2942,7 @@ ${plan}
|
|
|
2619
2942
|
};
|
|
2620
2943
|
const reasoningIds = /* @__PURE__ */ new Map();
|
|
2621
2944
|
const reasoningStarted = /* @__PURE__ */ new Map();
|
|
2945
|
+
let hadThinkingTextFromStream = false;
|
|
2622
2946
|
let turnCompleted = false;
|
|
2623
2947
|
let controllerClosed = false;
|
|
2624
2948
|
let pendingProxyUnsubscribe = null;
|
|
@@ -2765,11 +3089,6 @@ ${plan}
|
|
|
2765
3089
|
noteReasoning();
|
|
2766
3090
|
const reasoningId = generateId();
|
|
2767
3091
|
reasoningIds.set(idx, reasoningId);
|
|
2768
|
-
controller.enqueue({
|
|
2769
|
-
type: "reasoning-start",
|
|
2770
|
-
id: reasoningId
|
|
2771
|
-
});
|
|
2772
|
-
reasoningStarted.set(idx, true);
|
|
2773
3092
|
}
|
|
2774
3093
|
if (block.type === "text") {
|
|
2775
3094
|
textBlockIndices.add(idx);
|
|
@@ -2819,8 +3138,16 @@ ${plan}
|
|
|
2819
3138
|
const idx = msg.index;
|
|
2820
3139
|
if (delta.type === "thinking_delta" && delta.thinking) {
|
|
2821
3140
|
noteReasoning();
|
|
3141
|
+
hadThinkingTextFromStream = true;
|
|
2822
3142
|
const reasoningId = reasoningIds.get(idx);
|
|
2823
3143
|
if (reasoningId) {
|
|
3144
|
+
if (!reasoningStarted.get(idx)) {
|
|
3145
|
+
controller.enqueue({
|
|
3146
|
+
type: "reasoning-start",
|
|
3147
|
+
id: reasoningId
|
|
3148
|
+
});
|
|
3149
|
+
reasoningStarted.set(idx, true);
|
|
3150
|
+
}
|
|
2824
3151
|
controller.enqueue({
|
|
2825
3152
|
type: "reasoning-delta",
|
|
2826
3153
|
id: reasoningId,
|
|
@@ -2849,6 +3176,13 @@ ${plan}
|
|
|
2849
3176
|
});
|
|
2850
3177
|
}
|
|
2851
3178
|
}
|
|
3179
|
+
if (!KNOWN_DELTA_TYPES.has(delta.type)) {
|
|
3180
|
+
log.debug("unrecognized content_block_delta type", {
|
|
3181
|
+
type: delta.type,
|
|
3182
|
+
idx,
|
|
3183
|
+
keys: Object.keys(delta)
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
2852
3186
|
}
|
|
2853
3187
|
if (msg.type === "content_block_stop" && msg.index !== void 0) {
|
|
2854
3188
|
const idx = msg.index;
|
|
@@ -2946,6 +3280,42 @@ ${plan}
|
|
|
2946
3280
|
if (msg.type === "assistant" && msg.message && typeof msg.message.stop_reason === "string") {
|
|
2947
3281
|
lastStopReason = msg.message.stop_reason;
|
|
2948
3282
|
}
|
|
3283
|
+
if (msg.type === "assistant" && msg.message?.content && gotPartialEvents) {
|
|
3284
|
+
const thinkingBlocks = msg.message.content.filter(
|
|
3285
|
+
(b) => b.type === "thinking"
|
|
3286
|
+
);
|
|
3287
|
+
if (thinkingBlocks.length > 0) {
|
|
3288
|
+
log.info("assistant message thinking blocks", {
|
|
3289
|
+
count: thinkingBlocks.length,
|
|
3290
|
+
hasText: thinkingBlocks.some(
|
|
3291
|
+
(b) => typeof b.thinking === "string" && b.thinking.length > 0
|
|
3292
|
+
),
|
|
3293
|
+
hadStreamThinking: hadThinkingTextFromStream
|
|
3294
|
+
});
|
|
3295
|
+
if (!hadThinkingTextFromStream) {
|
|
3296
|
+
for (const block of thinkingBlocks) {
|
|
3297
|
+
if (block.thinking && block.thinking.length > 0) {
|
|
3298
|
+
noteReasoning();
|
|
3299
|
+
hadThinkingTextFromStream = true;
|
|
3300
|
+
const thinkingId = generateId();
|
|
3301
|
+
controller.enqueue({
|
|
3302
|
+
type: "reasoning-start",
|
|
3303
|
+
id: thinkingId
|
|
3304
|
+
});
|
|
3305
|
+
controller.enqueue({
|
|
3306
|
+
type: "reasoning-delta",
|
|
3307
|
+
id: thinkingId,
|
|
3308
|
+
delta: block.thinking
|
|
3309
|
+
});
|
|
3310
|
+
controller.enqueue({
|
|
3311
|
+
type: "reasoning-end",
|
|
3312
|
+
id: thinkingId
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
2949
3319
|
if (msg.type === "assistant" && msg.message?.content && !gotPartialEvents) {
|
|
2950
3320
|
const hasText = msg.message.content.some(
|
|
2951
3321
|
(b) => b.type === "text" && b.text
|
|
@@ -3234,7 +3604,10 @@ ${plan}
|
|
|
3234
3604
|
finishReason: toFinishReason("stop"),
|
|
3235
3605
|
usage: toUsage(msg.usage),
|
|
3236
3606
|
providerMetadata: {
|
|
3237
|
-
"claude-code":
|
|
3607
|
+
"claude-code": {
|
|
3608
|
+
...resultMeta,
|
|
3609
|
+
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
3610
|
+
},
|
|
3238
3611
|
...typeof msg.usage?.cache_creation_input_tokens === "number" ? {
|
|
3239
3612
|
anthropic: {
|
|
3240
3613
|
cacheCreationInputTokens: msg.usage.cache_creation_input_tokens
|
|
@@ -3275,7 +3648,10 @@ ${plan}
|
|
|
3275
3648
|
finishReason: toFinishReason("stop"),
|
|
3276
3649
|
usage: toUsage(),
|
|
3277
3650
|
providerMetadata: {
|
|
3278
|
-
"claude-code":
|
|
3651
|
+
"claude-code": {
|
|
3652
|
+
...resultMeta,
|
|
3653
|
+
...compactionMode ? { compactionModel: effectiveModelId } : {}
|
|
3654
|
+
}
|
|
3279
3655
|
}
|
|
3280
3656
|
});
|
|
3281
3657
|
try {
|
|
@@ -3831,10 +4207,6 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
3831
4207
|
}
|
|
3832
4208
|
|
|
3833
4209
|
// src/index.ts
|
|
3834
|
-
var opencodeProjectDirectory;
|
|
3835
|
-
function isUsableDirectory(d) {
|
|
3836
|
-
return typeof d === "string" && d.length > 1 && d !== "/";
|
|
3837
|
-
}
|
|
3838
4210
|
function pickOpencodeDirectory(input) {
|
|
3839
4211
|
if (!input || typeof input !== "object") return void 0;
|
|
3840
4212
|
const ctx = input;
|
|
@@ -3875,7 +4247,8 @@ function createClaudeCode(settings = {}) {
|
|
|
3875
4247
|
hotReloadMcp: settings.hotReloadMcp ?? true,
|
|
3876
4248
|
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
|
|
3877
4249
|
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
3878
|
-
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart"
|
|
4250
|
+
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart",
|
|
4251
|
+
compactionModel: settings.compactionModel
|
|
3879
4252
|
});
|
|
3880
4253
|
};
|
|
3881
4254
|
const provider = function(modelId) {
|
|
@@ -3971,7 +4344,6 @@ async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefault
|
|
|
3971
4344
|
const mergedOptions = {
|
|
3972
4345
|
cliPath: "claude",
|
|
3973
4346
|
proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
|
|
3974
|
-
...opencodeProjectDirectory ? { cwd: opencodeProjectDirectory } : {},
|
|
3975
4347
|
...optionDefaults,
|
|
3976
4348
|
...cleanProviderOptions(existing?.options),
|
|
3977
4349
|
providerID
|
|
@@ -4037,7 +4409,7 @@ var server = async (input) => {
|
|
|
4037
4409
|
if (input && typeof input === "object" && "client" in input) {
|
|
4038
4410
|
setOpencodeClient(input.client);
|
|
4039
4411
|
}
|
|
4040
|
-
|
|
4412
|
+
setOpencodeProjectDirectory(pickOpencodeDirectory(input));
|
|
4041
4413
|
return {
|
|
4042
4414
|
config: async (config) => {
|
|
4043
4415
|
config.provider ??= {};
|
|
@@ -4069,6 +4441,27 @@ var server = async (input) => {
|
|
|
4069
4441
|
provider: {
|
|
4070
4442
|
id: PROVIDER_ID2,
|
|
4071
4443
|
models: async (provider) => defaultModelsForProvider(provider.models)
|
|
4444
|
+
},
|
|
4445
|
+
// Inject opencode's agent name into providerOptions so the language
|
|
4446
|
+
// model can distinguish /compact (and title) calls from normal turns.
|
|
4447
|
+
// Without this, every no-tools call looks like a title request and
|
|
4448
|
+
// gets short-circuited to a synthetic stub.
|
|
4449
|
+
"chat.params": async (input2, output) => {
|
|
4450
|
+
const providerID = input2.model?.providerID ?? input2.provider?.info?.id;
|
|
4451
|
+
log.debug("chat.params hook fired", {
|
|
4452
|
+
agent: input2.agent,
|
|
4453
|
+
providerID,
|
|
4454
|
+
sessionID: input2.sessionID
|
|
4455
|
+
});
|
|
4456
|
+
if (typeof providerID !== "string") return;
|
|
4457
|
+
if (providerID !== PROVIDER_ID2 && !providerID.startsWith(`${PROVIDER_ID2}-`)) return;
|
|
4458
|
+
if (!input2.agent) return;
|
|
4459
|
+
output.options ??= {};
|
|
4460
|
+
output.options.opencodeAgent = input2.agent;
|
|
4461
|
+
log.debug("chat.params tagged providerOptions", {
|
|
4462
|
+
agent: input2.agent,
|
|
4463
|
+
providerID
|
|
4464
|
+
});
|
|
4072
4465
|
}
|
|
4073
4466
|
};
|
|
4074
4467
|
};
|