@khalilgharbaoui/opencode-claude-code-plugin 0.4.23 → 0.5.1
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 +37 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +155 -60
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -313,6 +313,32 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
|
|
|
313
313
|
|
|
314
314
|
---
|
|
315
315
|
|
|
316
|
+
## AskUserQuestion
|
|
317
|
+
|
|
318
|
+
opencode has no native structured ask-question executor to proxy through (unlike `Bash`/`Task`), so the plugin handles `AskUserQuestion` specially:
|
|
319
|
+
|
|
320
|
+
1. **It renders the full question.** The tool's payload — every question, header, option label, and option description — is emitted as readable markdown into the assistant stream so the user actually sees the choices (same approach as `ExitPlanMode`).
|
|
321
|
+
2. **It is never auto-allowed at the CLI gate.** Allowing it would let the headless Claude CLI resolve its own question (no TTY → fabricated/empty answer) and proceed on a guess. `controlRequestBehaviorForTool` hard-denies `AskUserQuestion` and returns a message telling the model to wait for the operator's answer — or, if the run is non-interactive, to proceed with the single most reasonable option and state its assumption rather than stall.
|
|
322
|
+
|
|
323
|
+
This hard-deny sits **below** `controlRequestToolBehaviors` in precedence but **above** the global `controlRequestBehavior`. So:
|
|
324
|
+
|
|
325
|
+
- The global `controlRequestBehavior: "allow"` does **not** override it (interactive setups stay correct by default).
|
|
326
|
+
- An explicit per-tool entry **does**. For a fully unattended/automated deployment that prefers "guess and continue" over "stop and wait", restore the old auto-allow:
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
"provider": {
|
|
330
|
+
"claude-code": {
|
|
331
|
+
"options": {
|
|
332
|
+
"controlRequestToolBehaviors": { "AskUserQuestion": "allow" }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
With `"allow"`, the Claude CLI answers its own `AskUserQuestion` internally and the run never blocks — appropriate only when no operator is watching and forward progress matters more than a correct decision.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
316
342
|
## Compaction
|
|
317
343
|
|
|
318
344
|
When you run `/compact` in opencode, the plugin handles it on a short-lived dedicated Claude CLI spawn instead of routing it through your main conversation process. Three reasons:
|
|
@@ -435,6 +461,7 @@ plugin internals.
|
|
|
435
461
|
- No streaming of tool inputs as they're being constructed (Anthropic's `input_json_delta`); the plugin emits them once complete.
|
|
436
462
|
- Raw chain-of-thought is not available. Claude 4 family models ship summarized thinking only. See [Extended thinking](#extended-thinking) for the full picture.
|
|
437
463
|
- Recommended Claude Code CLI: **2.1.142+**. Older CLIs work for everything else but skip the `--thinking-display` flag, so Claude Opus 4.7 turns may render empty Thinking rows. If something breaks after a Claude Code update, the CLI version is the first thing to check.
|
|
464
|
+
- **Subagent todos require explicit permission.** opencode's task tool gates `todowrite` per subagent: without a `permission: { todowrite: "allow" }` rule on the subagent definition, opencode injects `todowrite: false` into the tools dict and the plugin's synthetic `todowrite` emissions surface as `⚙ invalid todowrite` rows. The built-in `general` subagent denies `todowrite` by default; use a custom subagent for parallel work that needs todo visibility. Subagent todos render inline in the **subagent's** session view (navigate with the TUI's `session.child.next` / `session.parent` commands), not in the parent session's panel.
|
|
438
465
|
|
|
439
466
|
---
|
|
440
467
|
|
|
@@ -481,6 +508,16 @@ git push origin master --follow-tags
|
|
|
481
508
|
|
|
482
509
|
The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish --access public` on tag push (requires `NPM_TOKEN` secret in the repo settings — use a classic automation token so 2FA isn't required at workflow time).
|
|
483
510
|
|
|
511
|
+
## Star History
|
|
512
|
+
|
|
513
|
+
<a href="https://www.star-history.com/?repos=khalilgharbaoui%2Fopencode-claude-code-plugin&type=date&legend=top-left">
|
|
514
|
+
<picture>
|
|
515
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=khalilgharbaoui/opencode-claude-code-plugin&type=date&theme=dark&legend=top-left" />
|
|
516
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=khalilgharbaoui/opencode-claude-code-plugin&type=date&legend=top-left" />
|
|
517
|
+
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=khalilgharbaoui/opencode-claude-code-plugin&type=date&legend=top-left" />
|
|
518
|
+
</picture>
|
|
519
|
+
</a>
|
|
520
|
+
|
|
484
521
|
## License
|
|
485
522
|
|
|
486
523
|
MIT. See [LICENSE](./LICENSE).
|
package/dist/index.d.ts
CHANGED
|
@@ -89,6 +89,13 @@ type OpenCodeEvent = {
|
|
|
89
89
|
* for the current call ("default", "compaction", "title", etc.), the
|
|
90
90
|
* resolved model, and the user message. Output is the mutable params bag
|
|
91
91
|
* the hook can adjust before opencode forwards them to the LM.
|
|
92
|
+
*
|
|
93
|
+
* The plugin injects `input.agent` as `opencodeAgent` and `input.sessionID`
|
|
94
|
+
* as `opencodeSessionID` into `output.options` so the language model can
|
|
95
|
+
* read them from `providerOptions[providerID]` on every LLM request.
|
|
96
|
+
* `opencodeSessionID` serves as a fallback affinity token when the
|
|
97
|
+
* `x-session-affinity` request header is absent (provider switch
|
|
98
|
+
* mid-session, title synthesis paths, older opencode versions).
|
|
92
99
|
*/
|
|
93
100
|
type OpenCodeChatParamsInput = {
|
|
94
101
|
sessionID?: string;
|
|
@@ -450,11 +457,14 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
450
457
|
private ensureProxyServer;
|
|
451
458
|
private extractPendingProxyResult;
|
|
452
459
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
460
|
+
* Resolve the session affinity token for this LLM call. Delegates to the
|
|
461
|
+
* exported `resolveSessionAffinity` helper so the logic is unit-testable.
|
|
462
|
+
* Priority:
|
|
463
|
+
* 1. `x-session-affinity` request header (primary).
|
|
464
|
+
* 2. `opencodeSessionID` in providerOptions (chat.params hook fallback —
|
|
465
|
+
* covers provider switches mid-session and title synthesis paths
|
|
466
|
+
* where the header is absent).
|
|
467
|
+
* 3. `"default"`.
|
|
458
468
|
*/
|
|
459
469
|
private sessionAffinity;
|
|
460
470
|
private controlRequestBehaviorForTool;
|
package/dist/index.js
CHANGED
|
@@ -1955,6 +1955,22 @@ function resolveCompactionModel(configured) {
|
|
|
1955
1955
|
if (trimmed) return trimmed;
|
|
1956
1956
|
return DEFAULT_COMPACTION_MODEL;
|
|
1957
1957
|
}
|
|
1958
|
+
function resolveSessionAffinity(headers, providerOptions, providerKey) {
|
|
1959
|
+
if (headers) {
|
|
1960
|
+
for (const key of Object.keys(headers)) {
|
|
1961
|
+
if (key.toLowerCase() === "x-session-affinity") {
|
|
1962
|
+
const v = headers[key];
|
|
1963
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (providerOptions) {
|
|
1968
|
+
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
1969
|
+
const sid = bag?.opencodeSessionID;
|
|
1970
|
+
if (typeof sid === "string" && sid.length > 0) return sid;
|
|
1971
|
+
}
|
|
1972
|
+
return "default";
|
|
1973
|
+
}
|
|
1958
1974
|
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
1959
1975
|
"thinking_delta",
|
|
1960
1976
|
"text_delta",
|
|
@@ -1997,6 +2013,46 @@ var AUTO_CONTINUE_PROMPT = "Continue the task from where you stopped. Do not sum
|
|
|
1997
2013
|
function normalizeVisibleText(text) {
|
|
1998
2014
|
return text.replace(/\s+/g, " ").trim();
|
|
1999
2015
|
}
|
|
2016
|
+
function isAskUserQuestionTool(name) {
|
|
2017
|
+
if (!name) return false;
|
|
2018
|
+
const n = name.toLowerCase();
|
|
2019
|
+
return n === "askuserquestion" || n === "ask_user_question";
|
|
2020
|
+
}
|
|
2021
|
+
function formatAskUserQuestion(input) {
|
|
2022
|
+
const anyInput = input;
|
|
2023
|
+
const questions = Array.isArray(anyInput?.questions) ? anyInput.questions : [];
|
|
2024
|
+
if (questions.length === 0) {
|
|
2025
|
+
const single = anyInput?.question ?? anyInput?.text;
|
|
2026
|
+
const q = typeof single === "string" && single.trim() ? single.trim() : "Question?";
|
|
2027
|
+
return `
|
|
2028
|
+
|
|
2029
|
+
**${q}**
|
|
2030
|
+
|
|
2031
|
+
_Reply with your answer to continue._
|
|
2032
|
+
|
|
2033
|
+
`;
|
|
2034
|
+
}
|
|
2035
|
+
const out = ["\n\n"];
|
|
2036
|
+
const multiQ = questions.length > 1;
|
|
2037
|
+
questions.forEach((q, i) => {
|
|
2038
|
+
const text = typeof q?.question === "string" && q.question.trim() || typeof q?.text === "string" && q.text.trim() || "Question?";
|
|
2039
|
+
const header = typeof q?.header === "string" && q.header.trim() ? q.header.trim() : "";
|
|
2040
|
+
out.push(`**${multiQ ? `${i + 1}. ` : ""}${text}**`);
|
|
2041
|
+
if (header) out.push(` _(${header})_`);
|
|
2042
|
+
out.push("\n\n");
|
|
2043
|
+
const options = Array.isArray(q?.options) ? q.options : [];
|
|
2044
|
+
options.forEach((opt, j) => {
|
|
2045
|
+
const label = typeof opt?.label === "string" && opt.label.trim() || typeof opt === "string" && opt.trim() || `Option ${j + 1}`;
|
|
2046
|
+
const desc = typeof opt?.description === "string" && opt.description.trim() ? ` \u2014 ${opt.description.trim()}` : "";
|
|
2047
|
+
out.push(`${j + 1}. **${label}**${desc}
|
|
2048
|
+
`);
|
|
2049
|
+
});
|
|
2050
|
+
out.push(
|
|
2051
|
+
q?.multiSelect === true ? "\n_Select one or more \u2014 reply with the numbers or labels._\n\n" : "\n_Reply with your choice (the number or label)._\n\n"
|
|
2052
|
+
);
|
|
2053
|
+
});
|
|
2054
|
+
return out.join("");
|
|
2055
|
+
}
|
|
2000
2056
|
function looksLikeQuestion(text) {
|
|
2001
2057
|
const normalized = normalizeVisibleText(text).toLowerCase();
|
|
2002
2058
|
if (!normalized) return false;
|
|
@@ -2092,6 +2148,11 @@ function nearestWorkspaceAgentsPrompt(cwd) {
|
|
|
2092
2148
|
dir = parent;
|
|
2093
2149
|
}
|
|
2094
2150
|
}
|
|
2151
|
+
var AGENTS_MAINTENANCE_HINT = `## Keeping AGENTS.md up to date
|
|
2152
|
+
|
|
2153
|
+
When you complete a task, phase, or to-do item that is listed in AGENTS.md, update the file
|
|
2154
|
+
immediately after the work is done \u2014 mark it \u2705, check it off, or remove it. Do this inside
|
|
2155
|
+
the same turn so the next session does not repeat work that is already finished.`;
|
|
2095
2156
|
var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
|
|
2096
2157
|
|
|
2097
2158
|
opencode requires the user to press "continue" after each turn ends. When a
|
|
@@ -2100,13 +2161,43 @@ than pausing for user confirmation between subtasks. End the turn only
|
|
|
2100
2161
|
when the task is done, you need clarification on intent, or you hit a real
|
|
2101
2162
|
blocker. The user can interrupt or abort at any time; turn endings should
|
|
2102
2163
|
mark meaningful checkpoints, not every completed substep.`;
|
|
2103
|
-
|
|
2164
|
+
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
2165
|
+
|
|
2166
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
2167
|
+
|
|
2168
|
+
- The \`compress\` tool is NOT available. Do not attempt to call it.
|
|
2169
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
2170
|
+
- Context window management is handled automatically by Claude CLI's own session history.
|
|
2171
|
+
- Ignore any system instructions that tell you to call \`compress\` \u2014 they are intended for direct API providers, not this environment.
|
|
2172
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
2173
|
+
function extractSystemMessages(prompt) {
|
|
2174
|
+
const out = [];
|
|
2175
|
+
for (const msg of prompt) {
|
|
2176
|
+
if (msg.role !== "system") continue;
|
|
2177
|
+
if (typeof msg.content === "string") {
|
|
2178
|
+
if (msg.content.trim()) out.push(msg.content.trim());
|
|
2179
|
+
} else if (Array.isArray(msg.content)) {
|
|
2180
|
+
for (const part of msg.content) {
|
|
2181
|
+
if (part?.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
2182
|
+
out.push(part.text.trim());
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return out;
|
|
2188
|
+
}
|
|
2189
|
+
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = []) {
|
|
2104
2190
|
const parts = [];
|
|
2191
|
+
parts.push(CLAUDE_CLI_CONTEXT_NOTE);
|
|
2192
|
+
for (const s of extraSystemContent) {
|
|
2193
|
+
if (s.trim()) parts.push(s.trim());
|
|
2194
|
+
}
|
|
2105
2195
|
const configRoot = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
|
|
2106
2196
|
const globalAgents = readPromptFileIfPresent(join5(configRoot, "opencode", "AGENTS.md"));
|
|
2107
2197
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
2108
2198
|
if (globalAgents) parts.push(globalAgents);
|
|
2109
2199
|
if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
|
|
2200
|
+
if (globalAgents || workspaceAgents) parts.push(AGENTS_MAINTENANCE_HINT);
|
|
2110
2201
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
2111
2202
|
const content = parts.join("\n\n");
|
|
2112
2203
|
if (!content) return void 0;
|
|
@@ -2296,22 +2387,22 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2296
2387
|
return null;
|
|
2297
2388
|
}
|
|
2298
2389
|
/**
|
|
2299
|
-
*
|
|
2300
|
-
*
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2390
|
+
* Resolve the session affinity token for this LLM call. Delegates to the
|
|
2391
|
+
* exported `resolveSessionAffinity` helper so the logic is unit-testable.
|
|
2392
|
+
* Priority:
|
|
2393
|
+
* 1. `x-session-affinity` request header (primary).
|
|
2394
|
+
* 2. `opencodeSessionID` in providerOptions (chat.params hook fallback —
|
|
2395
|
+
* covers provider switches mid-session and title synthesis paths
|
|
2396
|
+
* where the header is absent).
|
|
2397
|
+
* 3. `"default"`.
|
|
2304
2398
|
*/
|
|
2305
2399
|
sessionAffinity(options) {
|
|
2306
2400
|
const headers = options?.headers;
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
return "default";
|
|
2401
|
+
return resolveSessionAffinity(
|
|
2402
|
+
headers,
|
|
2403
|
+
options.providerOptions,
|
|
2404
|
+
this.config.provider
|
|
2405
|
+
);
|
|
2315
2406
|
}
|
|
2316
2407
|
controlRequestBehaviorForTool(toolName) {
|
|
2317
2408
|
const configured = this.config.controlRequestToolBehaviors;
|
|
@@ -2325,6 +2416,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2325
2416
|
}
|
|
2326
2417
|
}
|
|
2327
2418
|
}
|
|
2419
|
+
if (isAskUserQuestionTool(toolName)) return "deny";
|
|
2328
2420
|
return this.config.controlRequestBehavior ?? "allow";
|
|
2329
2421
|
}
|
|
2330
2422
|
writeControlResponse(proc, requestId, response) {
|
|
@@ -2368,9 +2460,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2368
2460
|
toolName
|
|
2369
2461
|
});
|
|
2370
2462
|
} else {
|
|
2463
|
+
const denyMessage = isAskUserQuestionTool(toolName) ? "Your question and its options have already been presented to the operator in full. Prefer to stop here and wait for their answer in the next message \u2014 do not silently guess. But if this is an automated or otherwise non-interactive run where no operator will reply, do not stall: proceed with the single most reasonable option and state, in one line, the assumption you made so it can be corrected later." : this.config.controlRequestDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
|
|
2371
2464
|
this.writeControlResponse(proc, requestId, {
|
|
2372
2465
|
behavior: "deny",
|
|
2373
|
-
message:
|
|
2466
|
+
message: denyMessage,
|
|
2374
2467
|
toolUseID: request.tool_use_id
|
|
2375
2468
|
});
|
|
2376
2469
|
log.info("control request auto-denied", {
|
|
@@ -2621,7 +2714,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2621
2714
|
]);
|
|
2622
2715
|
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2623
2716
|
cwd,
|
|
2624
|
-
this.config.multiStepContinuation !== false
|
|
2717
|
+
this.config.multiStepContinuation !== false,
|
|
2718
|
+
extractSystemMessages(options.prompt)
|
|
2625
2719
|
);
|
|
2626
2720
|
const cliArgs = buildCliArgs({
|
|
2627
2721
|
sessionKey: sk,
|
|
@@ -2661,8 +2755,15 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2661
2755
|
let thinkingText = "";
|
|
2662
2756
|
let resultMeta = {};
|
|
2663
2757
|
const toolCalls = [];
|
|
2758
|
+
const toolCallStreams = /* @__PURE__ */ new Map();
|
|
2664
2759
|
let gotPartialEvents = false;
|
|
2665
2760
|
const result = await new Promise((resolve3, reject) => {
|
|
2761
|
+
const cleanup = () => {
|
|
2762
|
+
try {
|
|
2763
|
+
if (!proc.killed && proc.exitCode === null) proc.kill();
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2666
2767
|
rl.on("line", (line) => {
|
|
2667
2768
|
if (!line.trim()) return;
|
|
2668
2769
|
try {
|
|
@@ -2688,14 +2789,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
2688
2789
|
thinkingText += block.thinking;
|
|
2689
2790
|
}
|
|
2690
2791
|
if (block.type === "tool_use" && block.id && block.name) {
|
|
2691
|
-
if (block.name
|
|
2792
|
+
if (isAskUserQuestionTool(block.name)) {
|
|
2692
2793
|
const parsedInput = block.input ?? {};
|
|
2693
|
-
|
|
2694
|
-
responseText += `
|
|
2695
|
-
|
|
2696
|
-
_Asking: ${question}_
|
|
2697
|
-
|
|
2698
|
-
`;
|
|
2794
|
+
responseText += formatAskUserQuestion(parsedInput);
|
|
2699
2795
|
continue;
|
|
2700
2796
|
}
|
|
2701
2797
|
if (block.name === "ExitPlanMode") {
|
|
@@ -2718,30 +2814,41 @@ ${plan}
|
|
|
2718
2814
|
}
|
|
2719
2815
|
}
|
|
2720
2816
|
}
|
|
2721
|
-
if (msg.type === "content_block_start" && msg.content_block) {
|
|
2817
|
+
if (msg.type === "content_block_start" && msg.content_block && msg.index !== void 0) {
|
|
2722
2818
|
if (msg.content_block.type === "tool_use" && msg.content_block.id && msg.content_block.name) {
|
|
2723
|
-
|
|
2819
|
+
toolCallStreams.set(msg.index, {
|
|
2724
2820
|
id: msg.content_block.id,
|
|
2725
2821
|
name: msg.content_block.name,
|
|
2726
|
-
|
|
2822
|
+
inputJson: ""
|
|
2727
2823
|
});
|
|
2728
2824
|
}
|
|
2729
2825
|
}
|
|
2730
|
-
if (msg.type === "content_block_delta" && msg.delta) {
|
|
2826
|
+
if (msg.type === "content_block_delta" && msg.delta && msg.index !== void 0) {
|
|
2731
2827
|
if (msg.delta.type === "text_delta" && msg.delta.text) {
|
|
2732
2828
|
responseText += msg.delta.text;
|
|
2733
2829
|
}
|
|
2734
2830
|
if (msg.delta.type === "thinking_delta" && msg.delta.thinking) {
|
|
2735
2831
|
thinkingText += msg.delta.thinking;
|
|
2736
2832
|
}
|
|
2737
|
-
if (msg.delta.type === "input_json_delta" && msg.delta.partial_json
|
|
2738
|
-
const tc =
|
|
2739
|
-
if (tc)
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2833
|
+
if (msg.delta.type === "input_json_delta" && msg.delta.partial_json) {
|
|
2834
|
+
const tc = toolCallStreams.get(msg.index);
|
|
2835
|
+
if (tc) tc.inputJson += msg.delta.partial_json;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
if (msg.type === "content_block_stop" && msg.index !== void 0) {
|
|
2839
|
+
const tc = toolCallStreams.get(msg.index);
|
|
2840
|
+
if (tc) {
|
|
2841
|
+
let args = {};
|
|
2842
|
+
try {
|
|
2843
|
+
args = tc.inputJson ? JSON.parse(tc.inputJson) : {};
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
log.warn("tool input JSON parse failed", {
|
|
2846
|
+
name: tc.name,
|
|
2847
|
+
error: String(err)
|
|
2848
|
+
});
|
|
2744
2849
|
}
|
|
2850
|
+
toolCalls.push({ id: tc.id, name: tc.name, args });
|
|
2851
|
+
toolCallStreams.delete(msg.index);
|
|
2745
2852
|
}
|
|
2746
2853
|
}
|
|
2747
2854
|
if (msg.type === "result") {
|
|
@@ -2757,6 +2864,7 @@ ${plan}
|
|
|
2757
2864
|
durationMs: msg.duration_ms,
|
|
2758
2865
|
usage: msg.usage
|
|
2759
2866
|
};
|
|
2867
|
+
cleanup();
|
|
2760
2868
|
resolve3({
|
|
2761
2869
|
...resultMeta,
|
|
2762
2870
|
text: responseText,
|
|
@@ -2768,6 +2876,7 @@ ${plan}
|
|
|
2768
2876
|
}
|
|
2769
2877
|
});
|
|
2770
2878
|
rl.on("close", () => {
|
|
2879
|
+
cleanup();
|
|
2771
2880
|
resolve3({
|
|
2772
2881
|
...resultMeta,
|
|
2773
2882
|
text: responseText,
|
|
@@ -2777,6 +2886,7 @@ ${plan}
|
|
|
2777
2886
|
});
|
|
2778
2887
|
proc.on("error", (err) => {
|
|
2779
2888
|
log.error("process error", { error: err.message });
|
|
2889
|
+
cleanup();
|
|
2780
2890
|
reject(err);
|
|
2781
2891
|
});
|
|
2782
2892
|
proc.stderr?.on("data", (data) => {
|
|
@@ -3033,7 +3143,8 @@ ${plan}
|
|
|
3033
3143
|
);
|
|
3034
3144
|
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3035
3145
|
cwd,
|
|
3036
|
-
self.config.multiStepContinuation !== false
|
|
3146
|
+
self.config.multiStepContinuation !== false,
|
|
3147
|
+
extractSystemMessages(options.prompt)
|
|
3037
3148
|
);
|
|
3038
3149
|
cliArgs = buildCliArgs({
|
|
3039
3150
|
sessionKey: sk,
|
|
@@ -3356,22 +3467,12 @@ ${plan}
|
|
|
3356
3467
|
parsedInput = JSON.parse(tc.inputJson || "{}");
|
|
3357
3468
|
} catch {
|
|
3358
3469
|
}
|
|
3359
|
-
if (tc.name
|
|
3360
|
-
let question = "Question?";
|
|
3361
|
-
if (parsedInput?.questions && Array.isArray(parsedInput.questions) && parsedInput.questions.length > 0) {
|
|
3362
|
-
question = parsedInput.questions[0].question || parsedInput.questions[0].text || "Question?";
|
|
3363
|
-
} else {
|
|
3364
|
-
question = parsedInput?.question || parsedInput?.text || "Question?";
|
|
3365
|
-
}
|
|
3470
|
+
if (isAskUserQuestionTool(tc.name)) {
|
|
3366
3471
|
const askId = startTextBlock();
|
|
3367
3472
|
controller.enqueue({
|
|
3368
3473
|
type: "text-delta",
|
|
3369
3474
|
id: askId,
|
|
3370
|
-
delta:
|
|
3371
|
-
|
|
3372
|
-
_Asking: ${question}_
|
|
3373
|
-
|
|
3374
|
-
`
|
|
3475
|
+
delta: formatAskUserQuestion(parsedInput)
|
|
3375
3476
|
});
|
|
3376
3477
|
endTextBlock();
|
|
3377
3478
|
} else if (tc.name === "ExitPlanMode") {
|
|
@@ -3525,23 +3626,12 @@ ${plan}
|
|
|
3525
3626
|
name: block.name,
|
|
3526
3627
|
input: parsedInput
|
|
3527
3628
|
});
|
|
3528
|
-
if (block.name
|
|
3529
|
-
let question = "Question?";
|
|
3530
|
-
if (parsedInput?.questions && Array.isArray(parsedInput.questions) && parsedInput.questions.length > 0) {
|
|
3531
|
-
const q = parsedInput.questions[0];
|
|
3532
|
-
question = q.question || q.text || "Question?";
|
|
3533
|
-
} else {
|
|
3534
|
-
question = parsedInput?.question || parsedInput?.text || "Question?";
|
|
3535
|
-
}
|
|
3629
|
+
if (isAskUserQuestionTool(block.name)) {
|
|
3536
3630
|
const askId = startTextBlock();
|
|
3537
3631
|
controller.enqueue({
|
|
3538
3632
|
type: "text-delta",
|
|
3539
3633
|
id: askId,
|
|
3540
|
-
delta:
|
|
3541
|
-
|
|
3542
|
-
_Asking: ${question}_
|
|
3543
|
-
|
|
3544
|
-
`
|
|
3634
|
+
delta: formatAskUserQuestion(parsedInput)
|
|
3545
3635
|
});
|
|
3546
3636
|
endTextBlock();
|
|
3547
3637
|
} else if (block.name === "ExitPlanMode") {
|
|
@@ -4646,11 +4736,16 @@ var server = async (input) => {
|
|
|
4646
4736
|
});
|
|
4647
4737
|
if (typeof providerID !== "string") return;
|
|
4648
4738
|
if (providerID !== PROVIDER_ID2 && !providerID.startsWith(`${PROVIDER_ID2}-`)) return;
|
|
4739
|
+
if (typeof input2.sessionID === "string" && input2.sessionID.length > 0) {
|
|
4740
|
+
output.options ??= {};
|
|
4741
|
+
output.options.opencodeSessionID = input2.sessionID;
|
|
4742
|
+
}
|
|
4649
4743
|
if (!input2.agent) return;
|
|
4650
4744
|
output.options ??= {};
|
|
4651
4745
|
output.options.opencodeAgent = input2.agent;
|
|
4652
4746
|
log.debug("chat.params tagged providerOptions", {
|
|
4653
4747
|
agent: input2.agent,
|
|
4748
|
+
sessionID: input2.sessionID,
|
|
4654
4749
|
providerID
|
|
4655
4750
|
});
|
|
4656
4751
|
}
|