[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
548
electron/bridges/aiBridge/sdk/cursorDriver.test.cjs
Normal file
548
electron/bridges/aiBridge/sdk/cursorDriver.test.cjs
Normal file
@@ -0,0 +1,548 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
buildCursorAgentOptions,
|
||||
buildCursorSendMessage,
|
||||
formatCursorErrorForUser,
|
||||
isCursorAgentNotFoundError,
|
||||
mapCursorModels,
|
||||
runCursorTurn,
|
||||
toCursorMcpServers,
|
||||
translateCursorEvent,
|
||||
withTemporaryProcessEnv,
|
||||
} = require("./cursorDriver.cjs");
|
||||
|
||||
function makeEmitter() {
|
||||
const calls = [];
|
||||
return {
|
||||
calls,
|
||||
text: (value) => calls.push(["text", value]),
|
||||
reasoning: (value) => calls.push(["reasoning", value]),
|
||||
reasoningEnd: () => calls.push(["reasoningEnd"]),
|
||||
toolCall: (name, args, id) => calls.push(["toolCall", name, args, id]),
|
||||
toolResult: (id, result, name) => calls.push(["toolResult", id, result, name]),
|
||||
sessionId: (id) => calls.push(["sessionId", id]),
|
||||
emitDone: () => calls.push(["done"]),
|
||||
emitError: (message) => calls.push(["error", message]),
|
||||
};
|
||||
}
|
||||
|
||||
test("buildCursorAgentOptions uses api key, model, cwd, and injected MCP servers", () => {
|
||||
const options = buildCursorAgentOptions({
|
||||
apiKey: "cur-key",
|
||||
model: "composer-2",
|
||||
cwd: "/repo",
|
||||
injectedMcpServers: [
|
||||
{
|
||||
name: "netcatty",
|
||||
command: "node",
|
||||
args: ["server.cjs"],
|
||||
env: [{ name: "TOKEN", value: "abc" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(options, {
|
||||
apiKey: "cur-key",
|
||||
model: { id: "composer-2" },
|
||||
local: { cwd: "/repo", autoReview: false },
|
||||
mcpServers: {
|
||||
netcatty: {
|
||||
type: "stdio",
|
||||
command: "node",
|
||||
args: ["server.cjs"],
|
||||
env: { TOKEN: "abc" },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("buildCursorAgentOptions falls back to CURSOR_API_KEY and composer-2.5", () => {
|
||||
const options = buildCursorAgentOptions({
|
||||
env: { CURSOR_API_KEY: "env-key" },
|
||||
cwd: "/repo",
|
||||
});
|
||||
|
||||
assert.equal(options.apiKey, "env-key");
|
||||
assert.deepEqual(options.model, { id: "composer-2.5" });
|
||||
});
|
||||
|
||||
test("toCursorMcpServers drops invalid server configs", () => {
|
||||
assert.deepEqual(
|
||||
toCursorMcpServers([
|
||||
null,
|
||||
{ name: "", command: "node" },
|
||||
{ name: "ok", command: "node", args: [] },
|
||||
]),
|
||||
{ ok: { type: "stdio", command: "node", args: [], env: {} } },
|
||||
);
|
||||
});
|
||||
|
||||
test("withTemporaryProcessEnv restores env after async work", async () => {
|
||||
const original = process.env.NETCATTY_CURSOR_TEST_ENV;
|
||||
delete process.env.NETCATTY_CURSOR_TEST_ENV;
|
||||
|
||||
const value = await withTemporaryProcessEnv(
|
||||
{ NETCATTY_CURSOR_TEST_ENV: "present" },
|
||||
async () => process.env.NETCATTY_CURSOR_TEST_ENV,
|
||||
);
|
||||
|
||||
assert.equal(value, "present");
|
||||
assert.equal(process.env.NETCATTY_CURSOR_TEST_ENV, undefined);
|
||||
if (original !== undefined) process.env.NETCATTY_CURSOR_TEST_ENV = original;
|
||||
});
|
||||
|
||||
test("runCursorTurn exposes runtime env while creating and sending", async () => {
|
||||
const emitter = makeEmitter();
|
||||
const observed = [];
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
async create() {
|
||||
observed.push(["create", process.env.NETCATTY_TOOL_CLI_DISCOVERY_FILE]);
|
||||
return {
|
||||
agentId: "agent-env",
|
||||
async send() {
|
||||
observed.push(["send", process.env.NETCATTY_TOOL_CLI_DISCOVERY_FILE]);
|
||||
return {
|
||||
async *stream() {
|
||||
yield { type: "assistant", message: { content: [{ type: "text", text: "ok" }] } };
|
||||
},
|
||||
};
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await runCursorTurn({
|
||||
prompt: "hi",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
runtimeEnv: { NETCATTY_TOOL_CLI_DISCOVERY_FILE: "/tmp/discovery.json" },
|
||||
emitter,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
assert.deepEqual(observed, [
|
||||
["create", "/tmp/discovery.json"],
|
||||
["send", "/tmp/discovery.json"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("translateCursorEvent maps assistant, thinking, and tool events", () => {
|
||||
const emitter = makeEmitter();
|
||||
const state = {};
|
||||
|
||||
translateCursorEvent({ type: "thinking", text: "checking" }, emitter, state);
|
||||
translateCursorEvent({
|
||||
type: "assistant",
|
||||
message: {
|
||||
content: [
|
||||
{ type: "text", text: "hello" },
|
||||
{ type: "tool_use", id: "tool-1", name: "read_file", input: { path: "README.md" } },
|
||||
],
|
||||
},
|
||||
}, emitter, state);
|
||||
translateCursorEvent({
|
||||
type: "tool_call",
|
||||
call_id: "tool-1",
|
||||
name: "read_file",
|
||||
status: "completed",
|
||||
result: { content: [{ type: "text", text: "contents" }] },
|
||||
}, emitter, state);
|
||||
|
||||
assert.deepEqual(emitter.calls, [
|
||||
["reasoning", "checking"],
|
||||
["reasoningEnd"],
|
||||
["text", "hello"],
|
||||
["toolCall", "read_file", { path: "README.md" }, "tool-1"],
|
||||
["toolResult", "tool-1", "contents", "read_file"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("translateCursorEvent uses nested Cursor MCP toolName for display", () => {
|
||||
const emitter = makeEmitter();
|
||||
const state = {};
|
||||
const args = {
|
||||
providerIdentifier: "netcatty-remote-hosts",
|
||||
toolName: "terminal_execute",
|
||||
args: { command: "uname -a" },
|
||||
};
|
||||
|
||||
translateCursorEvent({
|
||||
type: "tool_call",
|
||||
call_id: "mcp-1",
|
||||
name: "mcp",
|
||||
status: "completed",
|
||||
args,
|
||||
result: { content: [{ type: "text", text: "Linux" }] },
|
||||
}, emitter, state);
|
||||
|
||||
assert.deepEqual(emitter.calls, [
|
||||
["toolCall", "terminal_execute", args, "mcp-1"],
|
||||
["toolResult", "mcp-1", "Linux", "terminal_execute"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("translateCursorEvent marks error status as failed", () => {
|
||||
const emitter = makeEmitter();
|
||||
const state = {};
|
||||
|
||||
const failed = translateCursorEvent({ type: "status", status: "ERROR", message: "bad key" }, emitter, state);
|
||||
|
||||
assert.equal(failed, true);
|
||||
assert.equal(state.failed, true);
|
||||
assert.deepEqual(emitter.calls, [["error", "bad key"]]);
|
||||
});
|
||||
|
||||
test("translateCursorEvent rewrites Cursor authentication errors", () => {
|
||||
const emitter = makeEmitter();
|
||||
const state = {};
|
||||
|
||||
const failed = translateCursorEvent({ type: "status", status: "ERROR", message: "bad API key" }, emitter, state);
|
||||
|
||||
assert.equal(failed, true);
|
||||
assert.equal(state.failed, true);
|
||||
assert.deepEqual(emitter.calls, [[
|
||||
"error",
|
||||
"Cursor authentication failed. Update the Cursor API Key in Settings -> AI.",
|
||||
]]);
|
||||
});
|
||||
|
||||
test("formatCursorErrorForUser points users to the settings API key", () => {
|
||||
assert.equal(
|
||||
formatCursorErrorForUser("unauthorized"),
|
||||
"Cursor authentication failed. Update the Cursor API Key in Settings -> AI.",
|
||||
);
|
||||
});
|
||||
|
||||
test("isCursorAgentNotFoundError detects stale resume ids", () => {
|
||||
assert.equal(isCursorAgentNotFoundError(new Error("Agent 61668441-bfcb-4795-a575-c46d70ad01fe not found")), true);
|
||||
assert.equal(isCursorAgentNotFoundError(new Error("unauthorized")), false);
|
||||
});
|
||||
|
||||
test("runCursorTurn falls back to create when resume agent is missing", async () => {
|
||||
const emitter = makeEmitter();
|
||||
const observed = [];
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
async resume(id) {
|
||||
observed.push(["resume", id]);
|
||||
throw new Error(`Agent ${id} not found`);
|
||||
},
|
||||
async create() {
|
||||
observed.push(["create"]);
|
||||
return {
|
||||
agentId: "agent-fresh",
|
||||
async send() {
|
||||
return {
|
||||
async *stream() {
|
||||
yield { type: "assistant", message: { content: [{ type: "text", text: "ok" }] } };
|
||||
},
|
||||
};
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await runCursorTurn({
|
||||
prompt: "hi",
|
||||
resumeSessionId: "61668441-bfcb-4795-a575-c46d70ad01fe",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
emitter,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
assert.deepEqual(observed, [
|
||||
["resume", "61668441-bfcb-4795-a575-c46d70ad01fe"],
|
||||
["create"],
|
||||
]);
|
||||
assert.equal(result.sessionId, "agent-fresh");
|
||||
assert.deepEqual(emitter.calls, [
|
||||
["sessionId", "agent-fresh"],
|
||||
["text", "ok"],
|
||||
["done"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("runCursorTurn creates or resumes an agent, streams events, and emits done", async () => {
|
||||
const emitter = makeEmitter();
|
||||
const captured = {};
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
async create(options) {
|
||||
captured.createOptions = options;
|
||||
return {
|
||||
agentId: "agent-new",
|
||||
async send(message) {
|
||||
captured.message = message;
|
||||
return {
|
||||
id: "run-1",
|
||||
agentId: "agent-new",
|
||||
async *stream() {
|
||||
yield { type: "assistant", message: { content: [{ type: "text", text: "done" }] } };
|
||||
},
|
||||
};
|
||||
},
|
||||
async close() {
|
||||
captured.closed = true;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await runCursorTurn({
|
||||
prompt: "hi",
|
||||
attachments: [{ mediaType: "image/png", base64Data: "abc", filename: "a.png" }],
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2" }, local: { cwd: "/repo" } },
|
||||
emitter,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
assert.equal(result.sessionId, "agent-new");
|
||||
assert.deepEqual(captured.message, {
|
||||
text: "hi",
|
||||
images: [{ data: "abc", mimeType: "image/png" }],
|
||||
});
|
||||
assert.deepEqual(emitter.calls, [
|
||||
["sessionId", "agent-new"],
|
||||
["text", "done"],
|
||||
["done"],
|
||||
]);
|
||||
assert.equal(captured.closed, true);
|
||||
});
|
||||
|
||||
test("runCursorTurn does not emit done after a Cursor error status", async () => {
|
||||
const emitter = makeEmitter();
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
async create() {
|
||||
return {
|
||||
agentId: "agent-error",
|
||||
async send() {
|
||||
return {
|
||||
async *stream() {
|
||||
yield { type: "status", status: "ERROR", message: "bad key" };
|
||||
yield { type: "assistant", message: { content: [{ type: "text", text: "late" }] } };
|
||||
},
|
||||
};
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await runCursorTurn({
|
||||
prompt: "hi",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
emitter,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
assert.equal(result.sessionId, "agent-error");
|
||||
assert.deepEqual(emitter.calls, [
|
||||
["sessionId", "agent-error"],
|
||||
["error", "bad key"],
|
||||
]);
|
||||
});
|
||||
|
||||
test("runCursorTurn returns when aborted while creating an agent", async () => {
|
||||
const emitter = makeEmitter();
|
||||
let resolveCreate;
|
||||
const createPromise = new Promise((resolve) => {
|
||||
resolveCreate = resolve;
|
||||
});
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
create() {
|
||||
return createPromise;
|
||||
},
|
||||
},
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const turnPromise = runCursorTurn({
|
||||
prompt: "hi",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
emitter,
|
||||
signal: controller.signal,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
const result = await turnPromise;
|
||||
assert.deepEqual(result, { sessionId: null });
|
||||
assert.deepEqual(emitter.calls, []);
|
||||
|
||||
let closed = false;
|
||||
resolveCreate({ agentId: "late", close: () => { closed = true; } });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(closed, true);
|
||||
});
|
||||
|
||||
test("runCursorTurn restores runtime env when aborted while creating an agent", async () => {
|
||||
const emitter = makeEmitter();
|
||||
const original = process.env.NETCATTY_CURSOR_ABORT_ENV;
|
||||
delete process.env.NETCATTY_CURSOR_ABORT_ENV;
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
create() {
|
||||
return new Promise(() => {});
|
||||
},
|
||||
},
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const turnPromise = runCursorTurn({
|
||||
prompt: "hi",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
runtimeEnv: { NETCATTY_CURSOR_ABORT_ENV: "present" },
|
||||
emitter,
|
||||
signal: controller.signal,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(process.env.NETCATTY_CURSOR_ABORT_ENV, "present");
|
||||
controller.abort();
|
||||
await turnPromise;
|
||||
assert.equal(process.env.NETCATTY_CURSOR_ABORT_ENV, undefined);
|
||||
if (original !== undefined) process.env.NETCATTY_CURSOR_ABORT_ENV = original;
|
||||
});
|
||||
|
||||
test("runCursorTurn cancels a late Cursor run when aborted while sending", async () => {
|
||||
const emitter = makeEmitter();
|
||||
let resolveSend;
|
||||
let cancelled = false;
|
||||
const sendPromise = new Promise((resolve) => {
|
||||
resolveSend = resolve;
|
||||
});
|
||||
const sdkModule = {
|
||||
Agent: {
|
||||
async create() {
|
||||
return {
|
||||
agentId: "agent-send-abort",
|
||||
send() {
|
||||
return sendPromise;
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const turnPromise = runCursorTurn({
|
||||
prompt: "hi",
|
||||
agentOptions: { apiKey: "key", model: { id: "composer-2.5" }, local: { cwd: "/repo" } },
|
||||
emitter,
|
||||
signal: controller.signal,
|
||||
sdkModule,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
controller.abort();
|
||||
const result = await turnPromise;
|
||||
assert.deepEqual(result, { sessionId: "agent-send-abort" });
|
||||
assert.deepEqual(emitter.calls, [["sessionId", "agent-send-abort"]]);
|
||||
|
||||
resolveSend({ cancel: async () => { cancelled = true; }, stream: async function* stream() {} });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.equal(cancelled, true);
|
||||
});
|
||||
|
||||
test("mapCursorModels prefers advertised effort parameter values over fallbacks", () => {
|
||||
assert.deepEqual(
|
||||
mapCursorModels([
|
||||
{
|
||||
id: "custom-reasoner",
|
||||
displayName: "Custom Reasoner",
|
||||
parameters: [
|
||||
{ id: "effort", values: [{ value: "low" }, { value: "xhigh" }] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "gpt-5",
|
||||
displayName: "GPT-5",
|
||||
parameters: [
|
||||
{ id: "effort", values: [{ value: "low" }, { value: "high" }] },
|
||||
],
|
||||
},
|
||||
]),
|
||||
[
|
||||
{
|
||||
id: "custom-reasoner",
|
||||
name: "Custom Reasoner",
|
||||
thinkingLevels: ["low", "xhigh"],
|
||||
defaultThinkingLevel: "low",
|
||||
},
|
||||
{
|
||||
id: "gpt-5",
|
||||
name: "GPT-5",
|
||||
thinkingLevels: ["low", "high"],
|
||||
defaultThinkingLevel: "low",
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("mapCursorModels maps display names and effort variants into thinkingLevels", () => {
|
||||
assert.deepEqual(
|
||||
mapCursorModels([
|
||||
{ id: "composer-2.5", displayName: "Composer 2.5", description: "Default" },
|
||||
{ id: "gpt-5", displayName: "GPT-5", variants: [{ displayName: "Fast", params: [{ id: "effort", value: "low" }] }] },
|
||||
]),
|
||||
[
|
||||
{ id: "composer-2.5", name: "Composer 2.5", description: "Default" },
|
||||
{
|
||||
id: "gpt-5",
|
||||
name: "GPT-5",
|
||||
thinkingLevels: ["low", "medium", "high"],
|
||||
defaultThinkingLevel: "medium",
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("mapCursorModels keeps extra-param variants as separate models", () => {
|
||||
const mapped = mapCursorModels([
|
||||
{
|
||||
id: "gpt-5",
|
||||
displayName: "GPT-5",
|
||||
variants: [
|
||||
{ displayName: "Fast", params: [{ id: "effort", value: "low" }] },
|
||||
{
|
||||
displayName: "Fast custom",
|
||||
params: [{ id: "effort", value: "low" }, { id: "mode", value: "fast" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(mapped, [
|
||||
{
|
||||
id: "gpt-5",
|
||||
name: "GPT-5",
|
||||
thinkingLevels: ["low", "medium", "high"],
|
||||
defaultThinkingLevel: "medium",
|
||||
},
|
||||
{
|
||||
id: "gpt-5?effort=low&mode=fast",
|
||||
name: "GPT-5 - Fast custom",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("parseCursorModelSelection accepts query and slash effort encodings", () => {
|
||||
const { parseCursorModelSelection, encodeCursorCliModel } = require("./cursorDriver.cjs");
|
||||
assert.deepEqual(parseCursorModelSelection("gpt-5/high"), {
|
||||
id: "gpt-5",
|
||||
params: [{ id: "effort", value: "high" }],
|
||||
});
|
||||
assert.deepEqual(parseCursorModelSelection("gpt-5?effort=low"), {
|
||||
id: "gpt-5",
|
||||
params: [{ id: "effort", value: "low" }],
|
||||
});
|
||||
assert.equal(encodeCursorCliModel("gpt-5/high"), "gpt-5?effort=high");
|
||||
});
|
||||
Reference in New Issue
Block a user