@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/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
- function compactConversationHistory(prompt) {
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 getClaudeUserMessage(prompt, includeHistoryContext = false, reasoningEffort) {
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 (includeHistoryContext) {
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
- const keyword = reasoningKeyword(reasoningEffort);
471
- if (keyword) {
472
- const lastTextPart = [...content].reverse().find((p) => p.type === "text");
473
- if (lastTextPart) {
474
- lastTextPart.text = lastTextPart.text ? `${lastTextPart.text}
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
- } else {
478
- content.push({ type: "text", text: `(${keyword})` });
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: { ...process.env, TERM: "xterm-256color" },
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 ?? process.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 getRuntimeMcpStatus();
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: { ...process.env, TERM: "xterm-256color" },
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 ?? process.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 sk = sessionKey(cwd, `${this.modelId}::${scope}::${affinity}`);
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 getRuntimeMcpStatus();
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: this.modelId,
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
- const discovery = self.effectiveMcpConfig(
2548
- cwd,
2549
- void 0,
2550
- runtimeStatus
2551
- );
2552
- const proxyMcpTools = await self.resolvedProxyMcpTools(
2553
- discovery.allEnabledServerNames
2554
- );
2555
- const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
2556
- const combinedProxyTools = resolvedProxy || proxyMcpTools ? [...resolvedProxy ?? [], ...proxyMcpTools ?? []] : null;
2557
- if (!proxyServer && combinedProxyTools) {
2558
- proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
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
- const proxyDisallowed = resolvedProxy ? disallowedToolFlags(resolvedProxy) : [];
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
- proxyServer,
2595
- mcp.bridgedHash,
2596
- systemPromptFile
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": resultMeta,
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": resultMeta
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
- opencodeProjectDirectory = pickOpencodeDirectory(input);
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
  };