@rama_nigg/open-cursor 2.1.6 → 2.2.0
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 +40 -61
- package/dist/cli/opencode-cursor.js +262 -20
- package/dist/index.js +669 -213
- package/dist/plugin-entry.js +642 -209
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12848,6 +12848,9 @@ ${result.output || "(no output)"}`
|
|
|
12848
12848
|
}
|
|
12849
12849
|
|
|
12850
12850
|
// src/utils/logger.ts
|
|
12851
|
+
import * as fs from "node:fs";
|
|
12852
|
+
import * as path from "node:path";
|
|
12853
|
+
import * as os from "node:os";
|
|
12851
12854
|
function getConfiguredLevel() {
|
|
12852
12855
|
const env = process.env.CURSOR_ACP_LOG_LEVEL?.toLowerCase();
|
|
12853
12856
|
if (env && env in LEVEL_PRIORITY) {
|
|
@@ -12878,32 +12881,90 @@ function formatMessage(level, component, message, data) {
|
|
|
12878
12881
|
}
|
|
12879
12882
|
return formatted;
|
|
12880
12883
|
}
|
|
12884
|
+
function isConsoleEnabled() {
|
|
12885
|
+
const consoleEnv = process.env.CURSOR_ACP_LOG_CONSOLE;
|
|
12886
|
+
return consoleEnv === "1" || consoleEnv === "true";
|
|
12887
|
+
}
|
|
12888
|
+
function ensureLogDir() {
|
|
12889
|
+
if (logDirEnsured)
|
|
12890
|
+
return;
|
|
12891
|
+
try {
|
|
12892
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
12893
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
12894
|
+
}
|
|
12895
|
+
logDirEnsured = true;
|
|
12896
|
+
} catch {
|
|
12897
|
+
logFileError = true;
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12900
|
+
function rotateIfNeeded() {
|
|
12901
|
+
try {
|
|
12902
|
+
const stats = fs.statSync(LOG_FILE);
|
|
12903
|
+
if (stats.size >= MAX_LOG_SIZE) {
|
|
12904
|
+
const backupFile = LOG_FILE + ".1";
|
|
12905
|
+
fs.renameSync(LOG_FILE, backupFile);
|
|
12906
|
+
}
|
|
12907
|
+
} catch {}
|
|
12908
|
+
}
|
|
12909
|
+
function writeToFile(message) {
|
|
12910
|
+
if (logFileError)
|
|
12911
|
+
return;
|
|
12912
|
+
ensureLogDir();
|
|
12913
|
+
if (logFileError)
|
|
12914
|
+
return;
|
|
12915
|
+
try {
|
|
12916
|
+
rotateIfNeeded();
|
|
12917
|
+
const timestamp = new Date().toISOString();
|
|
12918
|
+
fs.appendFileSync(LOG_FILE, `${timestamp} ${message}
|
|
12919
|
+
`);
|
|
12920
|
+
} catch {
|
|
12921
|
+
if (!logFileError) {
|
|
12922
|
+
logFileError = true;
|
|
12923
|
+
console.error(`[cursor-acp] Failed to write logs. Using: ${LOG_FILE}`);
|
|
12924
|
+
}
|
|
12925
|
+
}
|
|
12926
|
+
}
|
|
12881
12927
|
function createLogger(component) {
|
|
12882
12928
|
return {
|
|
12883
12929
|
debug: (message, data) => {
|
|
12884
|
-
if (shouldLog("debug"))
|
|
12885
|
-
|
|
12886
|
-
|
|
12930
|
+
if (!shouldLog("debug"))
|
|
12931
|
+
return;
|
|
12932
|
+
const formatted = formatMessage("debug", component, message, data);
|
|
12933
|
+
writeToFile(formatted);
|
|
12934
|
+
if (isConsoleEnabled())
|
|
12935
|
+
console.error(formatted);
|
|
12887
12936
|
},
|
|
12888
12937
|
info: (message, data) => {
|
|
12889
|
-
if (shouldLog("info"))
|
|
12890
|
-
|
|
12891
|
-
|
|
12938
|
+
if (!shouldLog("info"))
|
|
12939
|
+
return;
|
|
12940
|
+
const formatted = formatMessage("info", component, message, data);
|
|
12941
|
+
writeToFile(formatted);
|
|
12942
|
+
if (isConsoleEnabled())
|
|
12943
|
+
console.error(formatted);
|
|
12892
12944
|
},
|
|
12893
12945
|
warn: (message, data) => {
|
|
12894
|
-
if (shouldLog("warn"))
|
|
12895
|
-
|
|
12896
|
-
|
|
12946
|
+
if (!shouldLog("warn"))
|
|
12947
|
+
return;
|
|
12948
|
+
const formatted = formatMessage("warn", component, message, data);
|
|
12949
|
+
writeToFile(formatted);
|
|
12950
|
+
if (isConsoleEnabled())
|
|
12951
|
+
console.error(formatted);
|
|
12897
12952
|
},
|
|
12898
12953
|
error: (message, data) => {
|
|
12899
|
-
if (shouldLog("error"))
|
|
12900
|
-
|
|
12901
|
-
|
|
12954
|
+
if (!shouldLog("error"))
|
|
12955
|
+
return;
|
|
12956
|
+
const formatted = formatMessage("error", component, message, data);
|
|
12957
|
+
writeToFile(formatted);
|
|
12958
|
+
if (isConsoleEnabled())
|
|
12959
|
+
console.error(formatted);
|
|
12902
12960
|
}
|
|
12903
12961
|
};
|
|
12904
12962
|
}
|
|
12905
|
-
var LEVEL_PRIORITY;
|
|
12963
|
+
var LOG_DIR, LOG_FILE, MAX_LOG_SIZE, LEVEL_PRIORITY, logDirEnsured = false, logFileError = false;
|
|
12906
12964
|
var init_logger = __esm(() => {
|
|
12965
|
+
LOG_DIR = path.join(os.homedir(), ".opencode-cursor");
|
|
12966
|
+
LOG_FILE = path.join(LOG_DIR, "plugin.log");
|
|
12967
|
+
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
12907
12968
|
LEVEL_PRIORITY = {
|
|
12908
12969
|
debug: 0,
|
|
12909
12970
|
info: 1,
|
|
@@ -13004,15 +13065,15 @@ function formatErrorForUser(error45) {
|
|
|
13004
13065
|
|
|
13005
13066
|
// src/auth.ts
|
|
13006
13067
|
import { spawn } from "child_process";
|
|
13007
|
-
import { existsSync } from "fs";
|
|
13008
|
-
import { homedir, platform } from "os";
|
|
13009
|
-
import { join } from "path";
|
|
13068
|
+
import { existsSync as existsSync2 } from "fs";
|
|
13069
|
+
import { homedir as homedir2, platform } from "os";
|
|
13070
|
+
import { join as join2 } from "path";
|
|
13010
13071
|
function getHomeDir() {
|
|
13011
13072
|
const override = process.env.CURSOR_ACP_HOME_DIR;
|
|
13012
13073
|
if (override && override.length > 0) {
|
|
13013
13074
|
return override;
|
|
13014
13075
|
}
|
|
13015
|
-
return
|
|
13076
|
+
return homedir2();
|
|
13016
13077
|
}
|
|
13017
13078
|
async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_POLL_INTERVAL) {
|
|
13018
13079
|
const startTime = Date.now();
|
|
@@ -13021,7 +13082,7 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
|
|
|
13021
13082
|
const check2 = () => {
|
|
13022
13083
|
const elapsed = Date.now() - startTime;
|
|
13023
13084
|
for (const authPath of possiblePaths) {
|
|
13024
|
-
if (
|
|
13085
|
+
if (existsSync2(authPath)) {
|
|
13025
13086
|
log.debug("Auth file detected", { path: authPath });
|
|
13026
13087
|
resolve(true);
|
|
13027
13088
|
return;
|
|
@@ -13148,7 +13209,7 @@ async function startCursorOAuth() {
|
|
|
13148
13209
|
function verifyCursorAuth() {
|
|
13149
13210
|
const possiblePaths = getPossibleAuthPaths();
|
|
13150
13211
|
for (const authPath of possiblePaths) {
|
|
13151
|
-
if (
|
|
13212
|
+
if (existsSync2(authPath)) {
|
|
13152
13213
|
log.debug("Auth file found", { path: authPath });
|
|
13153
13214
|
return true;
|
|
13154
13215
|
}
|
|
@@ -13163,23 +13224,23 @@ function getPossibleAuthPaths() {
|
|
|
13163
13224
|
const authFiles = ["cli-config.json", "auth.json"];
|
|
13164
13225
|
if (isDarwin) {
|
|
13165
13226
|
for (const file2 of authFiles) {
|
|
13166
|
-
paths.push(
|
|
13227
|
+
paths.push(join2(home, ".cursor", file2));
|
|
13167
13228
|
}
|
|
13168
13229
|
for (const file2 of authFiles) {
|
|
13169
|
-
paths.push(
|
|
13230
|
+
paths.push(join2(home, ".config", "cursor", file2));
|
|
13170
13231
|
}
|
|
13171
13232
|
} else {
|
|
13172
13233
|
for (const file2 of authFiles) {
|
|
13173
|
-
paths.push(
|
|
13234
|
+
paths.push(join2(home, ".config", "cursor", file2));
|
|
13174
13235
|
}
|
|
13175
13236
|
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
13176
|
-
if (xdgConfig && xdgConfig !==
|
|
13237
|
+
if (xdgConfig && xdgConfig !== join2(home, ".config")) {
|
|
13177
13238
|
for (const file2 of authFiles) {
|
|
13178
|
-
paths.push(
|
|
13239
|
+
paths.push(join2(xdgConfig, "cursor", file2));
|
|
13179
13240
|
}
|
|
13180
13241
|
}
|
|
13181
13242
|
for (const file2 of authFiles) {
|
|
13182
|
-
paths.push(
|
|
13243
|
+
paths.push(join2(home, ".cursor", file2));
|
|
13183
13244
|
}
|
|
13184
13245
|
}
|
|
13185
13246
|
return paths;
|
|
@@ -13187,7 +13248,7 @@ function getPossibleAuthPaths() {
|
|
|
13187
13248
|
function getAuthFilePath() {
|
|
13188
13249
|
const possiblePaths = getPossibleAuthPaths();
|
|
13189
13250
|
for (const authPath of possiblePaths) {
|
|
13190
|
-
if (
|
|
13251
|
+
if (existsSync2(authPath)) {
|
|
13191
13252
|
return authPath;
|
|
13192
13253
|
}
|
|
13193
13254
|
}
|
|
@@ -13302,10 +13363,6 @@ class StreamToSseConverter {
|
|
|
13302
13363
|
}
|
|
13303
13364
|
handleEvent(event) {
|
|
13304
13365
|
if (isAssistantText(event)) {
|
|
13305
|
-
const hasPartialTimestamp = typeof event.timestamp_ms === "number";
|
|
13306
|
-
if (!hasPartialTimestamp) {
|
|
13307
|
-
return [];
|
|
13308
|
-
}
|
|
13309
13366
|
const delta = this.tracker.nextText(extractText(event));
|
|
13310
13367
|
return delta ? [this.chunkWith({ content: delta })] : [];
|
|
13311
13368
|
}
|
|
@@ -13419,7 +13476,68 @@ var init_perf = __esm(() => {
|
|
|
13419
13476
|
});
|
|
13420
13477
|
|
|
13421
13478
|
// src/proxy/prompt-builder.ts
|
|
13479
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
13480
|
+
import { homedir as homedir3 } from "node:os";
|
|
13481
|
+
import { join as join3 } from "node:path";
|
|
13482
|
+
function ensureLogDir2() {
|
|
13483
|
+
try {
|
|
13484
|
+
if (!existsSync3(DEBUG_LOG_DIR)) {
|
|
13485
|
+
mkdirSync2(DEBUG_LOG_DIR, { recursive: true });
|
|
13486
|
+
}
|
|
13487
|
+
} catch {}
|
|
13488
|
+
}
|
|
13489
|
+
function debugLogToFile(message, data) {
|
|
13490
|
+
try {
|
|
13491
|
+
ensureLogDir2();
|
|
13492
|
+
const timestamp = new Date().toISOString();
|
|
13493
|
+
const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
|
|
13494
|
+
`;
|
|
13495
|
+
appendFileSync2(DEBUG_LOG_FILE, logLine);
|
|
13496
|
+
} catch (err) {
|
|
13497
|
+
log4.debug(message, data);
|
|
13498
|
+
}
|
|
13499
|
+
}
|
|
13422
13500
|
function buildPromptFromMessages(messages, tools) {
|
|
13501
|
+
const messageSummary = messages.map((m, i) => {
|
|
13502
|
+
const role = m?.role ?? "?";
|
|
13503
|
+
const hasToolCalls = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
|
|
13504
|
+
const tcNames = hasToolCalls > 0 ? m.tool_calls.map((tc) => tc?.function?.name).join(",") : "";
|
|
13505
|
+
const contentType = typeof m?.content;
|
|
13506
|
+
const contentLen = typeof m?.content === "string" ? m.content.length : Array.isArray(m?.content) ? `arr:${m.content.length}` : "null";
|
|
13507
|
+
const toolCallId = m?.tool_call_id ?? null;
|
|
13508
|
+
return { i, role, hasToolCalls, tcNames, contentType, contentLen, toolCallId };
|
|
13509
|
+
});
|
|
13510
|
+
const assistantWithToolCalls = messages.filter((m) => m?.role === "assistant" && Array.isArray(m?.tool_calls) && m.tool_calls.length > 0);
|
|
13511
|
+
const assistantEmpty = messages.filter((m) => m?.role === "assistant" && (!m?.tool_calls || m.tool_calls.length === 0) && (!m?.content || m.content === "" || m.content === null));
|
|
13512
|
+
const toolResults = messages.filter((m) => m?.role === "tool");
|
|
13513
|
+
debugLogToFile("buildPromptFromMessages", {
|
|
13514
|
+
totalMessages: messages.length,
|
|
13515
|
+
totalTools: tools.length,
|
|
13516
|
+
messageSummary,
|
|
13517
|
+
stats: {
|
|
13518
|
+
assistantWithToolCalls: assistantWithToolCalls.length,
|
|
13519
|
+
assistantEmpty: assistantEmpty.length,
|
|
13520
|
+
toolResults: toolResults.length
|
|
13521
|
+
},
|
|
13522
|
+
assistantDetails: assistantWithToolCalls.length > 0 ? assistantWithToolCalls.map((m, i) => ({
|
|
13523
|
+
index: i,
|
|
13524
|
+
toolCallCount: Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0,
|
|
13525
|
+
toolCallIds: Array.isArray(m?.tool_calls) ? m.tool_calls.map((tc) => tc?.id).join(",") : "",
|
|
13526
|
+
toolCallNames: Array.isArray(m?.tool_calls) ? m.tool_calls.map((tc) => tc?.function?.name).join(",") : "",
|
|
13527
|
+
contentType: typeof m?.content,
|
|
13528
|
+
contentPreview: typeof m?.content === "string" ? m.content.slice(0, 50) : typeof m?.content
|
|
13529
|
+
})) : [],
|
|
13530
|
+
emptyAssistantDetails: assistantEmpty.length > 0 ? assistantEmpty.map((m, i) => ({
|
|
13531
|
+
index: i,
|
|
13532
|
+
contentType: typeof m?.content,
|
|
13533
|
+
contentPreview: typeof m?.content === "string" ? m.content.slice(0, 50) : typeof m?.content
|
|
13534
|
+
})) : [],
|
|
13535
|
+
toolResultDetails: toolResults.length > 0 ? toolResults.map((m, i) => ({
|
|
13536
|
+
index: i,
|
|
13537
|
+
toolCallId: m?.tool_call_id,
|
|
13538
|
+
contentPreview: typeof m?.content === "string" ? m.content.slice(0, 100) : typeof m?.content
|
|
13539
|
+
})) : []
|
|
13540
|
+
});
|
|
13423
13541
|
const lines = [];
|
|
13424
13542
|
if (tools.length > 0) {
|
|
13425
13543
|
const toolDescs = tools.map((t) => {
|
|
@@ -13433,6 +13551,7 @@ function buildPromptFromMessages(messages, tools) {
|
|
|
13433
13551
|
}).join(`
|
|
13434
13552
|
`);
|
|
13435
13553
|
lines.push(`SYSTEM: You have access to the following tools. When you need to use one, respond with a tool_call in the standard OpenAI format.
|
|
13554
|
+
` + `Tool guidance: prefer write/edit for file changes; use bash mainly to run commands/tests.
|
|
13436
13555
|
|
|
13437
13556
|
Available tools:
|
|
13438
13557
|
${toolDescs}`);
|
|
@@ -13472,10 +13591,30 @@ ${toolDescs}`);
|
|
|
13472
13591
|
}
|
|
13473
13592
|
}
|
|
13474
13593
|
}
|
|
13475
|
-
|
|
13594
|
+
const hasToolResults = messages.some((m) => m?.role === "tool");
|
|
13595
|
+
if (hasToolResults) {
|
|
13596
|
+
lines.push("The above tool calls have been executed. Continue your response based on these results.");
|
|
13597
|
+
}
|
|
13598
|
+
const finalPrompt = lines.join(`
|
|
13476
13599
|
|
|
13477
13600
|
`);
|
|
13478
|
-
|
|
13601
|
+
debugLogToFile("buildPromptFromMessages: final prompt", {
|
|
13602
|
+
lineCount: lines.length,
|
|
13603
|
+
promptLength: finalPrompt.length,
|
|
13604
|
+
promptPreview: finalPrompt.slice(0, 500),
|
|
13605
|
+
hasToolResultFormat: finalPrompt.includes("TOOL_RESULT"),
|
|
13606
|
+
hasAssistantToolCallFormat: finalPrompt.includes("tool_call(id:"),
|
|
13607
|
+
hasCompletionSignal: finalPrompt.includes("Based on the tool results")
|
|
13608
|
+
});
|
|
13609
|
+
return finalPrompt;
|
|
13610
|
+
}
|
|
13611
|
+
var log4, DEBUG_LOG_DIR, DEBUG_LOG_FILE;
|
|
13612
|
+
var init_prompt_builder = __esm(() => {
|
|
13613
|
+
init_logger();
|
|
13614
|
+
log4 = createLogger("proxy:prompt-builder");
|
|
13615
|
+
DEBUG_LOG_DIR = join3(homedir3(), ".config", "opencode", "logs");
|
|
13616
|
+
DEBUG_LOG_FILE = join3(DEBUG_LOG_DIR, "tool-loop-debug.log");
|
|
13617
|
+
});
|
|
13479
13618
|
|
|
13480
13619
|
// src/proxy/tool-loop.ts
|
|
13481
13620
|
function extractAllowedToolNames(tools) {
|
|
@@ -13501,7 +13640,7 @@ function extractOpenAiToolCall(event, allowedToolNames) {
|
|
|
13501
13640
|
}
|
|
13502
13641
|
const resolvedName = resolveAllowedToolName(name, allowedToolNames);
|
|
13503
13642
|
if (!resolvedName) {
|
|
13504
|
-
|
|
13643
|
+
log5.debug("Tool call name not allowed; skipping interception", {
|
|
13505
13644
|
name,
|
|
13506
13645
|
normalized: normalizeAliasKey(name),
|
|
13507
13646
|
allowedToolCount: allowedToolNames.size,
|
|
@@ -13510,7 +13649,7 @@ function extractOpenAiToolCall(event, allowedToolNames) {
|
|
|
13510
13649
|
return null;
|
|
13511
13650
|
}
|
|
13512
13651
|
if (args === undefined && event.subtype === "started") {
|
|
13513
|
-
|
|
13652
|
+
log5.debug("Tool call args extraction returned undefined", {
|
|
13514
13653
|
toolName: name,
|
|
13515
13654
|
subtype: event.subtype ?? "none",
|
|
13516
13655
|
payloadKeys: Object.entries(event.tool_call || {}).map(([k, v]) => `${k}:[${isRecord(v) ? Object.keys(v).join(",") : typeof v}]`),
|
|
@@ -13666,10 +13805,10 @@ function toOpenAiArguments(args) {
|
|
|
13666
13805
|
function isRecord(value) {
|
|
13667
13806
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13668
13807
|
}
|
|
13669
|
-
var
|
|
13808
|
+
var log5, TOOL_NAME_ALIASES;
|
|
13670
13809
|
var init_tool_loop = __esm(() => {
|
|
13671
13810
|
init_logger();
|
|
13672
|
-
|
|
13811
|
+
log5 = createLogger("proxy:tool-loop");
|
|
13673
13812
|
TOOL_NAME_ALIASES = new Map([
|
|
13674
13813
|
["runcommand", "bash"],
|
|
13675
13814
|
["executecommand", "bash"],
|
|
@@ -13783,7 +13922,7 @@ class OpenCodeToolDiscovery {
|
|
|
13783
13922
|
const mcpTools = await this.tryListMcpTools();
|
|
13784
13923
|
tools = tools.concat(mcpTools);
|
|
13785
13924
|
} catch (err) {
|
|
13786
|
-
|
|
13925
|
+
log6.debug("SDK tool.list failed, will try CLI", { error: String(err) });
|
|
13787
13926
|
}
|
|
13788
13927
|
}
|
|
13789
13928
|
if (tools.length === 0 && this.executorPref !== "sdk") {
|
|
@@ -13795,10 +13934,10 @@ class OpenCodeToolDiscovery {
|
|
|
13795
13934
|
if (parsed?.data?.tools?.length) {
|
|
13796
13935
|
tools = parsed.data.tools.map((t) => this.normalize(t, "cli"));
|
|
13797
13936
|
} else {
|
|
13798
|
-
|
|
13937
|
+
log6.debug("CLI tool list failed", { status: res.status, stderr: res.stderr });
|
|
13799
13938
|
}
|
|
13800
13939
|
} catch (err) {
|
|
13801
|
-
|
|
13940
|
+
log6.debug("CLI tool list error", { error: String(err) });
|
|
13802
13941
|
}
|
|
13803
13942
|
}
|
|
13804
13943
|
const map2 = new Map;
|
|
@@ -13834,7 +13973,7 @@ class OpenCodeToolDiscovery {
|
|
|
13834
13973
|
return [];
|
|
13835
13974
|
return mcpList.data.tools.map((t) => this.normalize(t, "mcp"));
|
|
13836
13975
|
} catch (err) {
|
|
13837
|
-
|
|
13976
|
+
log6.debug("MCP tool discovery skipped", { error: String(err) });
|
|
13838
13977
|
return [];
|
|
13839
13978
|
}
|
|
13840
13979
|
}
|
|
@@ -13855,11 +13994,11 @@ class OpenCodeToolDiscovery {
|
|
|
13855
13994
|
return null;
|
|
13856
13995
|
}
|
|
13857
13996
|
}
|
|
13858
|
-
var
|
|
13997
|
+
var log6;
|
|
13859
13998
|
var init_discovery = __esm(() => {
|
|
13860
13999
|
init_logger();
|
|
13861
14000
|
init_strip_ansi();
|
|
13862
|
-
|
|
14001
|
+
log6 = createLogger("tools:discovery");
|
|
13863
14002
|
});
|
|
13864
14003
|
|
|
13865
14004
|
// src/tools/schema.ts
|
|
@@ -13916,10 +14055,10 @@ function describeTool(t) {
|
|
|
13916
14055
|
const base = t.description || "OpenCode tool";
|
|
13917
14056
|
return base.length > 400 ? base.slice(0, 400) : base;
|
|
13918
14057
|
}
|
|
13919
|
-
var
|
|
14058
|
+
var log7;
|
|
13920
14059
|
var init_schema = __esm(() => {
|
|
13921
14060
|
init_logger();
|
|
13922
|
-
|
|
14061
|
+
log7 = createLogger("tools:schema");
|
|
13923
14062
|
});
|
|
13924
14063
|
|
|
13925
14064
|
// src/tools/router.ts
|
|
@@ -13944,18 +14083,18 @@ class ToolRouter {
|
|
|
13944
14083
|
}
|
|
13945
14084
|
const tool3 = this.ctx.toolsByName.get(name);
|
|
13946
14085
|
if (!tool3) {
|
|
13947
|
-
|
|
14086
|
+
log8.warn("Unknown tool call", { name });
|
|
13948
14087
|
return this.buildResult(meta, callId, name, { status: "error", error: `Unknown tool ${name}` });
|
|
13949
14088
|
}
|
|
13950
14089
|
const args = this.extractArgs(event);
|
|
13951
|
-
|
|
14090
|
+
log8.debug("Executing tool", { name, toolId: tool3.id });
|
|
13952
14091
|
const t0 = Date.now();
|
|
13953
14092
|
const result = await this.ctx.execute(tool3.id, args);
|
|
13954
14093
|
const elapsed = Date.now() - t0;
|
|
13955
14094
|
if (result.status === "error") {
|
|
13956
|
-
|
|
14095
|
+
log8.warn("Tool execution returned error", { name, error: result.error, elapsed });
|
|
13957
14096
|
} else {
|
|
13958
|
-
|
|
14097
|
+
log8.debug("Tool execution completed", { name, toolId: tool3.id, elapsed });
|
|
13959
14098
|
}
|
|
13960
14099
|
return this.buildResult(meta, callId, name, result);
|
|
13961
14100
|
}
|
|
@@ -14004,10 +14143,10 @@ class ToolRouter {
|
|
|
14004
14143
|
};
|
|
14005
14144
|
}
|
|
14006
14145
|
}
|
|
14007
|
-
var
|
|
14146
|
+
var log8;
|
|
14008
14147
|
var init_router = __esm(() => {
|
|
14009
14148
|
init_logger();
|
|
14010
|
-
|
|
14149
|
+
log8 = createLogger("tools:router");
|
|
14011
14150
|
});
|
|
14012
14151
|
|
|
14013
14152
|
// src/tools/skills/loader.ts
|
|
@@ -14319,7 +14458,7 @@ var separatorArrayExplode = (style) => {
|
|
|
14319
14458
|
};
|
|
14320
14459
|
|
|
14321
14460
|
// node_modules/@opencode-ai/sdk/dist/gen/core/utils.gen.js
|
|
14322
|
-
var PATH_PARAM_RE, defaultPathSerializer = ({ path, url: _url2 }) => {
|
|
14461
|
+
var PATH_PARAM_RE, defaultPathSerializer = ({ path: path2, url: _url2 }) => {
|
|
14323
14462
|
let url2 = _url2;
|
|
14324
14463
|
const matches = _url2.match(PATH_PARAM_RE);
|
|
14325
14464
|
if (matches) {
|
|
@@ -14338,7 +14477,7 @@ var PATH_PARAM_RE, defaultPathSerializer = ({ path, url: _url2 }) => {
|
|
|
14338
14477
|
name = name.substring(1);
|
|
14339
14478
|
style = "matrix";
|
|
14340
14479
|
}
|
|
14341
|
-
const value =
|
|
14480
|
+
const value = path2[name];
|
|
14342
14481
|
if (value === undefined || value === null) {
|
|
14343
14482
|
continue;
|
|
14344
14483
|
}
|
|
@@ -14368,11 +14507,11 @@ var PATH_PARAM_RE, defaultPathSerializer = ({ path, url: _url2 }) => {
|
|
|
14368
14507
|
}
|
|
14369
14508
|
}
|
|
14370
14509
|
return url2;
|
|
14371
|
-
}, getUrl = ({ baseUrl, path, query, querySerializer, url: _url2 }) => {
|
|
14510
|
+
}, getUrl = ({ baseUrl, path: path2, query, querySerializer, url: _url2 }) => {
|
|
14372
14511
|
const pathUrl = _url2.startsWith("/") ? _url2 : `/${_url2}`;
|
|
14373
14512
|
let url2 = (baseUrl ?? "") + pathUrl;
|
|
14374
|
-
if (
|
|
14375
|
-
url2 = defaultPathSerializer({ path, url: url2 });
|
|
14513
|
+
if (path2) {
|
|
14514
|
+
url2 = defaultPathSerializer({ path: path2, url: url2 });
|
|
14376
14515
|
}
|
|
14377
14516
|
let search = query ? querySerializer(query) : "";
|
|
14378
14517
|
if (search.startsWith("?")) {
|
|
@@ -15493,15 +15632,15 @@ class LocalExecutor {
|
|
|
15493
15632
|
const out = await handler(args);
|
|
15494
15633
|
return { status: "success", output: out };
|
|
15495
15634
|
} catch (err) {
|
|
15496
|
-
|
|
15635
|
+
log9.warn("Local tool execution failed", { toolId, error: String(err?.message || err) });
|
|
15497
15636
|
return { status: "error", error: String(err?.message || err) };
|
|
15498
15637
|
}
|
|
15499
15638
|
}
|
|
15500
15639
|
}
|
|
15501
|
-
var
|
|
15640
|
+
var log9;
|
|
15502
15641
|
var init_local = __esm(() => {
|
|
15503
15642
|
init_logger();
|
|
15504
|
-
|
|
15643
|
+
log9 = createLogger("tools:executor:local");
|
|
15505
15644
|
});
|
|
15506
15645
|
|
|
15507
15646
|
// src/tools/executors/sdk.ts
|
|
@@ -15528,7 +15667,7 @@ class SdkExecutor {
|
|
|
15528
15667
|
const out = typeof res === "string" ? res : JSON.stringify(res);
|
|
15529
15668
|
return { status: "success", output: out };
|
|
15530
15669
|
} catch (err) {
|
|
15531
|
-
|
|
15670
|
+
log10.warn("SDK tool execution failed", { toolId, error: String(err?.message || err) });
|
|
15532
15671
|
return { status: "error", error: String(err?.message || err) };
|
|
15533
15672
|
}
|
|
15534
15673
|
}
|
|
@@ -15541,10 +15680,10 @@ class SdkExecutor {
|
|
|
15541
15680
|
]);
|
|
15542
15681
|
}
|
|
15543
15682
|
}
|
|
15544
|
-
var
|
|
15683
|
+
var log10;
|
|
15545
15684
|
var init_sdk = __esm(() => {
|
|
15546
15685
|
init_logger();
|
|
15547
|
-
|
|
15686
|
+
log10 = createLogger("tools:executor:sdk");
|
|
15548
15687
|
});
|
|
15549
15688
|
|
|
15550
15689
|
// src/tools/executors/mcp.ts
|
|
@@ -15571,7 +15710,7 @@ class McpExecutor {
|
|
|
15571
15710
|
const out = typeof res === "string" ? res : JSON.stringify(res);
|
|
15572
15711
|
return { status: "success", output: out };
|
|
15573
15712
|
} catch (err) {
|
|
15574
|
-
|
|
15713
|
+
log11.warn("MCP tool execution failed", { toolId, error: String(err?.message || err) });
|
|
15575
15714
|
return { status: "error", error: String(err?.message || err) };
|
|
15576
15715
|
}
|
|
15577
15716
|
}
|
|
@@ -15584,10 +15723,10 @@ class McpExecutor {
|
|
|
15584
15723
|
]);
|
|
15585
15724
|
}
|
|
15586
15725
|
}
|
|
15587
|
-
var
|
|
15726
|
+
var log11;
|
|
15588
15727
|
var init_mcp = __esm(() => {
|
|
15589
15728
|
init_logger();
|
|
15590
|
-
|
|
15729
|
+
log11 = createLogger("tools:executor:mcp");
|
|
15591
15730
|
});
|
|
15592
15731
|
|
|
15593
15732
|
// src/tools/core/executor.ts
|
|
@@ -15597,17 +15736,17 @@ async function executeWithChain(executors, toolId, args) {
|
|
|
15597
15736
|
try {
|
|
15598
15737
|
return await ex.execute(toolId, args);
|
|
15599
15738
|
} catch (err) {
|
|
15600
|
-
|
|
15739
|
+
log12.warn("Executor threw unexpected error", { toolId, error: String(err?.message || err) });
|
|
15601
15740
|
return { status: "error", error: String(err?.message || err) };
|
|
15602
15741
|
}
|
|
15603
15742
|
}
|
|
15604
15743
|
}
|
|
15605
15744
|
return { status: "error", error: `No executor available for ${toolId}` };
|
|
15606
15745
|
}
|
|
15607
|
-
var
|
|
15746
|
+
var log12;
|
|
15608
15747
|
var init_executor = __esm(() => {
|
|
15609
15748
|
init_logger();
|
|
15610
|
-
|
|
15749
|
+
log12 = createLogger("tools:executor:chain");
|
|
15611
15750
|
});
|
|
15612
15751
|
|
|
15613
15752
|
// src/tools/defaults.ts
|
|
@@ -15615,7 +15754,7 @@ function registerDefaultTools(registry2) {
|
|
|
15615
15754
|
registry2.register({
|
|
15616
15755
|
id: "bash",
|
|
15617
15756
|
name: "bash",
|
|
15618
|
-
description: "Execute a shell command
|
|
15757
|
+
description: "Execute a shell command. Use this to run programs/tests; prefer write/edit for creating or modifying files.",
|
|
15619
15758
|
parameters: {
|
|
15620
15759
|
type: "object",
|
|
15621
15760
|
properties: {
|
|
@@ -15679,12 +15818,12 @@ function registerDefaultTools(registry2) {
|
|
|
15679
15818
|
},
|
|
15680
15819
|
source: "local"
|
|
15681
15820
|
}, async (args) => {
|
|
15682
|
-
const
|
|
15821
|
+
const fs2 = await import("fs");
|
|
15683
15822
|
try {
|
|
15684
|
-
const
|
|
15823
|
+
const path2 = args.path;
|
|
15685
15824
|
const offset = args.offset;
|
|
15686
15825
|
const limit = args.limit;
|
|
15687
|
-
let content =
|
|
15826
|
+
let content = fs2.readFileSync(path2, "utf-8");
|
|
15688
15827
|
if (offset !== undefined || limit !== undefined) {
|
|
15689
15828
|
const lines = content.split(`
|
|
15690
15829
|
`);
|
|
@@ -15701,7 +15840,7 @@ function registerDefaultTools(registry2) {
|
|
|
15701
15840
|
registry2.register({
|
|
15702
15841
|
id: "write",
|
|
15703
15842
|
name: "write",
|
|
15704
|
-
description: "Write content to a file (creates or overwrites)",
|
|
15843
|
+
description: "Write content to a file (creates or overwrites). Prefer this over using bash redirection/heredocs for file creation.",
|
|
15705
15844
|
parameters: {
|
|
15706
15845
|
type: "object",
|
|
15707
15846
|
properties: {
|
|
@@ -15718,16 +15857,16 @@ function registerDefaultTools(registry2) {
|
|
|
15718
15857
|
},
|
|
15719
15858
|
source: "local"
|
|
15720
15859
|
}, async (args) => {
|
|
15721
|
-
const
|
|
15722
|
-
const
|
|
15860
|
+
const fs2 = await import("fs");
|
|
15861
|
+
const path2 = await import("path");
|
|
15723
15862
|
try {
|
|
15724
15863
|
const filePath = args.path;
|
|
15725
15864
|
const content = args.content;
|
|
15726
|
-
const dir =
|
|
15727
|
-
if (!
|
|
15728
|
-
|
|
15865
|
+
const dir = path2.dirname(filePath);
|
|
15866
|
+
if (!fs2.existsSync(dir)) {
|
|
15867
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
15729
15868
|
}
|
|
15730
|
-
|
|
15869
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
15731
15870
|
return `File written successfully: ${filePath}`;
|
|
15732
15871
|
} catch (error45) {
|
|
15733
15872
|
throw error45;
|
|
@@ -15736,7 +15875,7 @@ function registerDefaultTools(registry2) {
|
|
|
15736
15875
|
registry2.register({
|
|
15737
15876
|
id: "edit",
|
|
15738
15877
|
name: "edit",
|
|
15739
|
-
description: "Edit a file by replacing old text with new text",
|
|
15878
|
+
description: "Edit a file by replacing old text with new text. Use for targeted replacements; use write to overwrite an entire file.",
|
|
15740
15879
|
parameters: {
|
|
15741
15880
|
type: "object",
|
|
15742
15881
|
properties: {
|
|
@@ -15757,8 +15896,8 @@ function registerDefaultTools(registry2) {
|
|
|
15757
15896
|
},
|
|
15758
15897
|
source: "local"
|
|
15759
15898
|
}, async (args) => {
|
|
15760
|
-
const
|
|
15761
|
-
const
|
|
15899
|
+
const fs2 = await import("fs");
|
|
15900
|
+
const path2 = await import("path");
|
|
15762
15901
|
try {
|
|
15763
15902
|
const resolvedArgs = resolveEditArguments(args);
|
|
15764
15903
|
const filePath = resolvedArgs.path;
|
|
@@ -15775,27 +15914,27 @@ function registerDefaultTools(registry2) {
|
|
|
15775
15914
|
}
|
|
15776
15915
|
let content = "";
|
|
15777
15916
|
try {
|
|
15778
|
-
content =
|
|
15917
|
+
content = fs2.readFileSync(filePath, "utf-8");
|
|
15779
15918
|
} catch (error45) {
|
|
15780
15919
|
if (error45?.code === "ENOENT") {
|
|
15781
|
-
const dir =
|
|
15782
|
-
if (!
|
|
15783
|
-
|
|
15920
|
+
const dir = path2.dirname(filePath);
|
|
15921
|
+
if (!fs2.existsSync(dir)) {
|
|
15922
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
15784
15923
|
}
|
|
15785
|
-
|
|
15924
|
+
fs2.writeFileSync(filePath, newString, "utf-8");
|
|
15786
15925
|
return `File did not exist. Created and wrote content: ${filePath}`;
|
|
15787
15926
|
}
|
|
15788
15927
|
throw error45;
|
|
15789
15928
|
}
|
|
15790
15929
|
if (!oldString) {
|
|
15791
|
-
|
|
15930
|
+
fs2.writeFileSync(filePath, newString, "utf-8");
|
|
15792
15931
|
return `File edited successfully: ${filePath}`;
|
|
15793
15932
|
}
|
|
15794
15933
|
if (!content.includes(oldString)) {
|
|
15795
15934
|
return `Error: Could not find the text to replace in ${filePath}`;
|
|
15796
15935
|
}
|
|
15797
15936
|
content = content.replaceAll(oldString, newString);
|
|
15798
|
-
|
|
15937
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
15799
15938
|
return `File edited successfully: ${filePath}`;
|
|
15800
15939
|
} catch (error45) {
|
|
15801
15940
|
throw error45;
|
|
@@ -15829,13 +15968,13 @@ function registerDefaultTools(registry2) {
|
|
|
15829
15968
|
const { promisify } = await import("util");
|
|
15830
15969
|
const execFileAsync = promisify(execFile);
|
|
15831
15970
|
const pattern = args.pattern;
|
|
15832
|
-
const
|
|
15971
|
+
const path2 = args.path;
|
|
15833
15972
|
const include = args.include;
|
|
15834
15973
|
const grepArgs = ["-r", "-n"];
|
|
15835
15974
|
if (include) {
|
|
15836
15975
|
grepArgs.push(`--include=${include}`);
|
|
15837
15976
|
}
|
|
15838
|
-
grepArgs.push(pattern,
|
|
15977
|
+
grepArgs.push(pattern, path2);
|
|
15839
15978
|
const runGrep = async (extraArgs = []) => {
|
|
15840
15979
|
return execFileAsync("grep", [...extraArgs, ...grepArgs], { timeout: 30000 });
|
|
15841
15980
|
};
|
|
@@ -15878,11 +16017,11 @@ function registerDefaultTools(registry2) {
|
|
|
15878
16017
|
},
|
|
15879
16018
|
source: "local"
|
|
15880
16019
|
}, async (args) => {
|
|
15881
|
-
const
|
|
15882
|
-
const
|
|
16020
|
+
const fs2 = await import("fs");
|
|
16021
|
+
const path2 = await import("path");
|
|
15883
16022
|
try {
|
|
15884
16023
|
const dirPath = args.path;
|
|
15885
|
-
const entries =
|
|
16024
|
+
const entries = fs2.readdirSync(dirPath, { withFileTypes: true });
|
|
15886
16025
|
const result = entries.map((entry) => {
|
|
15887
16026
|
const type = entry.isDirectory() ? "d" : entry.isSymbolicLink() ? "l" : entry.isFile() ? "f" : "?";
|
|
15888
16027
|
return `[${type}] ${entry.name}`;
|
|
@@ -15920,8 +16059,8 @@ function registerDefaultTools(registry2) {
|
|
|
15920
16059
|
if (!pattern) {
|
|
15921
16060
|
throw new Error("glob: missing required argument 'pattern'");
|
|
15922
16061
|
}
|
|
15923
|
-
const
|
|
15924
|
-
const cwd =
|
|
16062
|
+
const path2 = resolvePathArg(args, "glob");
|
|
16063
|
+
const cwd = path2 || ".";
|
|
15925
16064
|
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
15926
16065
|
const isPathPattern = normalizedPattern.includes("/");
|
|
15927
16066
|
const findArgs = [cwd, "-type", "f"];
|
|
@@ -16049,7 +16188,7 @@ function registerDefaultTools(registry2) {
|
|
|
16049
16188
|
});
|
|
16050
16189
|
}
|
|
16051
16190
|
function resolveEditArguments(args) {
|
|
16052
|
-
const
|
|
16191
|
+
const path2 = typeof args.path === "string" ? args.path : "";
|
|
16053
16192
|
let oldString = typeof args.old_string === "string" ? args.old_string : undefined;
|
|
16054
16193
|
let newString = typeof args.new_string === "string" ? args.new_string : undefined;
|
|
16055
16194
|
if (newString === undefined) {
|
|
@@ -16062,7 +16201,7 @@ function resolveEditArguments(args) {
|
|
|
16062
16201
|
oldString = "";
|
|
16063
16202
|
}
|
|
16064
16203
|
return {
|
|
16065
|
-
path,
|
|
16204
|
+
path: path2,
|
|
16066
16205
|
old_string: oldString,
|
|
16067
16206
|
new_string: newString
|
|
16068
16207
|
};
|
|
@@ -16405,6 +16544,23 @@ function normalizeToolSpecificArgs(toolName, args) {
|
|
|
16405
16544
|
todos
|
|
16406
16545
|
};
|
|
16407
16546
|
}
|
|
16547
|
+
if (normalizedToolName === "write") {
|
|
16548
|
+
const normalized = { ...args };
|
|
16549
|
+
if (normalized.content === undefined && normalized.new_string !== undefined) {
|
|
16550
|
+
const coerced = coerceToString2(normalized.new_string);
|
|
16551
|
+
if (coerced !== null) {
|
|
16552
|
+
normalized.content = coerced;
|
|
16553
|
+
}
|
|
16554
|
+
delete normalized.new_string;
|
|
16555
|
+
}
|
|
16556
|
+
if (normalized.content !== undefined && typeof normalized.content !== "string") {
|
|
16557
|
+
const coerced = coerceToString2(normalized.content);
|
|
16558
|
+
if (coerced !== null) {
|
|
16559
|
+
normalized.content = coerced;
|
|
16560
|
+
}
|
|
16561
|
+
}
|
|
16562
|
+
return normalized;
|
|
16563
|
+
}
|
|
16408
16564
|
if (normalizedToolName !== "edit" || !EDIT_COMPAT_REPAIR_ENABLED) {
|
|
16409
16565
|
return args;
|
|
16410
16566
|
}
|
|
@@ -16448,6 +16604,15 @@ function normalizeBashCommand(value) {
|
|
|
16448
16604
|
}
|
|
16449
16605
|
function normalizeTodoStatus(status) {
|
|
16450
16606
|
const normalized = status.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
16607
|
+
if (normalized === "todo_status_pending") {
|
|
16608
|
+
return "pending";
|
|
16609
|
+
}
|
|
16610
|
+
if (normalized === "todo_status_inprogress" || normalized === "todo_status_in_progress") {
|
|
16611
|
+
return "in_progress";
|
|
16612
|
+
}
|
|
16613
|
+
if (normalized === "todo_status_done" || normalized === "todo_status_complete" || normalized === "todo_status_completed") {
|
|
16614
|
+
return "completed";
|
|
16615
|
+
}
|
|
16451
16616
|
if (normalized === "todo" || normalized === "pending") {
|
|
16452
16617
|
return "pending";
|
|
16453
16618
|
}
|
|
@@ -16638,6 +16803,9 @@ var init_tool_schema_compat = __esm(() => {
|
|
|
16638
16803
|
["terminalcommand", "command"],
|
|
16639
16804
|
["contents", "content"],
|
|
16640
16805
|
["text", "content"],
|
|
16806
|
+
["body", "content"],
|
|
16807
|
+
["data", "content"],
|
|
16808
|
+
["payload", "content"],
|
|
16641
16809
|
["streamcontent", "content"],
|
|
16642
16810
|
["recursive", "force"],
|
|
16643
16811
|
["oldstring", "old_string"],
|
|
@@ -16651,7 +16819,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
16651
16819
|
event,
|
|
16652
16820
|
toolLoopMode,
|
|
16653
16821
|
allowedToolNames,
|
|
16654
|
-
toolSchemaMap
|
|
16822
|
+
toolSchemaMap,
|
|
16655
16823
|
toolLoopGuard,
|
|
16656
16824
|
toolMapper,
|
|
16657
16825
|
toolSessionId,
|
|
@@ -16666,11 +16834,44 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
16666
16834
|
} = options;
|
|
16667
16835
|
const interceptedToolCall = toolLoopMode === "opencode" ? extractOpenAiToolCall(event, allowedToolNames) : null;
|
|
16668
16836
|
if (interceptedToolCall) {
|
|
16669
|
-
const
|
|
16837
|
+
const compat2 = applyToolSchemaCompat(interceptedToolCall, toolSchemaMap);
|
|
16838
|
+
let normalizedToolCall = compat2.toolCall;
|
|
16839
|
+
log13.debug("Applied tool schema compatibility (legacy)", {
|
|
16840
|
+
tool: normalizedToolCall.function.name,
|
|
16841
|
+
originalArgKeys: compat2.originalArgKeys,
|
|
16842
|
+
normalizedArgKeys: compat2.normalizedArgKeys,
|
|
16843
|
+
collisionKeys: compat2.collisionKeys,
|
|
16844
|
+
validationOk: compat2.validation.ok
|
|
16845
|
+
});
|
|
16846
|
+
if (compat2.validation.hasSchema && !compat2.validation.ok) {
|
|
16847
|
+
const validationTermination = evaluateSchemaValidationLoopGuard(toolLoopGuard, normalizedToolCall, compat2.validation);
|
|
16848
|
+
if (validationTermination) {
|
|
16849
|
+
return { intercepted: false, skipConverter: true, terminate: validationTermination };
|
|
16850
|
+
}
|
|
16851
|
+
const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat2.normalizedArgs, allowedToolNames, toolSchemaMap);
|
|
16852
|
+
if (reroutedWrite) {
|
|
16853
|
+
log13.debug("Rerouting malformed edit call to write (legacy)", {
|
|
16854
|
+
path: reroutedWrite.path,
|
|
16855
|
+
missing: compat2.validation.missing,
|
|
16856
|
+
typeErrors: compat2.validation.typeErrors
|
|
16857
|
+
});
|
|
16858
|
+
normalizedToolCall = reroutedWrite.toolCall;
|
|
16859
|
+
} else if (shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat2.validation)) {
|
|
16860
|
+
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat2.validation);
|
|
16861
|
+
log13.debug("Emitting non-fatal schema validation hint in legacy and skipping malformed tool execution", {
|
|
16862
|
+
tool: normalizedToolCall.function.name,
|
|
16863
|
+
missing: compat2.validation.missing,
|
|
16864
|
+
typeErrors: compat2.validation.typeErrors
|
|
16865
|
+
});
|
|
16866
|
+
await onToolResult(hintChunk);
|
|
16867
|
+
return { intercepted: false, skipConverter: true };
|
|
16868
|
+
}
|
|
16869
|
+
}
|
|
16870
|
+
const termination = evaluateToolLoopGuard(toolLoopGuard, normalizedToolCall);
|
|
16670
16871
|
if (termination) {
|
|
16671
16872
|
return { intercepted: false, skipConverter: true, terminate: termination };
|
|
16672
16873
|
}
|
|
16673
|
-
await onInterceptedToolCall(
|
|
16874
|
+
await onInterceptedToolCall(normalizedToolCall);
|
|
16674
16875
|
return { intercepted: true, skipConverter: true };
|
|
16675
16876
|
}
|
|
16676
16877
|
const updates = await toolMapper.mapCursorEventToAcp(event, event.session_id ?? toolSessionId);
|
|
@@ -16723,7 +16924,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
16723
16924
|
rawArgs: safeArgTypeSummary(event),
|
|
16724
16925
|
normalizedArgs: compat2.normalizedArgs
|
|
16725
16926
|
} : undefined;
|
|
16726
|
-
|
|
16927
|
+
log13.debug("Applied tool schema compatibility", {
|
|
16727
16928
|
tool: interceptedToolCall.function.name,
|
|
16728
16929
|
originalArgKeys: compat2.originalArgKeys,
|
|
16729
16930
|
normalizedArgKeys: compat2.normalizedArgKeys,
|
|
@@ -16732,7 +16933,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
16732
16933
|
...editDiag ? { editDiag } : {}
|
|
16733
16934
|
});
|
|
16734
16935
|
if (compat2.validation.hasSchema && !compat2.validation.ok) {
|
|
16735
|
-
|
|
16936
|
+
log13.debug("Tool schema compatibility validation failed", {
|
|
16736
16937
|
tool: interceptedToolCall.function.name,
|
|
16737
16938
|
missing: compat2.validation.missing,
|
|
16738
16939
|
unexpected: compat2.validation.unexpected,
|
|
@@ -16749,7 +16950,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
16749
16950
|
}
|
|
16750
16951
|
const reroutedWrite = tryRerouteEditToWrite(interceptedToolCall, compat2.normalizedArgs, allowedToolNames, toolSchemaMap);
|
|
16751
16952
|
if (reroutedWrite) {
|
|
16752
|
-
|
|
16953
|
+
log13.debug("Rerouting malformed edit call to write", {
|
|
16753
16954
|
path: reroutedWrite.path,
|
|
16754
16955
|
missing: compat2.validation.missing,
|
|
16755
16956
|
typeErrors: compat2.validation.typeErrors
|
|
@@ -16769,7 +16970,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
16769
16970
|
}
|
|
16770
16971
|
if (schemaValidationFailureMode === "pass_through" && shouldEmitNonFatalSchemaValidationHint(interceptedToolCall, compat2.validation)) {
|
|
16771
16972
|
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, interceptedToolCall, compat2.validation);
|
|
16772
|
-
|
|
16973
|
+
log13.debug("Emitting non-fatal schema validation hint and skipping malformed tool execution", {
|
|
16773
16974
|
tool: interceptedToolCall.function.name,
|
|
16774
16975
|
missing: compat2.validation.missing,
|
|
16775
16976
|
typeErrors: compat2.validation.typeErrors
|
|
@@ -16787,7 +16988,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
16787
16988
|
terminate: createSchemaValidationTermination(interceptedToolCall, compat2.validation)
|
|
16788
16989
|
};
|
|
16789
16990
|
}
|
|
16790
|
-
|
|
16991
|
+
log13.debug("Forwarding schema-invalid tool call to OpenCode loop", {
|
|
16791
16992
|
tool: interceptedToolCall.function.name,
|
|
16792
16993
|
repairHint: compat2.validation.repairHint
|
|
16793
16994
|
});
|
|
@@ -16866,16 +17067,28 @@ function evaluateToolLoopGuard(toolLoopGuard, toolCall) {
|
|
|
16866
17067
|
if (!decision.triggered) {
|
|
16867
17068
|
return null;
|
|
16868
17069
|
}
|
|
16869
|
-
|
|
17070
|
+
log13.debug("Tool loop guard triggered", {
|
|
16870
17071
|
tool: toolCall.function.name,
|
|
16871
17072
|
fingerprint: decision.fingerprint,
|
|
16872
17073
|
repeatCount: decision.repeatCount,
|
|
16873
17074
|
maxRepeat: decision.maxRepeat,
|
|
16874
17075
|
errorClass: decision.errorClass
|
|
16875
17076
|
});
|
|
17077
|
+
if (decision.errorClass === "success") {
|
|
17078
|
+
return {
|
|
17079
|
+
reason: "loop_guard",
|
|
17080
|
+
message: "",
|
|
17081
|
+
tool: toolCall.function.name,
|
|
17082
|
+
fingerprint: decision.fingerprint,
|
|
17083
|
+
repeatCount: decision.repeatCount,
|
|
17084
|
+
maxRepeat: decision.maxRepeat,
|
|
17085
|
+
errorClass: decision.errorClass,
|
|
17086
|
+
silent: true
|
|
17087
|
+
};
|
|
17088
|
+
}
|
|
16876
17089
|
return {
|
|
16877
17090
|
reason: "loop_guard",
|
|
16878
|
-
message:
|
|
17091
|
+
message: `Tool loop guard stopped repeated failing calls to "${toolCall.function.name}" ` + `after ${decision.repeatCount} attempts (limit ${decision.maxRepeat}). ` + "Adjust tool arguments and retry.",
|
|
16879
17092
|
tool: toolCall.function.name,
|
|
16880
17093
|
fingerprint: decision.fingerprint,
|
|
16881
17094
|
repeatCount: decision.repeatCount,
|
|
@@ -16913,7 +17126,7 @@ function evaluateSchemaValidationLoopGuard(toolLoopGuard, toolCall, validation)
|
|
|
16913
17126
|
if (!decision.tracked || !decision.triggered) {
|
|
16914
17127
|
return null;
|
|
16915
17128
|
}
|
|
16916
|
-
|
|
17129
|
+
log13.warn("Tool loop guard triggered on schema validation", {
|
|
16917
17130
|
tool: toolCall.function.name,
|
|
16918
17131
|
fingerprint: decision.fingerprint,
|
|
16919
17132
|
repeatCount: decision.repeatCount,
|
|
@@ -17046,8 +17259,8 @@ function tryRerouteEditToWrite(toolCall, normalizedArgs, allowedToolNames, toolS
|
|
|
17046
17259
|
if (!allowedToolNames.has("write") || !toolSchemaMap.has("write")) {
|
|
17047
17260
|
return null;
|
|
17048
17261
|
}
|
|
17049
|
-
const
|
|
17050
|
-
if (!
|
|
17262
|
+
const path2 = typeof normalizedArgs.path === "string" && normalizedArgs.path.length > 0 ? normalizedArgs.path : null;
|
|
17263
|
+
if (!path2) {
|
|
17051
17264
|
return null;
|
|
17052
17265
|
}
|
|
17053
17266
|
const content = typeof normalizedArgs.new_string === "string" ? normalizedArgs.new_string : typeof normalizedArgs.content === "string" ? normalizedArgs.content : null;
|
|
@@ -17059,12 +17272,12 @@ function tryRerouteEditToWrite(toolCall, normalizedArgs, allowedToolNames, toolS
|
|
|
17059
17272
|
return null;
|
|
17060
17273
|
}
|
|
17061
17274
|
return {
|
|
17062
|
-
path,
|
|
17275
|
+
path: path2,
|
|
17063
17276
|
toolCall: {
|
|
17064
17277
|
...toolCall,
|
|
17065
17278
|
function: {
|
|
17066
17279
|
name: "write",
|
|
17067
|
-
arguments: JSON.stringify({ path, content })
|
|
17280
|
+
arguments: JSON.stringify({ path: path2, content })
|
|
17068
17281
|
}
|
|
17069
17282
|
}
|
|
17070
17283
|
};
|
|
@@ -17072,12 +17285,12 @@ function tryRerouteEditToWrite(toolCall, normalizedArgs, allowedToolNames, toolS
|
|
|
17072
17285
|
function isRecord3(value) {
|
|
17073
17286
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17074
17287
|
}
|
|
17075
|
-
var
|
|
17288
|
+
var log13, ToolBoundaryExtractionError;
|
|
17076
17289
|
var init_runtime_interception = __esm(() => {
|
|
17077
17290
|
init_tool_loop();
|
|
17078
17291
|
init_logger();
|
|
17079
17292
|
init_tool_schema_compat();
|
|
17080
|
-
|
|
17293
|
+
log13 = createLogger("provider:runtime-interception");
|
|
17081
17294
|
ToolBoundaryExtractionError = class ToolBoundaryExtractionError extends Error {
|
|
17082
17295
|
cause;
|
|
17083
17296
|
constructor(message, cause) {
|
|
@@ -17091,11 +17304,11 @@ var init_runtime_interception = __esm(() => {
|
|
|
17091
17304
|
// src/provider/tool-loop-guard.ts
|
|
17092
17305
|
function parseToolLoopMaxRepeat(value) {
|
|
17093
17306
|
if (value === undefined) {
|
|
17094
|
-
return { value:
|
|
17307
|
+
return { value: 2, valid: true };
|
|
17095
17308
|
}
|
|
17096
17309
|
const parsed = Number(value);
|
|
17097
17310
|
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
17098
|
-
return { value:
|
|
17311
|
+
return { value: 2, valid: false };
|
|
17099
17312
|
}
|
|
17100
17313
|
return { value: Math.floor(parsed), valid: true };
|
|
17101
17314
|
}
|
|
@@ -17122,12 +17335,18 @@ function createToolLoopGuard(messages, maxRepeat) {
|
|
|
17122
17335
|
const successFingerprint = `${toolCall.function.name}|values:${valueSignature}|success`;
|
|
17123
17336
|
const repeatCount = (counts.get(successFingerprint) ?? 0) + 1;
|
|
17124
17337
|
counts.set(successFingerprint, repeatCount);
|
|
17338
|
+
const coarseSuccessFingerprint = deriveSuccessCoarseFingerprint(toolCall.function.name, toolCall.function.arguments);
|
|
17339
|
+
const coarseRepeatCount = coarseSuccessFingerprint ? (coarseCounts.get(coarseSuccessFingerprint) ?? 0) + 1 : 0;
|
|
17340
|
+
if (coarseSuccessFingerprint) {
|
|
17341
|
+
coarseCounts.set(coarseSuccessFingerprint, coarseRepeatCount);
|
|
17342
|
+
}
|
|
17343
|
+
const coarseTriggered = coarseSuccessFingerprint ? coarseRepeatCount > maxRepeat : false;
|
|
17125
17344
|
return {
|
|
17126
|
-
fingerprint: successFingerprint,
|
|
17127
|
-
repeatCount,
|
|
17345
|
+
fingerprint: coarseTriggered ? coarseSuccessFingerprint : successFingerprint,
|
|
17346
|
+
repeatCount: coarseTriggered ? coarseRepeatCount : repeatCount,
|
|
17128
17347
|
maxRepeat,
|
|
17129
17348
|
errorClass,
|
|
17130
|
-
triggered: repeatCount > maxRepeat,
|
|
17349
|
+
triggered: repeatCount > maxRepeat || coarseTriggered,
|
|
17131
17350
|
tracked: true
|
|
17132
17351
|
};
|
|
17133
17352
|
}
|
|
@@ -17200,22 +17419,49 @@ function indexToolLoopHistory(messages) {
|
|
|
17200
17419
|
}
|
|
17201
17420
|
}
|
|
17202
17421
|
for (const call of assistantCalls) {
|
|
17422
|
+
const schemaSignature = deriveSchemaValidationSignature(call.name, call.argKeys);
|
|
17203
17423
|
const errorClass = normalizeErrorClassForTool(call.name, byCallId.get(call.id) ?? latestByToolName.get(call.name) ?? latest ?? "unknown");
|
|
17204
17424
|
if (errorClass === "success") {
|
|
17205
17425
|
incrementCount(initialCounts, `${call.name}|values:${call.argValueSignature}|success`);
|
|
17426
|
+
const coarseSuccessFP = deriveSuccessCoarseFingerprint(call.name, call.rawArguments);
|
|
17427
|
+
if (coarseSuccessFP) {
|
|
17428
|
+
incrementCount(initialCoarseCounts, coarseSuccessFP);
|
|
17429
|
+
}
|
|
17430
|
+
if (schemaSignature) {
|
|
17431
|
+
incrementCount(initialValidationCounts, `${call.name}|schema:${schemaSignature}|validation`);
|
|
17432
|
+
incrementCount(initialValidationCoarseCounts, `${call.name}|validation`);
|
|
17433
|
+
}
|
|
17206
17434
|
continue;
|
|
17207
17435
|
}
|
|
17208
17436
|
const strictFingerprint = `${call.name}|${call.argShape}|${errorClass}`;
|
|
17209
17437
|
const coarseFingerprint = `${call.name}|${errorClass}`;
|
|
17210
17438
|
incrementCount(initialCounts, strictFingerprint);
|
|
17211
17439
|
incrementCount(initialCoarseCounts, coarseFingerprint);
|
|
17212
|
-
const schemaSignature = deriveSchemaValidationSignature(call.name, call.argKeys);
|
|
17213
17440
|
if (!schemaSignature) {
|
|
17214
17441
|
continue;
|
|
17215
17442
|
}
|
|
17216
17443
|
incrementCount(initialValidationCounts, `${call.name}|schema:${schemaSignature}|validation`);
|
|
17217
17444
|
incrementCount(initialValidationCoarseCounts, `${call.name}|validation`);
|
|
17218
17445
|
}
|
|
17446
|
+
const strippedRounds = countStrippedAssistantRounds(messages);
|
|
17447
|
+
if (strippedRounds > 0 && assistantCalls.length > 0) {
|
|
17448
|
+
for (const call of assistantCalls) {
|
|
17449
|
+
const errorClass = normalizeErrorClassForTool(call.name, byCallId.get(call.id) ?? latestByToolName.get(call.name) ?? latest ?? "unknown");
|
|
17450
|
+
if (errorClass !== "success") {
|
|
17451
|
+
continue;
|
|
17452
|
+
}
|
|
17453
|
+
const coarseSuccessFP = deriveSuccessCoarseFingerprint(call.name, call.rawArguments);
|
|
17454
|
+
if (coarseSuccessFP) {
|
|
17455
|
+
for (let i = 0;i < strippedRounds; i++) {
|
|
17456
|
+
incrementCount(initialCoarseCounts, coarseSuccessFP);
|
|
17457
|
+
}
|
|
17458
|
+
}
|
|
17459
|
+
const successFP = `${call.name}|values:${call.argValueSignature}|success`;
|
|
17460
|
+
for (let i = 0;i < strippedRounds; i++) {
|
|
17461
|
+
incrementCount(initialCounts, successFP);
|
|
17462
|
+
}
|
|
17463
|
+
}
|
|
17464
|
+
}
|
|
17219
17465
|
return {
|
|
17220
17466
|
byCallId,
|
|
17221
17467
|
latest,
|
|
@@ -17281,6 +17527,31 @@ function deriveArgumentValueSignature(rawArguments) {
|
|
|
17281
17527
|
return `invalid:${hashString(rawArguments)}`;
|
|
17282
17528
|
}
|
|
17283
17529
|
}
|
|
17530
|
+
function deriveSuccessCoarseFingerprint(toolName, rawArguments) {
|
|
17531
|
+
const lowered = toolName.toLowerCase();
|
|
17532
|
+
if (lowered !== "edit" && lowered !== "write") {
|
|
17533
|
+
return null;
|
|
17534
|
+
}
|
|
17535
|
+
try {
|
|
17536
|
+
const parsed = JSON.parse(rawArguments);
|
|
17537
|
+
if (!isRecord4(parsed)) {
|
|
17538
|
+
return null;
|
|
17539
|
+
}
|
|
17540
|
+
const path2 = typeof parsed.path === "string" ? parsed.path : "";
|
|
17541
|
+
if (!path2) {
|
|
17542
|
+
return null;
|
|
17543
|
+
}
|
|
17544
|
+
if (lowered === "edit") {
|
|
17545
|
+
const oldString = typeof parsed.old_string === "string" ? parsed.old_string : null;
|
|
17546
|
+
if (oldString !== "") {
|
|
17547
|
+
return null;
|
|
17548
|
+
}
|
|
17549
|
+
}
|
|
17550
|
+
return `${toolName}|path:${hashString(path2)}|success`;
|
|
17551
|
+
} catch {
|
|
17552
|
+
return null;
|
|
17553
|
+
}
|
|
17554
|
+
}
|
|
17284
17555
|
function extractAssistantToolCalls(messages) {
|
|
17285
17556
|
const calls = [];
|
|
17286
17557
|
for (const message of messages) {
|
|
@@ -17301,6 +17572,7 @@ function extractAssistantToolCalls(messages) {
|
|
|
17301
17572
|
calls.push({
|
|
17302
17573
|
id,
|
|
17303
17574
|
name,
|
|
17575
|
+
rawArguments,
|
|
17304
17576
|
argShape: deriveArgumentShape(rawArguments),
|
|
17305
17577
|
argValueSignature: deriveArgumentValueSignature(rawArguments),
|
|
17306
17578
|
argKeys: extractArgumentKeys(rawArguments)
|
|
@@ -17412,6 +17684,23 @@ function normalizeErrorClassForTool(toolName, errorClass) {
|
|
|
17412
17684
|
}
|
|
17413
17685
|
return errorClass;
|
|
17414
17686
|
}
|
|
17687
|
+
function countStrippedAssistantRounds(messages) {
|
|
17688
|
+
let count = 0;
|
|
17689
|
+
for (const message of messages) {
|
|
17690
|
+
if (!isRecord4(message) || message.role !== "assistant") {
|
|
17691
|
+
continue;
|
|
17692
|
+
}
|
|
17693
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
17694
|
+
continue;
|
|
17695
|
+
}
|
|
17696
|
+
const content = message.content;
|
|
17697
|
+
const hasContent = typeof content === "string" && content.trim().length > 0 || Array.isArray(content) && content.length > 0;
|
|
17698
|
+
if (!hasContent) {
|
|
17699
|
+
count++;
|
|
17700
|
+
}
|
|
17701
|
+
}
|
|
17702
|
+
return count;
|
|
17703
|
+
}
|
|
17415
17704
|
function toLowerText(content) {
|
|
17416
17705
|
const rendered = renderContent(content);
|
|
17417
17706
|
return rendered.trim().toLowerCase();
|
|
@@ -17446,38 +17735,102 @@ var UNKNOWN_AS_SUCCESS_TOOLS;
|
|
|
17446
17735
|
var init_tool_loop_guard = __esm(() => {
|
|
17447
17736
|
UNKNOWN_AS_SUCCESS_TOOLS = new Set([
|
|
17448
17737
|
"bash",
|
|
17738
|
+
"shell",
|
|
17449
17739
|
"read",
|
|
17740
|
+
"write",
|
|
17741
|
+
"edit",
|
|
17450
17742
|
"grep",
|
|
17451
17743
|
"ls",
|
|
17452
17744
|
"glob",
|
|
17453
17745
|
"stat",
|
|
17454
|
-
"webfetch"
|
|
17746
|
+
"webfetch",
|
|
17747
|
+
"mkdir",
|
|
17748
|
+
"rm"
|
|
17455
17749
|
]);
|
|
17456
17750
|
});
|
|
17457
17751
|
|
|
17458
17752
|
// src/plugin.ts
|
|
17459
17753
|
var exports_plugin = {};
|
|
17460
17754
|
__export(exports_plugin, {
|
|
17755
|
+
shouldProcessModel: () => shouldProcessModel,
|
|
17461
17756
|
resolveChatParamTools: () => resolveChatParamTools,
|
|
17462
17757
|
ensurePluginDirectory: () => ensurePluginDirectory,
|
|
17463
17758
|
default: () => plugin_default,
|
|
17464
17759
|
CursorPlugin: () => CursorPlugin
|
|
17465
17760
|
});
|
|
17761
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync4, realpathSync } from "fs";
|
|
17466
17762
|
import { mkdir } from "fs/promises";
|
|
17467
|
-
import { homedir as
|
|
17468
|
-
import { isAbsolute, join as
|
|
17763
|
+
import { homedir as homedir4 } from "os";
|
|
17764
|
+
import { isAbsolute, join as join4, relative, resolve } from "path";
|
|
17765
|
+
function ensureDebugLogDir() {
|
|
17766
|
+
try {
|
|
17767
|
+
if (!existsSync4(DEBUG_LOG_DIR2)) {
|
|
17768
|
+
mkdir(DEBUG_LOG_DIR2, { recursive: true }).catch(() => {});
|
|
17769
|
+
}
|
|
17770
|
+
} catch {}
|
|
17771
|
+
}
|
|
17772
|
+
function debugLogToFile2(message, data) {
|
|
17773
|
+
try {
|
|
17774
|
+
ensureDebugLogDir();
|
|
17775
|
+
const timestamp = new Date().toISOString();
|
|
17776
|
+
const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
|
|
17777
|
+
`;
|
|
17778
|
+
appendFileSync3(DEBUG_LOG_FILE2, logLine);
|
|
17779
|
+
} catch {}
|
|
17780
|
+
}
|
|
17469
17781
|
async function ensurePluginDirectory() {
|
|
17470
|
-
const
|
|
17782
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve(process.env.XDG_CONFIG_HOME) : join4(homedir4(), ".config");
|
|
17783
|
+
const pluginDir = join4(configHome, "opencode", "plugin");
|
|
17471
17784
|
try {
|
|
17472
17785
|
await mkdir(pluginDir, { recursive: true });
|
|
17473
|
-
|
|
17786
|
+
log14.debug("Plugin directory ensured", { path: pluginDir });
|
|
17474
17787
|
} catch (error45) {
|
|
17475
|
-
|
|
17788
|
+
log14.warn("Failed to create plugin directory", { error: String(error45) });
|
|
17476
17789
|
}
|
|
17477
17790
|
}
|
|
17791
|
+
function shouldProcessModel(model) {
|
|
17792
|
+
if (!model)
|
|
17793
|
+
return false;
|
|
17794
|
+
return model.startsWith(CURSOR_PROVIDER_PREFIX);
|
|
17795
|
+
}
|
|
17478
17796
|
function getGlobalKey() {
|
|
17479
17797
|
return "__opencode_cursor_proxy_server__";
|
|
17480
17798
|
}
|
|
17799
|
+
function getOpenCodeConfigPrefix() {
|
|
17800
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve(process.env.XDG_CONFIG_HOME) : join4(homedir4(), ".config");
|
|
17801
|
+
return join4(configHome, "opencode");
|
|
17802
|
+
}
|
|
17803
|
+
function canonicalizePathForCompare(pathValue) {
|
|
17804
|
+
const resolvedPath = resolve(pathValue);
|
|
17805
|
+
let normalizedPath = resolvedPath;
|
|
17806
|
+
try {
|
|
17807
|
+
normalizedPath = typeof realpathSync.native === "function" ? realpathSync.native(resolvedPath) : realpathSync(resolvedPath);
|
|
17808
|
+
} catch {
|
|
17809
|
+
normalizedPath = resolvedPath;
|
|
17810
|
+
}
|
|
17811
|
+
if (process.platform === "darwin") {
|
|
17812
|
+
return normalizedPath.toLowerCase();
|
|
17813
|
+
}
|
|
17814
|
+
return normalizedPath;
|
|
17815
|
+
}
|
|
17816
|
+
function isWithinPath(root, candidate) {
|
|
17817
|
+
const normalizedRoot = canonicalizePathForCompare(root);
|
|
17818
|
+
const normalizedCandidate = canonicalizePathForCompare(candidate);
|
|
17819
|
+
const rel = relative(normalizedRoot, normalizedCandidate);
|
|
17820
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17821
|
+
}
|
|
17822
|
+
function resolveCandidate(value) {
|
|
17823
|
+
if (!value || value.trim().length === 0) {
|
|
17824
|
+
return "";
|
|
17825
|
+
}
|
|
17826
|
+
return resolve(value);
|
|
17827
|
+
}
|
|
17828
|
+
function isNonConfigPath(pathValue) {
|
|
17829
|
+
if (!pathValue) {
|
|
17830
|
+
return false;
|
|
17831
|
+
}
|
|
17832
|
+
return !isWithinPath(getOpenCodeConfigPrefix(), pathValue);
|
|
17833
|
+
}
|
|
17481
17834
|
function resolveWorkspaceDirectory(worktree, directory) {
|
|
17482
17835
|
const envWorkspace = process.env.CURSOR_ACP_WORKSPACE?.trim();
|
|
17483
17836
|
if (envWorkspace) {
|
|
@@ -17487,18 +17840,17 @@ function resolveWorkspaceDirectory(worktree, directory) {
|
|
|
17487
17840
|
if (envProjectDir) {
|
|
17488
17841
|
return resolve(envProjectDir);
|
|
17489
17842
|
}
|
|
17490
|
-
const
|
|
17491
|
-
const
|
|
17492
|
-
|
|
17493
|
-
if (worktreeCandidate && !worktreeCandidate.startsWith(configPrefix)) {
|
|
17843
|
+
const configPrefix = getOpenCodeConfigPrefix();
|
|
17844
|
+
const worktreeCandidate = resolveCandidate(worktree);
|
|
17845
|
+
if (worktreeCandidate && !isWithinPath(configPrefix, worktreeCandidate)) {
|
|
17494
17846
|
return worktreeCandidate;
|
|
17495
17847
|
}
|
|
17496
|
-
const dirCandidate =
|
|
17497
|
-
if (dirCandidate && !
|
|
17848
|
+
const dirCandidate = resolveCandidate(directory);
|
|
17849
|
+
if (dirCandidate && !isWithinPath(configPrefix, dirCandidate)) {
|
|
17498
17850
|
return dirCandidate;
|
|
17499
17851
|
}
|
|
17500
|
-
const cwd = process.cwd();
|
|
17501
|
-
if (cwd && !
|
|
17852
|
+
const cwd = resolve(process.cwd());
|
|
17853
|
+
if (cwd && !isWithinPath(configPrefix, cwd)) {
|
|
17502
17854
|
return cwd;
|
|
17503
17855
|
}
|
|
17504
17856
|
return dirCandidate || cwd || configPrefix;
|
|
@@ -17609,9 +17961,9 @@ function createBoundaryRuntimeContext(scope) {
|
|
|
17609
17961
|
error: toErrorMessage(error45)
|
|
17610
17962
|
};
|
|
17611
17963
|
if (!fallbackActive) {
|
|
17612
|
-
|
|
17964
|
+
log14.warn("Provider boundary v1 failed; switching to legacy for this request", details);
|
|
17613
17965
|
} else {
|
|
17614
|
-
|
|
17966
|
+
log14.debug("Provider boundary fallback already active", details);
|
|
17615
17967
|
}
|
|
17616
17968
|
fallbackActive = true;
|
|
17617
17969
|
return true;
|
|
@@ -17688,7 +18040,7 @@ async function findFirstAllowedToolCallInOutput(output, options) {
|
|
|
17688
18040
|
if (result.terminate) {
|
|
17689
18041
|
return {
|
|
17690
18042
|
toolCall: null,
|
|
17691
|
-
terminationMessage: result.terminate.message
|
|
18043
|
+
terminationMessage: result.terminate.silent ? null : result.terminate.message
|
|
17692
18044
|
};
|
|
17693
18045
|
}
|
|
17694
18046
|
if (result.intercepted && interceptedToolCall) {
|
|
@@ -17745,7 +18097,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17745
18097
|
headers: { "Content-Type": "application/json" }
|
|
17746
18098
|
});
|
|
17747
18099
|
} catch (err) {
|
|
17748
|
-
|
|
18100
|
+
log14.error("Failed to list models", { error: String(err) });
|
|
17749
18101
|
return new Response(JSON.stringify({ error: "Failed to fetch models from cursor-agent" }), {
|
|
17750
18102
|
status: 500,
|
|
17751
18103
|
headers: { "Content-Type": "application/json" }
|
|
@@ -17758,23 +18110,40 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17758
18110
|
headers: { "Content-Type": "application/json" }
|
|
17759
18111
|
});
|
|
17760
18112
|
}
|
|
17761
|
-
|
|
18113
|
+
log14.debug("Proxy request (bun)", { method: req.method, path: url2.pathname });
|
|
17762
18114
|
const body = await req.json().catch(() => ({}));
|
|
17763
18115
|
const messages = Array.isArray(body?.messages) ? body.messages : [];
|
|
17764
18116
|
const stream = body?.stream === true;
|
|
17765
18117
|
const tools = Array.isArray(body?.tools) ? body.tools : [];
|
|
18118
|
+
debugLogToFile2("raw_request_body", {
|
|
18119
|
+
model: body?.model,
|
|
18120
|
+
stream,
|
|
18121
|
+
toolCount: tools.length,
|
|
18122
|
+
toolNames: tools.map((t) => t?.function?.name ?? t?.name ?? "unknown"),
|
|
18123
|
+
messageCount: messages.length,
|
|
18124
|
+
messageRoles: messages.map((m) => m?.role),
|
|
18125
|
+
hasMessagesWithToolCalls: messages.some((m) => Array.isArray(m?.tool_calls) && m.tool_calls.length > 0),
|
|
18126
|
+
hasToolResultMessages: messages.some((m) => m?.role === "tool")
|
|
18127
|
+
});
|
|
17766
18128
|
const allowedToolNames = extractAllowedToolNames(tools);
|
|
17767
18129
|
const toolSchemaMap = buildToolSchemaMap(tools);
|
|
17768
18130
|
const toolLoopGuard = createToolLoopGuard(messages, TOOL_LOOP_MAX_REPEAT);
|
|
17769
18131
|
const boundaryContext = createBoundaryRuntimeContext("bun-handler");
|
|
17770
18132
|
const prompt = buildPromptFromMessages(messages, tools);
|
|
17771
18133
|
const model = boundaryContext.run("normalizeRuntimeModel", (boundary) => boundary.normalizeRuntimeModel(body?.model));
|
|
17772
|
-
|
|
18134
|
+
const msgSummaryBun = messages.map((m, i) => {
|
|
18135
|
+
const role = m?.role ?? "?";
|
|
18136
|
+
const hasTc = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
|
|
18137
|
+
const clen = typeof m?.content === "string" ? m.content.length : Array.isArray(m?.content) ? `arr${m.content.length}` : typeof m?.content;
|
|
18138
|
+
return `${i}:${role}${hasTc ? `(tc:${hasTc})` : ""}(clen:${clen})`;
|
|
18139
|
+
});
|
|
18140
|
+
log14.debug("Proxy chat request (bun)", {
|
|
17773
18141
|
stream,
|
|
17774
18142
|
model,
|
|
17775
18143
|
messages: messages.length,
|
|
17776
18144
|
tools: tools.length,
|
|
17777
|
-
promptChars: prompt.length
|
|
18145
|
+
promptChars: prompt.length,
|
|
18146
|
+
msgRoles: msgSummaryBun.join(",")
|
|
17778
18147
|
});
|
|
17779
18148
|
const bunAny = globalThis;
|
|
17780
18149
|
if (!bunAny.Bun?.spawn) {
|
|
@@ -17814,7 +18183,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17814
18183
|
const stdout = (stdoutText || "").trim();
|
|
17815
18184
|
const stderr = (stderrText || "").trim();
|
|
17816
18185
|
const exitCode = child.exitCode;
|
|
17817
|
-
|
|
18186
|
+
log14.debug("cursor-agent completed (bun non-stream)", {
|
|
17818
18187
|
exitCode,
|
|
17819
18188
|
stdoutChars: stdout.length,
|
|
17820
18189
|
stderrChars: stderr.length
|
|
@@ -17840,7 +18209,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17840
18209
|
});
|
|
17841
18210
|
}
|
|
17842
18211
|
if (intercepted.toolCall) {
|
|
17843
|
-
|
|
18212
|
+
log14.debug("Intercepted OpenCode tool call (non-stream)", {
|
|
17844
18213
|
name: intercepted.toolCall.function.name,
|
|
17845
18214
|
callId: intercepted.toolCall.id
|
|
17846
18215
|
});
|
|
@@ -17854,7 +18223,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17854
18223
|
const errSource = stderr || stdout || `cursor-agent exited with code ${String(exitCode ?? "unknown")} and no output`;
|
|
17855
18224
|
const parsed = parseAgentError(errSource);
|
|
17856
18225
|
const userError = formatErrorForUser(parsed);
|
|
17857
|
-
|
|
18226
|
+
log14.error("cursor-cli failed", {
|
|
17858
18227
|
type: parsed.type,
|
|
17859
18228
|
message: parsed.message,
|
|
17860
18229
|
code: exitCode
|
|
@@ -17888,7 +18257,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17888
18257
|
const converter = new StreamToSseConverter(model, { id, created });
|
|
17889
18258
|
const lineBuffer = new LineBuffer;
|
|
17890
18259
|
const emitToolCallAndTerminate = (toolCall) => {
|
|
17891
|
-
|
|
18260
|
+
log14.debug("Intercepted OpenCode tool call (stream)", {
|
|
17892
18261
|
name: toolCall.function.name,
|
|
17893
18262
|
callId: toolCall.id
|
|
17894
18263
|
});
|
|
@@ -17971,7 +18340,15 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17971
18340
|
}
|
|
17972
18341
|
});
|
|
17973
18342
|
if (result.terminate) {
|
|
17974
|
-
|
|
18343
|
+
if (!result.terminate.silent) {
|
|
18344
|
+
emitTerminalAssistantErrorAndTerminate(result.terminate.message);
|
|
18345
|
+
} else {
|
|
18346
|
+
controller.enqueue(encoder.encode(formatSseDone()));
|
|
18347
|
+
streamTerminated = true;
|
|
18348
|
+
try {
|
|
18349
|
+
child.kill();
|
|
18350
|
+
} catch {}
|
|
18351
|
+
}
|
|
17975
18352
|
break;
|
|
17976
18353
|
}
|
|
17977
18354
|
if (result.intercepted) {
|
|
@@ -18029,7 +18406,15 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18029
18406
|
}
|
|
18030
18407
|
});
|
|
18031
18408
|
if (result.terminate) {
|
|
18032
|
-
|
|
18409
|
+
if (!result.terminate.silent) {
|
|
18410
|
+
emitTerminalAssistantErrorAndTerminate(result.terminate.message);
|
|
18411
|
+
} else {
|
|
18412
|
+
controller.enqueue(encoder.encode(formatSseDone()));
|
|
18413
|
+
streamTerminated = true;
|
|
18414
|
+
try {
|
|
18415
|
+
child.kill();
|
|
18416
|
+
} catch {}
|
|
18417
|
+
}
|
|
18033
18418
|
break;
|
|
18034
18419
|
}
|
|
18035
18420
|
if (result.intercepted) {
|
|
@@ -18051,7 +18436,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18051
18436
|
const errSource = (stderrText || "").trim() || `cursor-agent exited with code ${String(child.exitCode ?? "unknown")} and no output`;
|
|
18052
18437
|
const parsed = parseAgentError(errSource);
|
|
18053
18438
|
const msg = formatErrorForUser(parsed);
|
|
18054
|
-
|
|
18439
|
+
log14.error("cursor-cli streaming failed", {
|
|
18055
18440
|
type: parsed.type,
|
|
18056
18441
|
code: child.exitCode
|
|
18057
18442
|
});
|
|
@@ -18062,7 +18447,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18062
18447
|
controller.enqueue(encoder.encode(formatSseDone()));
|
|
18063
18448
|
return;
|
|
18064
18449
|
}
|
|
18065
|
-
|
|
18450
|
+
log14.debug("cursor-agent completed (bun stream)", {
|
|
18066
18451
|
exitCode: child.exitCode
|
|
18067
18452
|
});
|
|
18068
18453
|
const doneChunk = createChatCompletionChunk(id, created, model, "", true);
|
|
@@ -18133,7 +18518,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18133
18518
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
18134
18519
|
res.end(JSON.stringify({ object: "list", data: models }));
|
|
18135
18520
|
} catch (err) {
|
|
18136
|
-
|
|
18521
|
+
log14.error("Failed to list models", { error: String(err) });
|
|
18137
18522
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
18138
18523
|
res.end(JSON.stringify({ error: "Failed to fetch models" }));
|
|
18139
18524
|
}
|
|
@@ -18144,7 +18529,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18144
18529
|
res.end(JSON.stringify({ error: `Unsupported path: ${url2.pathname}` }));
|
|
18145
18530
|
return;
|
|
18146
18531
|
}
|
|
18147
|
-
|
|
18532
|
+
log14.debug("Proxy request (node)", { method: req.method, path: url2.pathname });
|
|
18148
18533
|
let body = "";
|
|
18149
18534
|
for await (const chunk of req) {
|
|
18150
18535
|
body += chunk;
|
|
@@ -18159,12 +18544,21 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18159
18544
|
const boundaryContext = createBoundaryRuntimeContext("node-handler");
|
|
18160
18545
|
const prompt = buildPromptFromMessages(messages, tools);
|
|
18161
18546
|
const model = boundaryContext.run("normalizeRuntimeModel", (boundary) => boundary.normalizeRuntimeModel(bodyData?.model));
|
|
18162
|
-
|
|
18547
|
+
const msgSummary = messages.map((m, i) => {
|
|
18548
|
+
const role = m?.role ?? "?";
|
|
18549
|
+
const hasTc = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
|
|
18550
|
+
const tcId = m?.tool_call_id ? "yes" : "no";
|
|
18551
|
+
const tcName = m?.name ?? "";
|
|
18552
|
+
const contentLen = typeof m?.content === "string" ? m.content.length : Array.isArray(m?.content) ? `arr${m.content.length}` : typeof m?.content;
|
|
18553
|
+
return `${i}:${role}${hasTc ? `(tc:${hasTc})` : ""}${role === "tool" ? `(tcid:${tcId},name:${tcName},clen:${contentLen})` : `(clen:${contentLen})`}`;
|
|
18554
|
+
});
|
|
18555
|
+
log14.debug("Proxy chat request (node)", {
|
|
18163
18556
|
stream,
|
|
18164
18557
|
model,
|
|
18165
18558
|
messages: messages.length,
|
|
18166
18559
|
tools: tools.length,
|
|
18167
|
-
promptChars: prompt.length
|
|
18560
|
+
promptChars: prompt.length,
|
|
18561
|
+
msgRoles: msgSummary.join(",")
|
|
18168
18562
|
});
|
|
18169
18563
|
const cmd = [
|
|
18170
18564
|
"cursor-agent",
|
|
@@ -18189,14 +18583,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18189
18583
|
let spawnErrorText = null;
|
|
18190
18584
|
child.on("error", (error45) => {
|
|
18191
18585
|
spawnErrorText = String(error45?.message || error45);
|
|
18192
|
-
|
|
18586
|
+
log14.error("Failed to spawn cursor-agent", { error: spawnErrorText, model });
|
|
18193
18587
|
});
|
|
18194
18588
|
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
18195
18589
|
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
18196
18590
|
child.on("close", async (code) => {
|
|
18197
18591
|
const stdout = Buffer.concat(stdoutChunks).toString().trim();
|
|
18198
18592
|
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
18199
|
-
|
|
18593
|
+
log14.debug("cursor-agent completed (node non-stream)", {
|
|
18200
18594
|
code,
|
|
18201
18595
|
stdoutChars: stdout.length,
|
|
18202
18596
|
stderrChars: stderr.length,
|
|
@@ -18222,7 +18616,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18222
18616
|
return;
|
|
18223
18617
|
}
|
|
18224
18618
|
if (intercepted.toolCall) {
|
|
18225
|
-
|
|
18619
|
+
log14.debug("Intercepted OpenCode tool call (non-stream)", {
|
|
18226
18620
|
name: intercepted.toolCall.function.name,
|
|
18227
18621
|
callId: intercepted.toolCall.id
|
|
18228
18622
|
});
|
|
@@ -18236,7 +18630,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18236
18630
|
const errSource = stderr || stdout || spawnErrorText || `cursor-agent exited with code ${String(code ?? "unknown")} and no output`;
|
|
18237
18631
|
const parsed = parseAgentError(errSource);
|
|
18238
18632
|
const userError = formatErrorForUser(parsed);
|
|
18239
|
-
|
|
18633
|
+
log14.error("cursor-cli failed", {
|
|
18240
18634
|
type: parsed.type,
|
|
18241
18635
|
message: parsed.message,
|
|
18242
18636
|
code
|
|
@@ -18275,7 +18669,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18275
18669
|
return;
|
|
18276
18670
|
}
|
|
18277
18671
|
const errSource = String(error45?.message || error45);
|
|
18278
|
-
|
|
18672
|
+
log14.error("Failed to spawn cursor-agent (stream)", { error: errSource, model });
|
|
18279
18673
|
const parsed = parseAgentError(errSource);
|
|
18280
18674
|
const msg = formatErrorForUser(parsed);
|
|
18281
18675
|
const errChunk = createChatCompletionChunk(id, created, model, msg, true);
|
|
@@ -18290,7 +18684,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18290
18684
|
if (streamTerminated || res.writableEnded) {
|
|
18291
18685
|
return;
|
|
18292
18686
|
}
|
|
18293
|
-
|
|
18687
|
+
log14.debug("Intercepted OpenCode tool call (stream)", {
|
|
18294
18688
|
name: toolCall.function.name,
|
|
18295
18689
|
callId: toolCall.id
|
|
18296
18690
|
});
|
|
@@ -18372,7 +18766,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18372
18766
|
}
|
|
18373
18767
|
});
|
|
18374
18768
|
if (result.terminate) {
|
|
18375
|
-
|
|
18769
|
+
if (!result.terminate.silent) {
|
|
18770
|
+
emitTerminalAssistantErrorAndTerminate(result.terminate.message);
|
|
18771
|
+
} else {
|
|
18772
|
+
streamTerminated = true;
|
|
18773
|
+
try {
|
|
18774
|
+
child.kill();
|
|
18775
|
+
} catch {}
|
|
18776
|
+
}
|
|
18376
18777
|
break;
|
|
18377
18778
|
}
|
|
18378
18779
|
if (result.intercepted) {
|
|
@@ -18435,7 +18836,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18435
18836
|
}
|
|
18436
18837
|
});
|
|
18437
18838
|
if (result.terminate) {
|
|
18438
|
-
|
|
18839
|
+
if (!result.terminate.silent) {
|
|
18840
|
+
emitTerminalAssistantErrorAndTerminate(result.terminate.message);
|
|
18841
|
+
} else {
|
|
18842
|
+
streamTerminated = true;
|
|
18843
|
+
try {
|
|
18844
|
+
child.kill();
|
|
18845
|
+
} catch {}
|
|
18846
|
+
}
|
|
18439
18847
|
break;
|
|
18440
18848
|
}
|
|
18441
18849
|
if (result.intercepted) {
|
|
@@ -18458,7 +18866,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
18458
18866
|
perf.mark("request:done");
|
|
18459
18867
|
perf.summarize();
|
|
18460
18868
|
const stderrText = Buffer.concat(stderrChunks).toString().trim();
|
|
18461
|
-
|
|
18869
|
+
log14.debug("cursor-agent completed (node stream)", {
|
|
18462
18870
|
code,
|
|
18463
18871
|
stderrChars: stderrText.length
|
|
18464
18872
|
});
|
|
@@ -18584,19 +18992,39 @@ function jsonSchemaToZod(jsonSchema) {
|
|
|
18584
18992
|
}
|
|
18585
18993
|
return zodShape;
|
|
18586
18994
|
}
|
|
18587
|
-
function
|
|
18588
|
-
const
|
|
18589
|
-
const
|
|
18590
|
-
const
|
|
18591
|
-
|
|
18995
|
+
function resolveToolContextBaseDirWithSession(context, fallbackBaseDir, sessionWorkspaceBySession) {
|
|
18996
|
+
const sessionID = typeof context?.sessionID === "string" && context.sessionID.trim().length > 0 ? context.sessionID.trim() : "";
|
|
18997
|
+
const worktree = resolveCandidate(typeof context?.worktree === "string" ? context.worktree : undefined);
|
|
18998
|
+
const directory = resolveCandidate(typeof context?.directory === "string" ? context.directory : undefined);
|
|
18999
|
+
const fallback = resolveCandidate(fallbackBaseDir);
|
|
19000
|
+
const pinned = sessionID && sessionWorkspaceBySession ? resolveCandidate(sessionWorkspaceBySession.get(sessionID)) : "";
|
|
19001
|
+
const pinSession = (candidate) => {
|
|
19002
|
+
if (sessionID && sessionWorkspaceBySession && isNonConfigPath(candidate)) {
|
|
19003
|
+
if (!sessionWorkspaceBySession.has(sessionID) && sessionWorkspaceBySession.size >= SESSION_WORKSPACE_CACHE_LIMIT) {
|
|
19004
|
+
const oldestSession = sessionWorkspaceBySession.keys().next().value;
|
|
19005
|
+
if (typeof oldestSession === "string") {
|
|
19006
|
+
sessionWorkspaceBySession.delete(oldestSession);
|
|
19007
|
+
}
|
|
19008
|
+
}
|
|
19009
|
+
sessionWorkspaceBySession.set(sessionID, candidate);
|
|
19010
|
+
}
|
|
19011
|
+
};
|
|
19012
|
+
if (isNonConfigPath(worktree)) {
|
|
19013
|
+
pinSession(worktree);
|
|
18592
19014
|
return worktree;
|
|
18593
|
-
|
|
18594
|
-
if (
|
|
19015
|
+
}
|
|
19016
|
+
if (isNonConfigPath(pinned)) {
|
|
19017
|
+
return pinned;
|
|
19018
|
+
}
|
|
19019
|
+
if (isNonConfigPath(directory)) {
|
|
19020
|
+
pinSession(directory);
|
|
18595
19021
|
return directory;
|
|
18596
|
-
|
|
18597
|
-
if (fallback)
|
|
19022
|
+
}
|
|
19023
|
+
if (isNonConfigPath(fallback)) {
|
|
19024
|
+
pinSession(fallback);
|
|
18598
19025
|
return fallback;
|
|
18599
|
-
|
|
19026
|
+
}
|
|
19027
|
+
return null;
|
|
18600
19028
|
}
|
|
18601
19029
|
function toAbsoluteWithBase(value, baseDir) {
|
|
18602
19030
|
if (typeof value !== "string") {
|
|
@@ -18608,8 +19036,8 @@ function toAbsoluteWithBase(value, baseDir) {
|
|
|
18608
19036
|
}
|
|
18609
19037
|
return resolve(baseDir, trimmed);
|
|
18610
19038
|
}
|
|
18611
|
-
function applyToolContextDefaults(toolName, rawArgs, context, fallbackBaseDir) {
|
|
18612
|
-
const baseDir =
|
|
19039
|
+
function applyToolContextDefaults(toolName, rawArgs, context, fallbackBaseDir, sessionWorkspaceBySession) {
|
|
19040
|
+
const baseDir = resolveToolContextBaseDirWithSession(context, fallbackBaseDir, sessionWorkspaceBySession);
|
|
18613
19041
|
if (!baseDir) {
|
|
18614
19042
|
return rawArgs;
|
|
18615
19043
|
}
|
|
@@ -18638,6 +19066,7 @@ function applyToolContextDefaults(toolName, rawArgs, context, fallbackBaseDir) {
|
|
|
18638
19066
|
}
|
|
18639
19067
|
function buildToolHookEntries(registry2, fallbackBaseDir) {
|
|
18640
19068
|
const entries = {};
|
|
19069
|
+
const sessionWorkspaceBySession = new Map;
|
|
18641
19070
|
const tools = registry2.list();
|
|
18642
19071
|
for (const t of tools) {
|
|
18643
19072
|
const handler = registry2.getHandler(t.name);
|
|
@@ -18649,10 +19078,10 @@ function buildToolHookEntries(registry2, fallbackBaseDir) {
|
|
|
18649
19078
|
args: zodArgs,
|
|
18650
19079
|
async execute(args, context) {
|
|
18651
19080
|
try {
|
|
18652
|
-
const normalizedArgs = applyToolContextDefaults(toolName, args, context, fallbackBaseDir);
|
|
19081
|
+
const normalizedArgs = applyToolContextDefaults(toolName, args, context, fallbackBaseDir, sessionWorkspaceBySession);
|
|
18653
19082
|
return await handler(normalizedArgs);
|
|
18654
19083
|
} catch (error45) {
|
|
18655
|
-
|
|
19084
|
+
log14.debug("Tool hook execution failed", { tool: toolName, error: String(error45?.message || error45) });
|
|
18656
19085
|
throw error45;
|
|
18657
19086
|
}
|
|
18658
19087
|
}
|
|
@@ -18664,9 +19093,9 @@ function buildToolHookEntries(registry2, fallbackBaseDir) {
|
|
|
18664
19093
|
}
|
|
18665
19094
|
return entries;
|
|
18666
19095
|
}
|
|
18667
|
-
var
|
|
19096
|
+
var log14, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROVIDER_PREFIX, CURSOR_PROXY_HOST = "127.0.0.1", CURSOR_PROXY_DEFAULT_PORT = 32124, CURSOR_PROXY_DEFAULT_BASE_URL, REUSE_EXISTING_PROXY, SESSION_WORKSPACE_CACHE_LIMIT = 200, FORCE_TOOL_MODE, EMIT_TOOL_UPDATES, FORWARD_TOOL_CALLS, TOOL_LOOP_MODE_RAW, TOOL_LOOP_MODE, TOOL_LOOP_MODE_VALID, PROVIDER_BOUNDARY_MODE_RAW, PROVIDER_BOUNDARY_MODE, PROVIDER_BOUNDARY_MODE_VALID, LEGACY_PROVIDER_BOUNDARY, PROVIDER_BOUNDARY, ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK, TOOL_LOOP_MAX_REPEAT_RAW, TOOL_LOOP_MAX_REPEAT, TOOL_LOOP_MAX_REPEAT_VALID, PROXY_EXECUTE_TOOL_CALLS, SUPPRESS_CONVERTER_TOOL_EVENTS, SHOULD_EMIT_TOOL_UPDATES, CursorPlugin = async ({ $, directory, worktree, client: client3, serverUrl }) => {
|
|
18668
19097
|
const workspaceDirectory = resolveWorkspaceDirectory(worktree, directory);
|
|
18669
|
-
|
|
19098
|
+
log14.debug("Plugin initializing", {
|
|
18670
19099
|
directory,
|
|
18671
19100
|
worktree,
|
|
18672
19101
|
workspaceDirectory,
|
|
@@ -18674,22 +19103,22 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18674
19103
|
serverUrl: serverUrl?.toString()
|
|
18675
19104
|
});
|
|
18676
19105
|
if (!TOOL_LOOP_MODE_VALID) {
|
|
18677
|
-
|
|
19106
|
+
log14.warn("Invalid CURSOR_ACP_TOOL_LOOP_MODE; defaulting to opencode", { value: TOOL_LOOP_MODE_RAW });
|
|
18678
19107
|
}
|
|
18679
19108
|
if (!PROVIDER_BOUNDARY_MODE_VALID) {
|
|
18680
|
-
|
|
19109
|
+
log14.warn("Invalid CURSOR_ACP_PROVIDER_BOUNDARY; defaulting to v1", {
|
|
18681
19110
|
value: PROVIDER_BOUNDARY_MODE_RAW
|
|
18682
19111
|
});
|
|
18683
19112
|
}
|
|
18684
19113
|
if (!TOOL_LOOP_MAX_REPEAT_VALID) {
|
|
18685
|
-
|
|
19114
|
+
log14.warn("Invalid CURSOR_ACP_TOOL_LOOP_MAX_REPEAT; defaulting to 3", {
|
|
18686
19115
|
value: TOOL_LOOP_MAX_REPEAT_RAW
|
|
18687
19116
|
});
|
|
18688
19117
|
}
|
|
18689
19118
|
if (ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK && PROVIDER_BOUNDARY.mode !== "v1") {
|
|
18690
|
-
|
|
19119
|
+
log14.debug("Provider boundary auto-fallback is enabled but inactive unless mode=v1");
|
|
18691
19120
|
}
|
|
18692
|
-
|
|
19121
|
+
log14.info("Tool loop mode configured", {
|
|
18693
19122
|
mode: TOOL_LOOP_MODE,
|
|
18694
19123
|
providerBoundary: PROVIDER_BOUNDARY.mode,
|
|
18695
19124
|
proxyExecToolCalls: PROXY_EXECUTE_TOOL_CALLS,
|
|
@@ -18700,9 +19129,9 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18700
19129
|
const toolsEnabled = process.env.CURSOR_ACP_ENABLE_OPENCODE_TOOLS !== "false";
|
|
18701
19130
|
const legacyProxyToolPathsEnabled = toolsEnabled && TOOL_LOOP_MODE === "proxy-exec";
|
|
18702
19131
|
if (toolsEnabled && TOOL_LOOP_MODE === "opencode") {
|
|
18703
|
-
|
|
19132
|
+
log14.debug("OpenCode mode active; skipping legacy SDK/MCP discovery and proxy-side tool execution");
|
|
18704
19133
|
} else if (toolsEnabled && TOOL_LOOP_MODE === "off") {
|
|
18705
|
-
|
|
19134
|
+
log14.debug("Tool loop mode off; proxy-side tool execution disabled");
|
|
18706
19135
|
}
|
|
18707
19136
|
const serverClient = legacyProxyToolPathsEnabled ? createOpencodeClient({ baseUrl: serverUrl.toString(), directory: workspaceDirectory }) : null;
|
|
18708
19137
|
const discovery = legacyProxyToolPathsEnabled ? new OpenCodeToolDiscovery(serverClient ?? client3) : null;
|
|
@@ -18754,7 +19183,7 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18754
19183
|
discoveredList = await discovery.listTools();
|
|
18755
19184
|
discoveredList.forEach((t) => toolsByName.set(t.name, t));
|
|
18756
19185
|
} catch (err) {
|
|
18757
|
-
|
|
19186
|
+
log14.debug("Tool discovery failed, using local tools only", { error: String(err) });
|
|
18758
19187
|
}
|
|
18759
19188
|
}
|
|
18760
19189
|
const allTools = [...localTools, ...discoveredList];
|
|
@@ -18784,11 +19213,11 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18784
19213
|
}
|
|
18785
19214
|
lastToolNames = toolEntries.map((e) => e.function.name);
|
|
18786
19215
|
lastToolMap = allTools.map((t) => ({ id: t.id, name: t.name }));
|
|
18787
|
-
|
|
19216
|
+
log14.debug("Tools refreshed", { local: localTools.length, discovered: discoveredList.length, total: toolEntries.length });
|
|
18788
19217
|
return toolEntries;
|
|
18789
19218
|
}
|
|
18790
19219
|
const proxyBaseURL = await ensureCursorProxyServer(workspaceDirectory, router);
|
|
18791
|
-
|
|
19220
|
+
log14.debug("Proxy server started", { baseURL: proxyBaseURL });
|
|
18792
19221
|
const toolHookEntries = buildToolHookEntries(localRegistry, workspaceDirectory);
|
|
18793
19222
|
return {
|
|
18794
19223
|
tool: toolHookEntries,
|
|
@@ -18803,9 +19232,9 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18803
19232
|
type: "oauth",
|
|
18804
19233
|
async authorize() {
|
|
18805
19234
|
try {
|
|
18806
|
-
|
|
19235
|
+
log14.info("Starting OAuth flow");
|
|
18807
19236
|
const { url: url2, instructions, callback } = await startCursorOAuth();
|
|
18808
|
-
|
|
19237
|
+
log14.debug("Got OAuth URL", { url: url2.substring(0, 50) + "..." });
|
|
18809
19238
|
return {
|
|
18810
19239
|
url: url2,
|
|
18811
19240
|
instructions,
|
|
@@ -18813,7 +19242,7 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18813
19242
|
callback
|
|
18814
19243
|
};
|
|
18815
19244
|
} catch (error45) {
|
|
18816
|
-
|
|
19245
|
+
log14.error("OAuth error", { error: error45 });
|
|
18817
19246
|
throw error45;
|
|
18818
19247
|
}
|
|
18819
19248
|
}
|
|
@@ -18837,10 +19266,10 @@ var log13, CURSOR_PROVIDER_ID = "cursor-acp", CURSOR_PROXY_HOST = "127.0.0.1", C
|
|
|
18837
19266
|
output.options.tools = resolved.tools;
|
|
18838
19267
|
} else if (resolved.action === "preserve") {
|
|
18839
19268
|
const count = Array.isArray(existingTools) ? existingTools.length : 0;
|
|
18840
|
-
|
|
19269
|
+
log14.debug("Using OpenCode-provided tools from chat.params", { count });
|
|
18841
19270
|
}
|
|
18842
19271
|
} catch (err) {
|
|
18843
|
-
|
|
19272
|
+
log14.debug("Failed to refresh tools", { error: String(err) });
|
|
18844
19273
|
}
|
|
18845
19274
|
}
|
|
18846
19275
|
},
|
|
@@ -18861,6 +19290,7 @@ var init_plugin = __esm(() => {
|
|
|
18861
19290
|
init_parser();
|
|
18862
19291
|
init_logger();
|
|
18863
19292
|
init_perf();
|
|
19293
|
+
init_prompt_builder();
|
|
18864
19294
|
init_tool_loop();
|
|
18865
19295
|
init_discovery();
|
|
18866
19296
|
init_schema();
|
|
@@ -18874,7 +19304,10 @@ var init_plugin = __esm(() => {
|
|
|
18874
19304
|
init_runtime_interception();
|
|
18875
19305
|
init_tool_schema_compat();
|
|
18876
19306
|
init_tool_loop_guard();
|
|
18877
|
-
|
|
19307
|
+
log14 = createLogger("plugin");
|
|
19308
|
+
DEBUG_LOG_DIR2 = join4(homedir4(), ".config", "opencode", "logs");
|
|
19309
|
+
DEBUG_LOG_FILE2 = join4(DEBUG_LOG_DIR2, "tool-loop-debug.log");
|
|
19310
|
+
CURSOR_PROVIDER_PREFIX = `${CURSOR_PROVIDER_ID}/`;
|
|
18878
19311
|
CURSOR_PROXY_DEFAULT_BASE_URL = `http://${CURSOR_PROXY_HOST}:${CURSOR_PROXY_DEFAULT_PORT}/v1`;
|
|
18879
19312
|
REUSE_EXISTING_PROXY = process.env.CURSOR_ACP_REUSE_EXISTING_PROXY !== "false";
|
|
18880
19313
|
FORCE_TOOL_MODE = process.env.CURSOR_ACP_FORCE !== "false";
|
|
@@ -19113,16 +19546,31 @@ class SimpleCursorClient {
|
|
|
19113
19546
|
// src/proxy/server.ts
|
|
19114
19547
|
init_logger();
|
|
19115
19548
|
import { execSync } from "node:child_process";
|
|
19549
|
+
import { createServer } from "node:net";
|
|
19116
19550
|
import { platform as platform2 } from "node:os";
|
|
19117
|
-
var
|
|
19551
|
+
var log15 = createLogger("proxy-server");
|
|
19118
19552
|
var DEFAULT_PORT = 32124;
|
|
19119
19553
|
var PORT_RANGE_SIZE = 256;
|
|
19554
|
+
async function isPortAvailable(port, host) {
|
|
19555
|
+
return await new Promise((resolve2) => {
|
|
19556
|
+
const server2 = createServer();
|
|
19557
|
+
server2.unref();
|
|
19558
|
+
server2.once("error", () => {
|
|
19559
|
+
resolve2(false);
|
|
19560
|
+
});
|
|
19561
|
+
server2.listen({ port, host }, () => {
|
|
19562
|
+
server2.close(() => {
|
|
19563
|
+
resolve2(true);
|
|
19564
|
+
});
|
|
19565
|
+
});
|
|
19566
|
+
});
|
|
19567
|
+
}
|
|
19120
19568
|
function getUsedPortsInRange(minPort, maxPort) {
|
|
19121
19569
|
const used = new Set;
|
|
19122
|
-
const
|
|
19570
|
+
const os2 = platform2();
|
|
19123
19571
|
try {
|
|
19124
19572
|
let out;
|
|
19125
|
-
if (
|
|
19573
|
+
if (os2 === "linux") {
|
|
19126
19574
|
out = execSync("ss -tlnH", { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"] });
|
|
19127
19575
|
for (const line of out.split(`
|
|
19128
19576
|
`)) {
|
|
@@ -19135,7 +19583,7 @@ function getUsedPortsInRange(minPort, maxPort) {
|
|
|
19135
19583
|
if (!Number.isNaN(port) && port >= minPort && port < maxPort)
|
|
19136
19584
|
used.add(port);
|
|
19137
19585
|
}
|
|
19138
|
-
} else if (
|
|
19586
|
+
} else if (os2 === "darwin") {
|
|
19139
19587
|
out = execSync("lsof -iTCP -sTCP:LISTEN -nP", { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"] });
|
|
19140
19588
|
for (const line of out.split(`
|
|
19141
19589
|
`)) {
|
|
@@ -19147,21 +19595,29 @@ function getUsedPortsInRange(minPort, maxPort) {
|
|
|
19147
19595
|
}
|
|
19148
19596
|
}
|
|
19149
19597
|
} else {
|
|
19150
|
-
|
|
19598
|
+
log15.debug(`Port detection not supported on ${os2}. Using probe-based discovery.`);
|
|
19151
19599
|
}
|
|
19152
19600
|
} catch (error45) {
|
|
19153
19601
|
const msg = error45 instanceof Error ? error45.message : String(error45);
|
|
19154
|
-
|
|
19602
|
+
log15.debug(`Port detection failed: ${msg}. Using probe-based discovery.`);
|
|
19155
19603
|
}
|
|
19156
19604
|
return used;
|
|
19157
19605
|
}
|
|
19158
|
-
async function findAvailablePort() {
|
|
19606
|
+
async function findAvailablePort(host = "127.0.0.1") {
|
|
19159
19607
|
const minPort = DEFAULT_PORT;
|
|
19160
19608
|
const maxPort = DEFAULT_PORT + PORT_RANGE_SIZE;
|
|
19161
19609
|
const used = getUsedPortsInRange(minPort, maxPort);
|
|
19162
19610
|
for (let p = minPort;p < maxPort; p++) {
|
|
19163
|
-
if (
|
|
19611
|
+
if (used.has(p))
|
|
19612
|
+
continue;
|
|
19613
|
+
if (await isPortAvailable(p, host)) {
|
|
19164
19614
|
return p;
|
|
19615
|
+
}
|
|
19616
|
+
}
|
|
19617
|
+
for (let p = minPort;p < maxPort; p++) {
|
|
19618
|
+
if (await isPortAvailable(p, host)) {
|
|
19619
|
+
return p;
|
|
19620
|
+
}
|
|
19165
19621
|
}
|
|
19166
19622
|
throw new Error(`No available port in range ${minPort}-${maxPort - 1}`);
|
|
19167
19623
|
}
|
|
@@ -19182,8 +19638,8 @@ function createProxyServer(config2) {
|
|
|
19182
19638
|
hostname: host,
|
|
19183
19639
|
fetch(request) {
|
|
19184
19640
|
const url2 = new URL(request.url);
|
|
19185
|
-
const
|
|
19186
|
-
if (
|
|
19641
|
+
const path2 = url2.pathname;
|
|
19642
|
+
if (path2 === healthCheckPath && request.method === "GET") {
|
|
19187
19643
|
return Response.json({ ok: true });
|
|
19188
19644
|
}
|
|
19189
19645
|
return new Response("Not Found", { status: 404 });
|
|
@@ -19194,7 +19650,7 @@ function createProxyServer(config2) {
|
|
|
19194
19650
|
const err = error45 instanceof Error ? error45 : new Error(String(error45));
|
|
19195
19651
|
const isPortInUse = err.message.includes("EADDRINUSE") || err.message.includes("address already in use") || err.message.includes("port is already in use");
|
|
19196
19652
|
if (!isPortInUse) {
|
|
19197
|
-
|
|
19653
|
+
log15.debug(`Unexpected error starting on port ${port}: ${err.message}`);
|
|
19198
19654
|
}
|
|
19199
19655
|
return { success: false, error: err };
|
|
19200
19656
|
}
|
|
@@ -19210,16 +19666,16 @@ function createProxyServer(config2) {
|
|
|
19210
19666
|
if (result.success) {
|
|
19211
19667
|
port = requestedPort;
|
|
19212
19668
|
} else {
|
|
19213
|
-
|
|
19214
|
-
port = await findAvailablePort();
|
|
19669
|
+
log15.debug(`Requested port ${requestedPort} unavailable: ${result.error?.message ?? "unknown"}. Falling back to automatic port selection.`);
|
|
19670
|
+
port = await findAvailablePort(host);
|
|
19215
19671
|
const fallbackResult = tryStart(port);
|
|
19216
19672
|
if (!fallbackResult.success) {
|
|
19217
19673
|
throw new Error(`Failed to start server on port ${requestedPort} (${result.error?.message ?? "unknown"}) ` + `and fallback port ${port} (${fallbackResult.error?.message ?? "unknown"})`);
|
|
19218
19674
|
}
|
|
19219
|
-
|
|
19675
|
+
log15.debug(`Server started on fallback port ${port} instead of requested port ${requestedPort}`);
|
|
19220
19676
|
}
|
|
19221
19677
|
} else {
|
|
19222
|
-
port = await findAvailablePort();
|
|
19678
|
+
port = await findAvailablePort(host);
|
|
19223
19679
|
const result = tryStart(port);
|
|
19224
19680
|
if (!result.success) {
|
|
19225
19681
|
throw new Error(`Failed to start server on port ${port}: ${result.error?.message ?? "unknown"}`);
|
|
@@ -19561,12 +20017,12 @@ init_auth();
|
|
|
19561
20017
|
// src/commands/status.ts
|
|
19562
20018
|
init_auth();
|
|
19563
20019
|
init_logger();
|
|
19564
|
-
import { existsSync as
|
|
19565
|
-
var
|
|
20020
|
+
import { existsSync as existsSync5 } from "fs";
|
|
20021
|
+
var log16 = createLogger("status");
|
|
19566
20022
|
function checkAuthStatus() {
|
|
19567
20023
|
const authFilePath = getAuthFilePath();
|
|
19568
|
-
const exists =
|
|
19569
|
-
|
|
20024
|
+
const exists = existsSync5(authFilePath);
|
|
20025
|
+
log16.debug("Checking auth status", { path: authFilePath });
|
|
19570
20026
|
if (exists) {
|
|
19571
20027
|
return {
|
|
19572
20028
|
authenticated: true,
|