@rama_nigg/open-cursor 2.4.4 → 2.4.6
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 +2 -0
- package/dist/cli/discover.js +9 -0
- package/dist/cli/opencode-cursor.js +9 -0
- package/dist/index.js +335 -301
- package/dist/plugin-entry.js +318 -264
- package/package.json +1 -1
- package/src/auth.ts +2 -2
- package/src/client/simple.ts +3 -3
- package/src/mcp/tool-bridge.ts +4 -2
- package/src/plugin.ts +118 -181
- package/src/provider/runtime-interception.ts +40 -5
- package/src/provider/tool-schema-compat.ts +180 -2
- package/src/proxy/prompt-builder.ts +4 -35
- package/src/streaming/ai-sdk-parts.ts +8 -30
- package/src/streaming/delta-tracker.ts +42 -0
- package/src/streaming/openai-sse.ts +8 -33
- package/src/tools/defaults.ts +0 -4
- package/src/utils/binary.ts +17 -3
package/dist/index.js
CHANGED
|
@@ -456,6 +456,15 @@ function resolveCursorAgentBinary(deps = {}) {
|
|
|
456
456
|
log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
|
|
457
457
|
return "cursor-agent";
|
|
458
458
|
}
|
|
459
|
+
function formatShellCommandForPlatform(command, platform = process.platform) {
|
|
460
|
+
if (platform !== "win32") {
|
|
461
|
+
return command;
|
|
462
|
+
}
|
|
463
|
+
if (command.startsWith('"') && command.endsWith('"')) {
|
|
464
|
+
return command;
|
|
465
|
+
}
|
|
466
|
+
return `"${command}"`;
|
|
467
|
+
}
|
|
459
468
|
var log;
|
|
460
469
|
var init_binary = __esm(() => {
|
|
461
470
|
init_logger();
|
|
@@ -505,7 +514,7 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
|
|
|
505
514
|
async function startCursorOAuth() {
|
|
506
515
|
return new Promise((resolve, reject) => {
|
|
507
516
|
log2.info("Starting cursor-cli login process");
|
|
508
|
-
const proc = spawn(resolveCursorAgentBinary(), ["login"], {
|
|
517
|
+
const proc = spawn(formatShellCommandForPlatform(resolveCursorAgentBinary()), ["login"], {
|
|
509
518
|
stdio: ["pipe", "pipe", "pipe"],
|
|
510
519
|
shell: process.platform === "win32"
|
|
511
520
|
});
|
|
@@ -699,29 +708,6 @@ class LineBuffer {
|
|
|
699
708
|
}
|
|
700
709
|
}
|
|
701
710
|
|
|
702
|
-
// src/streaming/types.ts
|
|
703
|
-
var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
|
|
704
|
-
if (event.type === "thinking") {
|
|
705
|
-
return true;
|
|
706
|
-
}
|
|
707
|
-
return event.type === "assistant" && hasThinkingContent(event);
|
|
708
|
-
}, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
|
|
709
|
-
if (event.type === "thinking") {
|
|
710
|
-
return event.text ?? "";
|
|
711
|
-
}
|
|
712
|
-
return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
|
|
713
|
-
}, inferToolName = (event) => {
|
|
714
|
-
const [key] = Object.keys(event.tool_call ?? {});
|
|
715
|
-
if (!key) {
|
|
716
|
-
return "";
|
|
717
|
-
}
|
|
718
|
-
if (key.endsWith("ToolCall")) {
|
|
719
|
-
const base = key.slice(0, -"ToolCall".length);
|
|
720
|
-
return base.charAt(0).toLowerCase() + base.slice(1);
|
|
721
|
-
}
|
|
722
|
-
return key;
|
|
723
|
-
};
|
|
724
|
-
|
|
725
711
|
// src/streaming/delta-tracker.ts
|
|
726
712
|
class DeltaTracker {
|
|
727
713
|
lastText = "";
|
|
@@ -759,14 +745,70 @@ class DeltaTracker {
|
|
|
759
745
|
}
|
|
760
746
|
}
|
|
761
747
|
|
|
748
|
+
class MixedDeltaTracker {
|
|
749
|
+
emittedText = "";
|
|
750
|
+
emittedThinking = "";
|
|
751
|
+
nextText(value) {
|
|
752
|
+
const delta = this.diff(this.emittedText, value);
|
|
753
|
+
if (delta) {
|
|
754
|
+
this.emittedText += delta;
|
|
755
|
+
}
|
|
756
|
+
return delta;
|
|
757
|
+
}
|
|
758
|
+
nextThinking(value) {
|
|
759
|
+
const delta = this.diff(this.emittedThinking, value);
|
|
760
|
+
if (delta) {
|
|
761
|
+
this.emittedThinking += delta;
|
|
762
|
+
}
|
|
763
|
+
return delta;
|
|
764
|
+
}
|
|
765
|
+
reset() {
|
|
766
|
+
this.emittedText = "";
|
|
767
|
+
this.emittedThinking = "";
|
|
768
|
+
}
|
|
769
|
+
diff(emitted, current) {
|
|
770
|
+
if (!emitted) {
|
|
771
|
+
return current;
|
|
772
|
+
}
|
|
773
|
+
if (current.startsWith(emitted)) {
|
|
774
|
+
return current.slice(emitted.length);
|
|
775
|
+
}
|
|
776
|
+
if (emitted.startsWith(current)) {
|
|
777
|
+
return "";
|
|
778
|
+
}
|
|
779
|
+
return current;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/streaming/types.ts
|
|
784
|
+
var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
|
|
785
|
+
if (event.type === "thinking") {
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
return event.type === "assistant" && hasThinkingContent(event);
|
|
789
|
+
}, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
|
|
790
|
+
if (event.type === "thinking") {
|
|
791
|
+
return event.text ?? "";
|
|
792
|
+
}
|
|
793
|
+
return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
|
|
794
|
+
}, inferToolName = (event) => {
|
|
795
|
+
const [key] = Object.keys(event.tool_call ?? {});
|
|
796
|
+
if (!key) {
|
|
797
|
+
return "";
|
|
798
|
+
}
|
|
799
|
+
if (key.endsWith("ToolCall")) {
|
|
800
|
+
const base = key.slice(0, -"ToolCall".length);
|
|
801
|
+
return base.charAt(0).toLowerCase() + base.slice(1);
|
|
802
|
+
}
|
|
803
|
+
return key;
|
|
804
|
+
};
|
|
805
|
+
|
|
762
806
|
// src/streaming/openai-sse.ts
|
|
763
807
|
class StreamToSseConverter {
|
|
764
808
|
id;
|
|
765
809
|
created;
|
|
766
810
|
model;
|
|
767
|
-
tracker = new
|
|
768
|
-
sawAssistantPartials = false;
|
|
769
|
-
sawThinkingPartials = false;
|
|
811
|
+
tracker = new MixedDeltaTracker;
|
|
770
812
|
constructor(model, options) {
|
|
771
813
|
this.model = model;
|
|
772
814
|
this.id = options?.id ?? `cursor-acp-${Date.now()}`;
|
|
@@ -774,35 +816,17 @@ class StreamToSseConverter {
|
|
|
774
816
|
}
|
|
775
817
|
handleEvent(event) {
|
|
776
818
|
if (isAssistantText(event)) {
|
|
777
|
-
const
|
|
778
|
-
if (
|
|
779
|
-
const text = extractText(event);
|
|
780
|
-
if (text) {
|
|
781
|
-
this.sawAssistantPartials = true;
|
|
782
|
-
return [this.chunkWith({ content: text })];
|
|
783
|
-
}
|
|
784
|
-
return [];
|
|
785
|
-
}
|
|
786
|
-
if (this.sawAssistantPartials) {
|
|
819
|
+
const text = extractText(event);
|
|
820
|
+
if (!text)
|
|
787
821
|
return [];
|
|
788
|
-
|
|
789
|
-
const delta = this.tracker.nextText(extractText(event));
|
|
822
|
+
const delta = this.tracker.nextText(text);
|
|
790
823
|
return delta ? [this.chunkWith({ content: delta })] : [];
|
|
791
824
|
}
|
|
792
825
|
if (isThinking(event)) {
|
|
793
|
-
const
|
|
794
|
-
if (
|
|
795
|
-
const text = extractThinking(event);
|
|
796
|
-
if (text) {
|
|
797
|
-
this.sawThinkingPartials = true;
|
|
798
|
-
return [this.chunkWith({ reasoning_content: text })];
|
|
799
|
-
}
|
|
800
|
-
return [];
|
|
801
|
-
}
|
|
802
|
-
if (this.sawThinkingPartials) {
|
|
826
|
+
const text = extractThinking(event);
|
|
827
|
+
if (!text)
|
|
803
828
|
return [];
|
|
804
|
-
|
|
805
|
-
const delta = this.tracker.nextThinking(extractThinking(event));
|
|
829
|
+
const delta = this.tracker.nextThinking(text);
|
|
806
830
|
return delta ? [this.chunkWith({ reasoning_content: delta })] : [];
|
|
807
831
|
}
|
|
808
832
|
if (isToolCall(event)) {
|
|
@@ -978,27 +1002,6 @@ var init_perf = __esm(() => {
|
|
|
978
1002
|
});
|
|
979
1003
|
|
|
980
1004
|
// src/proxy/prompt-builder.ts
|
|
981
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
982
|
-
import { homedir as homedir3 } from "node:os";
|
|
983
|
-
import { join as join4 } from "node:path";
|
|
984
|
-
function ensureLogDir2() {
|
|
985
|
-
try {
|
|
986
|
-
if (!existsSync3(DEBUG_LOG_DIR)) {
|
|
987
|
-
mkdirSync2(DEBUG_LOG_DIR, { recursive: true });
|
|
988
|
-
}
|
|
989
|
-
} catch {}
|
|
990
|
-
}
|
|
991
|
-
function debugLogToFile(message, data) {
|
|
992
|
-
try {
|
|
993
|
-
ensureLogDir2();
|
|
994
|
-
const timestamp = new Date().toISOString();
|
|
995
|
-
const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
|
|
996
|
-
`;
|
|
997
|
-
appendFileSync2(DEBUG_LOG_FILE, logLine);
|
|
998
|
-
} catch (err) {
|
|
999
|
-
log5.debug(message, data);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
1005
|
function buildPromptFromMessages(messages, tools, subagentNames = []) {
|
|
1003
1006
|
const messageSummary = messages.map((m, i) => {
|
|
1004
1007
|
const role = m?.role ?? "?";
|
|
@@ -1012,7 +1015,7 @@ function buildPromptFromMessages(messages, tools, subagentNames = []) {
|
|
|
1012
1015
|
const assistantWithToolCalls = messages.filter((m) => m?.role === "assistant" && Array.isArray(m?.tool_calls) && m.tool_calls.length > 0);
|
|
1013
1016
|
const assistantEmpty = messages.filter((m) => m?.role === "assistant" && (!m?.tool_calls || m.tool_calls.length === 0) && (!m?.content || m.content === "" || m.content === null));
|
|
1014
1017
|
const toolResults = messages.filter((m) => m?.role === "tool");
|
|
1015
|
-
|
|
1018
|
+
log5.debug("buildPromptFromMessages", {
|
|
1016
1019
|
totalMessages: messages.length,
|
|
1017
1020
|
totalTools: tools.length,
|
|
1018
1021
|
messageSummary,
|
|
@@ -1107,22 +1110,20 @@ ${toolDescs}`);
|
|
|
1107
1110
|
const finalPrompt = lines.join(`
|
|
1108
1111
|
|
|
1109
1112
|
`);
|
|
1110
|
-
|
|
1113
|
+
log5.debug("buildPromptFromMessages: final prompt", {
|
|
1111
1114
|
lineCount: lines.length,
|
|
1112
1115
|
promptLength: finalPrompt.length,
|
|
1113
1116
|
promptPreview: finalPrompt.slice(0, 500),
|
|
1114
1117
|
hasToolResultFormat: finalPrompt.includes("TOOL_RESULT"),
|
|
1115
1118
|
hasAssistantToolCallFormat: finalPrompt.includes("tool_call(id:"),
|
|
1116
|
-
hasCompletionSignal: finalPrompt.includes("
|
|
1119
|
+
hasCompletionSignal: finalPrompt.includes("The above tool calls have been executed")
|
|
1117
1120
|
});
|
|
1118
1121
|
return finalPrompt;
|
|
1119
1122
|
}
|
|
1120
|
-
var log5
|
|
1123
|
+
var log5;
|
|
1121
1124
|
var init_prompt_builder = __esm(() => {
|
|
1122
1125
|
init_logger();
|
|
1123
1126
|
log5 = createLogger("proxy:prompt-builder");
|
|
1124
|
-
DEBUG_LOG_DIR = join4(homedir3(), ".config", "opencode", "logs");
|
|
1125
|
-
DEBUG_LOG_FILE = join4(DEBUG_LOG_DIR, "tool-loop-debug.log");
|
|
1126
1127
|
});
|
|
1127
1128
|
|
|
1128
1129
|
// src/proxy/tool-loop.ts
|
|
@@ -1803,9 +1804,9 @@ var init_model_discovery = __esm(() => {
|
|
|
1803
1804
|
});
|
|
1804
1805
|
|
|
1805
1806
|
// src/plugin-toggle.ts
|
|
1806
|
-
import { existsSync as
|
|
1807
|
-
import { homedir as
|
|
1808
|
-
import { join as
|
|
1807
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
1808
|
+
import { homedir as homedir3 } from "os";
|
|
1809
|
+
import { join as join4, resolve } from "path";
|
|
1809
1810
|
function matchesPlugin(entry) {
|
|
1810
1811
|
if (entry === CURSOR_PROVIDER_ID)
|
|
1811
1812
|
return true;
|
|
@@ -1819,8 +1820,8 @@ function resolveOpenCodeConfigPath(env = process.env) {
|
|
|
1819
1820
|
if (env.OPENCODE_CONFIG && env.OPENCODE_CONFIG.length > 0) {
|
|
1820
1821
|
return resolve(env.OPENCODE_CONFIG);
|
|
1821
1822
|
}
|
|
1822
|
-
const configHome = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0 ? env.XDG_CONFIG_HOME :
|
|
1823
|
-
return
|
|
1823
|
+
const configHome = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0 ? env.XDG_CONFIG_HOME : join4(homedir3(), ".config");
|
|
1824
|
+
return join4(configHome, "opencode", "opencode.json");
|
|
1824
1825
|
}
|
|
1825
1826
|
function isCursorPluginEnabledInConfig(config) {
|
|
1826
1827
|
if (!config || typeof config !== "object") {
|
|
@@ -1839,7 +1840,7 @@ function isCursorPluginEnabledInConfig(config) {
|
|
|
1839
1840
|
}
|
|
1840
1841
|
function shouldEnableCursorPlugin(env = process.env) {
|
|
1841
1842
|
const configPath = resolveOpenCodeConfigPath(env);
|
|
1842
|
-
if (!
|
|
1843
|
+
if (!existsSync3(configPath)) {
|
|
1843
1844
|
return {
|
|
1844
1845
|
enabled: true,
|
|
1845
1846
|
configPath,
|
|
@@ -11975,7 +11976,7 @@ function buildMcpToolDefinitions(tools) {
|
|
|
11975
11976
|
function namespaceMcpTool(serverName, toolName) {
|
|
11976
11977
|
const sanitizedServer = serverName.replace(/[^a-zA-Z0-9]/g, "_");
|
|
11977
11978
|
const sanitizedTool = toolName.replace(/[^a-zA-Z0-9]/g, "_");
|
|
11978
|
-
return
|
|
11979
|
+
return `${MCP_TOOL_PREFIX}${sanitizedServer}__${sanitizedTool}`;
|
|
11979
11980
|
}
|
|
11980
11981
|
function mcpSchemaToZod(inputSchema, z2) {
|
|
11981
11982
|
if (!inputSchema || typeof inputSchema !== "object") {
|
|
@@ -12017,7 +12018,7 @@ function mcpSchemaToZod(inputSchema, z2) {
|
|
|
12017
12018
|
}
|
|
12018
12019
|
return shape;
|
|
12019
12020
|
}
|
|
12020
|
-
var log13;
|
|
12021
|
+
var log13, MCP_TOOL_PREFIX = "mcp__";
|
|
12021
12022
|
var init_tool_bridge = __esm(() => {
|
|
12022
12023
|
init_logger();
|
|
12023
12024
|
log13 = createLogger("mcp:tool-bridge");
|
|
@@ -14022,9 +14023,6 @@ function resolveEditArguments(args) {
|
|
|
14022
14023
|
newString = fallbackContent;
|
|
14023
14024
|
}
|
|
14024
14025
|
}
|
|
14025
|
-
if (oldString === undefined && newString !== undefined) {
|
|
14026
|
-
oldString = "";
|
|
14027
|
-
}
|
|
14028
14026
|
return {
|
|
14029
14027
|
path: path2,
|
|
14030
14028
|
old_string: oldString,
|
|
@@ -14431,7 +14429,10 @@ function applyToolSchemaCompat(toolCall, toolSchemaMap) {
|
|
|
14431
14429
|
const toolSpecificArgs = normalizeToolSpecificArgs(toolCall.function.name, normalizedArgs);
|
|
14432
14430
|
const schema = toolSchemaMap.get(toolCall.function.name);
|
|
14433
14431
|
const sanitization = sanitizeArgumentsForSchema(toolSpecificArgs, schema);
|
|
14434
|
-
const validation = validateToolArguments(toolCall.function.name, sanitization.args, schema, sanitization.unexpected
|
|
14432
|
+
const validation = validateToolArguments(toolCall.function.name, sanitization.args, schema, sanitization.unexpected, {
|
|
14433
|
+
originalArgs: parsedArgs,
|
|
14434
|
+
writeSchema: toolSchemaMap.get("write")
|
|
14435
|
+
});
|
|
14435
14436
|
const normalizedToolCall = {
|
|
14436
14437
|
...toolCall,
|
|
14437
14438
|
function: {
|
|
@@ -14442,12 +14443,59 @@ function applyToolSchemaCompat(toolCall, toolSchemaMap) {
|
|
|
14442
14443
|
return {
|
|
14443
14444
|
toolCall: normalizedToolCall,
|
|
14444
14445
|
normalizedArgs: sanitization.args,
|
|
14446
|
+
originalArgs: parsedArgs,
|
|
14445
14447
|
originalArgKeys,
|
|
14446
14448
|
normalizedArgKeys: Object.keys(sanitization.args),
|
|
14447
14449
|
collisionKeys,
|
|
14448
14450
|
validation
|
|
14449
14451
|
};
|
|
14450
14452
|
}
|
|
14453
|
+
function isFullFileShapedEditValidationFailure(toolName, args, validation, originalArgs, writeSchema) {
|
|
14454
|
+
if (toolName.toLowerCase() !== "edit" || validation.ok) {
|
|
14455
|
+
return false;
|
|
14456
|
+
}
|
|
14457
|
+
return buildEditFullFileHint(args, validation.missing, validation.typeErrors, {
|
|
14458
|
+
originalArgs,
|
|
14459
|
+
writeSchema
|
|
14460
|
+
}) !== null;
|
|
14461
|
+
}
|
|
14462
|
+
function buildWriteArguments(filePath, content, writeSchema) {
|
|
14463
|
+
if (!isRecord3(writeSchema)) {
|
|
14464
|
+
return { path: filePath, content };
|
|
14465
|
+
}
|
|
14466
|
+
const required = Array.isArray(writeSchema.required) ? writeSchema.required.filter((value) => typeof value === "string") : [];
|
|
14467
|
+
if (required.includes("filePath")) {
|
|
14468
|
+
return { filePath, content };
|
|
14469
|
+
}
|
|
14470
|
+
return { path: filePath, content };
|
|
14471
|
+
}
|
|
14472
|
+
function tryRerouteEditToWrite(toolCall, compat, allowedToolNames, toolSchemaMap) {
|
|
14473
|
+
if (toolCall.function.name.toLowerCase() !== "edit") {
|
|
14474
|
+
return null;
|
|
14475
|
+
}
|
|
14476
|
+
if (!allowedToolNames.has("write") || !toolSchemaMap.has("write")) {
|
|
14477
|
+
return null;
|
|
14478
|
+
}
|
|
14479
|
+
const writeSchema = toolSchemaMap.get("write");
|
|
14480
|
+
if (!isFullFileShapedEditValidationFailure(toolCall.function.name, compat.normalizedArgs, compat.validation, compat.originalArgs, writeSchema)) {
|
|
14481
|
+
return null;
|
|
14482
|
+
}
|
|
14483
|
+
const filePath = typeof compat.normalizedArgs.path === "string" && compat.normalizedArgs.path.length > 0 ? compat.normalizedArgs.path : typeof compat.normalizedArgs.filePath === "string" && compat.normalizedArgs.filePath.length > 0 ? compat.normalizedArgs.filePath : null;
|
|
14484
|
+
if (!filePath) {
|
|
14485
|
+
return null;
|
|
14486
|
+
}
|
|
14487
|
+
const content = typeof compat.normalizedArgs.new_string === "string" ? compat.normalizedArgs.new_string : typeof compat.normalizedArgs.newString === "string" ? compat.normalizedArgs.newString : typeof compat.normalizedArgs.content === "string" ? compat.normalizedArgs.content : null;
|
|
14488
|
+
if (content === null) {
|
|
14489
|
+
return null;
|
|
14490
|
+
}
|
|
14491
|
+
return {
|
|
14492
|
+
...toolCall,
|
|
14493
|
+
function: {
|
|
14494
|
+
name: "write",
|
|
14495
|
+
arguments: JSON.stringify(buildWriteArguments(filePath, content, writeSchema))
|
|
14496
|
+
}
|
|
14497
|
+
};
|
|
14498
|
+
}
|
|
14451
14499
|
function parseArguments(rawArguments) {
|
|
14452
14500
|
try {
|
|
14453
14501
|
const parsed = JSON.parse(rawArguments);
|
|
@@ -14630,7 +14678,7 @@ function sanitizeArgumentsForSchema(args, schema) {
|
|
|
14630
14678
|
}
|
|
14631
14679
|
return { args: sanitized, unexpected };
|
|
14632
14680
|
}
|
|
14633
|
-
function validateToolArguments(toolName, args, schema, unexpected) {
|
|
14681
|
+
function validateToolArguments(toolName, args, schema, unexpected, context = {}) {
|
|
14634
14682
|
if (!isRecord3(schema)) {
|
|
14635
14683
|
return {
|
|
14636
14684
|
hasSchema: false,
|
|
@@ -14666,10 +14714,59 @@ function validateToolArguments(toolName, args, schema, unexpected) {
|
|
|
14666
14714
|
missing,
|
|
14667
14715
|
unexpected,
|
|
14668
14716
|
typeErrors,
|
|
14669
|
-
repairHint: ok ? undefined : buildRepairHint(toolName, missing, unexpected, typeErrors)
|
|
14717
|
+
repairHint: ok ? undefined : buildRepairHint(toolName, args, missing, unexpected, typeErrors, context)
|
|
14670
14718
|
};
|
|
14671
14719
|
}
|
|
14672
|
-
function
|
|
14720
|
+
function hadOldStringPropertyInPayload(args) {
|
|
14721
|
+
for (const key of Object.keys(args)) {
|
|
14722
|
+
const token = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
14723
|
+
if (token === "oldstring") {
|
|
14724
|
+
return true;
|
|
14725
|
+
}
|
|
14726
|
+
}
|
|
14727
|
+
return false;
|
|
14728
|
+
}
|
|
14729
|
+
function hasEditFilePath(args) {
|
|
14730
|
+
const pathValue = args.path ?? args.filePath;
|
|
14731
|
+
return typeof pathValue === "string" && pathValue.trim().length > 0;
|
|
14732
|
+
}
|
|
14733
|
+
function hasEditBody(args) {
|
|
14734
|
+
const body = args.new_string ?? args.newString ?? args.content;
|
|
14735
|
+
return typeof body === "string" && body.length > 0;
|
|
14736
|
+
}
|
|
14737
|
+
function writeToolExample(writeSchema) {
|
|
14738
|
+
if (!isRecord3(writeSchema)) {
|
|
14739
|
+
return "write with path and content";
|
|
14740
|
+
}
|
|
14741
|
+
const required = Array.isArray(writeSchema.required) ? writeSchema.required.filter((value) => typeof value === "string") : [];
|
|
14742
|
+
if (required.includes("filePath")) {
|
|
14743
|
+
return "write with filePath and content";
|
|
14744
|
+
}
|
|
14745
|
+
return "write with path and content";
|
|
14746
|
+
}
|
|
14747
|
+
function buildEditFullFileHint(args, missing, typeErrors, context) {
|
|
14748
|
+
if (typeErrors.length > 0) {
|
|
14749
|
+
return null;
|
|
14750
|
+
}
|
|
14751
|
+
const missingOldStringOnly = (missing.includes("old_string") || missing.includes("oldString")) && missing.every((key) => key === "old_string" || key === "oldString");
|
|
14752
|
+
if (!missingOldStringOnly) {
|
|
14753
|
+
return null;
|
|
14754
|
+
}
|
|
14755
|
+
const originalArgs = context.originalArgs ?? {};
|
|
14756
|
+
if (hadOldStringPropertyInPayload(originalArgs)) {
|
|
14757
|
+
return null;
|
|
14758
|
+
}
|
|
14759
|
+
if (!hasEditFilePath(args) || !hasEditBody(args)) {
|
|
14760
|
+
return null;
|
|
14761
|
+
}
|
|
14762
|
+
const example = writeToolExample(context.writeSchema);
|
|
14763
|
+
return `For a full file body, use ${example} instead of edit without old_string`;
|
|
14764
|
+
}
|
|
14765
|
+
function buildRepairHint(toolName, args, missing, unexpected, typeErrors, context = {}) {
|
|
14766
|
+
const fullFileHint = toolName.toLowerCase() === "edit" ? buildEditFullFileHint(args, missing, typeErrors, context) : null;
|
|
14767
|
+
if (fullFileHint) {
|
|
14768
|
+
return fullFileHint;
|
|
14769
|
+
}
|
|
14673
14770
|
const hints = [];
|
|
14674
14771
|
if (missing.length > 0) {
|
|
14675
14772
|
hints.push(`missing required: ${missing.join(", ")}`);
|
|
@@ -14680,7 +14777,7 @@ function buildRepairHint(toolName, missing, unexpected, typeErrors) {
|
|
|
14680
14777
|
if (typeErrors.length > 0) {
|
|
14681
14778
|
hints.push(`fix type errors: ${typeErrors.join("; ")}`);
|
|
14682
14779
|
}
|
|
14683
|
-
if (toolName.toLowerCase() === "edit" && (missing.includes("old_string") || missing.includes("new_string"))) {
|
|
14780
|
+
if (toolName.toLowerCase() === "edit" && (missing.includes("old_string") || missing.includes("oldString") || missing.includes("new_string") || missing.includes("newString"))) {
|
|
14684
14781
|
hints.push("edit requires path, old_string, and new_string");
|
|
14685
14782
|
}
|
|
14686
14783
|
return hints.join(" | ");
|
|
@@ -14867,6 +14964,14 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14867
14964
|
}
|
|
14868
14965
|
return { intercepted: false, skipConverter: true, terminate: validationTermination };
|
|
14869
14966
|
}
|
|
14967
|
+
const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat, allowedToolNames, toolSchemaMap);
|
|
14968
|
+
if (reroutedWrite) {
|
|
14969
|
+
log18.debug("Rerouting malformed edit call to write (legacy)", {
|
|
14970
|
+
missing: compat.validation.missing
|
|
14971
|
+
});
|
|
14972
|
+
await onInterceptedToolCall(reroutedWrite);
|
|
14973
|
+
return { intercepted: true, skipConverter: true };
|
|
14974
|
+
}
|
|
14870
14975
|
if (shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
|
|
14871
14976
|
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
|
|
14872
14977
|
log18.debug("Emitting non-fatal schema validation hint in legacy and skipping malformed tool execution", {
|
|
@@ -15018,6 +15123,18 @@ async function handleToolLoopEventV1(options) {
|
|
|
15018
15123
|
terminate: createSchemaValidationTermination(normalizedToolCall, compat.validation)
|
|
15019
15124
|
};
|
|
15020
15125
|
}
|
|
15126
|
+
const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat, allowedToolNames, toolSchemaMap);
|
|
15127
|
+
if (reroutedWrite) {
|
|
15128
|
+
log18.debug("Rerouting malformed edit call to write", {
|
|
15129
|
+
missing: compat.validation.missing,
|
|
15130
|
+
typeErrors: compat.validation.typeErrors
|
|
15131
|
+
});
|
|
15132
|
+
await onInterceptedToolCall(reroutedWrite);
|
|
15133
|
+
return {
|
|
15134
|
+
intercepted: true,
|
|
15135
|
+
skipConverter: true
|
|
15136
|
+
};
|
|
15137
|
+
}
|
|
15021
15138
|
if (schemaValidationFailureMode === "pass_through" && shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
|
|
15022
15139
|
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
|
|
15023
15140
|
log18.debug("Emitting non-fatal schema validation hint and skipping malformed tool execution", {
|
|
@@ -15228,8 +15345,9 @@ function shouldTerminateOnSchemaValidation(toolCall, validation) {
|
|
|
15228
15345
|
}
|
|
15229
15346
|
function createNonFatalSchemaValidationHintChunk(meta, toolCall, validation) {
|
|
15230
15347
|
const termination = createSchemaValidationTermination(toolCall, validation);
|
|
15231
|
-
const
|
|
15232
|
-
const
|
|
15348
|
+
const fallbackHint = "Use write for full-file replacement, or provide path, old_string, and new_string for edit.";
|
|
15349
|
+
const suffix = termination.repairHint ? "" : ` ${fallbackHint}`;
|
|
15350
|
+
const content = `Skipped malformed tool call "${toolCall.function.name}": ${termination.message}${suffix}`.trim();
|
|
15233
15351
|
return {
|
|
15234
15352
|
id: meta.id,
|
|
15235
15353
|
object: "chat.completion.chunk",
|
|
@@ -15870,25 +15988,13 @@ __export(exports_plugin, {
|
|
|
15870
15988
|
CursorPlugin: () => CursorPlugin
|
|
15871
15989
|
});
|
|
15872
15990
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
15873
|
-
import {
|
|
15991
|
+
import { realpathSync } from "fs";
|
|
15874
15992
|
import { mkdir } from "fs/promises";
|
|
15875
|
-
import { homedir as
|
|
15876
|
-
import { isAbsolute, join as
|
|
15877
|
-
function
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
mkdir(DEBUG_LOG_DIR2, { recursive: true }).catch(() => {});
|
|
15881
|
-
}
|
|
15882
|
-
} catch {}
|
|
15883
|
-
}
|
|
15884
|
-
function debugLogToFile2(message, data) {
|
|
15885
|
-
try {
|
|
15886
|
-
ensureDebugLogDir();
|
|
15887
|
-
const timestamp = new Date().toISOString();
|
|
15888
|
-
const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
|
|
15889
|
-
`;
|
|
15890
|
-
appendFileSync3(DEBUG_LOG_FILE2, logLine);
|
|
15891
|
-
} catch {}
|
|
15993
|
+
import { homedir as homedir4 } from "os";
|
|
15994
|
+
import { isAbsolute, join as join5, relative, resolve as resolve2 } from "path";
|
|
15995
|
+
function getMcpToolDefinitionName(mcpToolDefs, index) {
|
|
15996
|
+
const name = mcpToolDefs[index]?.function?.name;
|
|
15997
|
+
return typeof name === "string" && name.length > 0 ? name : undefined;
|
|
15892
15998
|
}
|
|
15893
15999
|
function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDefs, mcpToolSummaries, subagentNames = []) {
|
|
15894
16000
|
const parts = [];
|
|
@@ -15898,27 +16004,27 @@ function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDef
|
|
|
15898
16004
|
parts.push(`Available OpenCode tools (use via tool calls): ${names}. Original skill ids mapped as: ${mapping}. Aliases include oc_skill_* and oc_superskill_* when applicable.`);
|
|
15899
16005
|
}
|
|
15900
16006
|
if (mcpToolSummaries && mcpToolSummaries.length > 0) {
|
|
16007
|
+
const summariesWithCallNames = mcpToolSummaries.map((summary, index) => ({
|
|
16008
|
+
...summary,
|
|
16009
|
+
callName: summary.callName ?? getMcpToolDefinitionName(mcpToolDefs, index) ?? namespaceMcpTool(summary.serverName, summary.toolName)
|
|
16010
|
+
}));
|
|
15901
16011
|
const servers = new Map;
|
|
15902
|
-
for (const s of
|
|
16012
|
+
for (const s of summariesWithCallNames) {
|
|
15903
16013
|
const list = servers.get(s.serverName) ?? [];
|
|
15904
16014
|
list.push(s);
|
|
15905
16015
|
servers.set(s.serverName, list);
|
|
15906
16016
|
}
|
|
15907
16017
|
const lines = [
|
|
15908
|
-
|
|
15909
|
-
"
|
|
16018
|
+
`MCP TOOLS — Use via direct tool calls (\`${MCP_TOOL_PREFIX}<server>__<tool>\`).`,
|
|
16019
|
+
"These tools are exposed as first-class tool calls (e.g. mcp__filesystem__read_file).",
|
|
15910
16020
|
""
|
|
15911
16021
|
];
|
|
15912
16022
|
for (const [server2, tools] of servers) {
|
|
15913
16023
|
lines.push(`Server: ${server2}`);
|
|
15914
16024
|
for (const t of tools) {
|
|
15915
16025
|
const paramHint = t.params?.length ? ` (params: ${t.params.join(", ")})` : "";
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
if (tools.length > 0) {
|
|
15919
|
-
const ex = tools[0];
|
|
15920
|
-
const exArgs = ex.params?.length ? ` '{"${ex.params[0]}":"..."}'` : "";
|
|
15921
|
-
lines.push(` Example: mcptool call ${server2} ${ex.toolName}${exArgs}`);
|
|
16026
|
+
const sourceHint = t.callName === t.toolName ? "" : ` (server: ${t.serverName}; tool: ${t.toolName})`;
|
|
16027
|
+
lines.push(` - ${t.callName}${paramHint}${t.description ? " — " + t.description : ""}${sourceHint}`);
|
|
15922
16028
|
}
|
|
15923
16029
|
lines.push("");
|
|
15924
16030
|
}
|
|
@@ -15933,8 +16039,8 @@ function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDef
|
|
|
15933
16039
|
`) : null;
|
|
15934
16040
|
}
|
|
15935
16041
|
async function ensurePluginDirectory() {
|
|
15936
|
-
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) :
|
|
15937
|
-
const pluginDir =
|
|
16042
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join5(homedir4(), ".config");
|
|
16043
|
+
const pluginDir = join5(configHome, "opencode", "plugin");
|
|
15938
16044
|
try {
|
|
15939
16045
|
await mkdir(pluginDir, { recursive: true });
|
|
15940
16046
|
log20.debug("Plugin directory ensured", { path: pluginDir });
|
|
@@ -15951,8 +16057,8 @@ function getGlobalKey() {
|
|
|
15951
16057
|
return "__opencode_cursor_proxy_server__";
|
|
15952
16058
|
}
|
|
15953
16059
|
function getOpenCodeConfigPrefix() {
|
|
15954
|
-
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) :
|
|
15955
|
-
return
|
|
16060
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join5(homedir4(), ".config");
|
|
16061
|
+
return join5(configHome, "opencode");
|
|
15956
16062
|
}
|
|
15957
16063
|
function canonicalizePathForCompare(pathValue) {
|
|
15958
16064
|
const resolvedPath = resolve2(pathValue);
|
|
@@ -16029,7 +16135,7 @@ function resolveWorkspaceDirectory(worktree, directory) {
|
|
|
16029
16135
|
if (isAcceptableWorkspace(cwd, configPrefix)) {
|
|
16030
16136
|
return cwd;
|
|
16031
16137
|
}
|
|
16032
|
-
const home = resolveCandidate(
|
|
16138
|
+
const home = resolveCandidate(homedir4());
|
|
16033
16139
|
if (home && !isRootPath(home)) {
|
|
16034
16140
|
return home;
|
|
16035
16141
|
}
|
|
@@ -16110,6 +16216,7 @@ function extractCompletionFromStream(output) {
|
|
|
16110
16216
|
let usage;
|
|
16111
16217
|
let sawAssistantPartials = false;
|
|
16112
16218
|
let sawThinkingPartials = false;
|
|
16219
|
+
const tracker = new MixedDeltaTracker;
|
|
16113
16220
|
for (const line of lines) {
|
|
16114
16221
|
const event = parseStreamJsonLine(line);
|
|
16115
16222
|
if (!event) {
|
|
@@ -16121,8 +16228,8 @@ function extractCompletionFromStream(output) {
|
|
|
16121
16228
|
continue;
|
|
16122
16229
|
const isPartial = typeof event.timestamp_ms === "number";
|
|
16123
16230
|
if (isPartial) {
|
|
16124
|
-
assistantText += text;
|
|
16125
16231
|
sawAssistantPartials = true;
|
|
16232
|
+
assistantText += tracker.nextText(text);
|
|
16126
16233
|
} else if (!sawAssistantPartials) {
|
|
16127
16234
|
assistantText = text;
|
|
16128
16235
|
}
|
|
@@ -16132,8 +16239,8 @@ function extractCompletionFromStream(output) {
|
|
|
16132
16239
|
if (thinking) {
|
|
16133
16240
|
const isPartial = typeof event.timestamp_ms === "number";
|
|
16134
16241
|
if (isPartial) {
|
|
16135
|
-
reasoningText += thinking;
|
|
16136
16242
|
sawThinkingPartials = true;
|
|
16243
|
+
reasoningText += tracker.nextThinking(thinking);
|
|
16137
16244
|
} else if (!sawThinkingPartials) {
|
|
16138
16245
|
reasoningText = thinking;
|
|
16139
16246
|
}
|
|
@@ -16330,7 +16437,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16330
16437
|
const messages = Array.isArray(body?.messages) ? body.messages : [];
|
|
16331
16438
|
const stream = body?.stream === true;
|
|
16332
16439
|
const tools = Array.isArray(body?.tools) ? body.tools : [];
|
|
16333
|
-
|
|
16440
|
+
log20.debug("raw request body", {
|
|
16334
16441
|
model: body?.model,
|
|
16335
16442
|
cursorModel: body?.cursorModel,
|
|
16336
16443
|
stream,
|
|
@@ -16820,7 +16927,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16820
16927
|
if (FORCE_TOOL_MODE) {
|
|
16821
16928
|
cmd.push("--force");
|
|
16822
16929
|
}
|
|
16823
|
-
const child = spawn3(cmd[0], cmd.slice(1), {
|
|
16930
|
+
const child = spawn3(formatShellCommandForPlatform(cmd[0]), cmd.slice(1), {
|
|
16824
16931
|
stdio: ["pipe", "pipe", "pipe"],
|
|
16825
16932
|
shell: process.platform === "win32"
|
|
16826
16933
|
});
|
|
@@ -16967,22 +17074,18 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16967
17074
|
child.kill();
|
|
16968
17075
|
} catch {}
|
|
16969
17076
|
};
|
|
16970
|
-
|
|
16971
|
-
|
|
16972
|
-
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
for (const line of lineBuffer.push(chunk)) {
|
|
16979
|
-
if (streamTerminated || res.writableEnded) {
|
|
17077
|
+
const chunkQueue = [];
|
|
17078
|
+
let draining = false;
|
|
17079
|
+
let childClosed = false;
|
|
17080
|
+
let childCloseHandled = false;
|
|
17081
|
+
let childExitCode = null;
|
|
17082
|
+
const processLines = async (lines) => {
|
|
17083
|
+
for (const line of lines) {
|
|
17084
|
+
if (streamTerminated || res.writableEnded)
|
|
16980
17085
|
break;
|
|
16981
|
-
}
|
|
16982
17086
|
const event = parseStreamJsonLine(line);
|
|
16983
|
-
if (!event)
|
|
17087
|
+
if (!event)
|
|
16984
17088
|
continue;
|
|
16985
|
-
}
|
|
16986
17089
|
if (isResult(event)) {
|
|
16987
17090
|
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
16988
17091
|
}
|
|
@@ -17031,148 +17134,100 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
17031
17134
|
}
|
|
17032
17135
|
break;
|
|
17033
17136
|
}
|
|
17034
|
-
if (result.intercepted)
|
|
17137
|
+
if (result.intercepted)
|
|
17035
17138
|
break;
|
|
17036
|
-
|
|
17037
|
-
if (result.skipConverter) {
|
|
17139
|
+
if (result.skipConverter)
|
|
17038
17140
|
continue;
|
|
17039
|
-
}
|
|
17040
17141
|
}
|
|
17041
|
-
if (streamTerminated || res.writableEnded)
|
|
17142
|
+
if (streamTerminated || res.writableEnded)
|
|
17042
17143
|
break;
|
|
17043
|
-
}
|
|
17044
17144
|
for (const sse of converter.handleEvent(event)) {
|
|
17045
17145
|
res.write(sse);
|
|
17046
17146
|
}
|
|
17047
17147
|
}
|
|
17048
|
-
}
|
|
17049
|
-
|
|
17050
|
-
if (
|
|
17148
|
+
};
|
|
17149
|
+
const drainQueue = async () => {
|
|
17150
|
+
if (draining)
|
|
17051
17151
|
return;
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
}
|
|
17057
|
-
const event = parseStreamJsonLine(line);
|
|
17058
|
-
if (!event) {
|
|
17059
|
-
continue;
|
|
17060
|
-
}
|
|
17061
|
-
if (isResult(event)) {
|
|
17062
|
-
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
17063
|
-
}
|
|
17064
|
-
if (event.type === "tool_call") {
|
|
17065
|
-
const result = await handleToolLoopEventWithFallback({
|
|
17066
|
-
event,
|
|
17067
|
-
boundary: boundaryContext.getBoundary(),
|
|
17068
|
-
boundaryMode: boundaryContext.getBoundary().mode,
|
|
17069
|
-
autoFallbackToLegacy: ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK,
|
|
17070
|
-
toolLoopMode: TOOL_LOOP_MODE,
|
|
17071
|
-
allowedToolNames,
|
|
17072
|
-
toolSchemaMap,
|
|
17073
|
-
toolLoopGuard,
|
|
17074
|
-
toolMapper,
|
|
17075
|
-
toolSessionId,
|
|
17076
|
-
shouldEmitToolUpdates: SHOULD_EMIT_TOOL_UPDATES,
|
|
17077
|
-
proxyExecuteToolCalls: PROXY_EXECUTE_TOOL_CALLS,
|
|
17078
|
-
suppressConverterToolEvents: SUPPRESS_CONVERTER_TOOL_EVENTS,
|
|
17079
|
-
toolRouter,
|
|
17080
|
-
responseMeta: { id, created, model },
|
|
17081
|
-
passThroughTracker,
|
|
17082
|
-
onToolUpdate: (update) => {
|
|
17083
|
-
res.write(formatToolUpdateEvent(update));
|
|
17084
|
-
},
|
|
17085
|
-
onToolResult: (toolResult) => {
|
|
17086
|
-
res.write(`data: ${JSON.stringify(toolResult)}
|
|
17087
|
-
|
|
17088
|
-
`);
|
|
17089
|
-
},
|
|
17090
|
-
onInterceptedToolCall: (toolCall) => {
|
|
17091
|
-
emitToolCallAndTerminate(toolCall);
|
|
17092
|
-
},
|
|
17093
|
-
onFallbackToLegacy: (error) => {
|
|
17094
|
-
boundaryContext.activateLegacyFallback("handleToolLoopEvent.close", error);
|
|
17095
|
-
}
|
|
17096
|
-
});
|
|
17097
|
-
if (result.terminate) {
|
|
17098
|
-
if (!result.terminate.silent) {
|
|
17099
|
-
emitTerminalAssistantErrorAndTerminate(result.terminate.message);
|
|
17100
|
-
} else {
|
|
17101
|
-
streamTerminated = true;
|
|
17102
|
-
try {
|
|
17103
|
-
child.kill();
|
|
17104
|
-
} catch {}
|
|
17105
|
-
}
|
|
17106
|
-
break;
|
|
17107
|
-
}
|
|
17108
|
-
if (result.intercepted) {
|
|
17152
|
+
draining = true;
|
|
17153
|
+
try {
|
|
17154
|
+
while (chunkQueue.length > 0) {
|
|
17155
|
+
if (streamTerminated || res.writableEnded)
|
|
17109
17156
|
break;
|
|
17157
|
+
const chunk = chunkQueue.shift();
|
|
17158
|
+
if (!firstTokenReceived) {
|
|
17159
|
+
perf.mark("first-token");
|
|
17160
|
+
firstTokenReceived = true;
|
|
17110
17161
|
}
|
|
17111
|
-
|
|
17112
|
-
continue;
|
|
17113
|
-
}
|
|
17114
|
-
}
|
|
17115
|
-
if (streamTerminated || res.writableEnded) {
|
|
17116
|
-
break;
|
|
17117
|
-
}
|
|
17118
|
-
for (const sse of converter.handleEvent(event)) {
|
|
17119
|
-
res.write(sse);
|
|
17162
|
+
await processLines(lineBuffer.push(chunk));
|
|
17120
17163
|
}
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17164
|
+
if (childClosed && !childCloseHandled && !streamTerminated && !res.writableEnded) {
|
|
17165
|
+
childCloseHandled = true;
|
|
17166
|
+
await processLines(lineBuffer.flush());
|
|
17167
|
+
if (streamTerminated || res.writableEnded)
|
|
17168
|
+
return;
|
|
17169
|
+
perf.mark("request:done");
|
|
17170
|
+
perf.summarize();
|
|
17171
|
+
const stderrText = Buffer.concat(stderrChunks).toString().trim();
|
|
17172
|
+
log20.debug("cursor-agent completed (node stream)", {
|
|
17173
|
+
code: childExitCode,
|
|
17174
|
+
stderrChars: stderrText.length
|
|
17175
|
+
});
|
|
17176
|
+
if (childExitCode !== 0) {
|
|
17177
|
+
const errSource = stderrText || `cursor-agent exited with code ${String(childExitCode ?? "unknown")} and no output`;
|
|
17178
|
+
const parsed = parseAgentError(errSource);
|
|
17179
|
+
const msg = formatErrorForUser(parsed);
|
|
17180
|
+
const errChunk = createChatCompletionChunk(id, created, model, msg, true);
|
|
17181
|
+
res.write(`data: ${JSON.stringify(errChunk)}
|
|
17138
17182
|
|
|
17139
17183
|
`);
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
}
|
|
17145
|
-
const passThroughSummary = passThroughTracker.getSummary();
|
|
17146
|
-
if (passThroughSummary.hasActivity) {
|
|
17147
|
-
await toastService.showPassThroughSummary(passThroughSummary.tools);
|
|
17148
|
-
}
|
|
17149
|
-
if (passThroughSummary.errors.length > 0) {
|
|
17150
|
-
await toastService.showErrorSummary(passThroughSummary.errors);
|
|
17151
|
-
}
|
|
17152
|
-
const doneChunk = {
|
|
17153
|
-
id,
|
|
17154
|
-
object: "chat.completion.chunk",
|
|
17155
|
-
created,
|
|
17156
|
-
model,
|
|
17157
|
-
choices: [
|
|
17158
|
-
{
|
|
17159
|
-
index: 0,
|
|
17160
|
-
delta: {},
|
|
17161
|
-
finish_reason: "stop"
|
|
17184
|
+
res.write(formatSseDone());
|
|
17185
|
+
streamTerminated = true;
|
|
17186
|
+
res.end();
|
|
17187
|
+
return;
|
|
17162
17188
|
}
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17189
|
+
const passThroughSummary = passThroughTracker.getSummary();
|
|
17190
|
+
if (passThroughSummary.hasActivity) {
|
|
17191
|
+
await toastService.showPassThroughSummary(passThroughSummary.tools);
|
|
17192
|
+
}
|
|
17193
|
+
if (passThroughSummary.errors.length > 0) {
|
|
17194
|
+
await toastService.showErrorSummary(passThroughSummary.errors);
|
|
17195
|
+
}
|
|
17196
|
+
const doneChunk = {
|
|
17197
|
+
id,
|
|
17198
|
+
object: "chat.completion.chunk",
|
|
17199
|
+
created,
|
|
17200
|
+
model,
|
|
17201
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
|
|
17202
|
+
};
|
|
17203
|
+
res.write(`data: ${JSON.stringify(doneChunk)}
|
|
17166
17204
|
|
|
17167
17205
|
`);
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17206
|
+
if (usage) {
|
|
17207
|
+
const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
|
|
17208
|
+
res.write(`data: ${JSON.stringify(usageChunk)}
|
|
17171
17209
|
|
|
17172
17210
|
`);
|
|
17211
|
+
}
|
|
17212
|
+
res.write(formatSseDone());
|
|
17213
|
+
streamTerminated = true;
|
|
17214
|
+
res.end();
|
|
17215
|
+
}
|
|
17216
|
+
} finally {
|
|
17217
|
+
draining = false;
|
|
17218
|
+
if (!streamTerminated && !res.writableEnded && (chunkQueue.length > 0 || childClosed && !childCloseHandled)) {
|
|
17219
|
+
drainQueue();
|
|
17220
|
+
}
|
|
17173
17221
|
}
|
|
17174
|
-
|
|
17175
|
-
|
|
17222
|
+
};
|
|
17223
|
+
child.stdout.on("data", (chunk) => {
|
|
17224
|
+
chunkQueue.push(Buffer.from(chunk));
|
|
17225
|
+
drainQueue();
|
|
17226
|
+
});
|
|
17227
|
+
child.on("close", (code) => {
|
|
17228
|
+
childClosed = true;
|
|
17229
|
+
childExitCode = code;
|
|
17230
|
+
drainQueue();
|
|
17176
17231
|
});
|
|
17177
17232
|
}
|
|
17178
17233
|
} catch (error) {
|
|
@@ -17373,7 +17428,7 @@ function buildToolHookEntries(registry, fallbackBaseDir) {
|
|
|
17373
17428
|
}
|
|
17374
17429
|
return entries;
|
|
17375
17430
|
}
|
|
17376
|
-
var log20,
|
|
17431
|
+
var log20, CURSOR_PROVIDER_ID2 = "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, NATIVE_TOOL_HOOK_EXCLUSIONS, CursorPlugin = async ({ $, directory, worktree, client: client3, serverUrl }) => {
|
|
17377
17432
|
const workspaceDirectory = resolveWorkspaceDirectory(worktree, directory);
|
|
17378
17433
|
log20.debug("Plugin initializing", {
|
|
17379
17434
|
directory,
|
|
@@ -17429,6 +17484,7 @@ var log20, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17429
17484
|
mcpToolSummaries = tools.map((t) => ({
|
|
17430
17485
|
serverName: t.serverName,
|
|
17431
17486
|
toolName: t.name,
|
|
17487
|
+
callName: namespaceMcpTool(t.serverName, t.name),
|
|
17432
17488
|
description: t.description,
|
|
17433
17489
|
params: t.inputSchema ? Object.keys(t.inputSchema.properties ?? {}) : undefined
|
|
17434
17490
|
}));
|
|
@@ -17646,8 +17702,6 @@ var init_plugin = __esm(() => {
|
|
|
17646
17702
|
init_tool_loop_guard();
|
|
17647
17703
|
init_binary();
|
|
17648
17704
|
log20 = createLogger("plugin");
|
|
17649
|
-
DEBUG_LOG_DIR2 = join6(homedir5(), ".config", "opencode", "logs");
|
|
17650
|
-
DEBUG_LOG_FILE2 = join6(DEBUG_LOG_DIR2, "tool-loop-debug.log");
|
|
17651
17705
|
CURSOR_PROVIDER_PREFIX = `${CURSOR_PROVIDER_ID2}/`;
|
|
17652
17706
|
CURSOR_PROXY_DEFAULT_BASE_URL = `http://${CURSOR_PROXY_HOST}:${CURSOR_PROXY_DEFAULT_PORT}/v1`;
|
|
17653
17707
|
REUSE_EXISTING_PROXY = process.env.CURSOR_ACP_REUSE_EXISTING_PROXY !== "false";
|
|
@@ -17727,7 +17781,7 @@ class SimpleCursorClient {
|
|
|
17727
17781
|
args.push("--resume", resumeId);
|
|
17728
17782
|
}
|
|
17729
17783
|
this.log.debug("Executing prompt stream", { promptLength: prompt.length, mode, model });
|
|
17730
|
-
const child = spawn3(this.config.cursorAgentPath, args, {
|
|
17784
|
+
const child = spawn3(formatShellCommandForPlatform(this.config.cursorAgentPath), args, {
|
|
17731
17785
|
cwd,
|
|
17732
17786
|
stdio: ["pipe", "pipe", "pipe"],
|
|
17733
17787
|
shell: process.platform === "win32"
|
|
@@ -17814,7 +17868,7 @@ class SimpleCursorClient {
|
|
|
17814
17868
|
}
|
|
17815
17869
|
this.log.debug("Executing prompt", { promptLength: prompt.length, mode, model });
|
|
17816
17870
|
return new Promise((resolve3, reject) => {
|
|
17817
|
-
const child = spawn3(this.config.cursorAgentPath, args, {
|
|
17871
|
+
const child = spawn3(formatShellCommandForPlatform(this.config.cursorAgentPath), args, {
|
|
17818
17872
|
cwd,
|
|
17819
17873
|
stdio: ["pipe", "pipe", "pipe"],
|
|
17820
17874
|
shell: process.platform === "win32"
|
|
@@ -18063,42 +18117,22 @@ function createProxyServer(config) {
|
|
|
18063
18117
|
|
|
18064
18118
|
// src/streaming/ai-sdk-parts.ts
|
|
18065
18119
|
class StreamToAiSdkParts {
|
|
18066
|
-
tracker = new DeltaTracker;
|
|
18067
18120
|
toolArgsById = new Map;
|
|
18068
18121
|
startedToolIds = new Set;
|
|
18069
|
-
|
|
18070
|
-
sawThinkingPartials = false;
|
|
18122
|
+
tracker = new MixedDeltaTracker;
|
|
18071
18123
|
handleEvent(event) {
|
|
18072
18124
|
if (isAssistantText(event)) {
|
|
18073
|
-
const
|
|
18074
|
-
if (
|
|
18075
|
-
const text = extractText(event);
|
|
18076
|
-
if (text) {
|
|
18077
|
-
this.sawAssistantPartials = true;
|
|
18078
|
-
return [{ type: "text-delta", textDelta: text }];
|
|
18079
|
-
}
|
|
18080
|
-
return [];
|
|
18081
|
-
}
|
|
18082
|
-
if (this.sawAssistantPartials) {
|
|
18125
|
+
const text = extractText(event);
|
|
18126
|
+
if (!text)
|
|
18083
18127
|
return [];
|
|
18084
|
-
|
|
18085
|
-
const delta = this.tracker.nextText(extractText(event));
|
|
18128
|
+
const delta = this.tracker.nextText(text);
|
|
18086
18129
|
return delta ? [{ type: "text-delta", textDelta: delta }] : [];
|
|
18087
18130
|
}
|
|
18088
18131
|
if (isThinking(event)) {
|
|
18089
|
-
const
|
|
18090
|
-
if (
|
|
18091
|
-
const text = extractThinking(event);
|
|
18092
|
-
if (text) {
|
|
18093
|
-
this.sawThinkingPartials = true;
|
|
18094
|
-
return [{ type: "text-delta", textDelta: text }];
|
|
18095
|
-
}
|
|
18096
|
-
return [];
|
|
18097
|
-
}
|
|
18098
|
-
if (this.sawThinkingPartials) {
|
|
18132
|
+
const text = extractThinking(event);
|
|
18133
|
+
if (!text)
|
|
18099
18134
|
return [];
|
|
18100
|
-
|
|
18101
|
-
const delta = this.tracker.nextThinking(extractThinking(event));
|
|
18135
|
+
const delta = this.tracker.nextThinking(text);
|
|
18102
18136
|
return delta ? [{ type: "text-delta", textDelta: delta }] : [];
|
|
18103
18137
|
}
|
|
18104
18138
|
if (isToolCall(event)) {
|
|
@@ -18400,11 +18434,11 @@ init_auth();
|
|
|
18400
18434
|
// src/commands/status.ts
|
|
18401
18435
|
init_auth();
|
|
18402
18436
|
init_logger();
|
|
18403
|
-
import { existsSync as
|
|
18437
|
+
import { existsSync as existsSync4 } from "fs";
|
|
18404
18438
|
var log22 = createLogger("status");
|
|
18405
18439
|
function checkAuthStatus() {
|
|
18406
18440
|
const authFilePath = getAuthFilePath();
|
|
18407
|
-
const exists =
|
|
18441
|
+
const exists = existsSync4(authFilePath);
|
|
18408
18442
|
log22.debug("Checking auth status", { path: authFilePath });
|
|
18409
18443
|
if (exists) {
|
|
18410
18444
|
return {
|