@agentex/agent 0.0.26 → 0.0.28
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/CHANGELOG.md +118 -0
- package/README.md +58 -7
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +99 -2
- package/dist/derived.js.map +1 -1
- package/dist/goals/controller.d.ts.map +1 -1
- package/dist/goals/controller.js +21 -0
- package/dist/goals/controller.js.map +1 -1
- package/dist/goals/normalize.d.ts +1 -0
- package/dist/goals/normalize.d.ts.map +1 -1
- package/dist/goals/normalize.js +5 -0
- package/dist/goals/normalize.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/attach.d.ts.map +1 -1
- package/dist/providers/claude/attach.js +50 -9
- package/dist/providers/claude/attach.js.map +1 -1
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +8 -0
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +2 -0
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +17 -10
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts.map +1 -1
- package/dist/providers/codex/attach.js +40 -3
- package/dist/providers/codex/attach.js.map +1 -1
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +8 -0
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/session.d.ts.map +1 -1
- package/dist/providers/codex/session.js +6 -0
- package/dist/providers/codex/session.js.map +1 -1
- package/dist/providers/codex/transcript.d.ts +3 -1
- package/dist/providers/codex/transcript.d.ts.map +1 -1
- package/dist/providers/codex/transcript.js +3 -1
- package/dist/providers/codex/transcript.js.map +1 -1
- package/dist/providers/cursor/discovery.d.ts +6 -0
- package/dist/providers/cursor/discovery.d.ts.map +1 -0
- package/dist/providers/cursor/discovery.js +118 -0
- package/dist/providers/cursor/discovery.js.map +1 -0
- package/dist/providers/cursor/execute.d.ts.map +1 -1
- package/dist/providers/cursor/execute.js +83 -45
- package/dist/providers/cursor/execute.js.map +1 -1
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +17 -4
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/cursor/parse.js +1 -1
- package/dist/providers/cursor/parse.js.map +1 -1
- package/dist/providers/cursor/probe.d.ts +3 -0
- package/dist/providers/cursor/probe.d.ts.map +1 -0
- package/dist/providers/cursor/probe.js +89 -0
- package/dist/providers/cursor/probe.js.map +1 -0
- package/dist/providers/cursor/runtime.d.ts +4 -0
- package/dist/providers/cursor/runtime.d.ts.map +1 -0
- package/dist/providers/cursor/runtime.js +12 -0
- package/dist/providers/cursor/runtime.js.map +1 -0
- package/dist/providers/cursor/session.d.ts +3 -0
- package/dist/providers/cursor/session.d.ts.map +1 -0
- package/dist/providers/cursor/session.js +14 -0
- package/dist/providers/cursor/session.js.map +1 -0
- package/dist/providers/opencode/client.d.ts +14 -0
- package/dist/providers/opencode/client.d.ts.map +1 -0
- package/dist/providers/opencode/client.js +40 -0
- package/dist/providers/opencode/client.js.map +1 -0
- package/dist/providers/opencode/discovery.d.ts +4 -0
- package/dist/providers/opencode/discovery.d.ts.map +1 -0
- package/dist/providers/opencode/discovery.js +68 -0
- package/dist/providers/opencode/discovery.js.map +1 -0
- package/dist/providers/opencode/execute.d.ts.map +1 -1
- package/dist/providers/opencode/execute.js +219 -164
- package/dist/providers/opencode/execute.js.map +1 -1
- package/dist/providers/opencode/history.d.ts +24 -0
- package/dist/providers/opencode/history.d.ts.map +1 -0
- package/dist/providers/opencode/history.js +242 -0
- package/dist/providers/opencode/history.js.map +1 -0
- package/dist/providers/opencode/http-session.d.ts.map +1 -1
- package/dist/providers/opencode/http-session.js +333 -49
- package/dist/providers/opencode/http-session.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +26 -3
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/opencode/manager.d.ts +11 -0
- package/dist/providers/opencode/manager.d.ts.map +1 -0
- package/dist/providers/opencode/manager.js +240 -0
- package/dist/providers/opencode/manager.js.map +1 -0
- package/dist/providers/opencode/modes.d.ts +3 -0
- package/dist/providers/opencode/modes.d.ts.map +1 -0
- package/dist/providers/opencode/modes.js +30 -0
- package/dist/providers/opencode/modes.js.map +1 -0
- package/dist/providers/opencode/parse.d.ts.map +1 -1
- package/dist/providers/opencode/parse.js +2 -22
- package/dist/providers/opencode/parse.js.map +1 -1
- package/dist/providers/opencode/probe.d.ts +3 -0
- package/dist/providers/opencode/probe.d.ts.map +1 -0
- package/dist/providers/opencode/probe.js +107 -0
- package/dist/providers/opencode/probe.js.map +1 -0
- package/dist/providers/opencode/runtime.d.ts +8 -0
- package/dist/providers/opencode/runtime.d.ts.map +1 -0
- package/dist/providers/opencode/runtime.js +12 -0
- package/dist/providers/opencode/runtime.js.map +1 -0
- package/dist/providers/opencode/server.d.ts +18 -3
- package/dist/providers/opencode/server.d.ts.map +1 -1
- package/dist/providers/opencode/server.js +0 -0
- package/dist/providers/opencode/server.js.map +1 -1
- package/dist/providers/opencode/skill-config.d.ts +11 -0
- package/dist/providers/opencode/skill-config.d.ts.map +1 -0
- package/dist/providers/opencode/skill-config.js +57 -0
- package/dist/providers/opencode/skill-config.js.map +1 -0
- package/dist/sessions/exec-backed.d.ts +10 -0
- package/dist/sessions/exec-backed.d.ts.map +1 -0
- package/dist/sessions/exec-backed.js +141 -0
- package/dist/sessions/exec-backed.js.map +1 -0
- package/dist/sessions/history.d.ts +4 -0
- package/dist/sessions/history.d.ts.map +1 -0
- package/dist/sessions/history.js +59 -0
- package/dist/sessions/history.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/types.d.ts +149 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +36 -6
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/binary.d.ts.map +1 -1
- package/dist/utils/binary.js +3 -0
- package/dist/utils/binary.js.map +1 -1
- package/dist/utils/endpoint.js +3 -3
- package/dist/utils/endpoint.js.map +1 -1
- package/package.json +4 -2
- package/src/derived.ts +126 -3
- package/src/goals/controller.ts +17 -0
- package/src/goals/normalize.ts +5 -0
- package/src/index.ts +13 -0
- package/src/providers/claude/attach.ts +48 -6
- package/src/providers/claude/index.ts +8 -0
- package/src/providers/claude/session.ts +15 -10
- package/src/providers/codex/attach.ts +48 -3
- package/src/providers/codex/index.ts +8 -0
- package/src/providers/codex/session.ts +5 -0
- package/src/providers/codex/transcript.ts +5 -1
- package/src/providers/cursor/discovery.ts +114 -0
- package/src/providers/cursor/execute.ts +67 -30
- package/src/providers/cursor/index.ts +19 -5
- package/src/providers/cursor/parse.ts +1 -1
- package/src/providers/cursor/probe.ts +90 -0
- package/src/providers/cursor/runtime.ts +11 -0
- package/src/providers/cursor/session.ts +15 -0
- package/src/providers/opencode/client.ts +44 -0
- package/src/providers/opencode/discovery.ts +69 -0
- package/src/providers/opencode/execute.ts +64 -12
- package/src/providers/opencode/history.ts +270 -0
- package/src/providers/opencode/http-session.ts +319 -50
- package/src/providers/opencode/index.ts +26 -3
- package/src/providers/opencode/manager.ts +269 -0
- package/src/providers/opencode/modes.ts +31 -0
- package/src/providers/opencode/parse.ts +2 -22
- package/src/providers/opencode/probe.ts +113 -0
- package/src/providers/opencode/runtime.ts +13 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/opencode/skill-config.ts +64 -0
- package/src/sessions/exec-backed.ts +169 -0
- package/src/sessions/history.ts +76 -0
- package/src/sessions/index.ts +3 -0
- package/src/types.ts +160 -3
- package/src/utils/auth.ts +35 -6
- package/src/utils/binary.ts +3 -0
- package/src/utils/endpoint.ts +3 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { AgentMode, ListModelsOptions, ListModesOptions, ProviderModel } from "../../types.js";
|
|
2
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
3
|
+
import { runChildProcess } from "../../utils/process.js";
|
|
4
|
+
import { findCursorBinary } from "./runtime.js";
|
|
5
|
+
|
|
6
|
+
function rec(value: unknown): Record<string, unknown> {
|
|
7
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
8
|
+
? value as Record<string, unknown>
|
|
9
|
+
: {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseCursorModels(output: string): ProviderModel[] {
|
|
13
|
+
const trimmed = output.trim();
|
|
14
|
+
if (!trimmed) return [];
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(trimmed) as unknown;
|
|
17
|
+
const values = Array.isArray(parsed) ? parsed : Array.isArray(rec(parsed)["models"]) ? rec(parsed)["models"] as unknown[] : [];
|
|
18
|
+
const models = values.map((value) => {
|
|
19
|
+
if (typeof value === "string") return { id: value, name: value };
|
|
20
|
+
const model = rec(value);
|
|
21
|
+
const id = typeof model["id"] === "string"
|
|
22
|
+
? model["id"]
|
|
23
|
+
: typeof model["slug"] === "string" ? model["slug"] : "";
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
name: typeof model["name"] === "string" ? model["name"] : id,
|
|
27
|
+
};
|
|
28
|
+
}).filter((model) => model.id);
|
|
29
|
+
if (models.length) return models;
|
|
30
|
+
} catch {
|
|
31
|
+
// Fall through to the version-aware text parser.
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
const models: ProviderModel[] = [];
|
|
36
|
+
for (const line of trimmed.split(/\r?\n/)) {
|
|
37
|
+
const cleaned = line.replace(/^\s*[-*•]\s*/, "").trim();
|
|
38
|
+
if (!cleaned || /^(available\s+)?models?:?$/i.test(cleaned)) continue;
|
|
39
|
+
const match = cleaned.match(/^([\w./:-]+)(?:\s{2,}|\s+-\s+|\s+\()?(.*)$/);
|
|
40
|
+
const id = match?.[1] ?? "";
|
|
41
|
+
if (!id || seen.has(id) || /^(usage|error|warning)$/i.test(id)) continue;
|
|
42
|
+
seen.add(id);
|
|
43
|
+
const label = match?.[2]?.replace(/[()]$/, "").trim();
|
|
44
|
+
models.push({ id, name: label || id });
|
|
45
|
+
}
|
|
46
|
+
return models;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function cursorModesFromHelp(output: string): AgentMode[] {
|
|
50
|
+
if (!/--mode\b/.test(output)) return [];
|
|
51
|
+
const advertised = new Set<string>();
|
|
52
|
+
const modeLine = output.split(/\r?\n/).find((line) => /--mode\b/.test(line)) ?? "";
|
|
53
|
+
const match = modeLine.match(/--mode[^\n]*(?:<|\[)([^>\]]+)(?:>|\])/i);
|
|
54
|
+
if (match?.[1]) {
|
|
55
|
+
for (const value of match[1].split(/[|,\s]+/)) {
|
|
56
|
+
const mode = value.trim().toLowerCase();
|
|
57
|
+
if (mode) advertised.add(mode);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const candidates: AgentMode[] = [
|
|
61
|
+
{ id: "agent", name: "Agent" },
|
|
62
|
+
{ id: "plan", name: "Plan", description: "Read and plan before making changes" },
|
|
63
|
+
{ id: "ask", name: "Ask", description: "Answer questions without editing" },
|
|
64
|
+
];
|
|
65
|
+
for (const candidate of candidates) {
|
|
66
|
+
if (new RegExp(`\\b${candidate.id}\\b`, "i").test(modeLine)) {
|
|
67
|
+
advertised.add(candidate.id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return candidates.filter((mode) => advertised.has(mode.id));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function listCursorModels(options: ListModelsOptions = {}): Promise<ProviderModel[]> {
|
|
74
|
+
const resolved = await findCursorBinary(options);
|
|
75
|
+
const env = buildEnv(options.env);
|
|
76
|
+
ensurePathInEnv(env);
|
|
77
|
+
const cwd = options.cwd ?? process.cwd();
|
|
78
|
+
const attempts = [
|
|
79
|
+
["models", "--output-format", "json"],
|
|
80
|
+
["models", "--json"],
|
|
81
|
+
["--list-models"],
|
|
82
|
+
["models"],
|
|
83
|
+
];
|
|
84
|
+
for (const args of attempts) {
|
|
85
|
+
const result = await runChildProcess({
|
|
86
|
+
runId: "cursor-model-discovery",
|
|
87
|
+
command: resolved.bin,
|
|
88
|
+
args: [...resolved.prefixArgs, ...args],
|
|
89
|
+
cwd,
|
|
90
|
+
env,
|
|
91
|
+
timeoutSec: 15,
|
|
92
|
+
});
|
|
93
|
+
if (result.exitCode !== 0) continue;
|
|
94
|
+
const models = parseCursorModels(result.stdout || result.stderr);
|
|
95
|
+
if (models.length) return models;
|
|
96
|
+
}
|
|
97
|
+
throw new Error("The installed Cursor CLI does not expose model discovery");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function listCursorModes(options: ListModesOptions = {}): Promise<AgentMode[]> {
|
|
101
|
+
const resolved = await findCursorBinary(options);
|
|
102
|
+
const env = buildEnv(options.env);
|
|
103
|
+
ensurePathInEnv(env);
|
|
104
|
+
const result = await runChildProcess({
|
|
105
|
+
runId: "cursor-mode-probe",
|
|
106
|
+
command: resolved.bin,
|
|
107
|
+
args: [...resolved.prefixArgs, "--help"],
|
|
108
|
+
cwd: options.cwd ?? process.cwd(),
|
|
109
|
+
env,
|
|
110
|
+
timeoutSec: 10,
|
|
111
|
+
});
|
|
112
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
113
|
+
return cursorModesFromHelp(output);
|
|
114
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import type { ExecutionContext, ExecutionResult } from "../../types.js";
|
|
3
|
-
import { findBinary } from "../../utils/binary.js";
|
|
2
|
+
import type { ExecutionContext, ExecutionResult, StreamEvent } from "../../types.js";
|
|
4
3
|
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
5
4
|
import { runChildProcess, deriveErrorCode } from "../../utils/process.js";
|
|
6
5
|
import { detectAuth } from "../../utils/auth.js";
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
isCursorUnknownSessionError,
|
|
17
16
|
isCursorAuthRequired,
|
|
18
17
|
} from "./parse.js";
|
|
18
|
+
import { findCursorBinary } from "./runtime.js";
|
|
19
19
|
|
|
20
20
|
export async function executeCursorProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
|
|
21
21
|
const runId = ctx.runId ?? uuidv7();
|
|
@@ -28,7 +28,7 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
28
28
|
ctx.onLifecycle?.({ phase: "preparing", step: "binary" });
|
|
29
29
|
let resolvedBinary;
|
|
30
30
|
try {
|
|
31
|
-
resolvedBinary = await
|
|
31
|
+
resolvedBinary = await findCursorBinary({ cwd, env: ctx.env, config });
|
|
32
32
|
} catch (err) {
|
|
33
33
|
const errorMessage = err instanceof Error ? err.message : "Binary not found";
|
|
34
34
|
ctx.onLifecycle?.({ phase: "error", message: errorMessage });
|
|
@@ -96,11 +96,12 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
96
96
|
|
|
97
97
|
// 5. Build args
|
|
98
98
|
const buildArgs = (resumeSessionId: string | null): string[] => {
|
|
99
|
-
const args = [...resolvedBinary.prefixArgs, "-p", "--output-format", "stream-json"
|
|
99
|
+
const args = [...resolvedBinary.prefixArgs, "-p", "--output-format", "stream-json"];
|
|
100
100
|
if (resumeSessionId) args.push("--resume", resumeSessionId);
|
|
101
101
|
if (model) args.push("--model", model);
|
|
102
|
-
|
|
103
|
-
if (
|
|
102
|
+
const selectedMode = config.planMode ? "plan" : (config.modeId ?? config.mode);
|
|
103
|
+
if (selectedMode) args.push("--mode", selectedMode);
|
|
104
|
+
if (config.skipPermissions) args.push("--force");
|
|
104
105
|
if (config.extraArgs) args.push(...config.extraArgs);
|
|
105
106
|
return args;
|
|
106
107
|
};
|
|
@@ -109,6 +110,49 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
109
110
|
const runAttempt = async (resumeSessionId: string | null) => {
|
|
110
111
|
const args = buildArgs(resumeSessionId);
|
|
111
112
|
let stdoutLineBuffer = "";
|
|
113
|
+
let accepted = false;
|
|
114
|
+
let protocolViolation = false;
|
|
115
|
+
const bufferedEvents: StreamEvent[] = [];
|
|
116
|
+
const bufferedOutput: Array<{ stream: "stdout" | "stderr"; chunk: string }> = [];
|
|
117
|
+
|
|
118
|
+
const flushOutput = async (): Promise<void> => {
|
|
119
|
+
if (!ctx.onOutput) return;
|
|
120
|
+
for (const output of bufferedOutput.splice(0)) {
|
|
121
|
+
try { await ctx.onOutput(output.stream, output.chunk); } catch { /* swallow */ }
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const dispatchOutput = async (stream: "stdout" | "stderr", chunk: string): Promise<void> => {
|
|
126
|
+
if (!accepted) {
|
|
127
|
+
bufferedOutput.push({ stream, chunk });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (ctx.onOutput) {
|
|
131
|
+
try { await ctx.onOutput(stream, chunk); } catch { /* swallow */ }
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const dispatch = async (event: StreamEvent): Promise<void> => {
|
|
136
|
+
if (!accepted) {
|
|
137
|
+
bufferedEvents.push(event);
|
|
138
|
+
if (event.type !== "system" || event.subtype !== "init") {
|
|
139
|
+
if (event.type !== "unknown") protocolViolation = true;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (protocolViolation) return;
|
|
143
|
+
accepted = true;
|
|
144
|
+
await flushOutput();
|
|
145
|
+
if (ctx.onEvent) {
|
|
146
|
+
for (const buffered of bufferedEvents.splice(0)) {
|
|
147
|
+
try { await ctx.onEvent(buffered); } catch { /* swallow */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (ctx.onEvent) {
|
|
153
|
+
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
154
|
+
}
|
|
155
|
+
};
|
|
112
156
|
|
|
113
157
|
ctx.onLifecycle?.({ phase: "spawning" });
|
|
114
158
|
const proc = await runChildProcess({
|
|
@@ -127,9 +171,7 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
127
171
|
signal: ctx.signal,
|
|
128
172
|
onOutput: async (stream, chunk) => {
|
|
129
173
|
if (stream !== "stdout") {
|
|
130
|
-
|
|
131
|
-
try { await ctx.onOutput(stream, chunk); } catch { /* swallow */ }
|
|
132
|
-
}
|
|
174
|
+
await dispatchOutput(stream, chunk);
|
|
133
175
|
return;
|
|
134
176
|
}
|
|
135
177
|
|
|
@@ -142,16 +184,10 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
142
184
|
const normalized = normalizeCursorStreamLine(rawLine);
|
|
143
185
|
if (!normalized.line) continue;
|
|
144
186
|
|
|
145
|
-
|
|
146
|
-
try { await ctx.onOutput(normalized.stream ?? "stdout", `${normalized.line}\n`); } catch { /* swallow */ }
|
|
147
|
-
}
|
|
187
|
+
await dispatchOutput(normalized.stream ?? "stdout", `${normalized.line}\n`);
|
|
148
188
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (event) {
|
|
152
|
-
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
189
|
+
const event = parseCursorStreamLine(rawLine);
|
|
190
|
+
if (event) await dispatch(event);
|
|
155
191
|
}
|
|
156
192
|
},
|
|
157
193
|
});
|
|
@@ -159,22 +195,17 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
159
195
|
// Flush remaining buffer
|
|
160
196
|
if (stdoutLineBuffer.trim()) {
|
|
161
197
|
const normalized = normalizeCursorStreamLine(stdoutLineBuffer);
|
|
162
|
-
if (normalized.line
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (ctx.onEvent) {
|
|
166
|
-
const event = parseCursorStreamLine(stdoutLineBuffer);
|
|
167
|
-
if (event) {
|
|
168
|
-
try { await ctx.onEvent(event); } catch { /* swallow */ }
|
|
169
|
-
}
|
|
170
|
-
}
|
|
198
|
+
if (normalized.line) await dispatchOutput(normalized.stream ?? "stdout", `${normalized.line}\n`);
|
|
199
|
+
const event = parseCursorStreamLine(stdoutLineBuffer);
|
|
200
|
+
if (event) await dispatch(event);
|
|
171
201
|
}
|
|
172
202
|
|
|
173
|
-
return proc;
|
|
203
|
+
return { proc, accepted, protocolViolation: protocolViolation || (!accepted && proc.exitCode === 0) };
|
|
174
204
|
};
|
|
175
205
|
|
|
176
206
|
// 7. Initial attempt
|
|
177
|
-
let
|
|
207
|
+
let attempt = await runAttempt(sessionId);
|
|
208
|
+
let proc = attempt.proc;
|
|
178
209
|
let clearSession = false;
|
|
179
210
|
|
|
180
211
|
// 8. Check for unknown session — retry once
|
|
@@ -182,9 +213,11 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
182
213
|
sessionId &&
|
|
183
214
|
!proc.timedOut &&
|
|
184
215
|
(proc.exitCode ?? 0) !== 0 &&
|
|
216
|
+
!attempt.accepted &&
|
|
185
217
|
isCursorUnknownSessionError(proc.stdout, proc.stderr)
|
|
186
218
|
) {
|
|
187
|
-
|
|
219
|
+
attempt = await runAttempt(null);
|
|
220
|
+
proc = attempt.proc;
|
|
188
221
|
clearSession = true;
|
|
189
222
|
}
|
|
190
223
|
|
|
@@ -193,6 +226,7 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
193
226
|
const processErrorCode = deriveErrorCode(proc);
|
|
194
227
|
|
|
195
228
|
let errorCode = processErrorCode;
|
|
229
|
+
if (attempt.protocolViolation) errorCode = "protocol_degraded";
|
|
196
230
|
if (!errorCode && isCursorAuthRequired(proc.stdout, proc.stderr)) {
|
|
197
231
|
errorCode = "auth_required";
|
|
198
232
|
if (ctx.onEvent) {
|
|
@@ -219,6 +253,9 @@ export async function executeCursorProvider(ctx: ExecutionContext): Promise<Exec
|
|
|
219
253
|
const errorMessage = (() => {
|
|
220
254
|
if (proc.timedOut) return `Timed out after ${config.timeoutSec ?? 0}s`;
|
|
221
255
|
if (errorCode === "auth_required") return "Cursor requires authentication.";
|
|
256
|
+
if (errorCode === "protocol_degraded") {
|
|
257
|
+
return "Cursor did not emit the supported stream-json acceptance marker before visible output.";
|
|
258
|
+
}
|
|
222
259
|
if (parsed.errorMessage) return parsed.errorMessage;
|
|
223
260
|
if ((proc.exitCode ?? 0) !== 0 && !parsed.summary) {
|
|
224
261
|
const stderrLine = proc.stderr.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
import type { ProviderModule } from "../../types.js";
|
|
1
|
+
import type { AgentSession, ProviderModule, SessionContext } from "../../types.js";
|
|
2
2
|
import { cursorSessionCodec } from "./codec.js";
|
|
3
3
|
import { resolveAuthForProvider } from "../../utils/auth.js";
|
|
4
4
|
|
|
5
5
|
export const cursorProvider: ProviderModule = {
|
|
6
6
|
type: "cursor",
|
|
7
7
|
capabilities: {
|
|
8
|
-
sessions:
|
|
9
|
-
modelDiscovery:
|
|
8
|
+
sessions: true,
|
|
9
|
+
modelDiscovery: true,
|
|
10
10
|
quotaProbing: false,
|
|
11
11
|
mcp: false,
|
|
12
12
|
skills: true,
|
|
13
13
|
instructions: true,
|
|
14
14
|
workspace: true,
|
|
15
|
-
planMode:
|
|
15
|
+
planMode: true,
|
|
16
16
|
concurrentSend: false,
|
|
17
17
|
cancelQueuedMessage: false,
|
|
18
18
|
stopTask: false,
|
|
19
|
-
modes:
|
|
19
|
+
modes: true,
|
|
20
|
+
resume: true,
|
|
21
|
+
modelVariants: false,
|
|
22
|
+
permissionRequests: false,
|
|
23
|
+
questionRequests: false,
|
|
24
|
+
upstreamProviderDisconnect: false,
|
|
25
|
+
sessionModelChange: false,
|
|
26
|
+
sessionVariantChange: false,
|
|
27
|
+
sessionEffortChange: false,
|
|
28
|
+
sessionModeChange: false,
|
|
20
29
|
},
|
|
21
30
|
// execute.ts loads lazily on first use.
|
|
22
31
|
execute: async (ctx) => (await import("./execute.js")).executeCursorProvider(ctx),
|
|
32
|
+
createSession: async (ctx: SessionContext): Promise<AgentSession> =>
|
|
33
|
+
(await import("./session.js")).createCursorSession(ctx),
|
|
23
34
|
resolveAuth: (ctx) => resolveAuthForProvider("cursor", ctx),
|
|
24
35
|
sessionCodec: cursorSessionCodec,
|
|
36
|
+
listModels: (options) => import("./discovery.js").then((m) => m.listCursorModels(options)),
|
|
37
|
+
listModes: (options) => import("./discovery.js").then((m) => m.listCursorModes(options)),
|
|
38
|
+
probeCapabilities: (ctx) => import("./probe.js").then((m) => m.probeCursorCapabilities(ctx)),
|
|
25
39
|
};
|
|
@@ -281,7 +281,7 @@ export function isCursorUnknownSessionError(stdout: string, stderr: string): boo
|
|
|
281
281
|
return CURSOR_UNKNOWN_SESSION_RE.test(stdout) || CURSOR_UNKNOWN_SESSION_RE.test(stderr);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
const CURSOR_AUTH_RE = /CURSOR_API_KEY\s+is\s+not\s+set|
|
|
284
|
+
const CURSOR_AUTH_RE = /CURSOR_API_KEY\s+is\s+not\s+set|unauthorized|authentication.*required|not\s+logged\s+in/i;
|
|
285
285
|
|
|
286
286
|
export function isCursorAuthRequired(stdout: string, stderr: string): boolean {
|
|
287
287
|
return CURSOR_AUTH_RE.test(stdout) || CURSOR_AUTH_RE.test(stderr);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ProviderRuntimeContext, ProviderRuntimeReport } from "../../types.js";
|
|
2
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
3
|
+
import { runChildProcess } from "../../utils/process.js";
|
|
4
|
+
import { listCursorModels, listCursorModes } from "./discovery.js";
|
|
5
|
+
import { findCursorBinary } from "./runtime.js";
|
|
6
|
+
|
|
7
|
+
export async function probeCursorCapabilities(
|
|
8
|
+
ctx: ProviderRuntimeContext = {},
|
|
9
|
+
): Promise<ProviderRuntimeReport> {
|
|
10
|
+
let resolved;
|
|
11
|
+
try {
|
|
12
|
+
resolved = await findCursorBinary(ctx);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
binary: {
|
|
16
|
+
status: "missing",
|
|
17
|
+
command: null,
|
|
18
|
+
version: null,
|
|
19
|
+
protocolProfile: null,
|
|
20
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
21
|
+
},
|
|
22
|
+
capabilities: {},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const env = buildEnv(ctx.env);
|
|
26
|
+
ensurePathInEnv(env);
|
|
27
|
+
let version: string | null = null;
|
|
28
|
+
try {
|
|
29
|
+
const result = await runChildProcess({
|
|
30
|
+
runId: "cursor-version-probe",
|
|
31
|
+
command: resolved.bin,
|
|
32
|
+
args: [...resolved.prefixArgs, "--version"],
|
|
33
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
34
|
+
env,
|
|
35
|
+
timeoutSec: 5,
|
|
36
|
+
});
|
|
37
|
+
version = (result.stdout || result.stderr).match(/\d+\.\d+(?:\.\d+)?/)?.[0] ?? null;
|
|
38
|
+
} catch {
|
|
39
|
+
// Feature probes remain authoritative.
|
|
40
|
+
}
|
|
41
|
+
const [models, modes] = await Promise.all([
|
|
42
|
+
listCursorModels(ctx).catch(() => []),
|
|
43
|
+
listCursorModes(ctx).catch(() => []),
|
|
44
|
+
]);
|
|
45
|
+
const help = await runChildProcess({
|
|
46
|
+
runId: "cursor-protocol-probe",
|
|
47
|
+
command: resolved.bin,
|
|
48
|
+
args: [...resolved.prefixArgs, "--help"],
|
|
49
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
50
|
+
env,
|
|
51
|
+
timeoutSec: 10,
|
|
52
|
+
}).catch(() => null);
|
|
53
|
+
const helpOutput = help ? `${help.stdout}\n${help.stderr}` : "";
|
|
54
|
+
const protocolSupported = help?.exitCode === 0
|
|
55
|
+
&& /(?:^|\s)(?:-p(?:,|\s))?.*--print\b/m.test(helpOutput)
|
|
56
|
+
&& /--output-format\b/.test(helpOutput)
|
|
57
|
+
&& /\bstream-json\b/i.test(helpOutput)
|
|
58
|
+
&& /--resume\b/.test(helpOutput);
|
|
59
|
+
const modelDiscovery = models.length > 0;
|
|
60
|
+
const modeIds = new Set(modes.map((mode) => mode.id));
|
|
61
|
+
const supported = (value: boolean, reason?: string) => ({
|
|
62
|
+
supported: value,
|
|
63
|
+
status: value ? "supported" as const : "upgrade_required" as const,
|
|
64
|
+
...(!value && reason ? { reason } : {}),
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
binary: {
|
|
68
|
+
status: modelDiscovery && protocolSupported ? "supported" : "upgrade_required",
|
|
69
|
+
command: resolved.bin,
|
|
70
|
+
version,
|
|
71
|
+
protocolProfile: protocolSupported ? "cursor-stream-json-system-init-v1" : null,
|
|
72
|
+
...(!modelDiscovery
|
|
73
|
+
? { reason: "Upgrade Cursor CLI to a version with model listing" }
|
|
74
|
+
: !protocolSupported
|
|
75
|
+
? { reason: "Cursor CLI does not advertise print, stream output, and resume support" }
|
|
76
|
+
: {}),
|
|
77
|
+
},
|
|
78
|
+
capabilities: {
|
|
79
|
+
sessions: supported(Boolean(protocolSupported), "Cursor stream-json session flags are unavailable"),
|
|
80
|
+
resume: supported(Boolean(protocolSupported), "Cursor resume support is unavailable"),
|
|
81
|
+
modelDiscovery: supported(modelDiscovery, "Cursor model listing is unavailable"),
|
|
82
|
+
planMode: supported(modeIds.has("plan")),
|
|
83
|
+
modes: supported(modes.length > 0),
|
|
84
|
+
sessionModelChange: supported(false),
|
|
85
|
+
sessionVariantChange: supported(false),
|
|
86
|
+
sessionEffortChange: supported(false),
|
|
87
|
+
sessionModeChange: supported(false),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProviderRuntimeContext } from "../../types.js";
|
|
2
|
+
import { findBinary, type ResolvedBinary } from "../../utils/binary.js";
|
|
3
|
+
|
|
4
|
+
export async function findCursorBinary(ctx: ProviderRuntimeContext = {}): Promise<ResolvedBinary> {
|
|
5
|
+
if (ctx.config?.command) return findBinary("agent", ctx.config.command);
|
|
6
|
+
try {
|
|
7
|
+
return await findBinary("agent");
|
|
8
|
+
} catch {
|
|
9
|
+
return findBinary("cursor-agent");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentSession, SessionContext } from "../../types.js";
|
|
2
|
+
import { createExecBackedSession } from "../../sessions/exec-backed.js";
|
|
3
|
+
import { EMULATED_GOAL_CAPABILITY } from "../../goals/index.js";
|
|
4
|
+
import { executeCursorProvider } from "./execute.js";
|
|
5
|
+
import { cursorSessionCodec } from "./codec.js";
|
|
6
|
+
|
|
7
|
+
export async function createCursorSession(ctx: SessionContext): Promise<AgentSession> {
|
|
8
|
+
return createExecBackedSession({
|
|
9
|
+
providerType: "cursor",
|
|
10
|
+
execute: executeCursorProvider,
|
|
11
|
+
sessionCodec: cursorSessionCodec,
|
|
12
|
+
ctx,
|
|
13
|
+
capabilities: { goals: EMULATED_GOAL_CAPABILITY },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class OpenCodeHttpError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
readonly status: number,
|
|
4
|
+
readonly code: string,
|
|
5
|
+
) {
|
|
6
|
+
super(`OpenCode request failed (${status})`);
|
|
7
|
+
this.name = "OpenCodeHttpError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class OpenCodeClient {
|
|
12
|
+
private readonly authorization: string;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
readonly baseUrl: string,
|
|
16
|
+
password: string,
|
|
17
|
+
) {
|
|
18
|
+
this.authorization = `Basic ${Buffer.from(`opencode:${password}`, "utf8").toString("base64")}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async request(path: string, init: RequestInit = {}): Promise<Response> {
|
|
22
|
+
const headers = new Headers(init.headers);
|
|
23
|
+
headers.set("authorization", this.authorization);
|
|
24
|
+
if (init.body !== undefined && !headers.has("content-type")) {
|
|
25
|
+
headers.set("content-type", "application/json");
|
|
26
|
+
}
|
|
27
|
+
return fetch(`${this.baseUrl}${path}`, { ...init, headers });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async json<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
31
|
+
const response = await this.request(path, init);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new OpenCodeHttpError(response.status, `http_${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
return await response.json() as T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async ok(path: string, init: RequestInit = {}): Promise<void> {
|
|
39
|
+
const response = await this.request(path, init);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new OpenCodeHttpError(response.status, `http_${response.status}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ListModelsOptions, ProviderModel } from "../../types.js";
|
|
2
|
+
import { acquireOpenCodeRuntime } from "./runtime.js";
|
|
3
|
+
|
|
4
|
+
function record(value: unknown): Record<string, unknown> {
|
|
5
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
6
|
+
? value as Record<string, unknown>
|
|
7
|
+
: {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function number(value: unknown): number | undefined {
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function openCodeModelsFromPayload(payload: Record<string, unknown>): ProviderModel[] {
|
|
15
|
+
const providers = Array.isArray(payload["all"]) ? payload["all"] : [];
|
|
16
|
+
const models: ProviderModel[] = [];
|
|
17
|
+
for (const rawProvider of providers) {
|
|
18
|
+
const provider = record(rawProvider);
|
|
19
|
+
const providerId = typeof provider["id"] === "string" ? provider["id"] : "";
|
|
20
|
+
const providerName = typeof provider["name"] === "string" ? provider["name"] : providerId;
|
|
21
|
+
if (!providerId) continue;
|
|
22
|
+
for (const [key, rawModel] of Object.entries(record(provider["models"]))) {
|
|
23
|
+
const model = record(rawModel);
|
|
24
|
+
const modelId = typeof model["id"] === "string" ? model["id"] : key;
|
|
25
|
+
if (!modelId) continue;
|
|
26
|
+
const limits = record(model["limit"]);
|
|
27
|
+
const cost = record(model["cost"]);
|
|
28
|
+
const capabilities = record(model["capabilities"]);
|
|
29
|
+
const capabilityInput = record(capabilities["input"]);
|
|
30
|
+
const modalities = record(model["modalities"]);
|
|
31
|
+
const inputModalities = Array.isArray(modalities["input"])
|
|
32
|
+
? modalities["input"].filter((item): item is string => typeof item === "string")
|
|
33
|
+
: [];
|
|
34
|
+
const variants = Object.entries(record(model["variants"])).map(([id, rawVariant]) => {
|
|
35
|
+
const variant = record(rawVariant);
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
name: typeof variant["name"] === "string" ? variant["name"] : id,
|
|
39
|
+
...(typeof variant["description"] === "string" ? { description: variant["description"] } : {}),
|
|
40
|
+
...(variant["disabled"] === true ? { disabled: true } : {}),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
models.push({
|
|
44
|
+
id: `${providerId}/${modelId}`,
|
|
45
|
+
name: typeof model["name"] === "string" ? model["name"] : modelId,
|
|
46
|
+
provider: providerId,
|
|
47
|
+
providerName,
|
|
48
|
+
...(variants.length ? { variants } : {}),
|
|
49
|
+
...(number(limits["context"]) !== undefined ? { contextWindow: number(limits["context"]) } : {}),
|
|
50
|
+
...(number(limits["output"]) !== undefined ? { maxOutputTokens: number(limits["output"]) } : {}),
|
|
51
|
+
...(number(cost["input"]) !== undefined ? { inputCostPerMillion: number(cost["input"]) } : {}),
|
|
52
|
+
...(number(cost["output"]) !== undefined ? { outputCostPerMillion: number(cost["output"]) } : {}),
|
|
53
|
+
supportsImages: capabilityInput["image"] === true || inputModalities.includes("image"),
|
|
54
|
+
supportsTools: capabilities["toolcall"] === true || model["tool_call"] === true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return models;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function listOpenCodeModels(options: ListModelsOptions = {}): Promise<ProviderModel[]> {
|
|
62
|
+
const runtime = await acquireOpenCodeRuntime(options);
|
|
63
|
+
try {
|
|
64
|
+
const payload = await runtime.server.client.json<Record<string, unknown>>("/provider");
|
|
65
|
+
return openCodeModelsFromPayload(payload);
|
|
66
|
+
} finally {
|
|
67
|
+
runtime.server.release();
|
|
68
|
+
}
|
|
69
|
+
}
|