@khalilgharbaoui/opencode-claude-code-plugin 0.27.0 → 0.27.2
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.d.ts +6 -64
- package/dist/index.js +772 -732
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -647,7 +647,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
647
647
|
done
|
|
648
648
|
|
|
649
649
|
export CLAUDE_CONFIG_DIR=${shellSingleQuote(configDir)}
|
|
650
|
-
|
|
650
|
+
# \${args[@]+...} keeps an empty array legal under \`set -u\` on bash 3.2,
|
|
651
|
+
# which is the bash macOS ships and \`/usr/bin/env bash\` usually finds.
|
|
652
|
+
exec ${shellSingleQuote(baseCliPath)} \${args[@]+"\${args[@]}"}
|
|
651
653
|
`;
|
|
652
654
|
await writeFile(wrapperPath, script, "utf8");
|
|
653
655
|
await chmod(wrapperPath, 493);
|
|
@@ -6526,109 +6528,142 @@ function spawnInteractiveProcess(opts) {
|
|
|
6526
6528
|
};
|
|
6527
6529
|
}
|
|
6528
6530
|
|
|
6529
|
-
// src/
|
|
6531
|
+
// src/prompts.ts
|
|
6530
6532
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
6531
|
-
import { unlink as unlink3 } from "fs/promises";
|
|
6532
6533
|
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
6533
6534
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6534
6535
|
import { dirname as dirname5, join as join8 } from "path";
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6536
|
+
function readPromptFileIfPresent(path10) {
|
|
6537
|
+
try {
|
|
6538
|
+
const content = readFileSync5(path10, "utf8").trim();
|
|
6539
|
+
return content || void 0;
|
|
6540
|
+
} catch {
|
|
6541
|
+
return void 0;
|
|
6542
|
+
}
|
|
6540
6543
|
}
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6544
|
+
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
6545
|
+
let dir = cwd;
|
|
6546
|
+
while (true) {
|
|
6547
|
+
const content = readPromptFileIfPresent(join8(dir, "AGENTS.md"));
|
|
6548
|
+
if (content) return content;
|
|
6549
|
+
const parent = dirname5(dir);
|
|
6550
|
+
if (parent === dir) return void 0;
|
|
6551
|
+
dir = parent;
|
|
6552
|
+
}
|
|
6549
6553
|
}
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6554
|
+
var AGENTS_MAINTENANCE_HINT = `## Keeping AGENTS.md up to date
|
|
6555
|
+
|
|
6556
|
+
When you complete a task, phase, or to-do item that is listed in AGENTS.md, update the file
|
|
6557
|
+
immediately after the work is done \u2014 mark it \u2705, check it off, or remove it. Do this inside
|
|
6558
|
+
the same turn so the next session does not repeat work that is already finished.`;
|
|
6559
|
+
var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
|
|
6560
|
+
|
|
6561
|
+
opencode requires the user to press "continue" after each turn ends. When a
|
|
6562
|
+
task has multiple steps, do them all in one turn \u2014 chain tool calls rather
|
|
6563
|
+
than pausing for user confirmation between subtasks. End the turn only
|
|
6564
|
+
when the task is done, you need clarification on intent, or you hit a real
|
|
6565
|
+
blocker. The user can interrupt or abort at any time; turn endings should
|
|
6566
|
+
mark meaningful checkpoints, not every completed substep.`;
|
|
6567
|
+
var SUBAGENT_DISPATCH_HINT = `## opencode subagents
|
|
6568
|
+
|
|
6569
|
+
Subagent dispatch in this environment goes through exactly two tools: \`mcp__opencode_proxy__task\` for one subagent and \`mcp__opencode_proxy__task_batch\` for two or more at once.
|
|
6570
|
+
|
|
6571
|
+
- Two or more independent subagents in one response: make ONE \`mcp__opencode_proxy__task_batch\` call with a \`tasks\` array (each item is a normal task input). Claude Code runs MCP calls one at a time, so several \`mcp__opencode_proxy__task\` calls in the same response run serially; \`task_batch\` runs them concurrently in opencode and returns every result together, labelled in order.
|
|
6572
|
+
- When the user mentions \`@<agent>\` or an instruction says "call the task tool with subagent: <name>", call \`mcp__opencode_proxy__task\` with \`subagent_type: "<name>"\`.
|
|
6573
|
+
- If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__task\`), then call it.
|
|
6574
|
+
- Claude Code's built-in TaskCreate/TaskUpdate/TaskList manage a local todo list. They cannot dispatch subagents; creating a task there runs nothing. Never report a subagent as dispatched unless \`mcp__opencode_proxy__task\` returned its result.
|
|
6575
|
+
- Do not verify a subagent's existence by searching config files \u2014 the tool's description lists the available agent types, and invalid types fail fast with a clear error.`;
|
|
6576
|
+
var QUESTION_PROXY_HINT = `## Asking the operator questions
|
|
6577
|
+
|
|
6578
|
+
Structured questions in this environment go through exactly one tool: \`mcp__opencode_proxy__question\`.
|
|
6579
|
+
|
|
6580
|
+
- When you need to ask the operator a question with options, call \`mcp__opencode_proxy__question\` with a \`questions\` array (each item has \`question\`, \`header\`, \`options\` of \`{label, description}\`, and optional \`multiple\`).
|
|
6581
|
+
- If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__question\`), then call it by its FULL name.
|
|
6582
|
+
- Do NOT call bare \`question\` \u2014 that is not a tool. Always use the full \`mcp__opencode_proxy__question\` name when invoking it.
|
|
6583
|
+
- Claude Code's built-in \`AskUserQuestion\` is disabled in this environment; the proxy is the only way to ask structured questions.`;
|
|
6584
|
+
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
6585
|
+
|
|
6586
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
6587
|
+
|
|
6588
|
+
- The \`compress\` tool is NOT available. Do not attempt to call it.
|
|
6589
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
6590
|
+
- Context window management is handled automatically by Claude CLI's own session history.
|
|
6591
|
+
- Ignore any system instructions that tell you to call \`compress\` \u2014 they are intended for direct API providers, not this environment.
|
|
6592
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
6593
|
+
var CLAUDE_CLI_COMPRESS_NOTE = `## Runtime environment: Claude Code CLI
|
|
6594
|
+
|
|
6595
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
6596
|
+
|
|
6597
|
+
- To compress context, call \`mcp__opencode_proxy__compress\` with a \`summary\` argument. Use that exact full name.
|
|
6598
|
+
- The reset happens at the start of your NEXT turn: this Claude Code session is discarded and a fresh one starts with your summary as its only prior context. Keep working normally after the call.
|
|
6599
|
+
- Everything outside the summary is gone after the reset \u2014 tool output, files you read, and the earlier conversation are not replayed. Write the summary as the authoritative record.
|
|
6600
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
6601
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
6602
|
+
var CLAUDE_CLI_OPENCODE_COMPRESS_NOTE = `## Runtime environment: Claude Code CLI
|
|
6603
|
+
|
|
6604
|
+
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
6605
|
+
|
|
6606
|
+
- To compress context, call \`mcp__opencode_proxy__compress\`. Use that exact full name. It runs opencode's own \`compress\` tool, which is what a "MAX CONTEXT LIMIT REACHED" reminder is asking you to do.
|
|
6607
|
+
- It compresses opencode's stored conversation, NOT this Claude Code session. Your current session keeps the context it already has, so do not assume earlier detail is gone after the call.
|
|
6608
|
+
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
6609
|
+
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
6610
|
+
function extractSystemMessages(prompt) {
|
|
6611
|
+
const out = [];
|
|
6612
|
+
for (const msg of prompt) {
|
|
6613
|
+
if (msg.role !== "system") continue;
|
|
6614
|
+
if (typeof msg.content === "string") {
|
|
6615
|
+
if (msg.content.trim()) out.push(msg.content.trim());
|
|
6616
|
+
} else if (Array.isArray(msg.content)) {
|
|
6617
|
+
for (const part of msg.content) {
|
|
6618
|
+
if (part?.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
6619
|
+
out.push(part.text.trim());
|
|
6620
|
+
}
|
|
6556
6621
|
}
|
|
6557
6622
|
}
|
|
6558
6623
|
}
|
|
6559
|
-
|
|
6560
|
-
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
6561
|
-
const sid = bag?.opencodeSessionID;
|
|
6562
|
-
if (typeof sid === "string" && sid.length > 0) return sid;
|
|
6563
|
-
}
|
|
6564
|
-
return "default";
|
|
6624
|
+
return out;
|
|
6565
6625
|
}
|
|
6566
|
-
function
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6626
|
+
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = [], options = {}) {
|
|
6627
|
+
const parts = [];
|
|
6628
|
+
if (options.compressionSummary?.trim()) {
|
|
6629
|
+
parts.push(
|
|
6630
|
+
`## Summary of earlier work (context was compressed)
|
|
6631
|
+
|
|
6632
|
+
${options.compressionSummary.trim()}`
|
|
6633
|
+
);
|
|
6571
6634
|
}
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
}
|
|
6578
|
-
}
|
|
6635
|
+
parts.push(
|
|
6636
|
+
options.compressEnabled ? CLAUDE_CLI_COMPRESS_NOTE : options.opencodeCompressEnabled ? CLAUDE_CLI_OPENCODE_COMPRESS_NOTE : CLAUDE_CLI_CONTEXT_NOTE
|
|
6637
|
+
);
|
|
6638
|
+
for (const s of extraSystemContent) {
|
|
6639
|
+
if (s.trim()) parts.push(s.trim());
|
|
6579
6640
|
}
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6641
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join8(homedir6(), ".config");
|
|
6642
|
+
const globalAgents = readPromptFileIfPresent(join8(configRoot, "opencode", "AGENTS.md"));
|
|
6643
|
+
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
6644
|
+
const forwarded = extraSystemContent.join("\n\n");
|
|
6645
|
+
const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
|
|
6646
|
+
const pushWorkspace = !!workspaceAgents && workspaceAgents !== globalAgents && !forwarded.includes(workspaceAgents);
|
|
6647
|
+
if (pushGlobal) parts.push(globalAgents);
|
|
6648
|
+
if (pushWorkspace) parts.push(workspaceAgents);
|
|
6649
|
+
if (pushGlobal || pushWorkspace) parts.push(AGENTS_MAINTENANCE_HINT);
|
|
6650
|
+
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
6651
|
+
const content = parts.join("\n\n");
|
|
6652
|
+
if (!content) return void 0;
|
|
6653
|
+
const path10 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
6586
6654
|
try {
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
6593
|
-
"thinking_delta",
|
|
6594
|
-
"text_delta",
|
|
6595
|
-
"input_json_delta",
|
|
6596
|
-
"signature_delta"
|
|
6597
|
-
]);
|
|
6598
|
-
function hasNewUserContent(prompt) {
|
|
6599
|
-
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
6600
|
-
const msg = prompt[i];
|
|
6601
|
-
if (msg.role === "assistant") return false;
|
|
6602
|
-
if (msg.role === "tool") {
|
|
6603
|
-
const content2 = msg.content;
|
|
6604
|
-
if (Array.isArray(content2)) {
|
|
6605
|
-
for (const part of content2) {
|
|
6606
|
-
if (part?.type === "tool-result") return true;
|
|
6607
|
-
}
|
|
6608
|
-
}
|
|
6609
|
-
continue;
|
|
6610
|
-
}
|
|
6611
|
-
if (msg.role !== "user") continue;
|
|
6612
|
-
const content = msg.content;
|
|
6613
|
-
if (typeof content === "string") {
|
|
6614
|
-
if (content.trim()) return true;
|
|
6615
|
-
continue;
|
|
6616
|
-
}
|
|
6617
|
-
if (Array.isArray(content)) {
|
|
6618
|
-
for (const part of content) {
|
|
6619
|
-
if (part.type === "text" && part.text && part.text.trim()) return true;
|
|
6620
|
-
if (part.type === "tool-result") return true;
|
|
6621
|
-
if (part.type === "image" || part.type === "file") return true;
|
|
6622
|
-
}
|
|
6623
|
-
}
|
|
6655
|
+
writeFileSync4(path10, content, "utf8");
|
|
6656
|
+
return path10;
|
|
6657
|
+
} catch (err) {
|
|
6658
|
+
log.warn("failed to write system prompt file", { error: String(err) });
|
|
6659
|
+
return void 0;
|
|
6624
6660
|
}
|
|
6625
|
-
return false;
|
|
6626
6661
|
}
|
|
6662
|
+
|
|
6663
|
+
// src/auto-continue.ts
|
|
6627
6664
|
var AUTO_CONTINUE_MAX_ATTEMPTS = 8;
|
|
6628
6665
|
var AUTO_CONTINUE_MAX_ELAPSED_MS = 10 * 60 * 1e3;
|
|
6629
6666
|
var AUTO_CONTINUE_NO_PROGRESS_LIMIT = 2;
|
|
6630
|
-
var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
|
|
6631
|
-
var CHILD_EXIT_STATUS_GRACE_MS = 250;
|
|
6632
6667
|
var AUTO_CONTINUE_PROMPT = "Continue the task from where you stopped. Do not summarize; keep working until the requested task is complete, you need clarification, or you hit a real blocker.";
|
|
6633
6668
|
function autoContinueEnabledFor(compactionMode, configured) {
|
|
6634
6669
|
return compactionMode ? false : configured;
|
|
@@ -6639,51 +6674,6 @@ function isTruncationStopReason(stopReason) {
|
|
|
6639
6674
|
function normalizeVisibleText(text) {
|
|
6640
6675
|
return text.replace(/\s+/g, " ").trim();
|
|
6641
6676
|
}
|
|
6642
|
-
function isAskUserQuestionTool(name) {
|
|
6643
|
-
if (!name) return false;
|
|
6644
|
-
const n = name.toLowerCase();
|
|
6645
|
-
return n === "askuserquestion" || n === "ask_user_question";
|
|
6646
|
-
}
|
|
6647
|
-
var ASK_USER_QUESTION_DENY_MESSAGE = "Your question and its options have already been presented to the operator verbatim. This is NOT a cancellation or a refusal \u2014 the operator simply has not answered yet. Stop now: end your turn without calling any more tools and without answering the question yourself. Do not say the question was cancelled, skipped, or declined, and do not guess, assume, or proceed on their behalf. Wait for the operator's reply, which arrives as the next user message.";
|
|
6648
|
-
function denyMessageForTool(toolName, configuredDenyMessage) {
|
|
6649
|
-
if (isAskUserQuestionTool(toolName)) return ASK_USER_QUESTION_DENY_MESSAGE;
|
|
6650
|
-
return configuredDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
|
|
6651
|
-
}
|
|
6652
|
-
function formatAskUserQuestion(input) {
|
|
6653
|
-
const anyInput = input;
|
|
6654
|
-
const questions = Array.isArray(anyInput?.questions) ? anyInput.questions : [];
|
|
6655
|
-
if (questions.length === 0) {
|
|
6656
|
-
const single = anyInput?.question ?? anyInput?.text;
|
|
6657
|
-
const q = typeof single === "string" && single.trim() ? single.trim() : "Question?";
|
|
6658
|
-
return `
|
|
6659
|
-
|
|
6660
|
-
**${q}**
|
|
6661
|
-
|
|
6662
|
-
_Reply with your answer to continue._
|
|
6663
|
-
|
|
6664
|
-
`;
|
|
6665
|
-
}
|
|
6666
|
-
const out = ["\n\n"];
|
|
6667
|
-
const multiQ = questions.length > 1;
|
|
6668
|
-
questions.forEach((q, i) => {
|
|
6669
|
-
const text = typeof q?.question === "string" && q.question.trim() || typeof q?.text === "string" && q.text.trim() || "Question?";
|
|
6670
|
-
const header = typeof q?.header === "string" && q.header.trim() ? q.header.trim() : "";
|
|
6671
|
-
out.push(`**${multiQ ? `${i + 1}. ` : ""}${text}**`);
|
|
6672
|
-
if (header) out.push(` _(${header})_`);
|
|
6673
|
-
out.push("\n\n");
|
|
6674
|
-
const options = Array.isArray(q?.options) ? q.options : [];
|
|
6675
|
-
options.forEach((opt, j) => {
|
|
6676
|
-
const label = typeof opt?.label === "string" && opt.label.trim() || typeof opt === "string" && opt.trim() || `Option ${j + 1}`;
|
|
6677
|
-
const desc = typeof opt?.description === "string" && opt.description.trim() ? ` \u2014 ${opt.description.trim()}` : "";
|
|
6678
|
-
out.push(`${j + 1}. **${label}**${desc}
|
|
6679
|
-
`);
|
|
6680
|
-
});
|
|
6681
|
-
out.push(
|
|
6682
|
-
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"
|
|
6683
|
-
);
|
|
6684
|
-
});
|
|
6685
|
-
return out.join("");
|
|
6686
|
-
}
|
|
6687
6677
|
function looksLikeQuestion(text) {
|
|
6688
6678
|
const normalized = normalizeVisibleText(text).toLowerCase();
|
|
6689
6679
|
if (!normalized) return false;
|
|
@@ -6772,189 +6762,622 @@ function makeAutoContinueMessage() {
|
|
|
6772
6762
|
}
|
|
6773
6763
|
});
|
|
6774
6764
|
}
|
|
6775
|
-
function makeLateProxyResultMessage(entries) {
|
|
6776
|
-
const sections = entries.map(({ call, result }) => {
|
|
6777
|
-
const failed = result.kind === "error" || result.isError === true;
|
|
6778
|
-
const body = result.kind === "error" ? result.message : result.text;
|
|
6779
|
-
return `Your earlier \`${call.toolName}\` tool call (id ${call.toolCallId}) has ${failed ? "failed" : "completed"}, but delivery or continuation was interrupted. Treat the following as its ${failed ? "error" : "result"} and continue from there; do not re-run it.
|
|
6780
6765
|
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
return
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6766
|
+
// src/ask-user-question.ts
|
|
6767
|
+
function isAskUserQuestionTool(name) {
|
|
6768
|
+
if (!name) return false;
|
|
6769
|
+
const n = name.toLowerCase();
|
|
6770
|
+
return n === "askuserquestion" || n === "ask_user_question";
|
|
6771
|
+
}
|
|
6772
|
+
var ASK_USER_QUESTION_DENY_MESSAGE = "Your question and its options have already been presented to the operator verbatim. This is NOT a cancellation or a refusal \u2014 the operator simply has not answered yet. Stop now: end your turn without calling any more tools and without answering the question yourself. Do not say the question was cancelled, skipped, or declined, and do not guess, assume, or proceed on their behalf. Wait for the operator's reply, which arrives as the next user message.";
|
|
6773
|
+
function denyMessageForTool(toolName, configuredDenyMessage) {
|
|
6774
|
+
if (isAskUserQuestionTool(toolName)) return ASK_USER_QUESTION_DENY_MESSAGE;
|
|
6775
|
+
return configuredDenyMessage ?? `Denied by opencode-claude-code policy for tool ${toolName}`;
|
|
6776
|
+
}
|
|
6777
|
+
function formatAskUserQuestion(input) {
|
|
6778
|
+
const anyInput = input;
|
|
6779
|
+
const questions = Array.isArray(anyInput?.questions) ? anyInput.questions : [];
|
|
6780
|
+
if (questions.length === 0) {
|
|
6781
|
+
const single = anyInput?.question ?? anyInput?.text;
|
|
6782
|
+
const q = typeof single === "string" && single.trim() ? single.trim() : "Question?";
|
|
6783
|
+
return `
|
|
6784
|
+
|
|
6785
|
+
**${q}**
|
|
6786
|
+
|
|
6787
|
+
_Reply with your answer to continue._
|
|
6788
|
+
|
|
6789
|
+
`;
|
|
6790
|
+
}
|
|
6791
|
+
const out = ["\n\n"];
|
|
6792
|
+
const multiQ = questions.length > 1;
|
|
6793
|
+
questions.forEach((q, i) => {
|
|
6794
|
+
const text = typeof q?.question === "string" && q.question.trim() || typeof q?.text === "string" && q.text.trim() || "Question?";
|
|
6795
|
+
const header = typeof q?.header === "string" && q.header.trim() ? q.header.trim() : "";
|
|
6796
|
+
out.push(`**${multiQ ? `${i + 1}. ` : ""}${text}**`);
|
|
6797
|
+
if (header) out.push(` _(${header})_`);
|
|
6798
|
+
out.push("\n\n");
|
|
6799
|
+
const options = Array.isArray(q?.options) ? q.options : [];
|
|
6800
|
+
options.forEach((opt, j) => {
|
|
6801
|
+
const label = typeof opt?.label === "string" && opt.label.trim() || typeof opt === "string" && opt.trim() || `Option ${j + 1}`;
|
|
6802
|
+
const desc = typeof opt?.description === "string" && opt.description.trim() ? ` \u2014 ${opt.description.trim()}` : "";
|
|
6803
|
+
out.push(`${j + 1}. **${label}**${desc}
|
|
6804
|
+
`);
|
|
6805
|
+
});
|
|
6806
|
+
out.push(
|
|
6807
|
+
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"
|
|
6808
|
+
);
|
|
6789
6809
|
});
|
|
6810
|
+
return out.join("");
|
|
6790
6811
|
}
|
|
6791
|
-
|
|
6812
|
+
|
|
6813
|
+
// src/fast-mode.ts
|
|
6814
|
+
var FAST_MODE_REASONS = {
|
|
6815
|
+
sdk_opt_in_required: "the CLI did not receive the headless opt-in (--settings). This is a plugin bug, please report it",
|
|
6816
|
+
extra_usage_disabled: "your account has usage credits turned off. Run /usage-credits in an interactive `claude` session to enable them",
|
|
6817
|
+
free: "fast mode requires a paid subscription or purchased credits",
|
|
6818
|
+
preference: "fast mode is turned off for your organization",
|
|
6819
|
+
model_not_allowed: "this model is not in your organization's allowed models",
|
|
6820
|
+
not_first_party: "fast mode only works against the Anthropic API directly, not Bedrock / Vertex / Foundry",
|
|
6821
|
+
network_error: "the CLI could not reach Anthropic to check availability",
|
|
6822
|
+
disabled_by_env: "CLAUDE_CODE_DISABLE_FAST_MODE is set in the environment",
|
|
6823
|
+
pending: "the CLI is still checking availability"
|
|
6824
|
+
};
|
|
6825
|
+
var warnedFastModeReasons = /* @__PURE__ */ new Set();
|
|
6826
|
+
function reportFastModeState(msg, requested) {
|
|
6827
|
+
const state = msg.fast_mode_state;
|
|
6828
|
+
if (!state) return;
|
|
6829
|
+
if (!requested) {
|
|
6830
|
+
log.debug("fast mode state", { state });
|
|
6831
|
+
return;
|
|
6832
|
+
}
|
|
6833
|
+
if (state === "on") {
|
|
6834
|
+
log.info("fast mode active", { state });
|
|
6835
|
+
return;
|
|
6836
|
+
}
|
|
6837
|
+
const reason = msg.fast_mode_disabled_reason;
|
|
6838
|
+
if (state === "cooldown") {
|
|
6839
|
+
log.notice(
|
|
6840
|
+
"fast mode is in cooldown after a rate limit; this turn runs at standard speed and is billed at standard Opus rates, not the fast price shown in the model picker.",
|
|
6841
|
+
{ state, reason: reason ?? null }
|
|
6842
|
+
);
|
|
6843
|
+
return;
|
|
6844
|
+
}
|
|
6845
|
+
const key = reason ?? "unknown";
|
|
6846
|
+
const explanation = reason ? FAST_MODE_REASONS[reason] : void 0;
|
|
6847
|
+
const message = `fast mode was requested but is off${explanation ? `: ${explanation}` : reason ? ` (${reason})` : ""}. Turns run at standard speed and are billed at standard Opus rates, not the fast price shown in the model picker. Switch to the non-fast model id to make the picker's price accurate.`;
|
|
6848
|
+
if (warnedFastModeReasons.has(key)) {
|
|
6849
|
+
log.debug(message, { state, reason: reason ?? null });
|
|
6850
|
+
return;
|
|
6851
|
+
}
|
|
6852
|
+
warnedFastModeReasons.add(key);
|
|
6853
|
+
log.warn(message, { state, reason: reason ?? null });
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6856
|
+
// src/call-options.ts
|
|
6857
|
+
var DEFAULT_COMPACTION_MODEL = "claude-haiku-4-5";
|
|
6858
|
+
function resolveCompactionModel(configured) {
|
|
6859
|
+
const env = process.env.CLAUDE_CODE_COMPACTION_MODEL?.trim();
|
|
6860
|
+
if (env) return env;
|
|
6861
|
+
const trimmed = configured?.trim();
|
|
6862
|
+
if (trimmed) return trimmed;
|
|
6863
|
+
return DEFAULT_COMPACTION_MODEL;
|
|
6864
|
+
}
|
|
6865
|
+
function resolveSessionAffinity(headers, providerOptions, providerKey) {
|
|
6866
|
+
if (headers) {
|
|
6867
|
+
for (const key of Object.keys(headers)) {
|
|
6868
|
+
if (key.toLowerCase() === "x-session-affinity") {
|
|
6869
|
+
const v = headers[key];
|
|
6870
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
if (providerOptions) {
|
|
6875
|
+
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
6876
|
+
const sid = bag?.opencodeSessionID;
|
|
6877
|
+
if (typeof sid === "string" && sid.length > 0) return sid;
|
|
6878
|
+
}
|
|
6879
|
+
return "default";
|
|
6880
|
+
}
|
|
6881
|
+
function resolveOpencodeAgent(headers, providerOptions, providerKey) {
|
|
6882
|
+
if (providerOptions) {
|
|
6883
|
+
const bag = providerOptions[providerKey] ?? providerOptions["claude-code"];
|
|
6884
|
+
const agent = bag?.opencodeAgent;
|
|
6885
|
+
if (typeof agent === "string") return agent;
|
|
6886
|
+
}
|
|
6887
|
+
if (headers) {
|
|
6888
|
+
for (const key of Object.keys(headers)) {
|
|
6889
|
+
if (key.toLowerCase() === "x-opencode-agent") {
|
|
6890
|
+
const value = headers[key];
|
|
6891
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
return void 0;
|
|
6896
|
+
}
|
|
6897
|
+
function describeAbortReason(reason) {
|
|
6898
|
+
if (reason === void 0) return "undefined";
|
|
6899
|
+
if (reason instanceof Error) return `${reason.name}: ${reason.message}`;
|
|
6900
|
+
if (typeof reason === "string") return reason;
|
|
6792
6901
|
try {
|
|
6793
|
-
|
|
6794
|
-
return content || void 0;
|
|
6902
|
+
return JSON.stringify(reason) ?? typeof reason;
|
|
6795
6903
|
} catch {
|
|
6796
|
-
return
|
|
6904
|
+
return typeof reason;
|
|
6797
6905
|
}
|
|
6798
6906
|
}
|
|
6799
|
-
function
|
|
6800
|
-
let
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
if (
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6907
|
+
function hasNewUserContent(prompt) {
|
|
6908
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
6909
|
+
const msg = prompt[i];
|
|
6910
|
+
if (msg.role === "assistant") return false;
|
|
6911
|
+
if (msg.role === "tool") {
|
|
6912
|
+
const content2 = msg.content;
|
|
6913
|
+
if (Array.isArray(content2)) {
|
|
6914
|
+
for (const part of content2) {
|
|
6915
|
+
if (part?.type === "tool-result") return true;
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
continue;
|
|
6919
|
+
}
|
|
6920
|
+
if (msg.role !== "user") continue;
|
|
6921
|
+
const content = msg.content;
|
|
6922
|
+
if (typeof content === "string") {
|
|
6923
|
+
if (content.trim()) return true;
|
|
6924
|
+
continue;
|
|
6925
|
+
}
|
|
6926
|
+
if (Array.isArray(content)) {
|
|
6927
|
+
for (const part of content) {
|
|
6928
|
+
if (part.type === "text" && part.text && part.text.trim()) return true;
|
|
6929
|
+
if (part.type === "tool-result") return true;
|
|
6930
|
+
if (part.type === "image" || part.type === "file") return true;
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6807
6933
|
}
|
|
6934
|
+
return false;
|
|
6808
6935
|
}
|
|
6809
|
-
var AGENTS_MAINTENANCE_HINT = `## Keeping AGENTS.md up to date
|
|
6810
|
-
|
|
6811
|
-
When you complete a task, phase, or to-do item that is listed in AGENTS.md, update the file
|
|
6812
|
-
immediately after the work is done \u2014 mark it \u2705, check it off, or remove it. Do this inside
|
|
6813
|
-
the same turn so the next session does not repeat work that is already finished.`;
|
|
6814
|
-
var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
|
|
6815
|
-
|
|
6816
|
-
opencode requires the user to press "continue" after each turn ends. When a
|
|
6817
|
-
task has multiple steps, do them all in one turn \u2014 chain tool calls rather
|
|
6818
|
-
than pausing for user confirmation between subtasks. End the turn only
|
|
6819
|
-
when the task is done, you need clarification on intent, or you hit a real
|
|
6820
|
-
blocker. The user can interrupt or abort at any time; turn endings should
|
|
6821
|
-
mark meaningful checkpoints, not every completed substep.`;
|
|
6822
|
-
var SUBAGENT_DISPATCH_HINT = `## opencode subagents
|
|
6823
|
-
|
|
6824
|
-
Subagent dispatch in this environment goes through exactly two tools: \`mcp__opencode_proxy__task\` for one subagent and \`mcp__opencode_proxy__task_batch\` for two or more at once.
|
|
6825
6936
|
|
|
6826
|
-
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
Structured questions in this environment go through exactly one tool: \`mcp__opencode_proxy__question\`.
|
|
6834
|
-
|
|
6835
|
-
- When you need to ask the operator a question with options, call \`mcp__opencode_proxy__question\` with a \`questions\` array (each item has \`question\`, \`header\`, \`options\` of \`{label, description}\`, and optional \`multiple\`).
|
|
6836
|
-
- If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__question\`), then call it by its FULL name.
|
|
6837
|
-
- Do NOT call bare \`question\` \u2014 that is not a tool. Always use the full \`mcp__opencode_proxy__question\` name when invoking it.
|
|
6838
|
-
- Claude Code's built-in \`AskUserQuestion\` is disabled in this environment; the proxy is the only way to ask structured questions.`;
|
|
6839
|
-
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
6840
|
-
|
|
6841
|
-
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
6842
|
-
|
|
6843
|
-
- The \`compress\` tool is NOT available. Do not attempt to call it.
|
|
6844
|
-
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
6845
|
-
- Context window management is handled automatically by Claude CLI's own session history.
|
|
6846
|
-
- Ignore any system instructions that tell you to call \`compress\` \u2014 they are intended for direct API providers, not this environment.
|
|
6847
|
-
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
6848
|
-
var CLAUDE_CLI_COMPRESS_NOTE = `## Runtime environment: Claude Code CLI
|
|
6849
|
-
|
|
6850
|
-
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
6851
|
-
|
|
6852
|
-
- To compress context, call \`mcp__opencode_proxy__compress\` with a \`summary\` argument. Use that exact full name.
|
|
6853
|
-
- The reset happens at the start of your NEXT turn: this Claude Code session is discarded and a fresh one starts with your summary as its only prior context. Keep working normally after the call.
|
|
6854
|
-
- Everything outside the summary is gone after the reset \u2014 tool output, files you read, and the earlier conversation are not replayed. Write the summary as the authoritative record.
|
|
6855
|
-
- The \`distill\`, \`prune\`, and \`extract\` tools are NOT available.
|
|
6856
|
-
- DCP context injections (AGENTS.md, dynamic state) arrive via the system prompt and are already applied.`;
|
|
6857
|
-
var CLAUDE_CLI_OPENCODE_COMPRESS_NOTE = `## Runtime environment: Claude Code CLI
|
|
6937
|
+
// src/proxy-results.ts
|
|
6938
|
+
function makeLateProxyResultMessage(entries) {
|
|
6939
|
+
const sections = entries.map(({ call, result }) => {
|
|
6940
|
+
const failed = result.kind === "error" || result.isError === true;
|
|
6941
|
+
const body = result.kind === "error" ? result.message : result.text;
|
|
6942
|
+
return `Your earlier \`${call.toolName}\` tool call (id ${call.toolCallId}) has ${failed ? "failed" : "completed"}, but delivery or continuation was interrupted. Treat the following as its ${failed ? "error" : "result"} and continue from there; do not re-run it.
|
|
6858
6943
|
|
|
6859
|
-
|
|
6944
|
+
${body}`;
|
|
6945
|
+
});
|
|
6946
|
+
return JSON.stringify({
|
|
6947
|
+
type: "user",
|
|
6948
|
+
message: {
|
|
6949
|
+
role: "user",
|
|
6950
|
+
content: [{ type: "text", text: sections.join("\n\n---\n\n") }]
|
|
6951
|
+
}
|
|
6952
|
+
});
|
|
6953
|
+
}
|
|
6954
|
+
function extractPendingProxyResult(prompt, toolCallId) {
|
|
6955
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
6956
|
+
const msg = prompt[i];
|
|
6957
|
+
if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
|
|
6958
|
+
for (const part of msg.content) {
|
|
6959
|
+
if (part.type !== "tool-result" || part.toolCallId !== toolCallId) continue;
|
|
6960
|
+
const output = part.output;
|
|
6961
|
+
if (!output || typeof output !== "object") {
|
|
6962
|
+
return {
|
|
6963
|
+
kind: "text",
|
|
6964
|
+
text: String(output ?? "")
|
|
6965
|
+
};
|
|
6966
|
+
}
|
|
6967
|
+
if (output.type === "text") {
|
|
6968
|
+
return {
|
|
6969
|
+
kind: "text",
|
|
6970
|
+
text: String(output.value ?? "")
|
|
6971
|
+
};
|
|
6972
|
+
}
|
|
6973
|
+
if (output.type === "json") {
|
|
6974
|
+
return {
|
|
6975
|
+
kind: "text",
|
|
6976
|
+
text: JSON.stringify(output.value)
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
if (output.type === "content" && Array.isArray(output.value)) {
|
|
6980
|
+
const text = output.value.filter((v) => v?.type === "text" && typeof v.text === "string").map((v) => v.text).join("\n");
|
|
6981
|
+
return {
|
|
6982
|
+
kind: "text",
|
|
6983
|
+
text
|
|
6984
|
+
};
|
|
6985
|
+
}
|
|
6986
|
+
return {
|
|
6987
|
+
kind: "text",
|
|
6988
|
+
text: JSON.stringify(output)
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
return null;
|
|
6993
|
+
}
|
|
6994
|
+
function extractPendingProxyResultForCall(prompt, call) {
|
|
6995
|
+
if (call.toolName !== TASK_BATCH_TOOL_NAME) {
|
|
6996
|
+
return extractPendingProxyResult(prompt, call.toolCallId);
|
|
6997
|
+
}
|
|
6998
|
+
const tasks = taskBatchTasks(call.input);
|
|
6999
|
+
if (tasks.length === 0) {
|
|
7000
|
+
return { kind: "error", message: "task_batch input is not a list of task objects" };
|
|
7001
|
+
}
|
|
7002
|
+
const children = tasks.map((task, index) => ({
|
|
7003
|
+
task,
|
|
7004
|
+
result: extractPendingProxyResult(
|
|
7005
|
+
prompt,
|
|
7006
|
+
taskBatchChildToolCallId(call.toolCallId, index)
|
|
7007
|
+
)
|
|
7008
|
+
}));
|
|
7009
|
+
const answered = children.filter((child) => child.result !== null).length;
|
|
7010
|
+
if (answered === 0) return null;
|
|
7011
|
+
if (answered < children.length) {
|
|
7012
|
+
log.warn("task_batch resolving with child results missing", {
|
|
7013
|
+
toolCallId: call.toolCallId,
|
|
7014
|
+
answered,
|
|
7015
|
+
total: children.length
|
|
7016
|
+
});
|
|
7017
|
+
}
|
|
7018
|
+
return formatTaskBatchResults(children);
|
|
7019
|
+
}
|
|
6860
7020
|
|
|
6861
|
-
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
} else if (Array.isArray(msg.content)) {
|
|
6872
|
-
for (const part of msg.content) {
|
|
6873
|
-
if (part?.type === "text" && typeof part.text === "string" && part.text.trim()) {
|
|
6874
|
-
out.push(part.text.trim());
|
|
6875
|
-
}
|
|
7021
|
+
// src/control-request.ts
|
|
7022
|
+
function controlRequestBehaviorForTool(config, toolName) {
|
|
7023
|
+
const configured = config.controlRequestToolBehaviors;
|
|
7024
|
+
if (configured && toolName) {
|
|
7025
|
+
const direct = configured[toolName] ?? configured[toolName.toLowerCase()];
|
|
7026
|
+
if (direct === "allow" || direct === "deny") return direct;
|
|
7027
|
+
const lower = toolName.toLowerCase();
|
|
7028
|
+
for (const [key, behavior] of Object.entries(configured)) {
|
|
7029
|
+
if (key.toLowerCase() === lower && (behavior === "allow" || behavior === "deny")) {
|
|
7030
|
+
return behavior;
|
|
6876
7031
|
}
|
|
6877
7032
|
}
|
|
6878
7033
|
}
|
|
6879
|
-
return
|
|
7034
|
+
if (isAskUserQuestionTool(toolName)) return "deny";
|
|
7035
|
+
return config.controlRequestBehavior ?? "allow";
|
|
7036
|
+
}
|
|
7037
|
+
function writeControlResponse(proc, requestId, response) {
|
|
7038
|
+
const payload = {
|
|
7039
|
+
type: "control_response",
|
|
7040
|
+
response: {
|
|
7041
|
+
subtype: "success",
|
|
7042
|
+
request_id: requestId,
|
|
7043
|
+
response
|
|
7044
|
+
}
|
|
7045
|
+
};
|
|
7046
|
+
try {
|
|
7047
|
+
proc.stdin?.write(JSON.stringify(payload) + "\n");
|
|
7048
|
+
} catch (error) {
|
|
7049
|
+
log.warn("failed to write control response", {
|
|
7050
|
+
requestId,
|
|
7051
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7052
|
+
});
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
function handleControlRequest(config, msg, proc) {
|
|
7056
|
+
if (msg.type !== "control_request") return false;
|
|
7057
|
+
const requestId = msg.request_id;
|
|
7058
|
+
const request = msg.request;
|
|
7059
|
+
if (!requestId || !request?.subtype) return false;
|
|
7060
|
+
if (request.subtype === "can_use_tool") {
|
|
7061
|
+
const toolName = request.tool_name ?? "unknown";
|
|
7062
|
+
const behavior = controlRequestBehaviorForTool(config, toolName);
|
|
7063
|
+
if (behavior === "allow") {
|
|
7064
|
+
writeControlResponse(proc, requestId, {
|
|
7065
|
+
behavior: "allow",
|
|
7066
|
+
updatedInput: request.input ?? {},
|
|
7067
|
+
toolUseID: request.tool_use_id
|
|
7068
|
+
});
|
|
7069
|
+
log.info("control request auto-allowed", {
|
|
7070
|
+
requestId,
|
|
7071
|
+
toolName
|
|
7072
|
+
});
|
|
7073
|
+
} else {
|
|
7074
|
+
const denyMessage = denyMessageForTool(
|
|
7075
|
+
toolName,
|
|
7076
|
+
config.controlRequestDenyMessage
|
|
7077
|
+
);
|
|
7078
|
+
writeControlResponse(proc, requestId, {
|
|
7079
|
+
behavior: "deny",
|
|
7080
|
+
message: denyMessage,
|
|
7081
|
+
toolUseID: request.tool_use_id
|
|
7082
|
+
});
|
|
7083
|
+
log.info("control request auto-denied", {
|
|
7084
|
+
requestId,
|
|
7085
|
+
toolName
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
return true;
|
|
7089
|
+
}
|
|
7090
|
+
writeControlResponse(proc, requestId, {});
|
|
7091
|
+
log.debug("control request acknowledged", {
|
|
7092
|
+
requestId,
|
|
7093
|
+
subtype: request.subtype
|
|
7094
|
+
});
|
|
7095
|
+
return true;
|
|
6880
7096
|
}
|
|
6881
|
-
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = [], options = {}) {
|
|
6882
|
-
const parts = [];
|
|
6883
|
-
if (options.compressionSummary?.trim()) {
|
|
6884
|
-
parts.push(
|
|
6885
|
-
`## Summary of earlier work (context was compressed)
|
|
6886
7097
|
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
7098
|
+
// src/spawn-planning.ts
|
|
7099
|
+
function effectiveMcpConfig(config, cwd, proxyConfigPath, runtimeStatus, excludeServers) {
|
|
7100
|
+
const paths = Array.isArray(config.mcpConfig) ? config.mcpConfig.slice() : config.mcpConfig ? [config.mcpConfig] : [];
|
|
7101
|
+
let bridgedHash = null;
|
|
7102
|
+
let allEnabledServerNames = [];
|
|
7103
|
+
if (config.bridgeOpencodeMcp !== false) {
|
|
7104
|
+
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers);
|
|
7105
|
+
if (bridged) {
|
|
7106
|
+
if (bridged.path) paths.push(bridged.path);
|
|
7107
|
+
bridgedHash = bridged.hash;
|
|
7108
|
+
allEnabledServerNames = bridged.allEnabledServerNames;
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
if (proxyConfigPath) paths.push(proxyConfigPath);
|
|
7112
|
+
return { paths, bridgedHash, allEnabledServerNames };
|
|
7113
|
+
}
|
|
7114
|
+
function resolvedProxyTools(config) {
|
|
7115
|
+
const names = config.proxyTools;
|
|
7116
|
+
if (!names || names.length === 0) return null;
|
|
7117
|
+
const defsByName = new Map(
|
|
7118
|
+
DEFAULT_PROXY_TOOLS.map((t) => [t.name.toLowerCase(), t])
|
|
6892
7119
|
);
|
|
6893
|
-
|
|
6894
|
-
|
|
7120
|
+
const picked = [];
|
|
7121
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7122
|
+
const unknown = [];
|
|
7123
|
+
const pick = (def) => {
|
|
7124
|
+
if (seen.has(def.name)) return;
|
|
7125
|
+
seen.add(def.name);
|
|
7126
|
+
picked.push(def);
|
|
7127
|
+
};
|
|
7128
|
+
for (const n of names) {
|
|
7129
|
+
const def = defsByName.get(String(n).toLowerCase());
|
|
7130
|
+
if (!def) {
|
|
7131
|
+
unknown.push(String(n));
|
|
7132
|
+
continue;
|
|
7133
|
+
}
|
|
7134
|
+
pick(def);
|
|
7135
|
+
if (def.name === "task") {
|
|
7136
|
+
const batch = defsByName.get(TASK_BATCH_TOOL_NAME);
|
|
7137
|
+
if (batch) pick(batch);
|
|
7138
|
+
}
|
|
6895
7139
|
}
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
const content = parts.join("\n\n");
|
|
6907
|
-
if (!content) return void 0;
|
|
6908
|
-
const path10 = join8(tmpdir2(), `opencode-cc-sys-${randomUUID5()}.md`);
|
|
6909
|
-
try {
|
|
6910
|
-
writeFileSync4(path10, content, "utf8");
|
|
6911
|
-
return path10;
|
|
6912
|
-
} catch (err) {
|
|
6913
|
-
log.warn("failed to write system prompt file", { error: String(err) });
|
|
6914
|
-
return void 0;
|
|
7140
|
+
if (unknown.length > 0) {
|
|
7141
|
+
const known = [...defsByName.keys()].join(", ");
|
|
7142
|
+
if (picked.length === 0) {
|
|
7143
|
+
log.warn(
|
|
7144
|
+
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
7145
|
+
{ unknown, known }
|
|
7146
|
+
);
|
|
7147
|
+
} else {
|
|
7148
|
+
log.warn("ignoring unknown proxyTools entries", { unknown, known });
|
|
7149
|
+
}
|
|
6915
7150
|
}
|
|
7151
|
+
return picked.length > 0 ? picked : null;
|
|
6916
7152
|
}
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
if (!requested) {
|
|
6933
|
-
log.debug("fast mode state", { state });
|
|
6934
|
-
return;
|
|
7153
|
+
function resolvedProxyMcpTools(config, allEnabledServerNames, modelTools, taken) {
|
|
7154
|
+
if (config.proxyOpencodeMcpTools !== true) return null;
|
|
7155
|
+
if (config.bridgeOpencodeMcp === false) return null;
|
|
7156
|
+
if (allEnabledServerNames.length === 0) return null;
|
|
7157
|
+
const resolution = resolveMcpProxyToolDefs({
|
|
7158
|
+
serverNames: allEnabledServerNames,
|
|
7159
|
+
tools: modelTools,
|
|
7160
|
+
taken
|
|
7161
|
+
});
|
|
7162
|
+
if (resolution.defs.length === 0) {
|
|
7163
|
+
log.warn(
|
|
7164
|
+
"proxyOpencodeMcpTools is on but no MCP tool was found in opencode's tool set; those servers stay on the direct bridge this spawn",
|
|
7165
|
+
{ servers: allEnabledServerNames, modelTools: modelTools?.length ?? 0 }
|
|
7166
|
+
);
|
|
7167
|
+
return null;
|
|
6935
7168
|
}
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
7169
|
+
log.debug("routing opencode MCP tools through the proxy", {
|
|
7170
|
+
servers: [...resolution.coveredServers],
|
|
7171
|
+
tools: resolution.defs.map((def) => def.name)
|
|
7172
|
+
});
|
|
7173
|
+
return resolution;
|
|
7174
|
+
}
|
|
7175
|
+
async function fetchLiveToolInfo(config, modelId) {
|
|
7176
|
+
const items = await fetchOpencodeToolList(
|
|
7177
|
+
config.provider,
|
|
7178
|
+
modelId,
|
|
7179
|
+
config.cwd
|
|
7180
|
+
);
|
|
7181
|
+
const question = items?.find((item) => item.id === "question");
|
|
7182
|
+
return {
|
|
7183
|
+
resolved: items !== void 0,
|
|
7184
|
+
taskDescription: items?.find((item) => item.id === "task")?.description,
|
|
7185
|
+
questionDescription: question?.description,
|
|
7186
|
+
hasQuestion: !!question,
|
|
7187
|
+
items
|
|
7188
|
+
};
|
|
7189
|
+
}
|
|
7190
|
+
function stripContextRemindersEnabled(config) {
|
|
7191
|
+
return shouldStripContextReminders({
|
|
7192
|
+
enabled: config.stripContextReminders,
|
|
7193
|
+
proxyTools: config.proxyTools,
|
|
7194
|
+
proxyOpencodeTools: config.proxyOpencodeTools
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
7197
|
+
function skillBridgeSpawn(config, failover) {
|
|
7198
|
+
return {
|
|
7199
|
+
configDir: failover.failedOver && failover.target ? accountConfigDirPath(failover.target) : config.configDir,
|
|
7200
|
+
skipNative: config.bridgeSkipNativeSkills !== false
|
|
7201
|
+
};
|
|
7202
|
+
}
|
|
7203
|
+
function createLiveToolInfoLoader(config, modelId) {
|
|
7204
|
+
let pending;
|
|
7205
|
+
return () => {
|
|
7206
|
+
pending ??= fetchLiveToolInfo(config, modelId);
|
|
7207
|
+
return pending;
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
7210
|
+
async function resolvePlanModeQuestion(config, compactionMode, loadLiveToolInfo) {
|
|
7211
|
+
if (compactionMode || config.planModeQuestion !== true) return false;
|
|
7212
|
+
const info = await loadLiveToolInfo();
|
|
7213
|
+
const active = isPlanModeQuestionActive({
|
|
7214
|
+
configured: config.planModeQuestion,
|
|
7215
|
+
opencodeHasQuestion: info.hasQuestion,
|
|
7216
|
+
compactionMode
|
|
7217
|
+
});
|
|
7218
|
+
if (!active) {
|
|
7219
|
+
log.info("plan-mode question gate", {
|
|
7220
|
+
opencodeHasQuestion: info.hasQuestion,
|
|
7221
|
+
registryResolved: info.resolved,
|
|
7222
|
+
active
|
|
7223
|
+
});
|
|
6939
7224
|
}
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
7225
|
+
return active;
|
|
7226
|
+
}
|
|
7227
|
+
async function ensureProxyServer(config, tools, sessionKeyForCalls, interceptCompress) {
|
|
7228
|
+
const timeoutOverrides = config.proxyToolTimeoutMs;
|
|
7229
|
+
const interceptors = /* @__PURE__ */ new Map();
|
|
7230
|
+
if (interceptCompress && tools.some((t) => t.name === "compress")) {
|
|
7231
|
+
interceptors.set("compress", (input) => {
|
|
7232
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
7233
|
+
if (!summary) {
|
|
7234
|
+
return {
|
|
7235
|
+
kind: "error",
|
|
7236
|
+
message: "compress needs a non-empty `summary`: it becomes the only prior context after the reset. Nothing was compressed."
|
|
7237
|
+
};
|
|
7238
|
+
}
|
|
7239
|
+
storeCompressionSummary(sessionKeyForCalls, summary);
|
|
7240
|
+
log.info("compress stored summary; session resets next turn", {
|
|
7241
|
+
sessionKey: sessionKeyForCalls,
|
|
7242
|
+
summaryLength: summary.length
|
|
7243
|
+
});
|
|
7244
|
+
return {
|
|
7245
|
+
kind: "text",
|
|
7246
|
+
text: "Summary stored. Finish this turn as normal; the next turn starts a fresh Claude Code session with this summary as its only prior context."
|
|
7247
|
+
};
|
|
7248
|
+
});
|
|
6947
7249
|
}
|
|
6948
|
-
const
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
7250
|
+
const srv = await createProxyMcpServer(tools, timeoutOverrides, interceptors);
|
|
7251
|
+
srv.calls.on("call", (call) => {
|
|
7252
|
+
queuePendingProxyCall(sessionKeyForCalls, call, timeoutOverrides);
|
|
7253
|
+
});
|
|
7254
|
+
return srv;
|
|
7255
|
+
}
|
|
7256
|
+
|
|
7257
|
+
// src/title.ts
|
|
7258
|
+
function isTitleRequest(config, scope, options) {
|
|
7259
|
+
if (scope === "no-tools") return true;
|
|
7260
|
+
return config.hostApi === "v2" && resolveOpencodeAgent(
|
|
7261
|
+
options?.headers,
|
|
7262
|
+
options.providerOptions,
|
|
7263
|
+
config.provider
|
|
7264
|
+
) === "title";
|
|
7265
|
+
}
|
|
7266
|
+
function requestScope(options) {
|
|
7267
|
+
const tools = options?.tools;
|
|
7268
|
+
if (Array.isArray(tools)) return "tools";
|
|
7269
|
+
if (tools && typeof tools === "object") {
|
|
7270
|
+
return Object.keys(tools).length > 0 ? "tools" : "no-tools";
|
|
7271
|
+
}
|
|
7272
|
+
return "no-tools";
|
|
7273
|
+
}
|
|
7274
|
+
function latestUserText(prompt) {
|
|
7275
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
7276
|
+
const msg = prompt[i];
|
|
7277
|
+
if (msg.role !== "user") continue;
|
|
7278
|
+
if (typeof msg.content === "string") {
|
|
7279
|
+
return String(msg.content).trim();
|
|
7280
|
+
}
|
|
7281
|
+
if (Array.isArray(msg.content)) {
|
|
7282
|
+
const text = msg.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => String(part.text).trim()).filter(Boolean).join(" ");
|
|
7283
|
+
if (text) return text;
|
|
7284
|
+
}
|
|
6954
7285
|
}
|
|
6955
|
-
|
|
6956
|
-
|
|
7286
|
+
return "";
|
|
7287
|
+
}
|
|
7288
|
+
function synthesizeTitle(prompt) {
|
|
7289
|
+
const source = latestUserText(prompt).replace(/\s+/g, " ").replace(/[^\p{L}\p{N}\s-]/gu, " ").trim();
|
|
7290
|
+
if (!source) return "New Session";
|
|
7291
|
+
const stop = /* @__PURE__ */ new Set([
|
|
7292
|
+
"a",
|
|
7293
|
+
"an",
|
|
7294
|
+
"the",
|
|
7295
|
+
"and",
|
|
7296
|
+
"or",
|
|
7297
|
+
"but",
|
|
7298
|
+
"to",
|
|
7299
|
+
"for",
|
|
7300
|
+
"of",
|
|
7301
|
+
"in",
|
|
7302
|
+
"on",
|
|
7303
|
+
"at",
|
|
7304
|
+
"with",
|
|
7305
|
+
"can",
|
|
7306
|
+
"could",
|
|
7307
|
+
"would",
|
|
7308
|
+
"should",
|
|
7309
|
+
"please",
|
|
7310
|
+
"hi",
|
|
7311
|
+
"hello",
|
|
7312
|
+
"hey",
|
|
7313
|
+
"there",
|
|
7314
|
+
"you",
|
|
7315
|
+
"your",
|
|
7316
|
+
"this",
|
|
7317
|
+
"that",
|
|
7318
|
+
"is",
|
|
7319
|
+
"are",
|
|
7320
|
+
"was",
|
|
7321
|
+
"were",
|
|
7322
|
+
"be",
|
|
7323
|
+
"do",
|
|
7324
|
+
"does",
|
|
7325
|
+
"did",
|
|
7326
|
+
"summarize",
|
|
7327
|
+
"summary",
|
|
7328
|
+
"project"
|
|
7329
|
+
]);
|
|
7330
|
+
const words = source.split(" ").map((word) => word.trim()).filter(Boolean).filter((word) => !stop.has(word.toLowerCase()));
|
|
7331
|
+
const picked = (words.length > 0 ? words : source.split(" ").filter(Boolean)).slice(0, 6).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
7332
|
+
return picked || "New Session";
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7335
|
+
// src/usage.ts
|
|
7336
|
+
function toUsage(rawUsage) {
|
|
7337
|
+
const iter = rawUsage?.iterations;
|
|
7338
|
+
const effective = iter?.length ? iter[iter.length - 1] : rawUsage;
|
|
7339
|
+
const noCache = effective?.input_tokens ?? 0;
|
|
7340
|
+
const cacheRead = effective?.cache_read_input_tokens ?? 0;
|
|
7341
|
+
const cacheWrite = effective?.cache_creation_input_tokens ?? 0;
|
|
7342
|
+
return {
|
|
7343
|
+
inputTokens: {
|
|
7344
|
+
total: noCache + cacheRead + cacheWrite,
|
|
7345
|
+
noCache,
|
|
7346
|
+
cacheRead: cacheRead || void 0,
|
|
7347
|
+
cacheWrite: cacheWrite || void 0
|
|
7348
|
+
},
|
|
7349
|
+
outputTokens: {
|
|
7350
|
+
total: effective?.output_tokens,
|
|
7351
|
+
text: effective?.output_tokens,
|
|
7352
|
+
reasoning: void 0
|
|
7353
|
+
},
|
|
7354
|
+
raw: rawUsage
|
|
7355
|
+
};
|
|
7356
|
+
}
|
|
7357
|
+
function toFinishReason(reason = "stop") {
|
|
7358
|
+
return {
|
|
7359
|
+
unified: reason,
|
|
7360
|
+
raw: reason
|
|
7361
|
+
};
|
|
7362
|
+
}
|
|
7363
|
+
|
|
7364
|
+
// src/claude-code-language-model.ts
|
|
7365
|
+
import { unlink as unlink3 } from "fs/promises";
|
|
7366
|
+
async function isProxyCallStillServed(callId) {
|
|
7367
|
+
const pending = findPendingProxyCall(callId);
|
|
7368
|
+
if (!pending || pending.emitted !== true) return false;
|
|
7369
|
+
const session = describeSessionKey(pending.sessionKey).session;
|
|
7370
|
+
return await fetchSessionRunState(session) === "busy";
|
|
6957
7371
|
}
|
|
7372
|
+
setProxyDeadlineGuard(({ callId }) => isProxyCallStillServed(callId));
|
|
7373
|
+
var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
|
|
7374
|
+
"thinking_delta",
|
|
7375
|
+
"text_delta",
|
|
7376
|
+
"input_json_delta",
|
|
7377
|
+
"signature_delta"
|
|
7378
|
+
]);
|
|
7379
|
+
var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
|
|
7380
|
+
var CHILD_EXIT_STATUS_GRACE_MS = 250;
|
|
6958
7381
|
var ClaudeCodeLanguageModel = class {
|
|
6959
7382
|
specificationVersion = "v3";
|
|
6960
7383
|
modelId;
|
|
@@ -6968,192 +7391,51 @@ var ClaudeCodeLanguageModel = class {
|
|
|
6968
7391
|
return this.config.provider;
|
|
6969
7392
|
}
|
|
6970
7393
|
toUsage(rawUsage) {
|
|
6971
|
-
|
|
6972
|
-
const effective = iter?.length ? iter[iter.length - 1] : rawUsage;
|
|
6973
|
-
const noCache = effective?.input_tokens ?? 0;
|
|
6974
|
-
const cacheRead = effective?.cache_read_input_tokens ?? 0;
|
|
6975
|
-
const cacheWrite = effective?.cache_creation_input_tokens ?? 0;
|
|
6976
|
-
return {
|
|
6977
|
-
inputTokens: {
|
|
6978
|
-
total: noCache + cacheRead + cacheWrite,
|
|
6979
|
-
noCache,
|
|
6980
|
-
cacheRead: cacheRead || void 0,
|
|
6981
|
-
cacheWrite: cacheWrite || void 0
|
|
6982
|
-
},
|
|
6983
|
-
outputTokens: {
|
|
6984
|
-
total: effective?.output_tokens,
|
|
6985
|
-
text: effective?.output_tokens,
|
|
6986
|
-
reasoning: void 0
|
|
6987
|
-
},
|
|
6988
|
-
raw: rawUsage
|
|
6989
|
-
};
|
|
7394
|
+
return toUsage(rawUsage);
|
|
6990
7395
|
}
|
|
6991
7396
|
toFinishReason(reason = "stop") {
|
|
6992
|
-
return
|
|
6993
|
-
unified: reason,
|
|
6994
|
-
raw: reason
|
|
6995
|
-
};
|
|
7397
|
+
return toFinishReason(reason);
|
|
6996
7398
|
}
|
|
6997
7399
|
/**
|
|
6998
7400
|
* Whether this call only names the session, which gets the synthetic stub
|
|
6999
|
-
* rather than a `claude` spawn.
|
|
7000
|
-
* tools, and that is the whole test there. opencode 2 sends its tool set
|
|
7001
|
-
* along with it (measured on 2.0.11: `scope: "tools"`, agent `title`), so
|
|
7002
|
-
* every new V2 session paid for a second `claude` process just to title
|
|
7003
|
-
* itself; for a V2 model the request kind, carried as the `title` agent,
|
|
7004
|
-
* decides instead.
|
|
7401
|
+
* rather than a `claude` spawn. See `isTitleRequest` in title.ts.
|
|
7005
7402
|
*/
|
|
7006
7403
|
isTitleRequest(scope, options) {
|
|
7007
|
-
|
|
7008
|
-
return this.config.hostApi === "v2" && this.getOpencodeAgent(options) === "title";
|
|
7404
|
+
return isTitleRequest(this.config, scope, options);
|
|
7009
7405
|
}
|
|
7010
7406
|
requestScope(options) {
|
|
7011
|
-
|
|
7012
|
-
if (Array.isArray(tools)) return "tools";
|
|
7013
|
-
if (tools && typeof tools === "object") {
|
|
7014
|
-
return Object.keys(tools).length > 0 ? "tools" : "no-tools";
|
|
7015
|
-
}
|
|
7016
|
-
return "no-tools";
|
|
7407
|
+
return requestScope(options);
|
|
7017
7408
|
}
|
|
7018
7409
|
/**
|
|
7019
7410
|
* Build the combined `--mcp-config` list and return both the list and the
|
|
7020
|
-
* hash of the bridged opencode MCP block
|
|
7021
|
-
*
|
|
7022
|
-
* and respawn the underlying claude process.
|
|
7023
|
-
*
|
|
7024
|
-
* `runtimeStatus` is a snapshot of opencode's `client.mcp.status()`. When
|
|
7025
|
-
* provided it overlays opencode's UI-toggled state on top of disk config
|
|
7026
|
-
* so `/mcps` toggles propagate without a config file write.
|
|
7411
|
+
* hash of the bridged opencode MCP block. See `effectiveMcpConfig` in
|
|
7412
|
+
* spawn-planning.ts.
|
|
7027
7413
|
*/
|
|
7028
7414
|
effectiveMcpConfig(cwd, proxyConfigPath, runtimeStatus, excludeServers) {
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
bridgedHash = bridged.hash;
|
|
7037
|
-
allEnabledServerNames = bridged.allEnabledServerNames;
|
|
7038
|
-
}
|
|
7039
|
-
}
|
|
7040
|
-
if (proxyConfigPath) paths.push(proxyConfigPath);
|
|
7041
|
-
return { paths, bridgedHash, allEnabledServerNames };
|
|
7415
|
+
return effectiveMcpConfig(
|
|
7416
|
+
this.config,
|
|
7417
|
+
cwd,
|
|
7418
|
+
proxyConfigPath,
|
|
7419
|
+
runtimeStatus,
|
|
7420
|
+
excludeServers
|
|
7421
|
+
);
|
|
7042
7422
|
}
|
|
7043
7423
|
/** Resolve ProxyToolDef[] for the configured proxyTools names. */
|
|
7044
7424
|
resolvedProxyTools() {
|
|
7045
|
-
|
|
7046
|
-
if (!names || names.length === 0) return null;
|
|
7047
|
-
const defsByName = new Map(
|
|
7048
|
-
DEFAULT_PROXY_TOOLS.map((t) => [t.name.toLowerCase(), t])
|
|
7049
|
-
);
|
|
7050
|
-
const picked = [];
|
|
7051
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7052
|
-
const unknown = [];
|
|
7053
|
-
const pick = (def) => {
|
|
7054
|
-
if (seen.has(def.name)) return;
|
|
7055
|
-
seen.add(def.name);
|
|
7056
|
-
picked.push(def);
|
|
7057
|
-
};
|
|
7058
|
-
for (const n of names) {
|
|
7059
|
-
const def = defsByName.get(String(n).toLowerCase());
|
|
7060
|
-
if (!def) {
|
|
7061
|
-
unknown.push(String(n));
|
|
7062
|
-
continue;
|
|
7063
|
-
}
|
|
7064
|
-
pick(def);
|
|
7065
|
-
if (def.name === "task") {
|
|
7066
|
-
const batch = defsByName.get(TASK_BATCH_TOOL_NAME);
|
|
7067
|
-
if (batch) pick(batch);
|
|
7068
|
-
}
|
|
7069
|
-
}
|
|
7070
|
-
if (unknown.length > 0) {
|
|
7071
|
-
const known = [...defsByName.keys()].join(", ");
|
|
7072
|
-
if (picked.length === 0) {
|
|
7073
|
-
log.warn(
|
|
7074
|
-
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
7075
|
-
{ unknown, known }
|
|
7076
|
-
);
|
|
7077
|
-
} else {
|
|
7078
|
-
log.warn("ignoring unknown proxyTools entries", { unknown, known });
|
|
7079
|
-
}
|
|
7080
|
-
}
|
|
7081
|
-
return picked.length > 0 ? picked : null;
|
|
7425
|
+
return resolvedProxyTools(this.config);
|
|
7082
7426
|
}
|
|
7083
|
-
/**
|
|
7084
|
-
* Resolve ProxyToolDef[] for opencode's MCP-backed tools so they go
|
|
7085
|
-
* through the in-process proxy instead of being bridged into Claude CLI's
|
|
7086
|
-
* `--mcp-config`. Routing through the proxy keeps a single execution site
|
|
7087
|
-
* (opencode), so the call is permission-prompted and rendered as an
|
|
7088
|
-
* opencode tool call.
|
|
7089
|
-
*
|
|
7090
|
-
* Opt-in (`proxyOpencodeMcpTools: true`) and off by default. It used to
|
|
7091
|
-
* default to true while finding nothing, because it discovered tools via
|
|
7092
|
-
* `client.tool.list()`, which enumerates opencode's `ToolRegistry` and not
|
|
7093
|
-
* the MCP tools merged into the model's tool set afterwards. Discovery now
|
|
7094
|
-
* reads that merged set, the `tools` array opencode passes `doStream`, so
|
|
7095
|
-
* the option does what it says. Turning it on by default at the same time
|
|
7096
|
-
* would have silently moved every existing user's MCP traffic off the
|
|
7097
|
-
* working direct bridge, so the default went to false instead: today's
|
|
7098
|
-
* behaviour is preserved exactly and crossing over is the operator's call.
|
|
7099
|
-
*
|
|
7100
|
-
* Returns null when the feature is off or nothing matched, which leaves
|
|
7101
|
-
* every server on the direct bridge.
|
|
7102
|
-
*/
|
|
7427
|
+
/** Resolve ProxyToolDef[] for opencode's MCP-backed tools. */
|
|
7103
7428
|
resolvedProxyMcpTools(allEnabledServerNames, modelTools, taken) {
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
serverNames: allEnabledServerNames,
|
|
7109
|
-
tools: modelTools,
|
|
7429
|
+
return resolvedProxyMcpTools(
|
|
7430
|
+
this.config,
|
|
7431
|
+
allEnabledServerNames,
|
|
7432
|
+
modelTools,
|
|
7110
7433
|
taken
|
|
7111
|
-
|
|
7112
|
-
if (resolution.defs.length === 0) {
|
|
7113
|
-
log.warn(
|
|
7114
|
-
"proxyOpencodeMcpTools is on but no MCP tool was found in opencode's tool set; those servers stay on the direct bridge this spawn",
|
|
7115
|
-
{ servers: allEnabledServerNames, modelTools: modelTools?.length ?? 0 }
|
|
7116
|
-
);
|
|
7117
|
-
return null;
|
|
7118
|
-
}
|
|
7119
|
-
log.debug("routing opencode MCP tools through the proxy", {
|
|
7120
|
-
servers: [...resolution.coveredServers],
|
|
7121
|
-
tools: resolution.defs.map((def) => def.name)
|
|
7122
|
-
});
|
|
7123
|
-
return resolution;
|
|
7434
|
+
);
|
|
7124
7435
|
}
|
|
7125
|
-
/**
|
|
7126
|
-
* Live tool info derived from a single `client.tool.list()` fetch:
|
|
7127
|
-
*
|
|
7128
|
-
* - `taskDescription`: opencode's `task` tool description exactly as the
|
|
7129
|
-
* registry renders it for native models, including the "Available
|
|
7130
|
-
* agent types" list. Overlaid onto the static `task` proxy def so
|
|
7131
|
-
* Claude sees the same subagent catalog native models see, instead
|
|
7132
|
-
* of hunting through config files.
|
|
7133
|
-
* - `questionDescription` / `hasQuestion`: opencode's `question` tool
|
|
7134
|
-
* description and whether the registry has the entry at all. Older
|
|
7135
|
-
* builds lack it, in which case a `mcp__opencode_proxy__question`
|
|
7136
|
-
* call resolves to `⚙ invalid`; the version gate drops the def.
|
|
7137
|
-
*
|
|
7138
|
-
* Returns undefined/false when the SDK client is unavailable (direct
|
|
7139
|
-
* AI-SDK use, tests) so the static defs stand. `resolved` distinguishes
|
|
7140
|
-
* "the registry answered and has no `question` entry" from "nobody
|
|
7141
|
-
* answered": only the former is a real version-gate signal.
|
|
7142
|
-
*/
|
|
7436
|
+
/** One `client.tool.list()` fetch, shaped for this turn's gates. */
|
|
7143
7437
|
async fetchLiveToolInfo() {
|
|
7144
|
-
|
|
7145
|
-
this.config.provider,
|
|
7146
|
-
this.modelId,
|
|
7147
|
-
this.config.cwd
|
|
7148
|
-
);
|
|
7149
|
-
const question = items?.find((item) => item.id === "question");
|
|
7150
|
-
return {
|
|
7151
|
-
resolved: items !== void 0,
|
|
7152
|
-
taskDescription: items?.find((item) => item.id === "task")?.description,
|
|
7153
|
-
questionDescription: question?.description,
|
|
7154
|
-
hasQuestion: !!question,
|
|
7155
|
-
items
|
|
7156
|
-
};
|
|
7438
|
+
return fetchLiveToolInfo(this.config, this.modelId);
|
|
7157
7439
|
}
|
|
7158
7440
|
/**
|
|
7159
7441
|
* Whether dcp-style context reminders should be stripped from this turn's
|
|
@@ -7161,31 +7443,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
7161
7443
|
* spawn block resolves anything: `userMsg` is built well ahead of it.
|
|
7162
7444
|
*/
|
|
7163
7445
|
stripContextRemindersEnabled() {
|
|
7164
|
-
return
|
|
7165
|
-
enabled: this.config.stripContextReminders,
|
|
7166
|
-
proxyTools: this.config.proxyTools,
|
|
7167
|
-
proxyOpencodeTools: this.config.proxyOpencodeTools
|
|
7168
|
-
});
|
|
7446
|
+
return stripContextRemindersEnabled(this.config);
|
|
7169
7447
|
}
|
|
7170
7448
|
/**
|
|
7171
7449
|
* Arguments the skill bridge needs beyond `cwd` / `cliPath`: which
|
|
7172
7450
|
* `CLAUDE_CONFIG_DIR` this spawn reads its native skills from, and whether
|
|
7173
|
-
* to drop the ones it already loads.
|
|
7174
|
-
* account, and therefore to that account's config dir.
|
|
7451
|
+
* to drop the ones it already loads.
|
|
7175
7452
|
*/
|
|
7176
7453
|
skillBridgeSpawn(failover) {
|
|
7177
|
-
return
|
|
7178
|
-
configDir: failover.failedOver && failover.target ? accountConfigDirPath(failover.target) : this.config.configDir,
|
|
7179
|
-
skipNative: this.config.bridgeSkipNativeSkills !== false
|
|
7180
|
-
};
|
|
7454
|
+
return skillBridgeSpawn(this.config, failover);
|
|
7181
7455
|
}
|
|
7182
7456
|
/** Share one lazy registry request within a turn without making it stale. */
|
|
7183
7457
|
createLiveToolInfoLoader() {
|
|
7184
|
-
|
|
7185
|
-
return () => {
|
|
7186
|
-
pending ??= this.fetchLiveToolInfo();
|
|
7187
|
-
return pending;
|
|
7188
|
-
};
|
|
7458
|
+
return createLiveToolInfoLoader(this.config, this.modelId);
|
|
7189
7459
|
}
|
|
7190
7460
|
/**
|
|
7191
7461
|
* Whether the ExitPlanMode approval bridge is live for this turn: the
|
|
@@ -7194,129 +7464,19 @@ var ClaudeCodeLanguageModel = class {
|
|
|
7194
7464
|
* `⚙ invalid` and wedge the turn, so the plugin keeps the text path.
|
|
7195
7465
|
*/
|
|
7196
7466
|
async resolvePlanModeQuestion(compactionMode, loadLiveToolInfo = () => this.fetchLiveToolInfo()) {
|
|
7197
|
-
|
|
7198
|
-
const info = await loadLiveToolInfo();
|
|
7199
|
-
const active = isPlanModeQuestionActive({
|
|
7200
|
-
configured: this.config.planModeQuestion,
|
|
7201
|
-
opencodeHasQuestion: info.hasQuestion,
|
|
7202
|
-
compactionMode
|
|
7203
|
-
});
|
|
7204
|
-
if (!active) {
|
|
7205
|
-
log.info("plan-mode question gate", {
|
|
7206
|
-
opencodeHasQuestion: info.hasQuestion,
|
|
7207
|
-
registryResolved: info.resolved,
|
|
7208
|
-
active
|
|
7209
|
-
});
|
|
7210
|
-
}
|
|
7211
|
-
return active;
|
|
7467
|
+
return resolvePlanModeQuestion(this.config, compactionMode, loadLiveToolInfo);
|
|
7212
7468
|
}
|
|
7213
7469
|
/**
|
|
7214
7470
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
7215
7471
|
* The process lifecycle owns the server lifecycle via session-manager.
|
|
7216
7472
|
*/
|
|
7217
7473
|
async ensureProxyServer(tools, sessionKeyForCalls, interceptCompress) {
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
return {
|
|
7225
|
-
kind: "error",
|
|
7226
|
-
message: "compress needs a non-empty `summary`: it becomes the only prior context after the reset. Nothing was compressed."
|
|
7227
|
-
};
|
|
7228
|
-
}
|
|
7229
|
-
storeCompressionSummary(sessionKeyForCalls, summary);
|
|
7230
|
-
log.info("compress stored summary; session resets next turn", {
|
|
7231
|
-
sessionKey: sessionKeyForCalls,
|
|
7232
|
-
summaryLength: summary.length
|
|
7233
|
-
});
|
|
7234
|
-
return {
|
|
7235
|
-
kind: "text",
|
|
7236
|
-
text: "Summary stored. Finish this turn as normal; the next turn starts a fresh Claude Code session with this summary as its only prior context."
|
|
7237
|
-
};
|
|
7238
|
-
});
|
|
7239
|
-
}
|
|
7240
|
-
const srv = await createProxyMcpServer(tools, timeoutOverrides, interceptors);
|
|
7241
|
-
srv.calls.on("call", (call) => {
|
|
7242
|
-
queuePendingProxyCall(sessionKeyForCalls, call, timeoutOverrides);
|
|
7243
|
-
});
|
|
7244
|
-
return srv;
|
|
7245
|
-
}
|
|
7246
|
-
extractPendingProxyResult(prompt, toolCallId) {
|
|
7247
|
-
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
7248
|
-
const msg = prompt[i];
|
|
7249
|
-
if (msg.role !== "tool" || !Array.isArray(msg.content)) continue;
|
|
7250
|
-
for (const part of msg.content) {
|
|
7251
|
-
if (part.type !== "tool-result" || part.toolCallId !== toolCallId) continue;
|
|
7252
|
-
const output = part.output;
|
|
7253
|
-
if (!output || typeof output !== "object") {
|
|
7254
|
-
return {
|
|
7255
|
-
kind: "text",
|
|
7256
|
-
text: String(output ?? "")
|
|
7257
|
-
};
|
|
7258
|
-
}
|
|
7259
|
-
if (output.type === "text") {
|
|
7260
|
-
return {
|
|
7261
|
-
kind: "text",
|
|
7262
|
-
text: String(output.value ?? "")
|
|
7263
|
-
};
|
|
7264
|
-
}
|
|
7265
|
-
if (output.type === "json") {
|
|
7266
|
-
return {
|
|
7267
|
-
kind: "text",
|
|
7268
|
-
text: JSON.stringify(output.value)
|
|
7269
|
-
};
|
|
7270
|
-
}
|
|
7271
|
-
if (output.type === "content" && Array.isArray(output.value)) {
|
|
7272
|
-
const text = output.value.filter((v) => v?.type === "text" && typeof v.text === "string").map((v) => v.text).join("\n");
|
|
7273
|
-
return {
|
|
7274
|
-
kind: "text",
|
|
7275
|
-
text
|
|
7276
|
-
};
|
|
7277
|
-
}
|
|
7278
|
-
return {
|
|
7279
|
-
kind: "text",
|
|
7280
|
-
text: JSON.stringify(output)
|
|
7281
|
-
};
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
return null;
|
|
7285
|
-
}
|
|
7286
|
-
/**
|
|
7287
|
-
* The result opencode produced for a pending proxy call, if the prompt
|
|
7288
|
-
* carries it. For `task_batch` that means every child's result gathered
|
|
7289
|
-
* back onto the parent: opencode runs the children in one step and hands
|
|
7290
|
-
* all their results to the next call together, so a partial set is not
|
|
7291
|
-
* expected. If it ever happens the batch still resolves, with the gap
|
|
7292
|
-
* named in the text, because leaving the parent pending would send this
|
|
7293
|
-
* turn down the fresh-envelope path and reject the call as orphaned.
|
|
7294
|
-
*/
|
|
7295
|
-
extractPendingProxyResultForCall(prompt, call) {
|
|
7296
|
-
if (call.toolName !== TASK_BATCH_TOOL_NAME) {
|
|
7297
|
-
return this.extractPendingProxyResult(prompt, call.toolCallId);
|
|
7298
|
-
}
|
|
7299
|
-
const tasks = taskBatchTasks(call.input);
|
|
7300
|
-
if (tasks.length === 0) {
|
|
7301
|
-
return { kind: "error", message: "task_batch input is not a list of task objects" };
|
|
7302
|
-
}
|
|
7303
|
-
const children = tasks.map((task, index) => ({
|
|
7304
|
-
task,
|
|
7305
|
-
result: this.extractPendingProxyResult(
|
|
7306
|
-
prompt,
|
|
7307
|
-
taskBatchChildToolCallId(call.toolCallId, index)
|
|
7308
|
-
)
|
|
7309
|
-
}));
|
|
7310
|
-
const answered = children.filter((child) => child.result !== null).length;
|
|
7311
|
-
if (answered === 0) return null;
|
|
7312
|
-
if (answered < children.length) {
|
|
7313
|
-
log.warn("task_batch resolving with child results missing", {
|
|
7314
|
-
toolCallId: call.toolCallId,
|
|
7315
|
-
answered,
|
|
7316
|
-
total: children.length
|
|
7317
|
-
});
|
|
7318
|
-
}
|
|
7319
|
-
return formatTaskBatchResults(children);
|
|
7474
|
+
return ensureProxyServer(
|
|
7475
|
+
this.config,
|
|
7476
|
+
tools,
|
|
7477
|
+
sessionKeyForCalls,
|
|
7478
|
+
interceptCompress
|
|
7479
|
+
);
|
|
7320
7480
|
}
|
|
7321
7481
|
/**
|
|
7322
7482
|
* Resolve the session affinity token for this LLM call. Delegates to the
|
|
@@ -7337,83 +7497,17 @@ var ClaudeCodeLanguageModel = class {
|
|
|
7337
7497
|
);
|
|
7338
7498
|
}
|
|
7339
7499
|
controlRequestBehaviorForTool(toolName) {
|
|
7340
|
-
|
|
7341
|
-
if (configured && toolName) {
|
|
7342
|
-
const direct = configured[toolName] ?? configured[toolName.toLowerCase()];
|
|
7343
|
-
if (direct === "allow" || direct === "deny") return direct;
|
|
7344
|
-
const lower = toolName.toLowerCase();
|
|
7345
|
-
for (const [key, behavior] of Object.entries(configured)) {
|
|
7346
|
-
if (key.toLowerCase() === lower && (behavior === "allow" || behavior === "deny")) {
|
|
7347
|
-
return behavior;
|
|
7348
|
-
}
|
|
7349
|
-
}
|
|
7350
|
-
}
|
|
7351
|
-
if (isAskUserQuestionTool(toolName)) return "deny";
|
|
7352
|
-
return this.config.controlRequestBehavior ?? "allow";
|
|
7500
|
+
return controlRequestBehaviorForTool(this.config, toolName);
|
|
7353
7501
|
}
|
|
7354
7502
|
writeControlResponse(proc, requestId, response) {
|
|
7355
|
-
|
|
7356
|
-
type: "control_response",
|
|
7357
|
-
response: {
|
|
7358
|
-
subtype: "success",
|
|
7359
|
-
request_id: requestId,
|
|
7360
|
-
response
|
|
7361
|
-
}
|
|
7362
|
-
};
|
|
7363
|
-
try {
|
|
7364
|
-
proc.stdin?.write(JSON.stringify(payload) + "\n");
|
|
7365
|
-
} catch (error) {
|
|
7366
|
-
log.warn("failed to write control response", {
|
|
7367
|
-
requestId,
|
|
7368
|
-
error: error instanceof Error ? error.message : String(error)
|
|
7369
|
-
});
|
|
7370
|
-
}
|
|
7503
|
+
writeControlResponse(proc, requestId, response);
|
|
7371
7504
|
}
|
|
7372
7505
|
/**
|
|
7373
7506
|
* Handle Claude stream-json control requests (`can_use_tool`, etc.) and
|
|
7374
7507
|
* respond via stdin with a matching `control_response`.
|
|
7375
7508
|
*/
|
|
7376
7509
|
handleControlRequest(msg, proc) {
|
|
7377
|
-
|
|
7378
|
-
const requestId = msg.request_id;
|
|
7379
|
-
const request = msg.request;
|
|
7380
|
-
if (!requestId || !request?.subtype) return false;
|
|
7381
|
-
if (request.subtype === "can_use_tool") {
|
|
7382
|
-
const toolName = request.tool_name ?? "unknown";
|
|
7383
|
-
const behavior = this.controlRequestBehaviorForTool(toolName);
|
|
7384
|
-
if (behavior === "allow") {
|
|
7385
|
-
this.writeControlResponse(proc, requestId, {
|
|
7386
|
-
behavior: "allow",
|
|
7387
|
-
updatedInput: request.input ?? {},
|
|
7388
|
-
toolUseID: request.tool_use_id
|
|
7389
|
-
});
|
|
7390
|
-
log.info("control request auto-allowed", {
|
|
7391
|
-
requestId,
|
|
7392
|
-
toolName
|
|
7393
|
-
});
|
|
7394
|
-
} else {
|
|
7395
|
-
const denyMessage = denyMessageForTool(
|
|
7396
|
-
toolName,
|
|
7397
|
-
this.config.controlRequestDenyMessage
|
|
7398
|
-
);
|
|
7399
|
-
this.writeControlResponse(proc, requestId, {
|
|
7400
|
-
behavior: "deny",
|
|
7401
|
-
message: denyMessage,
|
|
7402
|
-
toolUseID: request.tool_use_id
|
|
7403
|
-
});
|
|
7404
|
-
log.info("control request auto-denied", {
|
|
7405
|
-
requestId,
|
|
7406
|
-
toolName
|
|
7407
|
-
});
|
|
7408
|
-
}
|
|
7409
|
-
return true;
|
|
7410
|
-
}
|
|
7411
|
-
this.writeControlResponse(proc, requestId, {});
|
|
7412
|
-
log.debug("control request acknowledged", {
|
|
7413
|
-
requestId,
|
|
7414
|
-
subtype: request.subtype
|
|
7415
|
-
});
|
|
7416
|
-
return true;
|
|
7510
|
+
return handleControlRequest(this.config, msg, proc);
|
|
7417
7511
|
}
|
|
7418
7512
|
getReasoningEffort(providerOptions) {
|
|
7419
7513
|
if (!providerOptions) return void 0;
|
|
@@ -7457,64 +7551,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
7457
7551
|
};
|
|
7458
7552
|
}
|
|
7459
7553
|
latestUserText(prompt) {
|
|
7460
|
-
|
|
7461
|
-
const msg = prompt[i];
|
|
7462
|
-
if (msg.role !== "user") continue;
|
|
7463
|
-
if (typeof msg.content === "string") {
|
|
7464
|
-
return String(msg.content).trim();
|
|
7465
|
-
}
|
|
7466
|
-
if (Array.isArray(msg.content)) {
|
|
7467
|
-
const text = msg.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => String(part.text).trim()).filter(Boolean).join(" ");
|
|
7468
|
-
if (text) return text;
|
|
7469
|
-
}
|
|
7470
|
-
}
|
|
7471
|
-
return "";
|
|
7554
|
+
return latestUserText(prompt);
|
|
7472
7555
|
}
|
|
7473
7556
|
synthesizeTitle(prompt) {
|
|
7474
|
-
|
|
7475
|
-
if (!source) return "New Session";
|
|
7476
|
-
const stop = /* @__PURE__ */ new Set([
|
|
7477
|
-
"a",
|
|
7478
|
-
"an",
|
|
7479
|
-
"the",
|
|
7480
|
-
"and",
|
|
7481
|
-
"or",
|
|
7482
|
-
"but",
|
|
7483
|
-
"to",
|
|
7484
|
-
"for",
|
|
7485
|
-
"of",
|
|
7486
|
-
"in",
|
|
7487
|
-
"on",
|
|
7488
|
-
"at",
|
|
7489
|
-
"with",
|
|
7490
|
-
"can",
|
|
7491
|
-
"could",
|
|
7492
|
-
"would",
|
|
7493
|
-
"should",
|
|
7494
|
-
"please",
|
|
7495
|
-
"hi",
|
|
7496
|
-
"hello",
|
|
7497
|
-
"hey",
|
|
7498
|
-
"there",
|
|
7499
|
-
"you",
|
|
7500
|
-
"your",
|
|
7501
|
-
"this",
|
|
7502
|
-
"that",
|
|
7503
|
-
"is",
|
|
7504
|
-
"are",
|
|
7505
|
-
"was",
|
|
7506
|
-
"were",
|
|
7507
|
-
"be",
|
|
7508
|
-
"do",
|
|
7509
|
-
"does",
|
|
7510
|
-
"did",
|
|
7511
|
-
"summarize",
|
|
7512
|
-
"summary",
|
|
7513
|
-
"project"
|
|
7514
|
-
]);
|
|
7515
|
-
const words = source.split(" ").map((word) => word.trim()).filter(Boolean).filter((word) => !stop.has(word.toLowerCase()));
|
|
7516
|
-
const picked = (words.length > 0 ? words : source.split(" ").filter(Boolean)).slice(0, 6).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
7517
|
-
return picked || "New Session";
|
|
7557
|
+
return synthesizeTitle(prompt);
|
|
7518
7558
|
}
|
|
7519
7559
|
async doGenerateViaStream(options) {
|
|
7520
7560
|
const result = await this.doStream(options);
|
|
@@ -8049,9 +8089,9 @@ ${plan}
|
|
|
8049
8089
|
`${effectiveModelId}::${scope}::${affinity}::context=${JSON.stringify([this.config.provider, this.getOpencodeAgent(options) ?? null])}`
|
|
8050
8090
|
);
|
|
8051
8091
|
const sk = compactionMode ? sessionKey(cwd, `${effectiveModelId}::compaction::${affinity}`) : effortSessionKey(baseKey, reasoningEffort);
|
|
8052
|
-
const
|
|
8053
|
-
const
|
|
8054
|
-
const
|
|
8092
|
+
const toUsage2 = this.toUsage.bind(this);
|
|
8093
|
+
const toFinishReason2 = this.toFinishReason.bind(this);
|
|
8094
|
+
const handleControlRequest2 = this.handleControlRequest.bind(this);
|
|
8055
8095
|
const flagOn = (v) => v !== void 0 && !["", "0", "false", "no", "off"].includes(v.trim().toLowerCase());
|
|
8056
8096
|
const interactivePref = this.config.interactive ?? flagOn(process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT);
|
|
8057
8097
|
const useInteractive = interactivePref && typeof globalThis.Bun?.Terminal === "function";
|
|
@@ -8086,8 +8126,8 @@ ${plan}
|
|
|
8086
8126
|
controller.enqueue({ type: "text-end", id });
|
|
8087
8127
|
controller.enqueue({
|
|
8088
8128
|
type: "finish",
|
|
8089
|
-
finishReason:
|
|
8090
|
-
usage:
|
|
8129
|
+
finishReason: toFinishReason2("stop"),
|
|
8130
|
+
usage: toUsage2({ input_tokens: 0, output_tokens: 0 }),
|
|
8091
8131
|
providerMetadata: {
|
|
8092
8132
|
"claude-code": { path: "doctor", synthetic: true, usageUnavailable: true }
|
|
8093
8133
|
}
|
|
@@ -8134,8 +8174,8 @@ ${plan}
|
|
|
8134
8174
|
controller.enqueue({ type: "text-end", id });
|
|
8135
8175
|
controller.enqueue({
|
|
8136
8176
|
type: "finish",
|
|
8137
|
-
finishReason:
|
|
8138
|
-
usage:
|
|
8177
|
+
finishReason: toFinishReason2("stop"),
|
|
8178
|
+
usage: toUsage2({}),
|
|
8139
8179
|
providerMetadata: { "claude-code": { path: "side-question", synthetic: answer.synthetic, usageUnavailable: true } }
|
|
8140
8180
|
});
|
|
8141
8181
|
} catch (error) {
|
|
@@ -8167,8 +8207,8 @@ ${plan}
|
|
|
8167
8207
|
controller.enqueue({ type: "text-end", id: textId });
|
|
8168
8208
|
controller.enqueue({
|
|
8169
8209
|
type: "finish",
|
|
8170
|
-
finishReason:
|
|
8171
|
-
usage:
|
|
8210
|
+
finishReason: toFinishReason2("stop"),
|
|
8211
|
+
usage: toUsage2({ input_tokens: 0, output_tokens: 0 }),
|
|
8172
8212
|
providerMetadata: {
|
|
8173
8213
|
"claude-code": {
|
|
8174
8214
|
synthetic: true,
|
|
@@ -8191,8 +8231,8 @@ ${plan}
|
|
|
8191
8231
|
controller.enqueue({ type: "stream-start", warnings });
|
|
8192
8232
|
controller.enqueue({
|
|
8193
8233
|
type: "finish",
|
|
8194
|
-
finishReason:
|
|
8195
|
-
usage:
|
|
8234
|
+
finishReason: toFinishReason2("stop"),
|
|
8235
|
+
usage: toUsage2({ input_tokens: 0, output_tokens: 0 }),
|
|
8196
8236
|
providerMetadata: {
|
|
8197
8237
|
"claude-code": { synthetic: true, path: "no-new-user-content" }
|
|
8198
8238
|
}
|
|
@@ -8236,7 +8276,7 @@ ${plan}
|
|
|
8236
8276
|
controller.enqueue({
|
|
8237
8277
|
type: "finish",
|
|
8238
8278
|
finishReason: { unified: "error", raw: "account_limit" },
|
|
8239
|
-
usage:
|
|
8279
|
+
usage: toUsage2({ input_tokens: 0, output_tokens: 0 }),
|
|
8240
8280
|
providerMetadata: {
|
|
8241
8281
|
"claude-code": {
|
|
8242
8282
|
path: "account-failover-stop",
|
|
@@ -8309,7 +8349,7 @@ ${plan}
|
|
|
8309
8349
|
const self = this;
|
|
8310
8350
|
const previousPendingProxyMatches = previousPendingProxyCalls.map((call) => ({
|
|
8311
8351
|
call,
|
|
8312
|
-
result:
|
|
8352
|
+
result: extractPendingProxyResultForCall(options.prompt, call)
|
|
8313
8353
|
}));
|
|
8314
8354
|
const hasMatchedPendingResults = previousPendingProxyMatches.some(
|
|
8315
8355
|
(m) => m.result !== null
|
|
@@ -8885,8 +8925,8 @@ ${plan}
|
|
|
8885
8925
|
}
|
|
8886
8926
|
controller.enqueue({
|
|
8887
8927
|
type: "finish",
|
|
8888
|
-
finishReason:
|
|
8889
|
-
usage:
|
|
8928
|
+
finishReason: toFinishReason2("tool-calls"),
|
|
8929
|
+
usage: toUsage2(resultMeta.usage),
|
|
8890
8930
|
providerMetadata: {
|
|
8891
8931
|
"claude-code": resultMeta
|
|
8892
8932
|
}
|
|
@@ -8916,8 +8956,8 @@ ${plan}
|
|
|
8916
8956
|
});
|
|
8917
8957
|
controller.enqueue({
|
|
8918
8958
|
type: "finish",
|
|
8919
|
-
finishReason:
|
|
8920
|
-
usage:
|
|
8959
|
+
finishReason: toFinishReason2("tool-calls"),
|
|
8960
|
+
usage: toUsage2(resultMeta.usage),
|
|
8921
8961
|
providerMetadata: {
|
|
8922
8962
|
"claude-code": resultMeta
|
|
8923
8963
|
}
|
|
@@ -9089,8 +9129,8 @@ ${plan}
|
|
|
9089
9129
|
}
|
|
9090
9130
|
controller.enqueue({
|
|
9091
9131
|
type: "finish",
|
|
9092
|
-
finishReason: resultFailure ? { unified: "error", raw: resultFailure } :
|
|
9093
|
-
usage:
|
|
9132
|
+
finishReason: resultFailure ? { unified: "error", raw: resultFailure } : toFinishReason2("stop"),
|
|
9133
|
+
usage: toUsage2(msg.usage),
|
|
9094
9134
|
providerMetadata: {
|
|
9095
9135
|
"claude-code": {
|
|
9096
9136
|
...resultMeta,
|
|
@@ -9134,7 +9174,7 @@ ${plan}
|
|
|
9134
9174
|
if (outer.type === "stream_event") {
|
|
9135
9175
|
gotPartialEvents = true;
|
|
9136
9176
|
}
|
|
9137
|
-
if (
|
|
9177
|
+
if (handleControlRequest2(msg, proc)) {
|
|
9138
9178
|
return;
|
|
9139
9179
|
}
|
|
9140
9180
|
log.debug("stream message", {
|
|
@@ -9838,8 +9878,8 @@ ${plan}
|
|
|
9838
9878
|
}
|
|
9839
9879
|
controller.enqueue({
|
|
9840
9880
|
type: "finish",
|
|
9841
|
-
finishReason:
|
|
9842
|
-
usage:
|
|
9881
|
+
finishReason: toFinishReason2(crashed ? "error" : "stop"),
|
|
9882
|
+
usage: toUsage2(),
|
|
9843
9883
|
providerMetadata: {
|
|
9844
9884
|
"claude-code": {
|
|
9845
9885
|
...resultMeta,
|