@rama_nigg/open-cursor 2.3.20 → 2.4.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 +50 -48
- package/dist/cli/discover.js +177 -8
- package/dist/cli/mcptool.js +6 -1
- package/dist/cli/opencode-cursor.js +930 -50
- package/dist/index.js +578 -226
- package/dist/plugin-entry.js +556 -206
- package/package.json +4 -2
- package/src/auth.ts +3 -1
- package/src/cli/model-discovery.ts +3 -2
- package/src/cli/opencode-cursor.ts +402 -23
- package/src/client/simple.ts +6 -3
- package/src/mcp/tool-bridge.ts +1 -1
- package/src/models/discovery.ts +3 -2
- package/src/models/pricing.ts +196 -0
- package/src/models/variants.ts +446 -0
- package/src/plugin-toggle.ts +7 -1
- package/src/plugin.ts +150 -32
- package/src/provider/boundary.ts +10 -0
- package/src/proxy/formatter.ts +30 -12
- package/src/streaming/types.ts +5 -0
- package/src/tools/defaults.ts +166 -0
- package/src/tools/executors/cli.ts +1 -0
- package/src/usage.ts +112 -0
- package/src/utils/binary.ts +57 -0
package/dist/index.js
CHANGED
|
@@ -421,11 +421,52 @@ function formatErrorForUser(error) {
|
|
|
421
421
|
return output;
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
+
// src/utils/binary.ts
|
|
425
|
+
import { existsSync as fsExistsSync } from "fs";
|
|
426
|
+
import * as pathModule from "path";
|
|
427
|
+
import { homedir as osHomedir } from "os";
|
|
428
|
+
function resolveCursorAgentBinary(deps = {}) {
|
|
429
|
+
const platform = deps.platform ?? process.platform;
|
|
430
|
+
const env = deps.env ?? process.env;
|
|
431
|
+
const checkExists = deps.existsSync ?? fsExistsSync;
|
|
432
|
+
const home = (deps.homedir ?? osHomedir)();
|
|
433
|
+
const envOverride = env.CURSOR_AGENT_EXECUTABLE;
|
|
434
|
+
if (envOverride && envOverride.length > 0) {
|
|
435
|
+
return envOverride;
|
|
436
|
+
}
|
|
437
|
+
if (platform === "win32") {
|
|
438
|
+
const pathJoin = pathModule.win32.join;
|
|
439
|
+
const localAppData = env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local");
|
|
440
|
+
const knownPath = pathJoin(localAppData, "cursor-agent", "cursor-agent.cmd");
|
|
441
|
+
if (checkExists(knownPath)) {
|
|
442
|
+
return knownPath;
|
|
443
|
+
}
|
|
444
|
+
log.warn("cursor-agent not found at known Windows path, falling back to PATH", { checkedPath: knownPath });
|
|
445
|
+
return "cursor-agent.cmd";
|
|
446
|
+
}
|
|
447
|
+
const knownPaths = [
|
|
448
|
+
pathModule.join(home, ".cursor-agent", "cursor-agent"),
|
|
449
|
+
"/usr/local/bin/cursor-agent"
|
|
450
|
+
];
|
|
451
|
+
for (const p of knownPaths) {
|
|
452
|
+
if (checkExists(p)) {
|
|
453
|
+
return p;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
|
|
457
|
+
return "cursor-agent";
|
|
458
|
+
}
|
|
459
|
+
var log;
|
|
460
|
+
var init_binary = __esm(() => {
|
|
461
|
+
init_logger();
|
|
462
|
+
log = createLogger("binary");
|
|
463
|
+
});
|
|
464
|
+
|
|
424
465
|
// src/auth.ts
|
|
425
466
|
import { spawn } from "child_process";
|
|
426
467
|
import { existsSync as existsSync2 } from "fs";
|
|
427
468
|
import { homedir as homedir2, platform } from "os";
|
|
428
|
-
import { join as
|
|
469
|
+
import { join as join3 } from "path";
|
|
429
470
|
function getHomeDir() {
|
|
430
471
|
const override = process.env.CURSOR_ACP_HOME_DIR;
|
|
431
472
|
if (override && override.length > 0) {
|
|
@@ -441,18 +482,18 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
|
|
|
441
482
|
const elapsed = Date.now() - startTime;
|
|
442
483
|
for (const authPath of possiblePaths) {
|
|
443
484
|
if (existsSync2(authPath)) {
|
|
444
|
-
|
|
485
|
+
log2.debug("Auth file detected", { path: authPath });
|
|
445
486
|
resolve(true);
|
|
446
487
|
return;
|
|
447
488
|
}
|
|
448
489
|
}
|
|
449
|
-
|
|
490
|
+
log2.debug("Polling for auth file", {
|
|
450
491
|
checkedPaths: possiblePaths,
|
|
451
492
|
elapsed: `${elapsed}ms`,
|
|
452
493
|
timeout: `${timeoutMs}ms`
|
|
453
494
|
});
|
|
454
495
|
if (elapsed >= timeoutMs) {
|
|
455
|
-
|
|
496
|
+
log2.debug("Auth file polling timed out");
|
|
456
497
|
resolve(false);
|
|
457
498
|
return;
|
|
458
499
|
}
|
|
@@ -463,9 +504,10 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
|
|
|
463
504
|
}
|
|
464
505
|
async function startCursorOAuth() {
|
|
465
506
|
return new Promise((resolve, reject) => {
|
|
466
|
-
|
|
467
|
-
const proc = spawn(
|
|
468
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
507
|
+
log2.info("Starting cursor-cli login process");
|
|
508
|
+
const proc = spawn(resolveCursorAgentBinary(), ["login"], {
|
|
509
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
510
|
+
shell: process.platform === "win32"
|
|
469
511
|
});
|
|
470
512
|
let stdout = "";
|
|
471
513
|
let stderr = "";
|
|
@@ -489,9 +531,9 @@ async function startCursorOAuth() {
|
|
|
489
531
|
const url = extractUrl();
|
|
490
532
|
if (url && !urlExtracted) {
|
|
491
533
|
urlExtracted = true;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
534
|
+
log2.debug("Captured stdout", { length: stdout.length });
|
|
535
|
+
log2.debug("Extracted URL", { url: url.substring(0, 50) + "..." });
|
|
536
|
+
log2.info("Got login URL, waiting for browser auth");
|
|
495
537
|
resolve({
|
|
496
538
|
url,
|
|
497
539
|
instructions: "Click 'Continue with Cursor' in your browser to authenticate",
|
|
@@ -505,26 +547,26 @@ async function startCursorOAuth() {
|
|
|
505
547
|
}
|
|
506
548
|
};
|
|
507
549
|
proc.on("close", async (code) => {
|
|
508
|
-
|
|
550
|
+
log2.debug("Login process closed", { code });
|
|
509
551
|
if (code === 0) {
|
|
510
|
-
|
|
552
|
+
log2.info("Process exited successfully, polling for auth file...");
|
|
511
553
|
const isAuthenticated = await pollForAuthFile();
|
|
512
554
|
if (isAuthenticated) {
|
|
513
|
-
|
|
555
|
+
log2.info("Authentication successful");
|
|
514
556
|
resolveOnce({
|
|
515
557
|
type: "success",
|
|
516
558
|
provider: "cursor-acp",
|
|
517
559
|
key: "cursor-auth"
|
|
518
560
|
});
|
|
519
561
|
} else {
|
|
520
|
-
|
|
562
|
+
log2.warn("Auth file not found after polling");
|
|
521
563
|
resolveOnce({
|
|
522
564
|
type: "failed",
|
|
523
565
|
error: "Authentication was not completed. Please try again."
|
|
524
566
|
});
|
|
525
567
|
}
|
|
526
568
|
} else {
|
|
527
|
-
|
|
569
|
+
log2.warn("Login process failed", { code });
|
|
528
570
|
resolveOnce({
|
|
529
571
|
type: "failed",
|
|
530
572
|
error: stderr ? stripAnsi(stderr) : `Authentication failed with code ${code}`
|
|
@@ -532,7 +574,7 @@ async function startCursorOAuth() {
|
|
|
532
574
|
}
|
|
533
575
|
});
|
|
534
576
|
setTimeout(() => {
|
|
535
|
-
|
|
577
|
+
log2.warn("Authentication timed out after 5 minutes");
|
|
536
578
|
proc.kill();
|
|
537
579
|
resolveOnce({
|
|
538
580
|
type: "failed",
|
|
@@ -552,7 +594,7 @@ async function startCursorOAuth() {
|
|
|
552
594
|
if (elapsed >= URL_EXTRACTION_TIMEOUT) {
|
|
553
595
|
proc.kill();
|
|
554
596
|
const errorMsg = stderr ? stripAnsi(stderr) : "No login URL received within timeout";
|
|
555
|
-
|
|
597
|
+
log2.error("Failed to extract login URL", { error: errorMsg, elapsed: `${elapsed}ms` });
|
|
556
598
|
reject(new Error(`Failed to get login URL: ${errorMsg}`));
|
|
557
599
|
return;
|
|
558
600
|
}
|
|
@@ -568,11 +610,11 @@ function verifyCursorAuth() {
|
|
|
568
610
|
const possiblePaths = getPossibleAuthPaths();
|
|
569
611
|
for (const authPath of possiblePaths) {
|
|
570
612
|
if (existsSync2(authPath)) {
|
|
571
|
-
|
|
613
|
+
log2.debug("Auth file found", { path: authPath });
|
|
572
614
|
return true;
|
|
573
615
|
}
|
|
574
616
|
}
|
|
575
|
-
|
|
617
|
+
log2.debug("No auth file found", { checkedPaths: possiblePaths });
|
|
576
618
|
return false;
|
|
577
619
|
}
|
|
578
620
|
function getPossibleAuthPaths() {
|
|
@@ -582,23 +624,23 @@ function getPossibleAuthPaths() {
|
|
|
582
624
|
const authFiles = ["cli-config.json", "auth.json"];
|
|
583
625
|
if (isDarwin) {
|
|
584
626
|
for (const file of authFiles) {
|
|
585
|
-
paths.push(
|
|
627
|
+
paths.push(join3(home, ".cursor", file));
|
|
586
628
|
}
|
|
587
629
|
for (const file of authFiles) {
|
|
588
|
-
paths.push(
|
|
630
|
+
paths.push(join3(home, ".config", "cursor", file));
|
|
589
631
|
}
|
|
590
632
|
} else {
|
|
591
633
|
for (const file of authFiles) {
|
|
592
|
-
paths.push(
|
|
634
|
+
paths.push(join3(home, ".config", "cursor", file));
|
|
593
635
|
}
|
|
594
636
|
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
595
|
-
if (xdgConfig && xdgConfig !==
|
|
637
|
+
if (xdgConfig && xdgConfig !== join3(home, ".config")) {
|
|
596
638
|
for (const file of authFiles) {
|
|
597
|
-
paths.push(
|
|
639
|
+
paths.push(join3(xdgConfig, "cursor", file));
|
|
598
640
|
}
|
|
599
641
|
}
|
|
600
642
|
for (const file of authFiles) {
|
|
601
|
-
paths.push(
|
|
643
|
+
paths.push(join3(home, ".cursor", file));
|
|
602
644
|
}
|
|
603
645
|
}
|
|
604
646
|
return paths;
|
|
@@ -612,10 +654,11 @@ function getAuthFilePath() {
|
|
|
612
654
|
}
|
|
613
655
|
return possiblePaths[0];
|
|
614
656
|
}
|
|
615
|
-
var
|
|
657
|
+
var log2, AUTH_POLL_INTERVAL = 2000, AUTH_POLL_TIMEOUT, URL_EXTRACTION_TIMEOUT = 1e4;
|
|
616
658
|
var init_auth = __esm(() => {
|
|
617
659
|
init_logger();
|
|
618
|
-
|
|
660
|
+
init_binary();
|
|
661
|
+
log2 = createLogger("auth");
|
|
619
662
|
AUTH_POLL_TIMEOUT = 5 * 60 * 1000;
|
|
620
663
|
});
|
|
621
664
|
|
|
@@ -662,7 +705,7 @@ var hasTextContent = (event) => event.message.content.some((content) => content.
|
|
|
662
705
|
return true;
|
|
663
706
|
}
|
|
664
707
|
return event.type === "assistant" && hasThinkingContent(event);
|
|
665
|
-
}, isToolCall = (event) => event.type === "tool_call", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (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) => {
|
|
666
709
|
if (event.type === "thinking") {
|
|
667
710
|
return event.text ?? "";
|
|
668
711
|
}
|
|
@@ -811,7 +854,7 @@ var createChunk = (id, created, model, delta) => ({
|
|
|
811
854
|
var init_openai_sse = () => {};
|
|
812
855
|
|
|
813
856
|
// src/streaming/parser.ts
|
|
814
|
-
var
|
|
857
|
+
var log3, parseStreamJsonLine = (line) => {
|
|
815
858
|
const trimmed = line.trim();
|
|
816
859
|
if (!trimmed) {
|
|
817
860
|
return null;
|
|
@@ -823,15 +866,82 @@ var log2, parseStreamJsonLine = (line) => {
|
|
|
823
866
|
}
|
|
824
867
|
return parsed;
|
|
825
868
|
} catch {
|
|
826
|
-
|
|
869
|
+
log3.debug("Failed to parse NDJSON line", { line: trimmed.substring(0, 100) });
|
|
827
870
|
return null;
|
|
828
871
|
}
|
|
829
872
|
};
|
|
830
873
|
var init_parser = __esm(() => {
|
|
831
874
|
init_logger();
|
|
832
|
-
|
|
875
|
+
log3 = createLogger("streaming:parser");
|
|
833
876
|
});
|
|
834
877
|
|
|
878
|
+
// src/usage.ts
|
|
879
|
+
function readTokenCount(value) {
|
|
880
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
881
|
+
return 0;
|
|
882
|
+
}
|
|
883
|
+
return Math.floor(value);
|
|
884
|
+
}
|
|
885
|
+
function readOptionalCost(value) {
|
|
886
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
return value;
|
|
890
|
+
}
|
|
891
|
+
function normalizeCursorUsage(value) {
|
|
892
|
+
if (!value || typeof value !== "object") {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const usage = value;
|
|
896
|
+
const metrics = {
|
|
897
|
+
inputTokens: readTokenCount(usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens),
|
|
898
|
+
outputTokens: readTokenCount(usage.outputTokens ?? usage.output_tokens ?? usage.completion_tokens),
|
|
899
|
+
reasoningTokens: readTokenCount(usage.reasoningTokens ?? usage.reasoning_tokens),
|
|
900
|
+
cacheReadTokens: readTokenCount(usage.cacheReadTokens ?? usage.cache_read_tokens),
|
|
901
|
+
cacheWriteTokens: readTokenCount(usage.cacheWriteTokens ?? usage.cache_write_tokens)
|
|
902
|
+
};
|
|
903
|
+
const cost = readOptionalCost(usage.cost ?? usage.totalCost ?? usage.total_cost);
|
|
904
|
+
if (cost !== undefined) {
|
|
905
|
+
metrics.cost = cost;
|
|
906
|
+
}
|
|
907
|
+
const hasUsage = metrics.inputTokens > 0 || metrics.outputTokens > 0 || metrics.reasoningTokens > 0 || metrics.cacheReadTokens > 0 || metrics.cacheWriteTokens > 0 || cost !== undefined;
|
|
908
|
+
return hasUsage ? metrics : undefined;
|
|
909
|
+
}
|
|
910
|
+
function createOpenAiUsage(metrics) {
|
|
911
|
+
const promptTokens = metrics.inputTokens + metrics.cacheReadTokens + metrics.cacheWriteTokens;
|
|
912
|
+
const totalTokens = promptTokens + metrics.outputTokens + metrics.reasoningTokens;
|
|
913
|
+
const usage = {
|
|
914
|
+
prompt_tokens: promptTokens,
|
|
915
|
+
completion_tokens: metrics.outputTokens,
|
|
916
|
+
total_tokens: totalTokens,
|
|
917
|
+
prompt_tokens_details: {
|
|
918
|
+
cached_tokens: metrics.cacheReadTokens,
|
|
919
|
+
cache_write_tokens: metrics.cacheWriteTokens
|
|
920
|
+
},
|
|
921
|
+
completion_tokens_details: {
|
|
922
|
+
reasoning_tokens: metrics.reasoningTokens
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
if (metrics.cost !== undefined) {
|
|
926
|
+
usage.cost = metrics.cost;
|
|
927
|
+
}
|
|
928
|
+
return usage;
|
|
929
|
+
}
|
|
930
|
+
function extractOpenAiUsageFromResult(event) {
|
|
931
|
+
const metrics = normalizeCursorUsage(event.usage);
|
|
932
|
+
return metrics ? createOpenAiUsage(metrics) : undefined;
|
|
933
|
+
}
|
|
934
|
+
function createChatCompletionUsageChunk(id, created, model, usage) {
|
|
935
|
+
return {
|
|
936
|
+
id,
|
|
937
|
+
object: "chat.completion.chunk",
|
|
938
|
+
created,
|
|
939
|
+
model,
|
|
940
|
+
choices: [],
|
|
941
|
+
usage
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
835
945
|
// src/utils/perf.ts
|
|
836
946
|
class RequestPerf {
|
|
837
947
|
markers = [];
|
|
@@ -852,7 +962,7 @@ class RequestPerf {
|
|
|
852
962
|
phases[this.markers[i].name] = this.markers[i].ts - this.markers[i - 1].ts;
|
|
853
963
|
}
|
|
854
964
|
const total = this.markers[this.markers.length - 1].ts - start;
|
|
855
|
-
|
|
965
|
+
log4.debug("Request timing", { requestId: this.requestId, total, phases });
|
|
856
966
|
}
|
|
857
967
|
elapsed() {
|
|
858
968
|
return this.markers.length > 0 ? Date.now() - this.markers[0].ts : 0;
|
|
@@ -861,16 +971,16 @@ class RequestPerf {
|
|
|
861
971
|
return this.markers;
|
|
862
972
|
}
|
|
863
973
|
}
|
|
864
|
-
var
|
|
974
|
+
var log4;
|
|
865
975
|
var init_perf = __esm(() => {
|
|
866
976
|
init_logger();
|
|
867
|
-
|
|
977
|
+
log4 = createLogger("perf");
|
|
868
978
|
});
|
|
869
979
|
|
|
870
980
|
// src/proxy/prompt-builder.ts
|
|
871
981
|
import { appendFileSync as appendFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
872
982
|
import { homedir as homedir3 } from "node:os";
|
|
873
|
-
import { join as
|
|
983
|
+
import { join as join4 } from "node:path";
|
|
874
984
|
function ensureLogDir2() {
|
|
875
985
|
try {
|
|
876
986
|
if (!existsSync3(DEBUG_LOG_DIR)) {
|
|
@@ -886,7 +996,7 @@ function debugLogToFile(message, data) {
|
|
|
886
996
|
`;
|
|
887
997
|
appendFileSync2(DEBUG_LOG_FILE, logLine);
|
|
888
998
|
} catch (err) {
|
|
889
|
-
|
|
999
|
+
log5.debug(message, data);
|
|
890
1000
|
}
|
|
891
1001
|
}
|
|
892
1002
|
function buildPromptFromMessages(messages, tools, subagentNames = []) {
|
|
@@ -1007,12 +1117,12 @@ ${toolDescs}`);
|
|
|
1007
1117
|
});
|
|
1008
1118
|
return finalPrompt;
|
|
1009
1119
|
}
|
|
1010
|
-
var
|
|
1120
|
+
var log5, DEBUG_LOG_DIR, DEBUG_LOG_FILE;
|
|
1011
1121
|
var init_prompt_builder = __esm(() => {
|
|
1012
1122
|
init_logger();
|
|
1013
|
-
|
|
1014
|
-
DEBUG_LOG_DIR =
|
|
1015
|
-
DEBUG_LOG_FILE =
|
|
1123
|
+
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");
|
|
1016
1126
|
});
|
|
1017
1127
|
|
|
1018
1128
|
// src/proxy/tool-loop.ts
|
|
@@ -1040,7 +1150,7 @@ function extractOpenAiToolCall(event, allowedToolNames) {
|
|
|
1040
1150
|
const resolvedName = resolveAllowedToolName(name, allowedToolNames);
|
|
1041
1151
|
if (resolvedName) {
|
|
1042
1152
|
if (args === undefined && event.subtype === "started") {
|
|
1043
|
-
|
|
1153
|
+
log6.debug("Tool call args extraction returned undefined", {
|
|
1044
1154
|
toolName: name,
|
|
1045
1155
|
subtype: event.subtype ?? "none",
|
|
1046
1156
|
payloadKeys: Object.entries(event.tool_call || {}).map(([k, v]) => `${k}:[${isRecord(v) ? Object.keys(v).join(",") : typeof v}]`),
|
|
@@ -1060,7 +1170,7 @@ function extractOpenAiToolCall(event, allowedToolNames) {
|
|
|
1060
1170
|
}
|
|
1061
1171
|
};
|
|
1062
1172
|
}
|
|
1063
|
-
|
|
1173
|
+
log6.debug("Tool call not in allowlist; passing through to cursor-agent", {
|
|
1064
1174
|
name,
|
|
1065
1175
|
normalized: normalizeAliasKey(name),
|
|
1066
1176
|
allowedToolCount: allowedToolNames.size
|
|
@@ -1209,10 +1319,10 @@ function toOpenAiArguments(args) {
|
|
|
1209
1319
|
function isRecord(value) {
|
|
1210
1320
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1211
1321
|
}
|
|
1212
|
-
var
|
|
1322
|
+
var log6, TOOL_NAME_ALIASES;
|
|
1213
1323
|
var init_tool_loop = __esm(() => {
|
|
1214
1324
|
init_logger();
|
|
1215
|
-
|
|
1325
|
+
log6 = createLogger("proxy:tool-loop");
|
|
1216
1326
|
TOOL_NAME_ALIASES = new Map([
|
|
1217
1327
|
["runcommand", "bash"],
|
|
1218
1328
|
["executecommand", "bash"],
|
|
@@ -1326,7 +1436,7 @@ class OpenCodeToolDiscovery {
|
|
|
1326
1436
|
const mcpTools = await this.tryListMcpTools();
|
|
1327
1437
|
tools = tools.concat(mcpTools);
|
|
1328
1438
|
} catch (err) {
|
|
1329
|
-
|
|
1439
|
+
log7.debug("SDK tool.list failed, will try CLI", { error: String(err) });
|
|
1330
1440
|
}
|
|
1331
1441
|
}
|
|
1332
1442
|
if (tools.length === 0 && this.executorPref !== "sdk") {
|
|
@@ -1338,10 +1448,10 @@ class OpenCodeToolDiscovery {
|
|
|
1338
1448
|
if (parsed?.data?.tools?.length) {
|
|
1339
1449
|
tools = parsed.data.tools.map((t) => this.normalize(t, "cli"));
|
|
1340
1450
|
} else {
|
|
1341
|
-
|
|
1451
|
+
log7.debug("CLI tool list failed", { status: res.status, stderr: res.stderr });
|
|
1342
1452
|
}
|
|
1343
1453
|
} catch (err) {
|
|
1344
|
-
|
|
1454
|
+
log7.debug("CLI tool list error", { error: String(err) });
|
|
1345
1455
|
}
|
|
1346
1456
|
}
|
|
1347
1457
|
const map = new Map;
|
|
@@ -1377,7 +1487,7 @@ class OpenCodeToolDiscovery {
|
|
|
1377
1487
|
return [];
|
|
1378
1488
|
return mcpList.data.tools.map((t) => this.normalize(t, "mcp"));
|
|
1379
1489
|
} catch (err) {
|
|
1380
|
-
|
|
1490
|
+
log7.debug("MCP tool discovery skipped", { error: String(err) });
|
|
1381
1491
|
return [];
|
|
1382
1492
|
}
|
|
1383
1493
|
}
|
|
@@ -1398,11 +1508,11 @@ class OpenCodeToolDiscovery {
|
|
|
1398
1508
|
return null;
|
|
1399
1509
|
}
|
|
1400
1510
|
}
|
|
1401
|
-
var
|
|
1511
|
+
var log7;
|
|
1402
1512
|
var init_discovery = __esm(() => {
|
|
1403
1513
|
init_logger();
|
|
1404
1514
|
init_strip_ansi();
|
|
1405
|
-
|
|
1515
|
+
log7 = createLogger("tools:discovery");
|
|
1406
1516
|
});
|
|
1407
1517
|
|
|
1408
1518
|
// src/tools/schema.ts
|
|
@@ -1459,10 +1569,10 @@ function describeTool(t) {
|
|
|
1459
1569
|
const base = t.description || "OpenCode tool";
|
|
1460
1570
|
return base.length > 400 ? base.slice(0, 400) : base;
|
|
1461
1571
|
}
|
|
1462
|
-
var
|
|
1572
|
+
var log8;
|
|
1463
1573
|
var init_schema = __esm(() => {
|
|
1464
1574
|
init_logger();
|
|
1465
|
-
|
|
1575
|
+
log8 = createLogger("tools:schema");
|
|
1466
1576
|
});
|
|
1467
1577
|
|
|
1468
1578
|
// src/tools/router.ts
|
|
@@ -1487,18 +1597,18 @@ class ToolRouter {
|
|
|
1487
1597
|
}
|
|
1488
1598
|
const tool = this.ctx.toolsByName.get(name);
|
|
1489
1599
|
if (!tool) {
|
|
1490
|
-
|
|
1600
|
+
log9.warn("Unknown tool call", { name });
|
|
1491
1601
|
return this.buildResult(meta, callId, name, { status: "error", error: `Unknown tool ${name}` });
|
|
1492
1602
|
}
|
|
1493
1603
|
const args = this.extractArgs(event);
|
|
1494
|
-
|
|
1604
|
+
log9.debug("Executing tool", { name, toolId: tool.id });
|
|
1495
1605
|
const t0 = Date.now();
|
|
1496
1606
|
const result = await this.ctx.execute(tool.id, args);
|
|
1497
1607
|
const elapsed = Date.now() - t0;
|
|
1498
1608
|
if (result.status === "error") {
|
|
1499
|
-
|
|
1609
|
+
log9.warn("Tool execution returned error", { name, error: result.error, elapsed });
|
|
1500
1610
|
} else {
|
|
1501
|
-
|
|
1611
|
+
log9.debug("Tool execution completed", { name, toolId: tool.id, elapsed });
|
|
1502
1612
|
}
|
|
1503
1613
|
return this.buildResult(meta, callId, name, result);
|
|
1504
1614
|
}
|
|
@@ -1547,10 +1657,10 @@ class ToolRouter {
|
|
|
1547
1657
|
};
|
|
1548
1658
|
}
|
|
1549
1659
|
}
|
|
1550
|
-
var
|
|
1660
|
+
var log9;
|
|
1551
1661
|
var init_router = __esm(() => {
|
|
1552
1662
|
init_logger();
|
|
1553
|
-
|
|
1663
|
+
log9 = createLogger("tools:router");
|
|
1554
1664
|
});
|
|
1555
1665
|
|
|
1556
1666
|
// src/tools/skills/loader.ts
|
|
@@ -1651,9 +1761,9 @@ function parseCursorModelsOutput(output) {
|
|
|
1651
1761
|
return models;
|
|
1652
1762
|
}
|
|
1653
1763
|
function discoverModelsFromCursorAgent() {
|
|
1654
|
-
const raw = execFileSync(
|
|
1764
|
+
const raw = execFileSync(resolveCursorAgentBinary(), ["models"], {
|
|
1655
1765
|
encoding: "utf8",
|
|
1656
|
-
killSignal: "SIGTERM",
|
|
1766
|
+
...process.platform !== "win32" && { killSignal: "SIGTERM" },
|
|
1657
1767
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1658
1768
|
timeout: MODEL_DISCOVERY_TIMEOUT_MS
|
|
1659
1769
|
});
|
|
@@ -1688,12 +1798,14 @@ function fallbackModels() {
|
|
|
1688
1798
|
];
|
|
1689
1799
|
}
|
|
1690
1800
|
var MODEL_DISCOVERY_TIMEOUT_MS = 5000;
|
|
1691
|
-
var init_model_discovery = () => {
|
|
1801
|
+
var init_model_discovery = __esm(() => {
|
|
1802
|
+
init_binary();
|
|
1803
|
+
});
|
|
1692
1804
|
|
|
1693
1805
|
// src/plugin-toggle.ts
|
|
1694
1806
|
import { existsSync as existsSync4, readFileSync } from "fs";
|
|
1695
1807
|
import { homedir as homedir4 } from "os";
|
|
1696
|
-
import { join as
|
|
1808
|
+
import { join as join5, resolve } from "path";
|
|
1697
1809
|
function matchesPlugin(entry) {
|
|
1698
1810
|
if (entry === CURSOR_PROVIDER_ID)
|
|
1699
1811
|
return true;
|
|
@@ -1707,14 +1819,19 @@ function resolveOpenCodeConfigPath(env = process.env) {
|
|
|
1707
1819
|
if (env.OPENCODE_CONFIG && env.OPENCODE_CONFIG.length > 0) {
|
|
1708
1820
|
return resolve(env.OPENCODE_CONFIG);
|
|
1709
1821
|
}
|
|
1710
|
-
const configHome = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0 ? env.XDG_CONFIG_HOME :
|
|
1711
|
-
return
|
|
1822
|
+
const configHome = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0 ? env.XDG_CONFIG_HOME : join5(homedir4(), ".config");
|
|
1823
|
+
return join5(configHome, "opencode", "opencode.json");
|
|
1712
1824
|
}
|
|
1713
1825
|
function isCursorPluginEnabledInConfig(config) {
|
|
1714
1826
|
if (!config || typeof config !== "object") {
|
|
1715
1827
|
return true;
|
|
1716
1828
|
}
|
|
1717
1829
|
const configObject = config;
|
|
1830
|
+
if (configObject.provider && typeof configObject.provider === "object") {
|
|
1831
|
+
if (CURSOR_PROVIDER_ID in configObject.provider) {
|
|
1832
|
+
return true;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1718
1835
|
if (Array.isArray(configObject.plugin)) {
|
|
1719
1836
|
return configObject.plugin.some((entry) => matchesPlugin(entry));
|
|
1720
1837
|
}
|
|
@@ -1736,7 +1853,7 @@ function shouldEnableCursorPlugin(env = process.env) {
|
|
|
1736
1853
|
return {
|
|
1737
1854
|
enabled,
|
|
1738
1855
|
configPath,
|
|
1739
|
-
reason: enabled ? "
|
|
1856
|
+
reason: enabled ? "enabled" : "disabled_in_plugin_array"
|
|
1740
1857
|
};
|
|
1741
1858
|
} catch {
|
|
1742
1859
|
return {
|
|
@@ -1838,18 +1955,18 @@ async function autoRefreshModels(deps = {}) {
|
|
|
1838
1955
|
resolvedDeps.log.debug("Model auto-refresh failed", { error: String(err) });
|
|
1839
1956
|
}
|
|
1840
1957
|
}
|
|
1841
|
-
var
|
|
1958
|
+
var log10, PROVIDER_ID = "cursor-acp", defaultDeps;
|
|
1842
1959
|
var init_sync = __esm(() => {
|
|
1843
1960
|
init_model_discovery();
|
|
1844
1961
|
init_plugin_toggle();
|
|
1845
1962
|
init_logger();
|
|
1846
|
-
|
|
1963
|
+
log10 = createLogger("model-sync");
|
|
1847
1964
|
defaultDeps = {
|
|
1848
1965
|
defer: () => Promise.resolve(),
|
|
1849
1966
|
discoverModels: discoverModelsFromCursorAgent,
|
|
1850
1967
|
env: process.env,
|
|
1851
1968
|
existsSync: nodeExistsSync,
|
|
1852
|
-
log:
|
|
1969
|
+
log: log10,
|
|
1853
1970
|
readFileSync: nodeReadFileSync,
|
|
1854
1971
|
writeFileSync: nodeWriteFileSync
|
|
1855
1972
|
};
|
|
@@ -1910,7 +2027,7 @@ function readMcpConfigs(deps = {}) {
|
|
|
1910
2027
|
timeout: typeof e.timeout === "number" ? e.timeout : undefined
|
|
1911
2028
|
});
|
|
1912
2029
|
} else {
|
|
1913
|
-
|
|
2030
|
+
log11.debug("Skipping unrecognised MCP config entry", { name, type: e.type });
|
|
1914
2031
|
}
|
|
1915
2032
|
}
|
|
1916
2033
|
return configs;
|
|
@@ -1954,11 +2071,11 @@ function readSubagentNames(deps = {}) {
|
|
|
1954
2071
|
function isStringRecord(v) {
|
|
1955
2072
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1956
2073
|
}
|
|
1957
|
-
var
|
|
2074
|
+
var log11;
|
|
1958
2075
|
var init_config = __esm(() => {
|
|
1959
2076
|
init_plugin_toggle();
|
|
1960
2077
|
init_logger();
|
|
1961
|
-
|
|
2078
|
+
log11 = createLogger("mcp:config");
|
|
1962
2079
|
});
|
|
1963
2080
|
|
|
1964
2081
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
@@ -8095,11 +8212,11 @@ var require_core = __commonJS((exports) => {
|
|
|
8095
8212
|
Ajv.ValidationError = validation_error_1.default;
|
|
8096
8213
|
Ajv.MissingRefError = ref_error_1.default;
|
|
8097
8214
|
exports.default = Ajv;
|
|
8098
|
-
function checkOptions(checkOpts, options, msg,
|
|
8215
|
+
function checkOptions(checkOpts, options, msg, log12 = "error") {
|
|
8099
8216
|
for (const key in checkOpts) {
|
|
8100
8217
|
const opt = key;
|
|
8101
8218
|
if (opt in options)
|
|
8102
|
-
this.logger[
|
|
8219
|
+
this.logger[log12](`${msg}: option ${key}. ${checkOpts[opt]}`);
|
|
8103
8220
|
}
|
|
8104
8221
|
}
|
|
8105
8222
|
function getSchEnv(keyRef) {
|
|
@@ -11716,14 +11833,14 @@ class McpClientManager {
|
|
|
11716
11833
|
}
|
|
11717
11834
|
async connectServer(config) {
|
|
11718
11835
|
if (this.connections.has(config.name)) {
|
|
11719
|
-
|
|
11836
|
+
log12.debug("Server already connected, skipping", { server: config.name });
|
|
11720
11837
|
return;
|
|
11721
11838
|
}
|
|
11722
11839
|
if (!this.deps) {
|
|
11723
11840
|
try {
|
|
11724
11841
|
this.deps = await loadDefaultDeps();
|
|
11725
11842
|
} catch (err) {
|
|
11726
|
-
|
|
11843
|
+
log12.warn("Failed to load MCP SDK", { error: String(err) });
|
|
11727
11844
|
return;
|
|
11728
11845
|
}
|
|
11729
11846
|
}
|
|
@@ -11734,7 +11851,7 @@ class McpClientManager {
|
|
|
11734
11851
|
const transport = deps.createTransport(config);
|
|
11735
11852
|
await client.connect(transport);
|
|
11736
11853
|
} catch (err) {
|
|
11737
|
-
|
|
11854
|
+
log12.warn("MCP server connection failed", {
|
|
11738
11855
|
server: config.name,
|
|
11739
11856
|
error: String(err)
|
|
11740
11857
|
});
|
|
@@ -11744,12 +11861,12 @@ class McpClientManager {
|
|
|
11744
11861
|
try {
|
|
11745
11862
|
const result = await client.listTools();
|
|
11746
11863
|
tools = result?.tools ?? [];
|
|
11747
|
-
|
|
11864
|
+
log12.info("MCP server connected", {
|
|
11748
11865
|
server: config.name,
|
|
11749
11866
|
tools: tools.length
|
|
11750
11867
|
});
|
|
11751
11868
|
} catch (err) {
|
|
11752
|
-
|
|
11869
|
+
log12.warn("MCP tool discovery failed", {
|
|
11753
11870
|
server: config.name,
|
|
11754
11871
|
error: String(err)
|
|
11755
11872
|
});
|
|
@@ -11781,7 +11898,7 @@ class McpClientManager {
|
|
|
11781
11898
|
}
|
|
11782
11899
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
11783
11900
|
} catch (err) {
|
|
11784
|
-
|
|
11901
|
+
log12.warn("MCP tool call failed", {
|
|
11785
11902
|
server: serverName,
|
|
11786
11903
|
tool: toolName,
|
|
11787
11904
|
error: String(err?.message || err)
|
|
@@ -11793,9 +11910,9 @@ class McpClientManager {
|
|
|
11793
11910
|
for (const [name, conn] of this.connections) {
|
|
11794
11911
|
try {
|
|
11795
11912
|
await conn.client.close();
|
|
11796
|
-
|
|
11913
|
+
log12.debug("MCP server disconnected", { server: name });
|
|
11797
11914
|
} catch (err) {
|
|
11798
|
-
|
|
11915
|
+
log12.debug("MCP server disconnect failed", { server: name, error: String(err) });
|
|
11799
11916
|
}
|
|
11800
11917
|
}
|
|
11801
11918
|
this.connections.clear();
|
|
@@ -11804,10 +11921,10 @@ class McpClientManager {
|
|
|
11804
11921
|
return Array.from(this.connections.keys());
|
|
11805
11922
|
}
|
|
11806
11923
|
}
|
|
11807
|
-
var
|
|
11924
|
+
var log12, defaultDeps2 = null;
|
|
11808
11925
|
var init_client_manager = __esm(() => {
|
|
11809
11926
|
init_logger();
|
|
11810
|
-
|
|
11927
|
+
log12 = createLogger("mcp:client-manager");
|
|
11811
11928
|
});
|
|
11812
11929
|
|
|
11813
11930
|
// src/mcp/tool-bridge.ts
|
|
@@ -11818,7 +11935,7 @@ function buildMcpToolHookEntries(tools, manager) {
|
|
|
11818
11935
|
for (const t of tools) {
|
|
11819
11936
|
const hookName = namespaceMcpTool(t.serverName, t.name);
|
|
11820
11937
|
if (entries[hookName]) {
|
|
11821
|
-
|
|
11938
|
+
log13.debug("Duplicate MCP tool name, skipping", { hookName });
|
|
11822
11939
|
continue;
|
|
11823
11940
|
}
|
|
11824
11941
|
const zodArgs = mcpSchemaToZod(t.inputSchema, z2);
|
|
@@ -11828,7 +11945,7 @@ function buildMcpToolHookEntries(tools, manager) {
|
|
|
11828
11945
|
description: t.description || `MCP tool: ${t.name} (server: ${t.serverName})`,
|
|
11829
11946
|
args: zodArgs,
|
|
11830
11947
|
async execute(args) {
|
|
11831
|
-
|
|
11948
|
+
log13.debug("Executing MCP tool", { server: serverName, tool: toolName });
|
|
11832
11949
|
const result = await manager.callTool(serverName, toolName, args ?? {});
|
|
11833
11950
|
if (result.startsWith("Error:")) {
|
|
11834
11951
|
throw new Error(result);
|
|
@@ -11837,7 +11954,7 @@ function buildMcpToolHookEntries(tools, manager) {
|
|
|
11837
11954
|
}
|
|
11838
11955
|
});
|
|
11839
11956
|
}
|
|
11840
|
-
|
|
11957
|
+
log13.debug("Built MCP tool hook entries", { count: Object.keys(entries).length });
|
|
11841
11958
|
return entries;
|
|
11842
11959
|
}
|
|
11843
11960
|
function buildMcpToolDefinitions(tools) {
|
|
@@ -11884,7 +12001,7 @@ function mcpSchemaToZod(inputSchema, z2) {
|
|
|
11884
12001
|
zodType = z2.array(z2.any());
|
|
11885
12002
|
break;
|
|
11886
12003
|
case "object":
|
|
11887
|
-
zodType = z2.record(z2.any());
|
|
12004
|
+
zodType = z2.record(z2.string(), z2.any());
|
|
11888
12005
|
break;
|
|
11889
12006
|
default:
|
|
11890
12007
|
zodType = z2.any();
|
|
@@ -11900,10 +12017,10 @@ function mcpSchemaToZod(inputSchema, z2) {
|
|
|
11900
12017
|
}
|
|
11901
12018
|
return shape;
|
|
11902
12019
|
}
|
|
11903
|
-
var
|
|
12020
|
+
var log13;
|
|
11904
12021
|
var init_tool_bridge = __esm(() => {
|
|
11905
12022
|
init_logger();
|
|
11906
|
-
|
|
12023
|
+
log13 = createLogger("mcp:tool-bridge");
|
|
11907
12024
|
});
|
|
11908
12025
|
|
|
11909
12026
|
// node_modules/@opencode-ai/sdk/dist/gen/types.gen.js
|
|
@@ -13314,15 +13431,15 @@ class LocalExecutor {
|
|
|
13314
13431
|
const out = await handler(args);
|
|
13315
13432
|
return { status: "success", output: out };
|
|
13316
13433
|
} catch (err) {
|
|
13317
|
-
|
|
13434
|
+
log14.warn("Local tool execution failed", { toolId, error: String(err?.message || err) });
|
|
13318
13435
|
return { status: "error", error: String(err?.message || err) };
|
|
13319
13436
|
}
|
|
13320
13437
|
}
|
|
13321
13438
|
}
|
|
13322
|
-
var
|
|
13439
|
+
var log14;
|
|
13323
13440
|
var init_local = __esm(() => {
|
|
13324
13441
|
init_logger();
|
|
13325
|
-
|
|
13442
|
+
log14 = createLogger("tools:executor:local");
|
|
13326
13443
|
});
|
|
13327
13444
|
|
|
13328
13445
|
// src/tools/executors/sdk.ts
|
|
@@ -13349,7 +13466,7 @@ class SdkExecutor {
|
|
|
13349
13466
|
const out = typeof res === "string" ? res : JSON.stringify(res);
|
|
13350
13467
|
return { status: "success", output: out };
|
|
13351
13468
|
} catch (err) {
|
|
13352
|
-
|
|
13469
|
+
log15.warn("SDK tool execution failed", { toolId, error: String(err?.message || err) });
|
|
13353
13470
|
return { status: "error", error: String(err?.message || err) };
|
|
13354
13471
|
}
|
|
13355
13472
|
}
|
|
@@ -13362,10 +13479,10 @@ class SdkExecutor {
|
|
|
13362
13479
|
]);
|
|
13363
13480
|
}
|
|
13364
13481
|
}
|
|
13365
|
-
var
|
|
13482
|
+
var log15;
|
|
13366
13483
|
var init_sdk = __esm(() => {
|
|
13367
13484
|
init_logger();
|
|
13368
|
-
|
|
13485
|
+
log15 = createLogger("tools:executor:sdk");
|
|
13369
13486
|
});
|
|
13370
13487
|
|
|
13371
13488
|
// src/tools/executors/mcp.ts
|
|
@@ -13392,7 +13509,7 @@ class McpExecutor {
|
|
|
13392
13509
|
const out = typeof res === "string" ? res : JSON.stringify(res);
|
|
13393
13510
|
return { status: "success", output: out };
|
|
13394
13511
|
} catch (err) {
|
|
13395
|
-
|
|
13512
|
+
log16.warn("MCP tool execution failed", { toolId, error: String(err?.message || err) });
|
|
13396
13513
|
return { status: "error", error: String(err?.message || err) };
|
|
13397
13514
|
}
|
|
13398
13515
|
}
|
|
@@ -13405,10 +13522,10 @@ class McpExecutor {
|
|
|
13405
13522
|
]);
|
|
13406
13523
|
}
|
|
13407
13524
|
}
|
|
13408
|
-
var
|
|
13525
|
+
var log16;
|
|
13409
13526
|
var init_mcp = __esm(() => {
|
|
13410
13527
|
init_logger();
|
|
13411
|
-
|
|
13528
|
+
log16 = createLogger("tools:executor:mcp");
|
|
13412
13529
|
});
|
|
13413
13530
|
|
|
13414
13531
|
// src/tools/core/executor.ts
|
|
@@ -13418,17 +13535,17 @@ async function executeWithChain(executors, toolId, args) {
|
|
|
13418
13535
|
try {
|
|
13419
13536
|
return await ex.execute(toolId, args);
|
|
13420
13537
|
} catch (err) {
|
|
13421
|
-
|
|
13538
|
+
log17.warn("Executor threw unexpected error", { toolId, error: String(err?.message || err) });
|
|
13422
13539
|
return { status: "error", error: String(err?.message || err) };
|
|
13423
13540
|
}
|
|
13424
13541
|
}
|
|
13425
13542
|
}
|
|
13426
13543
|
return { status: "error", error: `No executor available for ${toolId}` };
|
|
13427
13544
|
}
|
|
13428
|
-
var
|
|
13545
|
+
var log17;
|
|
13429
13546
|
var init_executor = __esm(() => {
|
|
13430
13547
|
init_logger();
|
|
13431
|
-
|
|
13548
|
+
log17 = createLogger("tools:executor:chain");
|
|
13432
13549
|
});
|
|
13433
13550
|
|
|
13434
13551
|
// src/tools/defaults.ts
|
|
@@ -13672,6 +13789,9 @@ ${output}`);
|
|
|
13672
13789
|
const pattern = args.pattern;
|
|
13673
13790
|
const path2 = args.path;
|
|
13674
13791
|
const include = args.include;
|
|
13792
|
+
if (process.platform === "win32") {
|
|
13793
|
+
return nodeFallbackGrep(pattern, path2, include);
|
|
13794
|
+
}
|
|
13675
13795
|
const grepArgs = ["-r", "-n"];
|
|
13676
13796
|
if (include) {
|
|
13677
13797
|
grepArgs.push(`--include=${include}`);
|
|
@@ -13764,6 +13884,9 @@ ${output}`);
|
|
|
13764
13884
|
const path2 = resolvePathArg(args, "glob");
|
|
13765
13885
|
const cwd = path2 || ".";
|
|
13766
13886
|
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
13887
|
+
if (process.platform === "win32") {
|
|
13888
|
+
return nodeFallbackGlob(normalizedPattern, cwd);
|
|
13889
|
+
}
|
|
13767
13890
|
const isPathPattern = normalizedPattern.includes("/");
|
|
13768
13891
|
const findArgs = [cwd, "-type", "f"];
|
|
13769
13892
|
if (isPathPattern) {
|
|
@@ -14040,6 +14163,146 @@ function coerceToString(value) {
|
|
|
14040
14163
|
}
|
|
14041
14164
|
return null;
|
|
14042
14165
|
}
|
|
14166
|
+
async function nodeFallbackGrep(pattern, searchPath, include) {
|
|
14167
|
+
const fs2 = await import("fs/promises");
|
|
14168
|
+
const path2 = await import("path");
|
|
14169
|
+
let regex2;
|
|
14170
|
+
try {
|
|
14171
|
+
regex2 = new RegExp(pattern);
|
|
14172
|
+
} catch {
|
|
14173
|
+
return "Invalid regex pattern";
|
|
14174
|
+
}
|
|
14175
|
+
let includeRegex;
|
|
14176
|
+
if (include) {
|
|
14177
|
+
const incPattern = include.replace(/\./g, "\\.").replace(/\?/g, ".").replace(/\*/g, ".*");
|
|
14178
|
+
includeRegex = new RegExp(`^${incPattern}$`);
|
|
14179
|
+
}
|
|
14180
|
+
const results = [];
|
|
14181
|
+
async function walk(dir) {
|
|
14182
|
+
if (results.length >= 100)
|
|
14183
|
+
return;
|
|
14184
|
+
let entries;
|
|
14185
|
+
try {
|
|
14186
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
14187
|
+
} catch (err) {
|
|
14188
|
+
if (err?.code !== "ENOENT" && err?.code !== "EACCES") {
|
|
14189
|
+
fallbackLog.error("Unexpected error reading directory", { dir, code: err?.code, message: err?.message });
|
|
14190
|
+
}
|
|
14191
|
+
return;
|
|
14192
|
+
}
|
|
14193
|
+
for (const entry of entries) {
|
|
14194
|
+
if (results.length >= 100)
|
|
14195
|
+
return;
|
|
14196
|
+
const fullPath = path2.join(dir, entry.name);
|
|
14197
|
+
if (entry.isDirectory()) {
|
|
14198
|
+
if (!FALLBACK_SKIP_DIRS.has(entry.name)) {
|
|
14199
|
+
await walk(fullPath);
|
|
14200
|
+
}
|
|
14201
|
+
} else if (entry.isFile()) {
|
|
14202
|
+
if (includeRegex && !includeRegex.test(entry.name))
|
|
14203
|
+
continue;
|
|
14204
|
+
let content;
|
|
14205
|
+
try {
|
|
14206
|
+
content = await fs2.readFile(fullPath, "utf-8");
|
|
14207
|
+
} catch (err) {
|
|
14208
|
+
if (err?.code !== "ENOENT" && err?.code !== "EACCES") {
|
|
14209
|
+
fallbackLog.error("Unexpected error reading file", { path: fullPath, code: err?.code, message: err?.message });
|
|
14210
|
+
}
|
|
14211
|
+
continue;
|
|
14212
|
+
}
|
|
14213
|
+
const lines = content.split(`
|
|
14214
|
+
`);
|
|
14215
|
+
for (let i = 0;i < lines.length; i++) {
|
|
14216
|
+
if (regex2.test(lines[i])) {
|
|
14217
|
+
results.push(`${fullPath}:${i + 1}:${lines[i]}`);
|
|
14218
|
+
if (results.length >= 100)
|
|
14219
|
+
break;
|
|
14220
|
+
}
|
|
14221
|
+
}
|
|
14222
|
+
}
|
|
14223
|
+
}
|
|
14224
|
+
}
|
|
14225
|
+
let stat;
|
|
14226
|
+
try {
|
|
14227
|
+
stat = await fs2.stat(searchPath);
|
|
14228
|
+
} catch {
|
|
14229
|
+
return "Path not found";
|
|
14230
|
+
}
|
|
14231
|
+
if (stat.isFile()) {
|
|
14232
|
+
let content;
|
|
14233
|
+
try {
|
|
14234
|
+
content = await fs2.readFile(searchPath, "utf-8");
|
|
14235
|
+
} catch (err) {
|
|
14236
|
+
if (err?.code !== "ENOENT" && err?.code !== "EACCES") {
|
|
14237
|
+
fallbackLog.error("Unexpected error reading file", { path: searchPath, code: err?.code, message: err?.message });
|
|
14238
|
+
}
|
|
14239
|
+
return "Path not found";
|
|
14240
|
+
}
|
|
14241
|
+
const lines = content.split(`
|
|
14242
|
+
`);
|
|
14243
|
+
for (let i = 0;i < lines.length; i++) {
|
|
14244
|
+
if (regex2.test(lines[i])) {
|
|
14245
|
+
results.push(`${searchPath}:${i + 1}:${lines[i]}`);
|
|
14246
|
+
if (results.length >= 100)
|
|
14247
|
+
break;
|
|
14248
|
+
}
|
|
14249
|
+
}
|
|
14250
|
+
} else {
|
|
14251
|
+
await walk(searchPath);
|
|
14252
|
+
}
|
|
14253
|
+
return results.join(`
|
|
14254
|
+
`) || "No matches found";
|
|
14255
|
+
}
|
|
14256
|
+
async function nodeFallbackGlob(pattern, searchPath) {
|
|
14257
|
+
const fs2 = await import("fs/promises");
|
|
14258
|
+
const path2 = await import("path");
|
|
14259
|
+
const results = [];
|
|
14260
|
+
const isPathPattern = pattern.includes("/");
|
|
14261
|
+
let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "\x00").replace(/\*/g, "[^/]*").replace(/\x00/g, ".*");
|
|
14262
|
+
let regex2;
|
|
14263
|
+
try {
|
|
14264
|
+
regex2 = isPathPattern ? new RegExp(`${regexPattern}$`) : new RegExp(`^${regexPattern}$`);
|
|
14265
|
+
} catch {
|
|
14266
|
+
return "No files found";
|
|
14267
|
+
}
|
|
14268
|
+
async function walk(dir) {
|
|
14269
|
+
if (results.length >= 50)
|
|
14270
|
+
return;
|
|
14271
|
+
let entries;
|
|
14272
|
+
try {
|
|
14273
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
14274
|
+
} catch (err) {
|
|
14275
|
+
if (err?.code !== "ENOENT" && err?.code !== "EACCES") {
|
|
14276
|
+
fallbackLog.error("Unexpected error reading directory", { dir, code: err?.code, message: err?.message });
|
|
14277
|
+
}
|
|
14278
|
+
return;
|
|
14279
|
+
}
|
|
14280
|
+
for (const entry of entries) {
|
|
14281
|
+
if (results.length >= 50)
|
|
14282
|
+
return;
|
|
14283
|
+
const fullPath = path2.join(dir, entry.name);
|
|
14284
|
+
if (entry.isDirectory()) {
|
|
14285
|
+
if (!FALLBACK_SKIP_DIRS.has(entry.name)) {
|
|
14286
|
+
await walk(fullPath);
|
|
14287
|
+
}
|
|
14288
|
+
} else if (entry.isFile()) {
|
|
14289
|
+
const matchTarget = isPathPattern ? fullPath.replace(/\\/g, "/") : entry.name;
|
|
14290
|
+
if (regex2.test(matchTarget)) {
|
|
14291
|
+
results.push(fullPath);
|
|
14292
|
+
}
|
|
14293
|
+
}
|
|
14294
|
+
}
|
|
14295
|
+
}
|
|
14296
|
+
await walk(searchPath);
|
|
14297
|
+
return results.join(`
|
|
14298
|
+
`) || "No files found";
|
|
14299
|
+
}
|
|
14300
|
+
var FALLBACK_SKIP_DIRS, fallbackLog;
|
|
14301
|
+
var init_defaults = __esm(() => {
|
|
14302
|
+
init_logger();
|
|
14303
|
+
FALLBACK_SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build"]);
|
|
14304
|
+
fallbackLog = createLogger("tools:fallback");
|
|
14305
|
+
});
|
|
14043
14306
|
|
|
14044
14307
|
// src/provider/boundary.ts
|
|
14045
14308
|
function parseProviderBoundaryMode(value) {
|
|
@@ -14104,6 +14367,13 @@ function createSharedBoundary(providerId) {
|
|
|
14104
14367
|
}
|
|
14105
14368
|
return raw;
|
|
14106
14369
|
},
|
|
14370
|
+
resolveRuntimeModel(model, cursorModel) {
|
|
14371
|
+
const rawCursorModel = typeof cursorModel === "string" ? cursorModel.trim() : "";
|
|
14372
|
+
if (rawCursorModel.length > 0) {
|
|
14373
|
+
return this.normalizeRuntimeModel(rawCursorModel);
|
|
14374
|
+
}
|
|
14375
|
+
return this.normalizeRuntimeModel(model);
|
|
14376
|
+
},
|
|
14107
14377
|
applyChatParamDefaults(output, proxyBaseURL, defaultBaseURL, defaultApiKey) {
|
|
14108
14378
|
output.options = output.options || {};
|
|
14109
14379
|
output.options.baseURL = proxyBaseURL || defaultBaseURL;
|
|
@@ -14544,7 +14814,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14544
14814
|
const extraction = toolLoopMode === "opencode" ? extractOpenAiToolCall(event, allowedToolNames) : { action: "skip", skipReason: "tool_loop_mode_not_opencode" };
|
|
14545
14815
|
if (extraction.action === "passthrough") {
|
|
14546
14816
|
passThroughTracker?.trackTool(extraction.passthroughName);
|
|
14547
|
-
|
|
14817
|
+
log18.debug("MCP tool passed through to cursor-agent (legacy)", {
|
|
14548
14818
|
tool: extraction.passthroughName
|
|
14549
14819
|
});
|
|
14550
14820
|
return { intercepted: false, skipConverter: false };
|
|
@@ -14568,7 +14838,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14568
14838
|
if (interceptedToolCall) {
|
|
14569
14839
|
const compat = applyToolSchemaCompat(interceptedToolCall, toolSchemaMap);
|
|
14570
14840
|
let normalizedToolCall = compat.toolCall;
|
|
14571
|
-
|
|
14841
|
+
log18.debug("Applied tool schema compatibility (legacy)", {
|
|
14572
14842
|
tool: normalizedToolCall.function.name,
|
|
14573
14843
|
originalArgKeys: compat.originalArgKeys,
|
|
14574
14844
|
normalizedArgKeys: compat.normalizedArgKeys,
|
|
@@ -14580,7 +14850,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14580
14850
|
if (validationTermination) {
|
|
14581
14851
|
if (validationTermination.soft) {
|
|
14582
14852
|
const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, validationTermination);
|
|
14583
|
-
|
|
14853
|
+
log18.debug("Soft-blocking schema validation loop guard in legacy (emitting hint)", {
|
|
14584
14854
|
tool: normalizedToolCall.function.name,
|
|
14585
14855
|
fingerprint: validationTermination.fingerprint
|
|
14586
14856
|
});
|
|
@@ -14591,7 +14861,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14591
14861
|
}
|
|
14592
14862
|
const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat.normalizedArgs, allowedToolNames, toolSchemaMap);
|
|
14593
14863
|
if (reroutedWrite) {
|
|
14594
|
-
|
|
14864
|
+
log18.debug("Rerouting malformed edit call to write (legacy)", {
|
|
14595
14865
|
path: reroutedWrite.path,
|
|
14596
14866
|
missing: compat.validation.missing,
|
|
14597
14867
|
typeErrors: compat.validation.typeErrors
|
|
@@ -14599,7 +14869,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14599
14869
|
normalizedToolCall = reroutedWrite.toolCall;
|
|
14600
14870
|
} else if (shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
|
|
14601
14871
|
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
|
|
14602
|
-
|
|
14872
|
+
log18.debug("Emitting non-fatal schema validation hint in legacy and skipping malformed tool execution", {
|
|
14603
14873
|
tool: normalizedToolCall.function.name,
|
|
14604
14874
|
missing: compat.validation.missing,
|
|
14605
14875
|
typeErrors: compat.validation.typeErrors
|
|
@@ -14612,7 +14882,7 @@ async function handleToolLoopEventLegacy(options) {
|
|
|
14612
14882
|
if (termination) {
|
|
14613
14883
|
if (termination.soft) {
|
|
14614
14884
|
const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination);
|
|
14615
|
-
|
|
14885
|
+
log18.debug("Soft-blocking tool loop guard in legacy (emitting hint)", {
|
|
14616
14886
|
tool: normalizedToolCall.function.name,
|
|
14617
14887
|
fingerprint: termination.fingerprint
|
|
14618
14888
|
});
|
|
@@ -14670,7 +14940,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14670
14940
|
}
|
|
14671
14941
|
if (extraction.action === "passthrough") {
|
|
14672
14942
|
passThroughTracker?.trackTool(extraction.passthroughName);
|
|
14673
|
-
|
|
14943
|
+
log18.debug("MCP tool passed through to cursor-agent (v1)", {
|
|
14674
14944
|
tool: extraction.passthroughName
|
|
14675
14945
|
});
|
|
14676
14946
|
return { intercepted: false, skipConverter: false };
|
|
@@ -14697,7 +14967,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14697
14967
|
rawArgs: safeArgTypeSummary(event),
|
|
14698
14968
|
normalizedArgs: compat.normalizedArgs
|
|
14699
14969
|
} : undefined;
|
|
14700
|
-
|
|
14970
|
+
log18.debug("Applied tool schema compatibility", {
|
|
14701
14971
|
tool: normalizedToolCall.function.name,
|
|
14702
14972
|
originalArgKeys: compat.originalArgKeys,
|
|
14703
14973
|
normalizedArgKeys: compat.normalizedArgKeys,
|
|
@@ -14706,7 +14976,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14706
14976
|
...editDiag ? { editDiag } : {}
|
|
14707
14977
|
});
|
|
14708
14978
|
if (compat.validation.hasSchema && !compat.validation.ok) {
|
|
14709
|
-
|
|
14979
|
+
log18.debug("Tool schema compatibility validation failed", {
|
|
14710
14980
|
tool: normalizedToolCall.function.name,
|
|
14711
14981
|
missing: compat.validation.missing,
|
|
14712
14982
|
unexpected: compat.validation.unexpected,
|
|
@@ -14717,7 +14987,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14717
14987
|
if (validationTermination) {
|
|
14718
14988
|
if (validationTermination.soft) {
|
|
14719
14989
|
const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, validationTermination);
|
|
14720
|
-
|
|
14990
|
+
log18.debug("Soft-blocking schema validation loop guard (emitting hint)", {
|
|
14721
14991
|
tool: normalizedToolCall.function.name,
|
|
14722
14992
|
fingerprint: validationTermination.fingerprint,
|
|
14723
14993
|
repeatCount: validationTermination.repeatCount
|
|
@@ -14731,7 +15001,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14731
15001
|
if (termination2) {
|
|
14732
15002
|
if (termination2.soft) {
|
|
14733
15003
|
const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination2);
|
|
14734
|
-
|
|
15004
|
+
log18.debug("Soft-blocking tool loop guard in validation path (emitting hint)", {
|
|
14735
15005
|
tool: normalizedToolCall.function.name,
|
|
14736
15006
|
fingerprint: termination2.fingerprint,
|
|
14737
15007
|
repeatCount: termination2.repeatCount
|
|
@@ -14743,7 +15013,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14743
15013
|
}
|
|
14744
15014
|
const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat.normalizedArgs, allowedToolNames, toolSchemaMap);
|
|
14745
15015
|
if (reroutedWrite) {
|
|
14746
|
-
|
|
15016
|
+
log18.debug("Rerouting malformed edit call to write", {
|
|
14747
15017
|
path: reroutedWrite.path,
|
|
14748
15018
|
missing: compat.validation.missing,
|
|
14749
15019
|
typeErrors: compat.validation.typeErrors
|
|
@@ -14763,7 +15033,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14763
15033
|
}
|
|
14764
15034
|
if (schemaValidationFailureMode === "pass_through" && shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
|
|
14765
15035
|
const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
|
|
14766
|
-
|
|
15036
|
+
log18.debug("Emitting non-fatal schema validation hint and skipping malformed tool execution", {
|
|
14767
15037
|
tool: normalizedToolCall.function.name,
|
|
14768
15038
|
missing: compat.validation.missing,
|
|
14769
15039
|
typeErrors: compat.validation.typeErrors
|
|
@@ -14781,7 +15051,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14781
15051
|
terminate: createSchemaValidationTermination(normalizedToolCall, compat.validation)
|
|
14782
15052
|
};
|
|
14783
15053
|
}
|
|
14784
|
-
|
|
15054
|
+
log18.debug("Forwarding schema-invalid tool call to OpenCode loop", {
|
|
14785
15055
|
tool: normalizedToolCall.function.name,
|
|
14786
15056
|
repairHint: compat.validation.repairHint
|
|
14787
15057
|
});
|
|
@@ -14795,7 +15065,7 @@ async function handleToolLoopEventV1(options) {
|
|
|
14795
15065
|
if (termination) {
|
|
14796
15066
|
if (termination.soft) {
|
|
14797
15067
|
const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination);
|
|
14798
|
-
|
|
15068
|
+
log18.debug("Soft-blocking tool loop guard (emitting hint)", {
|
|
14799
15069
|
tool: normalizedToolCall.function.name,
|
|
14800
15070
|
fingerprint: termination.fingerprint,
|
|
14801
15071
|
repeatCount: termination.repeatCount
|
|
@@ -14854,7 +15124,7 @@ function evaluateToolLoopGuard(toolLoopGuard, toolCall) {
|
|
|
14854
15124
|
if (!decision.triggered) {
|
|
14855
15125
|
return null;
|
|
14856
15126
|
}
|
|
14857
|
-
|
|
15127
|
+
log18.debug("Tool loop guard triggered", {
|
|
14858
15128
|
tool: toolCall.function.name,
|
|
14859
15129
|
fingerprint: decision.fingerprint,
|
|
14860
15130
|
repeatCount: decision.repeatCount,
|
|
@@ -14916,7 +15186,7 @@ function evaluateSchemaValidationLoopGuard(toolLoopGuard, toolCall, validation)
|
|
|
14916
15186
|
return null;
|
|
14917
15187
|
}
|
|
14918
15188
|
const isFirstTrigger = decision.repeatCount === decision.maxRepeat + 1;
|
|
14919
|
-
|
|
15189
|
+
log18.debug("Tool loop guard triggered on schema validation", {
|
|
14920
15190
|
tool: toolCall.function.name,
|
|
14921
15191
|
fingerprint: decision.fingerprint,
|
|
14922
15192
|
repeatCount: decision.repeatCount,
|
|
@@ -15096,12 +15366,12 @@ function tryRerouteEditToWrite(toolCall, normalizedArgs, allowedToolNames, toolS
|
|
|
15096
15366
|
function isRecord4(value) {
|
|
15097
15367
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15098
15368
|
}
|
|
15099
|
-
var
|
|
15369
|
+
var log18, ToolBoundaryExtractionError;
|
|
15100
15370
|
var init_runtime_interception = __esm(() => {
|
|
15101
15371
|
init_tool_loop();
|
|
15102
15372
|
init_logger();
|
|
15103
15373
|
init_tool_schema_compat();
|
|
15104
|
-
|
|
15374
|
+
log18 = createLogger("provider:runtime-interception");
|
|
15105
15375
|
ToolBoundaryExtractionError = class ToolBoundaryExtractionError extends Error {
|
|
15106
15376
|
cause;
|
|
15107
15377
|
constructor(message, cause) {
|
|
@@ -15143,7 +15413,7 @@ class ToastService {
|
|
|
15143
15413
|
}
|
|
15144
15414
|
async show(options) {
|
|
15145
15415
|
if (!this.client?.tui?.showToast) {
|
|
15146
|
-
|
|
15416
|
+
log19.debug("Toast not available; client.tui.showToast missing", { message: options.message });
|
|
15147
15417
|
return;
|
|
15148
15418
|
}
|
|
15149
15419
|
try {
|
|
@@ -15155,7 +15425,7 @@ class ToastService {
|
|
|
15155
15425
|
}
|
|
15156
15426
|
});
|
|
15157
15427
|
} catch (error) {
|
|
15158
|
-
|
|
15428
|
+
log19.debug("Toast failed", { error, message: options.message });
|
|
15159
15429
|
}
|
|
15160
15430
|
}
|
|
15161
15431
|
async showPassThroughSummary(tools) {
|
|
@@ -15179,10 +15449,10 @@ class ToastService {
|
|
|
15179
15449
|
});
|
|
15180
15450
|
}
|
|
15181
15451
|
}
|
|
15182
|
-
var
|
|
15452
|
+
var log19, toastService;
|
|
15183
15453
|
var init_toast_service = __esm(() => {
|
|
15184
15454
|
init_logger();
|
|
15185
|
-
|
|
15455
|
+
log19 = createLogger("services:toast");
|
|
15186
15456
|
toastService = new ToastService;
|
|
15187
15457
|
});
|
|
15188
15458
|
|
|
@@ -15631,9 +15901,12 @@ var init_tool_loop_guard = __esm(() => {
|
|
|
15631
15901
|
var exports_plugin = {};
|
|
15632
15902
|
__export(exports_plugin, {
|
|
15633
15903
|
shouldProcessModel: () => shouldProcessModel,
|
|
15904
|
+
resolveWorkspaceDirectory: () => resolveWorkspaceDirectory,
|
|
15634
15905
|
resolveChatParamTools: () => resolveChatParamTools,
|
|
15635
15906
|
normalizeWorkspaceForCompare: () => normalizeWorkspaceForCompare,
|
|
15907
|
+
isRootPath: () => isRootPath,
|
|
15636
15908
|
isReusableProxyHealthPayload: () => isReusableProxyHealthPayload,
|
|
15909
|
+
extractCompletionFromStream: () => extractCompletionFromStream,
|
|
15637
15910
|
ensurePluginDirectory: () => ensurePluginDirectory,
|
|
15638
15911
|
default: () => plugin_default,
|
|
15639
15912
|
buildAvailableToolsSystemMessage: () => buildAvailableToolsSystemMessage,
|
|
@@ -15643,7 +15916,7 @@ import { tool as tool2 } from "@opencode-ai/plugin";
|
|
|
15643
15916
|
import { appendFileSync as appendFileSync3, existsSync as existsSync5, realpathSync } from "fs";
|
|
15644
15917
|
import { mkdir } from "fs/promises";
|
|
15645
15918
|
import { homedir as homedir5 } from "os";
|
|
15646
|
-
import { isAbsolute, join as
|
|
15919
|
+
import { isAbsolute, join as join6, relative, resolve as resolve2 } from "path";
|
|
15647
15920
|
function ensureDebugLogDir() {
|
|
15648
15921
|
try {
|
|
15649
15922
|
if (!existsSync5(DEBUG_LOG_DIR2)) {
|
|
@@ -15703,13 +15976,13 @@ function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDef
|
|
|
15703
15976
|
`) : null;
|
|
15704
15977
|
}
|
|
15705
15978
|
async function ensurePluginDirectory() {
|
|
15706
|
-
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) :
|
|
15707
|
-
const pluginDir =
|
|
15979
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join6(homedir5(), ".config");
|
|
15980
|
+
const pluginDir = join6(configHome, "opencode", "plugin");
|
|
15708
15981
|
try {
|
|
15709
15982
|
await mkdir(pluginDir, { recursive: true });
|
|
15710
|
-
|
|
15983
|
+
log20.debug("Plugin directory ensured", { path: pluginDir });
|
|
15711
15984
|
} catch (error) {
|
|
15712
|
-
|
|
15985
|
+
log20.warn("Failed to create plugin directory", { error: String(error) });
|
|
15713
15986
|
}
|
|
15714
15987
|
}
|
|
15715
15988
|
function shouldProcessModel(model) {
|
|
@@ -15721,8 +15994,8 @@ function getGlobalKey() {
|
|
|
15721
15994
|
return "__opencode_cursor_proxy_server__";
|
|
15722
15995
|
}
|
|
15723
15996
|
function getOpenCodeConfigPrefix() {
|
|
15724
|
-
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) :
|
|
15725
|
-
return
|
|
15997
|
+
const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join6(homedir5(), ".config");
|
|
15998
|
+
return join6(configHome, "opencode");
|
|
15726
15999
|
}
|
|
15727
16000
|
function canonicalizePathForCompare(pathValue) {
|
|
15728
16001
|
const resolvedPath = resolve2(pathValue);
|
|
@@ -15732,7 +16005,7 @@ function canonicalizePathForCompare(pathValue) {
|
|
|
15732
16005
|
} catch {
|
|
15733
16006
|
normalizedPath = resolvedPath;
|
|
15734
16007
|
}
|
|
15735
|
-
if (process.platform === "darwin") {
|
|
16008
|
+
if (process.platform === "darwin" || process.platform === "win32") {
|
|
15736
16009
|
return normalizedPath.toLowerCase();
|
|
15737
16010
|
}
|
|
15738
16011
|
return normalizedPath;
|
|
@@ -15755,32 +16028,62 @@ function isNonConfigPath(pathValue) {
|
|
|
15755
16028
|
}
|
|
15756
16029
|
return !isWithinPath(getOpenCodeConfigPrefix(), pathValue);
|
|
15757
16030
|
}
|
|
15758
|
-
function
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
16031
|
+
function isRootPath(pathValue) {
|
|
16032
|
+
if (!pathValue) {
|
|
16033
|
+
return false;
|
|
16034
|
+
}
|
|
16035
|
+
const resolved = resolve2(pathValue);
|
|
16036
|
+
if (resolved === "/") {
|
|
16037
|
+
return true;
|
|
16038
|
+
}
|
|
16039
|
+
return /^[A-Za-z]:[\\/]?$/.test(resolved);
|
|
16040
|
+
}
|
|
16041
|
+
function isAcceptableWorkspace(pathValue, configPrefix) {
|
|
16042
|
+
if (!pathValue) {
|
|
16043
|
+
return false;
|
|
15762
16044
|
}
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
return resolve2(envProjectDir);
|
|
16045
|
+
if (isRootPath(pathValue)) {
|
|
16046
|
+
return false;
|
|
15766
16047
|
}
|
|
16048
|
+
if (isWithinPath(configPrefix, pathValue)) {
|
|
16049
|
+
return false;
|
|
16050
|
+
}
|
|
16051
|
+
return true;
|
|
16052
|
+
}
|
|
16053
|
+
function resolveWorkspaceDirectory(worktree, directory) {
|
|
15767
16054
|
const configPrefix = getOpenCodeConfigPrefix();
|
|
16055
|
+
const envWorkspace = resolveCandidate(process.env.CURSOR_ACP_WORKSPACE);
|
|
16056
|
+
if (envWorkspace && !isRootPath(envWorkspace)) {
|
|
16057
|
+
return envWorkspace;
|
|
16058
|
+
}
|
|
16059
|
+
const envProjectDir = resolveCandidate(process.env.OPENCODE_CURSOR_PROJECT_DIR);
|
|
16060
|
+
if (envProjectDir && !isRootPath(envProjectDir)) {
|
|
16061
|
+
return envProjectDir;
|
|
16062
|
+
}
|
|
15768
16063
|
const worktreeCandidate = resolveCandidate(worktree);
|
|
15769
|
-
if (worktreeCandidate
|
|
16064
|
+
if (isAcceptableWorkspace(worktreeCandidate, configPrefix)) {
|
|
15770
16065
|
return worktreeCandidate;
|
|
15771
16066
|
}
|
|
15772
16067
|
const dirCandidate = resolveCandidate(directory);
|
|
15773
|
-
if (dirCandidate
|
|
16068
|
+
if (isAcceptableWorkspace(dirCandidate, configPrefix)) {
|
|
15774
16069
|
return dirCandidate;
|
|
15775
16070
|
}
|
|
15776
16071
|
const cwd = resolve2(process.cwd());
|
|
15777
|
-
if (cwd
|
|
16072
|
+
if (isAcceptableWorkspace(cwd, configPrefix)) {
|
|
15778
16073
|
return cwd;
|
|
15779
16074
|
}
|
|
15780
|
-
|
|
16075
|
+
const home = resolveCandidate(homedir5());
|
|
16076
|
+
if (home && !isRootPath(home)) {
|
|
16077
|
+
return home;
|
|
16078
|
+
}
|
|
16079
|
+
return configPrefix;
|
|
15781
16080
|
}
|
|
15782
16081
|
function normalizeWorkspaceForCompare(pathValue) {
|
|
15783
|
-
|
|
16082
|
+
const resolved = resolve2(pathValue);
|
|
16083
|
+
if (process.platform === "darwin" || process.platform === "win32") {
|
|
16084
|
+
return resolved.toLowerCase();
|
|
16085
|
+
}
|
|
16086
|
+
return resolved;
|
|
15784
16087
|
}
|
|
15785
16088
|
function isReusableProxyHealthPayload(payload, workspaceDirectory) {
|
|
15786
16089
|
if (!payload || payload.ok !== true) {
|
|
@@ -15801,7 +16104,7 @@ function parseToolLoopMode(value) {
|
|
|
15801
16104
|
function resolveChatParamTools(mode, existingTools, refreshedTools) {
|
|
15802
16105
|
return PROVIDER_BOUNDARY.resolveChatParamTools(mode, existingTools, refreshedTools);
|
|
15803
16106
|
}
|
|
15804
|
-
function createChatCompletionResponse(model, content, reasoningContent) {
|
|
16107
|
+
function createChatCompletionResponse(model, content, reasoningContent, usage) {
|
|
15805
16108
|
const message = {
|
|
15806
16109
|
role: "assistant",
|
|
15807
16110
|
content
|
|
@@ -15809,7 +16112,7 @@ function createChatCompletionResponse(model, content, reasoningContent) {
|
|
|
15809
16112
|
if (reasoningContent && reasoningContent.length > 0) {
|
|
15810
16113
|
message.reasoning_content = reasoningContent;
|
|
15811
16114
|
}
|
|
15812
|
-
|
|
16115
|
+
const response = {
|
|
15813
16116
|
id: `cursor-acp-${Date.now()}`,
|
|
15814
16117
|
object: "chat.completion",
|
|
15815
16118
|
created: Math.floor(Date.now() / 1000),
|
|
@@ -15822,6 +16125,10 @@ function createChatCompletionResponse(model, content, reasoningContent) {
|
|
|
15822
16125
|
}
|
|
15823
16126
|
]
|
|
15824
16127
|
};
|
|
16128
|
+
if (usage) {
|
|
16129
|
+
response.usage = usage;
|
|
16130
|
+
}
|
|
16131
|
+
return response;
|
|
15825
16132
|
}
|
|
15826
16133
|
function createChatCompletionChunk(id, created, model, deltaContent, done = false) {
|
|
15827
16134
|
return {
|
|
@@ -15843,7 +16150,9 @@ function extractCompletionFromStream(output) {
|
|
|
15843
16150
|
`);
|
|
15844
16151
|
let assistantText = "";
|
|
15845
16152
|
let reasoningText = "";
|
|
16153
|
+
let usage;
|
|
15846
16154
|
let sawAssistantPartials = false;
|
|
16155
|
+
let sawThinkingPartials = false;
|
|
15847
16156
|
for (const line of lines) {
|
|
15848
16157
|
const event = parseStreamJsonLine(line);
|
|
15849
16158
|
if (!event) {
|
|
@@ -15864,11 +16173,20 @@ function extractCompletionFromStream(output) {
|
|
|
15864
16173
|
if (isThinking(event)) {
|
|
15865
16174
|
const thinking = extractThinking(event);
|
|
15866
16175
|
if (thinking) {
|
|
15867
|
-
|
|
16176
|
+
const isPartial = typeof event.timestamp_ms === "number";
|
|
16177
|
+
if (isPartial) {
|
|
16178
|
+
reasoningText += thinking;
|
|
16179
|
+
sawThinkingPartials = true;
|
|
16180
|
+
} else if (!sawThinkingPartials) {
|
|
16181
|
+
reasoningText = thinking;
|
|
16182
|
+
}
|
|
15868
16183
|
}
|
|
15869
16184
|
}
|
|
16185
|
+
if (isResult(event)) {
|
|
16186
|
+
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
16187
|
+
}
|
|
15870
16188
|
}
|
|
15871
|
-
return { assistantText, reasoningText };
|
|
16189
|
+
return { assistantText, reasoningText, usage };
|
|
15872
16190
|
}
|
|
15873
16191
|
function formatToolUpdateEvent(update) {
|
|
15874
16192
|
return `event: tool_update
|
|
@@ -15897,9 +16215,9 @@ function createBoundaryRuntimeContext(scope) {
|
|
|
15897
16215
|
error: toErrorMessage(error)
|
|
15898
16216
|
};
|
|
15899
16217
|
if (!fallbackActive) {
|
|
15900
|
-
|
|
16218
|
+
log20.warn("Provider boundary v1 failed; switching to legacy for this request", details);
|
|
15901
16219
|
} else {
|
|
15902
|
-
|
|
16220
|
+
log20.debug("Provider boundary fallback already active", details);
|
|
15903
16221
|
}
|
|
15904
16222
|
fallbackActive = true;
|
|
15905
16223
|
return true;
|
|
@@ -16012,7 +16330,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16012
16330
|
if (url.pathname === "/v1/models" || url.pathname === "/models") {
|
|
16013
16331
|
try {
|
|
16014
16332
|
const bunAny2 = globalThis;
|
|
16015
|
-
const proc = bunAny2.Bun.spawn([
|
|
16333
|
+
const proc = bunAny2.Bun.spawn([resolveCursorAgentBinary(), "models"], {
|
|
16016
16334
|
stdout: "pipe",
|
|
16017
16335
|
stderr: "pipe"
|
|
16018
16336
|
});
|
|
@@ -16037,7 +16355,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16037
16355
|
headers: { "Content-Type": "application/json" }
|
|
16038
16356
|
});
|
|
16039
16357
|
} catch (err) {
|
|
16040
|
-
|
|
16358
|
+
log20.error("Failed to list models", { error: String(err) });
|
|
16041
16359
|
return new Response(JSON.stringify({ error: "Failed to fetch models from cursor-agent" }), {
|
|
16042
16360
|
status: 500,
|
|
16043
16361
|
headers: { "Content-Type": "application/json" }
|
|
@@ -16050,13 +16368,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16050
16368
|
headers: { "Content-Type": "application/json" }
|
|
16051
16369
|
});
|
|
16052
16370
|
}
|
|
16053
|
-
|
|
16371
|
+
log20.debug("Proxy request (bun)", { method: req.method, path: url.pathname });
|
|
16054
16372
|
const body = await req.json().catch(() => ({}));
|
|
16055
16373
|
const messages = Array.isArray(body?.messages) ? body.messages : [];
|
|
16056
16374
|
const stream = body?.stream === true;
|
|
16057
16375
|
const tools = Array.isArray(body?.tools) ? body.tools : [];
|
|
16058
16376
|
debugLogToFile2("raw_request_body", {
|
|
16059
16377
|
model: body?.model,
|
|
16378
|
+
cursorModel: body?.cursorModel,
|
|
16060
16379
|
stream,
|
|
16061
16380
|
toolCount: tools.length,
|
|
16062
16381
|
toolNames: tools.map((t) => t?.function?.name ?? t?.name ?? "unknown"),
|
|
@@ -16071,14 +16390,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16071
16390
|
const boundaryContext = createBoundaryRuntimeContext("bun-handler");
|
|
16072
16391
|
const subagentNames = readSubagentNames();
|
|
16073
16392
|
const prompt = buildPromptFromMessages(messages, tools, subagentNames);
|
|
16074
|
-
const model = boundaryContext.run("
|
|
16393
|
+
const model = boundaryContext.run("resolveRuntimeModel", (boundary) => boundary.resolveRuntimeModel(body?.model, body?.cursorModel));
|
|
16075
16394
|
const msgSummaryBun = messages.map((m, i) => {
|
|
16076
16395
|
const role = m?.role ?? "?";
|
|
16077
16396
|
const hasTc = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
|
|
16078
16397
|
const clen = typeof m?.content === "string" ? m.content.length : Array.isArray(m?.content) ? `arr${m.content.length}` : typeof m?.content;
|
|
16079
16398
|
return `${i}:${role}${hasTc ? `(tc:${hasTc})` : ""}(clen:${clen})`;
|
|
16080
16399
|
});
|
|
16081
|
-
|
|
16400
|
+
log20.debug("Proxy chat request (bun)", {
|
|
16082
16401
|
stream,
|
|
16083
16402
|
model,
|
|
16084
16403
|
messages: messages.length,
|
|
@@ -16094,7 +16413,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16094
16413
|
});
|
|
16095
16414
|
}
|
|
16096
16415
|
const cmd = [
|
|
16097
|
-
|
|
16416
|
+
resolveCursorAgentBinary(),
|
|
16098
16417
|
"--print",
|
|
16099
16418
|
"--output-format",
|
|
16100
16419
|
"stream-json",
|
|
@@ -16124,7 +16443,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16124
16443
|
const stdout = (stdoutText || "").trim();
|
|
16125
16444
|
const stderr = (stderrText || "").trim();
|
|
16126
16445
|
const exitCode = await child.exited;
|
|
16127
|
-
|
|
16446
|
+
log20.debug("cursor-agent completed (bun non-stream)", {
|
|
16128
16447
|
exitCode,
|
|
16129
16448
|
stdoutChars: stdout.length,
|
|
16130
16449
|
stderrChars: stderr.length
|
|
@@ -16150,7 +16469,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16150
16469
|
});
|
|
16151
16470
|
}
|
|
16152
16471
|
if (intercepted.toolCall) {
|
|
16153
|
-
|
|
16472
|
+
log20.debug("Intercepted OpenCode tool call (non-stream)", {
|
|
16154
16473
|
name: intercepted.toolCall.function.name,
|
|
16155
16474
|
callId: intercepted.toolCall.id
|
|
16156
16475
|
});
|
|
@@ -16164,7 +16483,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16164
16483
|
const errSource = stderr || stdout || `cursor-agent exited with code ${String(exitCode ?? "unknown")} and no output`;
|
|
16165
16484
|
const parsed = parseAgentError(errSource);
|
|
16166
16485
|
const userError = formatErrorForUser(parsed);
|
|
16167
|
-
|
|
16486
|
+
log20.error("cursor-cli failed", {
|
|
16168
16487
|
type: parsed.type,
|
|
16169
16488
|
message: parsed.message,
|
|
16170
16489
|
code: exitCode
|
|
@@ -16176,7 +16495,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16176
16495
|
});
|
|
16177
16496
|
}
|
|
16178
16497
|
const completion = extractCompletionFromStream(stdout);
|
|
16179
|
-
const payload = createChatCompletionResponse(model, completion.assistantText || stdout || stderr, completion.reasoningText || undefined);
|
|
16498
|
+
const payload = createChatCompletionResponse(model, completion.assistantText || stdout || stderr, completion.reasoningText || undefined, completion.usage);
|
|
16180
16499
|
return new Response(JSON.stringify(payload), {
|
|
16181
16500
|
status: 200,
|
|
16182
16501
|
headers: { "Content-Type": "application/json" }
|
|
@@ -16194,12 +16513,13 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16194
16513
|
async start(controller) {
|
|
16195
16514
|
let streamTerminated = false;
|
|
16196
16515
|
let firstTokenReceived = false;
|
|
16516
|
+
let usage;
|
|
16197
16517
|
try {
|
|
16198
16518
|
const reader = child.stdout.getReader();
|
|
16199
16519
|
const converter = new StreamToSseConverter(model, { id, created });
|
|
16200
16520
|
const lineBuffer = new LineBuffer;
|
|
16201
16521
|
const emitToolCallAndTerminate = (toolCall) => {
|
|
16202
|
-
|
|
16522
|
+
log20.debug("Intercepted OpenCode tool call (stream)", {
|
|
16203
16523
|
name: toolCall.function.name,
|
|
16204
16524
|
callId: toolCall.id
|
|
16205
16525
|
});
|
|
@@ -16248,6 +16568,9 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16248
16568
|
if (!event) {
|
|
16249
16569
|
continue;
|
|
16250
16570
|
}
|
|
16571
|
+
if (isResult(event)) {
|
|
16572
|
+
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
16573
|
+
}
|
|
16251
16574
|
if (event.type === "tool_call") {
|
|
16252
16575
|
perf.mark("tool-call");
|
|
16253
16576
|
const result = await handleToolLoopEventWithFallback({
|
|
@@ -16316,6 +16639,9 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16316
16639
|
if (!event) {
|
|
16317
16640
|
continue;
|
|
16318
16641
|
}
|
|
16642
|
+
if (isResult(event)) {
|
|
16643
|
+
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
16644
|
+
}
|
|
16319
16645
|
if (event.type === "tool_call") {
|
|
16320
16646
|
const result = await handleToolLoopEventWithFallback({
|
|
16321
16647
|
event,
|
|
@@ -16381,7 +16707,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16381
16707
|
const errSource = (stderrText || "").trim() || `cursor-agent exited with code ${String(exitCode ?? "unknown")} and no output`;
|
|
16382
16708
|
const parsed = parseAgentError(errSource);
|
|
16383
16709
|
const msg = formatErrorForUser(parsed);
|
|
16384
|
-
|
|
16710
|
+
log20.error("cursor-cli streaming failed", {
|
|
16385
16711
|
type: parsed.type,
|
|
16386
16712
|
code: exitCode
|
|
16387
16713
|
});
|
|
@@ -16392,7 +16718,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16392
16718
|
controller.enqueue(encoder.encode(formatSseDone()));
|
|
16393
16719
|
return;
|
|
16394
16720
|
}
|
|
16395
|
-
|
|
16721
|
+
log20.debug("cursor-agent completed (bun stream)", {
|
|
16396
16722
|
exitCode
|
|
16397
16723
|
});
|
|
16398
16724
|
const passThroughSummary = passThroughTracker.getSummary();
|
|
@@ -16406,6 +16732,12 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16406
16732
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify(doneChunk)}
|
|
16407
16733
|
|
|
16408
16734
|
`));
|
|
16735
|
+
if (usage) {
|
|
16736
|
+
const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
|
|
16737
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(usageChunk)}
|
|
16738
|
+
|
|
16739
|
+
`));
|
|
16740
|
+
}
|
|
16409
16741
|
controller.enqueue(encoder.encode(formatSseDone()));
|
|
16410
16742
|
} finally {
|
|
16411
16743
|
perf.mark("request:done");
|
|
@@ -16455,8 +16787,8 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16455
16787
|
}
|
|
16456
16788
|
if (url.pathname === "/v1/models" || url.pathname === "/models") {
|
|
16457
16789
|
try {
|
|
16458
|
-
const {
|
|
16459
|
-
const output =
|
|
16790
|
+
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
16791
|
+
const output = execFileSync2(resolveCursorAgentBinary(), ["models"], { encoding: "utf-8", timeout: 30000 });
|
|
16460
16792
|
const clean = stripAnsi(output);
|
|
16461
16793
|
const models = [];
|
|
16462
16794
|
for (const line of clean.split(`
|
|
@@ -16474,7 +16806,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16474
16806
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
16475
16807
|
res.end(JSON.stringify({ object: "list", data: models }));
|
|
16476
16808
|
} catch (err) {
|
|
16477
|
-
|
|
16809
|
+
log20.error("Failed to list models", { error: String(err) });
|
|
16478
16810
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
16479
16811
|
res.end(JSON.stringify({ error: "Failed to fetch models" }));
|
|
16480
16812
|
}
|
|
@@ -16485,7 +16817,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16485
16817
|
res.end(JSON.stringify({ error: `Unsupported path: ${url.pathname}` }));
|
|
16486
16818
|
return;
|
|
16487
16819
|
}
|
|
16488
|
-
|
|
16820
|
+
log20.debug("Proxy request (node)", { method: req.method, path: url.pathname });
|
|
16489
16821
|
let body = "";
|
|
16490
16822
|
for await (const chunk of req) {
|
|
16491
16823
|
body += chunk;
|
|
@@ -16500,7 +16832,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16500
16832
|
const boundaryContext = createBoundaryRuntimeContext("node-handler");
|
|
16501
16833
|
const subagentNames = readSubagentNames();
|
|
16502
16834
|
const prompt = buildPromptFromMessages(messages, tools, subagentNames);
|
|
16503
|
-
const model = boundaryContext.run("
|
|
16835
|
+
const model = boundaryContext.run("resolveRuntimeModel", (boundary) => boundary.resolveRuntimeModel(bodyData?.model, bodyData?.cursorModel));
|
|
16504
16836
|
const msgSummary = messages.map((m, i) => {
|
|
16505
16837
|
const role = m?.role ?? "?";
|
|
16506
16838
|
const hasTc = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
|
|
@@ -16509,7 +16841,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16509
16841
|
const contentLen = typeof m?.content === "string" ? m.content.length : Array.isArray(m?.content) ? `arr${m.content.length}` : typeof m?.content;
|
|
16510
16842
|
return `${i}:${role}${hasTc ? `(tc:${hasTc})` : ""}${role === "tool" ? `(tcid:${tcId},name:${tcName},clen:${contentLen})` : `(clen:${contentLen})`}`;
|
|
16511
16843
|
});
|
|
16512
|
-
|
|
16844
|
+
log20.debug("Proxy chat request (node)", {
|
|
16513
16845
|
stream,
|
|
16514
16846
|
model,
|
|
16515
16847
|
messages: messages.length,
|
|
@@ -16518,7 +16850,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16518
16850
|
msgRoles: msgSummary.join(",")
|
|
16519
16851
|
});
|
|
16520
16852
|
const cmd = [
|
|
16521
|
-
|
|
16853
|
+
resolveCursorAgentBinary(),
|
|
16522
16854
|
"--print",
|
|
16523
16855
|
"--output-format",
|
|
16524
16856
|
"stream-json",
|
|
@@ -16531,7 +16863,10 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16531
16863
|
if (FORCE_TOOL_MODE) {
|
|
16532
16864
|
cmd.push("--force");
|
|
16533
16865
|
}
|
|
16534
|
-
const child = spawn3(cmd[0], cmd.slice(1), {
|
|
16866
|
+
const child = spawn3(cmd[0], cmd.slice(1), {
|
|
16867
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16868
|
+
shell: process.platform === "win32"
|
|
16869
|
+
});
|
|
16535
16870
|
child.stdin.write(prompt);
|
|
16536
16871
|
child.stdin.end();
|
|
16537
16872
|
if (!stream) {
|
|
@@ -16540,14 +16875,14 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16540
16875
|
let spawnErrorText = null;
|
|
16541
16876
|
child.on("error", (error) => {
|
|
16542
16877
|
spawnErrorText = String(error?.message || error);
|
|
16543
|
-
|
|
16878
|
+
log20.error("Failed to spawn cursor-agent", { error: spawnErrorText, model });
|
|
16544
16879
|
});
|
|
16545
16880
|
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
16546
16881
|
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
16547
16882
|
child.on("close", async (code) => {
|
|
16548
16883
|
const stdout = Buffer.concat(stdoutChunks).toString().trim();
|
|
16549
16884
|
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
16550
|
-
|
|
16885
|
+
log20.debug("cursor-agent completed (node non-stream)", {
|
|
16551
16886
|
code,
|
|
16552
16887
|
stdoutChars: stdout.length,
|
|
16553
16888
|
stderrChars: stderr.length,
|
|
@@ -16573,7 +16908,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16573
16908
|
return;
|
|
16574
16909
|
}
|
|
16575
16910
|
if (intercepted.toolCall) {
|
|
16576
|
-
|
|
16911
|
+
log20.debug("Intercepted OpenCode tool call (non-stream)", {
|
|
16577
16912
|
name: intercepted.toolCall.function.name,
|
|
16578
16913
|
callId: intercepted.toolCall.id
|
|
16579
16914
|
});
|
|
@@ -16587,7 +16922,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16587
16922
|
const errSource = stderr || stdout || spawnErrorText || `cursor-agent exited with code ${String(code ?? "unknown")} and no output`;
|
|
16588
16923
|
const parsed = parseAgentError(errSource);
|
|
16589
16924
|
const userError = formatErrorForUser(parsed);
|
|
16590
|
-
|
|
16925
|
+
log20.error("cursor-cli failed", {
|
|
16591
16926
|
type: parsed.type,
|
|
16592
16927
|
message: parsed.message,
|
|
16593
16928
|
code
|
|
@@ -16597,7 +16932,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16597
16932
|
res.end(JSON.stringify(errorResponse));
|
|
16598
16933
|
return;
|
|
16599
16934
|
}
|
|
16600
|
-
const response = createChatCompletionResponse(model, completion.assistantText || stdout || stderr, completion.reasoningText || undefined);
|
|
16935
|
+
const response = createChatCompletionResponse(model, completion.assistantText || stdout || stderr, completion.reasoningText || undefined, completion.usage);
|
|
16601
16936
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
16602
16937
|
res.end(JSON.stringify(response));
|
|
16603
16938
|
});
|
|
@@ -16619,6 +16954,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16619
16954
|
const stderrChunks = [];
|
|
16620
16955
|
let streamTerminated = false;
|
|
16621
16956
|
let firstTokenReceived = false;
|
|
16957
|
+
let usage;
|
|
16622
16958
|
child.stderr.on("data", (chunk) => {
|
|
16623
16959
|
stderrChunks.push(Buffer.from(chunk));
|
|
16624
16960
|
});
|
|
@@ -16627,7 +16963,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16627
16963
|
return;
|
|
16628
16964
|
}
|
|
16629
16965
|
const errSource = String(error?.message || error);
|
|
16630
|
-
|
|
16966
|
+
log20.error("Failed to spawn cursor-agent (stream)", { error: errSource, model });
|
|
16631
16967
|
const parsed = parseAgentError(errSource);
|
|
16632
16968
|
const msg = formatErrorForUser(parsed);
|
|
16633
16969
|
const errChunk = createChatCompletionChunk(id, created, model, msg, true);
|
|
@@ -16642,7 +16978,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16642
16978
|
if (streamTerminated || res.writableEnded) {
|
|
16643
16979
|
return;
|
|
16644
16980
|
}
|
|
16645
|
-
|
|
16981
|
+
log20.debug("Intercepted OpenCode tool call (stream)", {
|
|
16646
16982
|
name: toolCall.function.name,
|
|
16647
16983
|
callId: toolCall.id
|
|
16648
16984
|
});
|
|
@@ -16690,6 +17026,9 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16690
17026
|
if (!event) {
|
|
16691
17027
|
continue;
|
|
16692
17028
|
}
|
|
17029
|
+
if (isResult(event)) {
|
|
17030
|
+
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
17031
|
+
}
|
|
16693
17032
|
if (event.type === "tool_call") {
|
|
16694
17033
|
perf.mark("tool-call");
|
|
16695
17034
|
const result = await handleToolLoopEventWithFallback({
|
|
@@ -16762,6 +17101,9 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16762
17101
|
if (!event) {
|
|
16763
17102
|
continue;
|
|
16764
17103
|
}
|
|
17104
|
+
if (isResult(event)) {
|
|
17105
|
+
usage = extractOpenAiUsageFromResult(event) ?? usage;
|
|
17106
|
+
}
|
|
16765
17107
|
if (event.type === "tool_call") {
|
|
16766
17108
|
const result = await handleToolLoopEventWithFallback({
|
|
16767
17109
|
event,
|
|
@@ -16826,7 +17168,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16826
17168
|
perf.mark("request:done");
|
|
16827
17169
|
perf.summarize();
|
|
16828
17170
|
const stderrText = Buffer.concat(stderrChunks).toString().trim();
|
|
16829
|
-
|
|
17171
|
+
log20.debug("cursor-agent completed (node stream)", {
|
|
16830
17172
|
code,
|
|
16831
17173
|
stderrChars: stderrText.length
|
|
16832
17174
|
});
|
|
@@ -16866,6 +17208,12 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
|
|
|
16866
17208
|
res.write(`data: ${JSON.stringify(doneChunk)}
|
|
16867
17209
|
|
|
16868
17210
|
`);
|
|
17211
|
+
if (usage) {
|
|
17212
|
+
const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
|
|
17213
|
+
res.write(`data: ${JSON.stringify(usageChunk)}
|
|
17214
|
+
|
|
17215
|
+
`);
|
|
17216
|
+
}
|
|
16869
17217
|
res.write(formatSseDone());
|
|
16870
17218
|
res.end();
|
|
16871
17219
|
});
|
|
@@ -16943,7 +17291,7 @@ function jsonSchemaToZod(jsonSchema) {
|
|
|
16943
17291
|
}
|
|
16944
17292
|
break;
|
|
16945
17293
|
case "object":
|
|
16946
|
-
zodType = z2.record(z2.any());
|
|
17294
|
+
zodType = z2.record(z2.string(), z2.any());
|
|
16947
17295
|
if (p.description) {
|
|
16948
17296
|
zodType = zodType.describe(p.description);
|
|
16949
17297
|
}
|
|
@@ -17054,7 +17402,7 @@ function buildToolHookEntries(registry, fallbackBaseDir) {
|
|
|
17054
17402
|
const normalizedArgs = applyToolContextDefaults(toolName, args, context, fallbackBaseDir, sessionWorkspaceBySession);
|
|
17055
17403
|
return await handler(normalizedArgs);
|
|
17056
17404
|
} catch (error) {
|
|
17057
|
-
|
|
17405
|
+
log20.debug("Tool hook execution failed", { tool: toolName, error: String(error?.message || error) });
|
|
17058
17406
|
throw error;
|
|
17059
17407
|
}
|
|
17060
17408
|
}
|
|
@@ -17066,9 +17414,9 @@ function buildToolHookEntries(registry, fallbackBaseDir) {
|
|
|
17066
17414
|
}
|
|
17067
17415
|
return entries;
|
|
17068
17416
|
}
|
|
17069
|
-
var
|
|
17417
|
+
var log20, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, 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, CursorPlugin = async ({ $, directory, worktree, client: client3, serverUrl }) => {
|
|
17070
17418
|
const workspaceDirectory = resolveWorkspaceDirectory(worktree, directory);
|
|
17071
|
-
|
|
17419
|
+
log20.debug("Plugin initializing", {
|
|
17072
17420
|
directory,
|
|
17073
17421
|
worktree,
|
|
17074
17422
|
workspaceDirectory,
|
|
@@ -17076,22 +17424,22 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17076
17424
|
serverUrl: serverUrl?.toString()
|
|
17077
17425
|
});
|
|
17078
17426
|
if (!TOOL_LOOP_MODE_VALID) {
|
|
17079
|
-
|
|
17427
|
+
log20.warn("Invalid CURSOR_ACP_TOOL_LOOP_MODE; defaulting to opencode", { value: TOOL_LOOP_MODE_RAW });
|
|
17080
17428
|
}
|
|
17081
17429
|
if (!PROVIDER_BOUNDARY_MODE_VALID) {
|
|
17082
|
-
|
|
17430
|
+
log20.warn("Invalid CURSOR_ACP_PROVIDER_BOUNDARY; defaulting to v1", {
|
|
17083
17431
|
value: PROVIDER_BOUNDARY_MODE_RAW
|
|
17084
17432
|
});
|
|
17085
17433
|
}
|
|
17086
17434
|
if (!TOOL_LOOP_MAX_REPEAT_VALID) {
|
|
17087
|
-
|
|
17435
|
+
log20.warn("Invalid CURSOR_ACP_TOOL_LOOP_MAX_REPEAT; defaulting to 3", {
|
|
17088
17436
|
value: TOOL_LOOP_MAX_REPEAT_RAW
|
|
17089
17437
|
});
|
|
17090
17438
|
}
|
|
17091
17439
|
if (ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK && PROVIDER_BOUNDARY.mode !== "v1") {
|
|
17092
|
-
|
|
17440
|
+
log20.debug("Provider boundary auto-fallback is enabled but inactive unless mode=v1");
|
|
17093
17441
|
}
|
|
17094
|
-
|
|
17442
|
+
log20.info("Tool loop mode configured", {
|
|
17095
17443
|
mode: TOOL_LOOP_MODE,
|
|
17096
17444
|
providerBoundary: PROVIDER_BOUNDARY.mode,
|
|
17097
17445
|
proxyExecToolCalls: PROXY_EXECUTE_TOOL_CALLS,
|
|
@@ -17109,13 +17457,13 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17109
17457
|
try {
|
|
17110
17458
|
const configs = readMcpConfigs();
|
|
17111
17459
|
if (configs.length === 0) {
|
|
17112
|
-
|
|
17460
|
+
log20.debug("No MCP servers configured, skipping MCP bridge");
|
|
17113
17461
|
} else {
|
|
17114
|
-
|
|
17462
|
+
log20.debug("MCP bridge: connecting to servers", { count: configs.length });
|
|
17115
17463
|
await Promise.allSettled(configs.map((c) => mcpManager.connectServer(c)));
|
|
17116
17464
|
const tools = mcpManager.listTools();
|
|
17117
17465
|
if (tools.length === 0) {
|
|
17118
|
-
|
|
17466
|
+
log20.debug("MCP bridge: no tools discovered");
|
|
17119
17467
|
} else {
|
|
17120
17468
|
mcpToolEntries = buildMcpToolHookEntries(tools, mcpManager);
|
|
17121
17469
|
mcpToolDefs = buildMcpToolDefinitions(tools);
|
|
@@ -17125,23 +17473,23 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17125
17473
|
description: t.description,
|
|
17126
17474
|
params: t.inputSchema ? Object.keys(t.inputSchema.properties ?? {}) : undefined
|
|
17127
17475
|
}));
|
|
17128
|
-
|
|
17476
|
+
log20.info("MCP bridge: registered tools", {
|
|
17129
17477
|
servers: mcpManager.connectedServers.length,
|
|
17130
17478
|
tools: Object.keys(mcpToolEntries).length
|
|
17131
17479
|
});
|
|
17132
17480
|
}
|
|
17133
17481
|
}
|
|
17134
17482
|
} catch (err) {
|
|
17135
|
-
|
|
17483
|
+
log20.debug("MCP bridge init failed", { error: String(err) });
|
|
17136
17484
|
}
|
|
17137
17485
|
}
|
|
17138
17486
|
toastService.setClient(client3);
|
|
17139
17487
|
const toolsEnabled = process.env.CURSOR_ACP_ENABLE_OPENCODE_TOOLS !== "false";
|
|
17140
17488
|
const legacyProxyToolPathsEnabled = toolsEnabled && TOOL_LOOP_MODE === "proxy-exec";
|
|
17141
17489
|
if (toolsEnabled && TOOL_LOOP_MODE === "opencode") {
|
|
17142
|
-
|
|
17490
|
+
log20.debug("OpenCode mode active; skipping legacy SDK/MCP discovery and proxy-side tool execution");
|
|
17143
17491
|
} else if (toolsEnabled && TOOL_LOOP_MODE === "off") {
|
|
17144
|
-
|
|
17492
|
+
log20.debug("Tool loop mode off; proxy-side tool execution disabled");
|
|
17145
17493
|
}
|
|
17146
17494
|
const serverClient = legacyProxyToolPathsEnabled ? createOpencodeClient({ baseUrl: serverUrl.toString(), directory: workspaceDirectory }) : null;
|
|
17147
17495
|
const discovery = legacyProxyToolPathsEnabled ? new OpenCodeToolDiscovery(serverClient ?? client3) : null;
|
|
@@ -17193,7 +17541,7 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17193
17541
|
discoveredList = await discovery.listTools();
|
|
17194
17542
|
discoveredList.forEach((t) => toolsByName.set(t.name, t));
|
|
17195
17543
|
} catch (err) {
|
|
17196
|
-
|
|
17544
|
+
log20.debug("Tool discovery failed, using local tools only", { error: String(err) });
|
|
17197
17545
|
}
|
|
17198
17546
|
}
|
|
17199
17547
|
const allTools = [...localTools, ...discoveredList];
|
|
@@ -17223,11 +17571,11 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17223
17571
|
}
|
|
17224
17572
|
lastToolNames = toolEntries.map((e) => e.function.name);
|
|
17225
17573
|
lastToolMap = allTools.map((t) => ({ id: t.id, name: t.name }));
|
|
17226
|
-
|
|
17574
|
+
log20.debug("Tools refreshed", { local: localTools.length, discovered: discoveredList.length, total: toolEntries.length });
|
|
17227
17575
|
return toolEntries;
|
|
17228
17576
|
}
|
|
17229
17577
|
const proxyBaseURL = await ensureCursorProxyServer(workspaceDirectory, router);
|
|
17230
|
-
|
|
17578
|
+
log20.debug("Proxy server started", { baseURL: proxyBaseURL });
|
|
17231
17579
|
const toolHookEntries = buildToolHookEntries(localRegistry, workspaceDirectory);
|
|
17232
17580
|
return {
|
|
17233
17581
|
tool: { ...toolHookEntries, ...mcpToolEntries },
|
|
@@ -17242,9 +17590,9 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17242
17590
|
type: "oauth",
|
|
17243
17591
|
async authorize() {
|
|
17244
17592
|
try {
|
|
17245
|
-
|
|
17593
|
+
log20.info("Starting OAuth flow");
|
|
17246
17594
|
const { url, instructions, callback } = await startCursorOAuth();
|
|
17247
|
-
|
|
17595
|
+
log20.debug("Got OAuth URL", { url: url.substring(0, 50) + "..." });
|
|
17248
17596
|
return {
|
|
17249
17597
|
url,
|
|
17250
17598
|
instructions,
|
|
@@ -17252,7 +17600,7 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17252
17600
|
callback
|
|
17253
17601
|
};
|
|
17254
17602
|
} catch (error) {
|
|
17255
|
-
|
|
17603
|
+
log20.error("OAuth error", { error });
|
|
17256
17604
|
throw error;
|
|
17257
17605
|
}
|
|
17258
17606
|
}
|
|
@@ -17276,10 +17624,10 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17276
17624
|
output.options.tools = resolved.tools;
|
|
17277
17625
|
} else if (resolved.action === "preserve") {
|
|
17278
17626
|
const count = Array.isArray(existingTools) ? existingTools.length : 0;
|
|
17279
|
-
|
|
17627
|
+
log20.debug("Using OpenCode-provided tools from chat.params", { count });
|
|
17280
17628
|
}
|
|
17281
17629
|
} catch (err) {
|
|
17282
|
-
|
|
17630
|
+
log20.debug("Failed to refresh tools", { error: String(err) });
|
|
17283
17631
|
}
|
|
17284
17632
|
}
|
|
17285
17633
|
if (mcpToolDefs.length > 0) {
|
|
@@ -17290,7 +17638,7 @@ var log19, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
|
|
|
17290
17638
|
output.options.tools = mcpToolDefs;
|
|
17291
17639
|
}
|
|
17292
17640
|
const afterTools = Array.isArray(output.options.tools) ? output.options.tools : [];
|
|
17293
|
-
|
|
17641
|
+
log20.debug("Injected MCP tool definitions into chat.params", {
|
|
17294
17642
|
injectedCount: mcpToolDefs.length,
|
|
17295
17643
|
beforeCount: beforeTools.length,
|
|
17296
17644
|
afterCount: afterTools.length,
|
|
@@ -17331,14 +17679,16 @@ var init_plugin = __esm(() => {
|
|
|
17331
17679
|
init_sdk();
|
|
17332
17680
|
init_mcp();
|
|
17333
17681
|
init_executor();
|
|
17682
|
+
init_defaults();
|
|
17334
17683
|
init_boundary();
|
|
17335
17684
|
init_runtime_interception();
|
|
17336
17685
|
init_toast_service();
|
|
17337
17686
|
init_tool_schema_compat();
|
|
17338
17687
|
init_tool_loop_guard();
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17688
|
+
init_binary();
|
|
17689
|
+
log20 = createLogger("plugin");
|
|
17690
|
+
DEBUG_LOG_DIR2 = join6(homedir5(), ".config", "opencode", "logs");
|
|
17691
|
+
DEBUG_LOG_FILE2 = join6(DEBUG_LOG_DIR2, "tool-loop-debug.log");
|
|
17342
17692
|
CURSOR_PROVIDER_PREFIX = `${CURSOR_PROVIDER_ID2}/`;
|
|
17343
17693
|
CURSOR_PROXY_DEFAULT_BASE_URL = `http://${CURSOR_PROXY_HOST}:${CURSOR_PROXY_DEFAULT_PORT}/v1`;
|
|
17344
17694
|
REUSE_EXISTING_PROXY = process.env.CURSOR_ACP_REUSE_EXISTING_PROXY !== "false";
|
|
@@ -17374,6 +17724,7 @@ init_plugin();
|
|
|
17374
17724
|
// src/client/simple.ts
|
|
17375
17725
|
init_parser();
|
|
17376
17726
|
init_logger();
|
|
17727
|
+
init_binary();
|
|
17377
17728
|
import { spawn as spawn3 } from "child_process";
|
|
17378
17729
|
|
|
17379
17730
|
class SimpleCursorClient {
|
|
@@ -17384,7 +17735,7 @@ class SimpleCursorClient {
|
|
|
17384
17735
|
timeout: 30000,
|
|
17385
17736
|
maxRetries: 3,
|
|
17386
17737
|
streamOutput: true,
|
|
17387
|
-
cursorAgentPath:
|
|
17738
|
+
cursorAgentPath: resolveCursorAgentBinary(),
|
|
17388
17739
|
...config
|
|
17389
17740
|
};
|
|
17390
17741
|
this.log = createLogger("cursor-client");
|
|
@@ -17418,7 +17769,8 @@ class SimpleCursorClient {
|
|
|
17418
17769
|
this.log.debug("Executing prompt stream", { promptLength: prompt.length, mode, model });
|
|
17419
17770
|
const child = spawn3(this.config.cursorAgentPath, args, {
|
|
17420
17771
|
cwd,
|
|
17421
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
17772
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17773
|
+
shell: process.platform === "win32"
|
|
17422
17774
|
});
|
|
17423
17775
|
if (prompt) {
|
|
17424
17776
|
child.stdin.write(prompt);
|
|
@@ -17504,7 +17856,8 @@ class SimpleCursorClient {
|
|
|
17504
17856
|
return new Promise((resolve3, reject) => {
|
|
17505
17857
|
const child = spawn3(this.config.cursorAgentPath, args, {
|
|
17506
17858
|
cwd,
|
|
17507
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
17859
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17860
|
+
shell: process.platform === "win32"
|
|
17508
17861
|
});
|
|
17509
17862
|
let stdoutBuffer = "";
|
|
17510
17863
|
let stderrBuffer = "";
|
|
@@ -17593,7 +17946,7 @@ init_logger();
|
|
|
17593
17946
|
import { execSync } from "node:child_process";
|
|
17594
17947
|
import { createServer } from "node:net";
|
|
17595
17948
|
import { platform as platform2 } from "node:os";
|
|
17596
|
-
var
|
|
17949
|
+
var log21 = createLogger("proxy-server");
|
|
17597
17950
|
var DEFAULT_PORT = 32124;
|
|
17598
17951
|
var PORT_RANGE_SIZE = 256;
|
|
17599
17952
|
async function isPortAvailable(port, host) {
|
|
@@ -17640,11 +17993,11 @@ function getUsedPortsInRange(minPort, maxPort) {
|
|
|
17640
17993
|
}
|
|
17641
17994
|
}
|
|
17642
17995
|
} else {
|
|
17643
|
-
|
|
17996
|
+
log21.debug(`Port detection not supported on ${os2}. Using probe-based discovery.`);
|
|
17644
17997
|
}
|
|
17645
17998
|
} catch (error) {
|
|
17646
17999
|
const msg = error instanceof Error ? error.message : String(error);
|
|
17647
|
-
|
|
18000
|
+
log21.debug(`Port detection failed: ${msg}. Using probe-based discovery.`);
|
|
17648
18001
|
}
|
|
17649
18002
|
return used;
|
|
17650
18003
|
}
|
|
@@ -17695,7 +18048,7 @@ function createProxyServer(config) {
|
|
|
17695
18048
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
17696
18049
|
const isPortInUse = err.message.includes("EADDRINUSE") || err.message.includes("address already in use") || err.message.includes("port is already in use");
|
|
17697
18050
|
if (!isPortInUse) {
|
|
17698
|
-
|
|
18051
|
+
log21.debug(`Unexpected error starting on port ${port}: ${err.message}`);
|
|
17699
18052
|
}
|
|
17700
18053
|
return { success: false, error: err };
|
|
17701
18054
|
}
|
|
@@ -17711,13 +18064,13 @@ function createProxyServer(config) {
|
|
|
17711
18064
|
if (result.success) {
|
|
17712
18065
|
port = requestedPort;
|
|
17713
18066
|
} else {
|
|
17714
|
-
|
|
18067
|
+
log21.debug(`Requested port ${requestedPort} unavailable: ${result.error?.message ?? "unknown"}. Falling back to automatic port selection.`);
|
|
17715
18068
|
port = await findAvailablePort(host);
|
|
17716
18069
|
const fallbackResult = tryStart(port);
|
|
17717
18070
|
if (!fallbackResult.success) {
|
|
17718
18071
|
throw new Error(`Failed to start server on port ${requestedPort} (${result.error?.message ?? "unknown"}) ` + `and fallback port ${port} (${fallbackResult.error?.message ?? "unknown"})`);
|
|
17719
18072
|
}
|
|
17720
|
-
|
|
18073
|
+
log21.debug(`Server started on fallback port ${port} instead of requested port ${requestedPort}`);
|
|
17721
18074
|
}
|
|
17722
18075
|
} else {
|
|
17723
18076
|
port = await findAvailablePort(host);
|
|
@@ -18046,8 +18399,8 @@ function parseOpenAIRequest(body) {
|
|
|
18046
18399
|
};
|
|
18047
18400
|
}
|
|
18048
18401
|
// src/proxy/formatter.ts
|
|
18049
|
-
function createChatCompletionResponse2(model, content) {
|
|
18050
|
-
|
|
18402
|
+
function createChatCompletionResponse2(model, content, usage) {
|
|
18403
|
+
const response = {
|
|
18051
18404
|
id: `cursor-acp-${Date.now()}`,
|
|
18052
18405
|
object: "chat.completion",
|
|
18053
18406
|
created: Math.floor(Date.now() / 1000),
|
|
@@ -18058,13 +18411,12 @@ function createChatCompletionResponse2(model, content) {
|
|
|
18058
18411
|
message: { role: "assistant", content },
|
|
18059
18412
|
finish_reason: "stop"
|
|
18060
18413
|
}
|
|
18061
|
-
]
|
|
18062
|
-
usage: {
|
|
18063
|
-
prompt_tokens: 0,
|
|
18064
|
-
completion_tokens: 0,
|
|
18065
|
-
total_tokens: 0
|
|
18066
|
-
}
|
|
18414
|
+
]
|
|
18067
18415
|
};
|
|
18416
|
+
if (usage) {
|
|
18417
|
+
response.usage = usage;
|
|
18418
|
+
}
|
|
18419
|
+
return response;
|
|
18068
18420
|
}
|
|
18069
18421
|
function createChatCompletionChunk2(id, created, model, deltaContent, done = false) {
|
|
18070
18422
|
return {
|
|
@@ -18089,11 +18441,11 @@ init_auth();
|
|
|
18089
18441
|
init_auth();
|
|
18090
18442
|
init_logger();
|
|
18091
18443
|
import { existsSync as existsSync6 } from "fs";
|
|
18092
|
-
var
|
|
18444
|
+
var log22 = createLogger("status");
|
|
18093
18445
|
function checkAuthStatus() {
|
|
18094
18446
|
const authFilePath = getAuthFilePath();
|
|
18095
18447
|
const exists = existsSync6(authFilePath);
|
|
18096
|
-
|
|
18448
|
+
log22.debug("Checking auth status", { path: authFilePath });
|
|
18097
18449
|
if (exists) {
|
|
18098
18450
|
return {
|
|
18099
18451
|
authenticated: true,
|