opencode-cursor-provider 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +55 -0
  2. package/README.md +112 -0
  3. package/dist/auth/credential.d.ts +44 -0
  4. package/dist/auth/credential.js +85 -0
  5. package/dist/auth/link.d.ts +14 -0
  6. package/dist/auth/link.js +120 -0
  7. package/dist/auth/state.d.ts +54 -0
  8. package/dist/auth/state.js +54 -0
  9. package/dist/bridge/agent.d.ts +17 -0
  10. package/dist/bridge/agent.js +39 -0
  11. package/dist/bridge/binding.d.ts +35 -0
  12. package/dist/bridge/binding.js +59 -0
  13. package/dist/bridge/bridge.d.ts +16 -0
  14. package/dist/bridge/bridge.js +21 -0
  15. package/dist/bridge/conversation.d.ts +64 -0
  16. package/dist/bridge/conversation.js +86 -0
  17. package/dist/bridge/correlation.d.ts +10 -0
  18. package/dist/bridge/correlation.js +52 -0
  19. package/dist/bridge/lock.d.ts +4 -0
  20. package/dist/bridge/lock.js +24 -0
  21. package/dist/bridge/response-journal.d.ts +7 -0
  22. package/dist/bridge/response-journal.js +49 -0
  23. package/dist/bridge/translate.d.ts +60 -0
  24. package/dist/bridge/translate.js +188 -0
  25. package/dist/bridge/turn.d.ts +29 -0
  26. package/dist/bridge/turn.js +219 -0
  27. package/dist/catalog/catalog.d.ts +20 -0
  28. package/dist/catalog/catalog.js +192 -0
  29. package/dist/catalog/source.d.ts +8 -0
  30. package/dist/catalog/source.js +30 -0
  31. package/dist/errors.d.ts +35 -0
  32. package/dist/errors.js +36 -0
  33. package/dist/ids.d.ts +33 -0
  34. package/dist/ids.js +52 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.js +3 -0
  37. package/dist/model/language-model.d.ts +10 -0
  38. package/dist/model/language-model.js +539 -0
  39. package/dist/model/provider-options.d.ts +19 -0
  40. package/dist/model/provider-options.js +84 -0
  41. package/dist/plugin.d.ts +2 -0
  42. package/dist/plugin.js +127 -0
  43. package/dist/runtime.d.ts +8 -0
  44. package/dist/runtime.js +25 -0
  45. package/package.json +33 -0
@@ -0,0 +1,59 @@
1
+ import { extendsCheckpoint } from "./conversation.js";
2
+ export function route(input) {
3
+ if (input.scope === undefined)
4
+ return "ONE_SHOT";
5
+ if (input.binding === undefined)
6
+ return "FRESH";
7
+ if (input.binding.modelID !== input.modelID)
8
+ return "FRESH";
9
+ if (input.binding.cwd !== input.scope.cwd)
10
+ return "FRESH";
11
+ if (!sameParams(input.binding.params, input.params))
12
+ return "FRESH";
13
+ if (input.binding.mode !== input.mode)
14
+ return "FRESH";
15
+ if (!sameAgentOptions(input.binding.agentOptions, input.agentOptions))
16
+ return "FRESH";
17
+ if (!extendsCheckpoint(input.conversation, input.binding.checkpoint))
18
+ return "FRESH";
19
+ return "RESUME";
20
+ }
21
+ function sameAgentOptions(left, right) {
22
+ if (left === undefined || right === undefined)
23
+ return left === right;
24
+ return (sameSet(left.tools, right.tools) &&
25
+ sameSet(left.disallowedTools, right.disallowedTools) &&
26
+ left.sandboxOptions?.enabled === right.sandboxOptions?.enabled &&
27
+ left.autoReview === right.autoReview &&
28
+ sameSet(left.settingSources, right.settingSources));
29
+ }
30
+ function sameSet(left, right) {
31
+ if (left === undefined || right === undefined)
32
+ return left === right;
33
+ const leftSet = new Set(left);
34
+ const rightSet = new Set(right);
35
+ return leftSet.size === rightSet.size && [...leftSet].every((value) => rightSet.has(value));
36
+ }
37
+ function sameParams(left, right) {
38
+ if (left === undefined || right === undefined)
39
+ return left === right;
40
+ if (left.length !== right.length)
41
+ return false;
42
+ const leftValues = left.map((param) => JSON.stringify([param.id, param.value])).sort();
43
+ const rightValues = right.map((param) => JSON.stringify([param.id, param.value])).sort();
44
+ return leftValues.every((value, index) => value === rightValues[index]);
45
+ }
46
+ export function createBindingStore() {
47
+ const bindings = new Map();
48
+ return {
49
+ get(sessionID) {
50
+ return bindings.get(sessionID);
51
+ },
52
+ put(binding) {
53
+ bindings.set(binding.sessionID, binding);
54
+ },
55
+ drop(sessionID) {
56
+ bindings.delete(sessionID);
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,16 @@
1
+ import type { CursorLink } from "../auth/link.ts";
2
+ import type { CursorModelDescriptor } from "../catalog/catalog.ts";
3
+ import { type BindingStore, type TurnScope } from "./binding.ts";
4
+ import { type TurnRequest } from "./turn.ts";
5
+ import type { TurnEvent } from "./translate.ts";
6
+ export type { TurnRequest } from "./turn.ts";
7
+ export interface SessionAgentBridge {
8
+ annotate(system: string[], scope: TurnScope): string[];
9
+ turn(request: TurnRequest): AsyncIterable<TurnEvent>;
10
+ }
11
+ export declare function createSessionAgentBridge(input: {
12
+ link: CursorLink;
13
+ models: () => readonly CursorModelDescriptor[];
14
+ bindings?: BindingStore;
15
+ clock?: () => number;
16
+ }): SessionAgentBridge;
@@ -0,0 +1,21 @@
1
+ import { createBindingStore } from "./binding.js";
2
+ import { stampSystem } from "./correlation.js";
3
+ import { createLock } from "./lock.js";
4
+ import { createTurnRunner } from "./turn.js";
5
+ export function createSessionAgentBridge(input) {
6
+ const runTurn = createTurnRunner({
7
+ link: input.link,
8
+ models: input.models,
9
+ bindings: input.bindings ?? createBindingStore(),
10
+ clock: input.clock ?? Date.now,
11
+ lock: createLock(),
12
+ });
13
+ return {
14
+ annotate(system, scope) {
15
+ return stampSystem(system, scope);
16
+ },
17
+ turn(request) {
18
+ return runTurn(request);
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,64 @@
1
+ import type { SDKUserMessage } from "@cursor/sdk";
2
+ export interface CursorImage {
3
+ readonly data: string;
4
+ readonly mimeType: string;
5
+ }
6
+ export type UserPart = {
7
+ readonly type: "text";
8
+ readonly text: string;
9
+ } | {
10
+ readonly type: "image";
11
+ readonly image: CursorImage;
12
+ };
13
+ export type AssistantPart = {
14
+ readonly type: "text";
15
+ readonly text: string;
16
+ } | {
17
+ readonly type: "reasoning";
18
+ readonly text: string;
19
+ } | {
20
+ readonly type: "tool-call";
21
+ readonly id: string;
22
+ readonly name: string;
23
+ readonly input: string;
24
+ } | {
25
+ readonly type: "tool-result";
26
+ readonly id: string;
27
+ readonly name: string;
28
+ readonly output: readonly UserPart[];
29
+ readonly isError: boolean;
30
+ };
31
+ export type ToolPart = Extract<AssistantPart, {
32
+ type: "tool-result";
33
+ }> | {
34
+ readonly type: "tool-approval";
35
+ readonly id: string;
36
+ readonly approved: boolean;
37
+ readonly reason?: string;
38
+ };
39
+ export type ConversationTurn = {
40
+ readonly role: "user";
41
+ readonly parts: readonly UserPart[];
42
+ } | {
43
+ readonly role: "assistant";
44
+ readonly parts: readonly AssistantPart[];
45
+ } | {
46
+ readonly role: "tool";
47
+ readonly parts: readonly ToolPart[];
48
+ };
49
+ export interface Conversation {
50
+ readonly system: readonly string[];
51
+ readonly turns: readonly ConversationTurn[];
52
+ }
53
+ export interface ConversationCheckpoint {
54
+ readonly system: string;
55
+ readonly turns: readonly string[];
56
+ }
57
+ export declare function render(system: readonly string[], turns: readonly ConversationTurn[]): string;
58
+ export declare function cursorMessage(system: readonly string[], turns: readonly ConversationTurn[]): string | SDKUserMessage;
59
+ export declare function checkpointOf(conversation: Conversation): ConversationCheckpoint;
60
+ export declare function resumeTurn(conversation: Conversation, checkpoint: ConversationCheckpoint): Extract<ConversationTurn, {
61
+ role: "user";
62
+ }> | undefined;
63
+ export declare function extendsCheckpoint(conversation: Conversation, checkpoint: ConversationCheckpoint): boolean;
64
+ export declare function canonicalJson(value: unknown): string;
@@ -0,0 +1,86 @@
1
+ import { createHash } from "node:crypto";
2
+ export function render(system, turns) {
3
+ return renderCursorMessage(system, turns).text;
4
+ }
5
+ export function cursorMessage(system, turns) {
6
+ const message = renderCursorMessage(system, turns);
7
+ return message.images === undefined ? message.text : message;
8
+ }
9
+ export function checkpointOf(conversation) {
10
+ return {
11
+ system: digest(conversation.system),
12
+ turns: conversation.turns.map(digest),
13
+ };
14
+ }
15
+ export function resumeTurn(conversation, checkpoint) {
16
+ if (digest(conversation.system) !== checkpoint.system)
17
+ return undefined;
18
+ if (conversation.turns.length !== checkpoint.turns.length + 1)
19
+ return undefined;
20
+ for (let index = 0; index < checkpoint.turns.length; index += 1) {
21
+ const turn = conversation.turns[index];
22
+ if (turn === undefined || digest(turn) !== checkpoint.turns[index])
23
+ return undefined;
24
+ }
25
+ const suffix = conversation.turns.at(-1);
26
+ return suffix?.role === "user" ? suffix : undefined;
27
+ }
28
+ export function extendsCheckpoint(conversation, checkpoint) {
29
+ return resumeTurn(conversation, checkpoint) !== undefined;
30
+ }
31
+ export function canonicalJson(value) {
32
+ return JSON.stringify(ordered(value)) ?? "null";
33
+ }
34
+ function renderCursorMessage(system, turns) {
35
+ const sections = [];
36
+ const images = [];
37
+ for (const instruction of system)
38
+ sections.push(`System: ${instruction}`);
39
+ for (const turn of turns) {
40
+ const role = turn.role === "user" ? "User" : turn.role === "assistant" ? "Assistant" : "Tool";
41
+ const parts = turn.parts.map((part) => renderPart(part, images));
42
+ sections.push(`${role}: ${parts.join("\n\n")}`);
43
+ }
44
+ const text = sections.join("\n\n");
45
+ return images.length === 0 ? { text } : { text, images };
46
+ }
47
+ function renderPart(part, images) {
48
+ switch (part.type) {
49
+ case "text":
50
+ return part.text;
51
+ case "image":
52
+ images.push(part.image);
53
+ return `[Image ${images.length}: ${part.image.mimeType}]`;
54
+ case "reasoning":
55
+ return `[Reasoning]\n${part.text}`;
56
+ case "tool-call":
57
+ return `[Tool call ${part.name} (${part.id})]\n${part.input}`;
58
+ case "tool-result":
59
+ return [
60
+ `[Tool ${part.isError ? "error" : "result"} ${part.name} (${part.id})]`,
61
+ ...part.output.map((output) => renderPart(output, images)),
62
+ ].join("\n");
63
+ case "tool-approval":
64
+ return `[Tool approval ${part.id}]\n${part.approved ? "approved" : "denied"}${part.reason ? `: ${part.reason}` : ""}`;
65
+ default: {
66
+ const _exhaustive = part;
67
+ return _exhaustive;
68
+ }
69
+ }
70
+ }
71
+ function digest(value) {
72
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
73
+ }
74
+ function ordered(value) {
75
+ if (value === null || typeof value === "string" || typeof value === "boolean")
76
+ return value;
77
+ if (typeof value === "number")
78
+ return Number.isFinite(value) ? value : String(value);
79
+ if (typeof value !== "object")
80
+ return String(value);
81
+ if (Array.isArray(value))
82
+ return value.map(ordered);
83
+ return Object.fromEntries(Object.keys(value)
84
+ .sort()
85
+ .map((key) => [key, ordered(Object.getOwnPropertyDescriptor(value, key)?.value)]));
86
+ }
@@ -0,0 +1,10 @@
1
+ import type { TurnScope } from "./binding.ts";
2
+ export declare const SENTINEL_VERSION = 1;
3
+ export declare function encodeSentinel(scope: TurnScope): string;
4
+ export declare function decodeSentinel(text: string): TurnScope | undefined;
5
+ export declare function stampSystem(system: string[], scope: TurnScope): string[];
6
+ export declare function extractScope(system: readonly string[]): {
7
+ readonly scope: TurnScope | undefined;
8
+ readonly system: readonly string[];
9
+ };
10
+ export declare function sessionFromId(sessionID: string, cwd: string): TurnScope;
@@ -0,0 +1,52 @@
1
+ import { asSessionID } from "../ids.js";
2
+ export const SENTINEL_VERSION = 1;
3
+ const PREFIX = `<!-- opencode-cursor-provider:${SENTINEL_VERSION}:`;
4
+ export function encodeSentinel(scope) {
5
+ return `${PREFIX}${JSON.stringify({ sessionID: scope.sessionID, cwd: scope.cwd })} -->`;
6
+ }
7
+ export function decodeSentinel(text) {
8
+ const start = text.indexOf(PREFIX);
9
+ if (start === -1)
10
+ return undefined;
11
+ const jsonStart = start + PREFIX.length;
12
+ const end = text.indexOf(" -->", jsonStart);
13
+ if (end === -1)
14
+ return undefined;
15
+ try {
16
+ const parsed = JSON.parse(text.slice(jsonStart, end));
17
+ if (typeof parsed !== "object" || parsed === null)
18
+ return undefined;
19
+ const record = parsed;
20
+ if (typeof record.sessionID !== "string" || typeof record.cwd !== "string")
21
+ return undefined;
22
+ if (record.sessionID.length === 0 || record.cwd.length === 0)
23
+ return undefined;
24
+ return { sessionID: asSessionID(record.sessionID), cwd: record.cwd };
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ export function stampSystem(system, scope) {
31
+ const marker = encodeSentinel(scope);
32
+ if (system.some((line) => line.includes(PREFIX))) {
33
+ return system.map((line) => (line.includes(PREFIX) ? marker : line));
34
+ }
35
+ return [...system, marker];
36
+ }
37
+ export function extractScope(system) {
38
+ let scope;
39
+ const cleaned = [];
40
+ for (const line of system) {
41
+ const found = decodeSentinel(line);
42
+ if (found) {
43
+ scope = found;
44
+ continue;
45
+ }
46
+ cleaned.push(line);
47
+ }
48
+ return { scope, system: cleaned };
49
+ }
50
+ export function sessionFromId(sessionID, cwd) {
51
+ return { sessionID: asSessionID(sessionID), cwd };
52
+ }
@@ -0,0 +1,4 @@
1
+ export interface KeyedLock {
2
+ acquire(key: string): Promise<() => void>;
3
+ }
4
+ export declare function createLock(): KeyedLock;
@@ -0,0 +1,24 @@
1
+ export function createLock() {
2
+ const locks = new Map();
3
+ return {
4
+ async acquire(key) {
5
+ const previous = locks.get(key) ?? Promise.resolve();
6
+ let releaseCurrent = () => { };
7
+ const current = new Promise((resolve) => {
8
+ releaseCurrent = resolve;
9
+ });
10
+ const queued = previous.then(() => current);
11
+ locks.set(key, queued);
12
+ await previous;
13
+ let released = false;
14
+ return () => {
15
+ if (released)
16
+ return;
17
+ released = true;
18
+ releaseCurrent();
19
+ if (locks.get(key) === queued)
20
+ locks.delete(key);
21
+ };
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,7 @@
1
+ import { type AssistantPart } from "./conversation.ts";
2
+ import type { TurnEvent } from "./translate.ts";
3
+ export interface ResponseJournal {
4
+ accept(event: TurnEvent): TurnEvent | undefined;
5
+ parts(): readonly AssistantPart[];
6
+ }
7
+ export declare function createResponseJournal(): ResponseJournal;
@@ -0,0 +1,49 @@
1
+ import { canonicalJson } from "./conversation.js";
2
+ export function createResponseJournal() {
3
+ const parts = [];
4
+ const toolCalls = new Set();
5
+ const toolResults = new Set();
6
+ return {
7
+ accept(event) {
8
+ if (event.type === "text" || event.type === "reasoning") {
9
+ const previous = parts.at(-1);
10
+ if (previous?.type === event.type) {
11
+ parts.splice(-1, 1, { type: event.type, text: previous.text + event.delta });
12
+ }
13
+ else {
14
+ parts.push({ type: event.type, text: event.delta });
15
+ }
16
+ return event;
17
+ }
18
+ if (event.type === "tool-call") {
19
+ if (toolCalls.has(event.id))
20
+ return undefined;
21
+ toolCalls.add(event.id);
22
+ parts.push({ type: "tool-call", id: event.id, name: event.name, input: canonicalJson(event.input) });
23
+ return event;
24
+ }
25
+ if (event.type === "tool-result") {
26
+ if (toolResults.has(event.id))
27
+ return undefined;
28
+ toolResults.add(event.id);
29
+ parts.push({
30
+ type: "tool-result",
31
+ id: event.id,
32
+ name: event.name,
33
+ output: [
34
+ {
35
+ type: "text",
36
+ text: typeof event.result === "string" ? event.result : canonicalJson(event.result),
37
+ },
38
+ ],
39
+ isError: event.isError,
40
+ });
41
+ return event;
42
+ }
43
+ return event;
44
+ },
45
+ parts() {
46
+ return parts;
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,60 @@
1
+ import type { SDKMessage } from "@cursor/sdk";
2
+ import type { CursorPluginError } from "../errors.ts";
3
+ export type TurnEvent = {
4
+ readonly type: "raw";
5
+ readonly value: SDKMessage;
6
+ } | {
7
+ readonly type: "response-metadata";
8
+ readonly id: string;
9
+ readonly timestamp?: number;
10
+ readonly modelId?: string;
11
+ } | {
12
+ readonly type: "text";
13
+ readonly delta: string;
14
+ } | {
15
+ readonly type: "reasoning";
16
+ readonly delta: string;
17
+ } | {
18
+ readonly type: "tool-call";
19
+ readonly id: string;
20
+ readonly name: string;
21
+ readonly input: JsonValue;
22
+ } | {
23
+ readonly type: "tool-result";
24
+ readonly id: string;
25
+ readonly name: string;
26
+ readonly result: NonNullJsonValue;
27
+ readonly isError: boolean;
28
+ } | {
29
+ readonly type: "usage";
30
+ readonly input: number;
31
+ readonly output: number;
32
+ readonly cacheRead: number;
33
+ readonly cacheWrite: number;
34
+ readonly reasoning: number;
35
+ readonly total: number;
36
+ } | {
37
+ readonly type: "done";
38
+ readonly reason: "stop" | "length" | "aborted";
39
+ readonly metadata?: {
40
+ readonly runId: string;
41
+ readonly requestId?: string;
42
+ readonly durationMs?: number;
43
+ readonly modelId?: string;
44
+ readonly git?: readonly {
45
+ readonly repoUrl: string;
46
+ readonly branch?: string;
47
+ readonly prUrl?: string;
48
+ }[];
49
+ };
50
+ } | {
51
+ readonly type: "failed";
52
+ readonly error: CursorPluginError;
53
+ };
54
+ type JsonValue = null | string | number | boolean | JsonValue[] | {
55
+ [key: string]: JsonValue;
56
+ };
57
+ type NonNullJsonValue = Exclude<JsonValue, null>;
58
+ export declare function translate(message: SDKMessage): readonly TurnEvent[];
59
+ export declare function createMessageTranslator(): (message: SDKMessage) => readonly TurnEvent[];
60
+ export {};
@@ -0,0 +1,188 @@
1
+ export function translate(message) {
2
+ return translateMessage(message, new Map());
3
+ }
4
+ export function createMessageTranslator() {
5
+ const toolInputs = new Map();
6
+ return (message) => translateMessage(message, toolInputs);
7
+ }
8
+ function translateMessage(message, toolInputs) {
9
+ switch (message.type) {
10
+ case "assistant": {
11
+ const events = [];
12
+ for (const block of message.message.content) {
13
+ if (block.type === "text" && block.text.length > 0) {
14
+ events.push({ type: "text", delta: block.text });
15
+ }
16
+ if (block.type === "tool_use")
17
+ toolInputs.set(block.id, block.input);
18
+ }
19
+ return events;
20
+ }
21
+ case "thinking":
22
+ return message.text.length > 0 ? [{ type: "reasoning", delta: message.text }] : [];
23
+ case "tool_call": {
24
+ if (message.status === "running") {
25
+ if (message.args !== undefined && message.truncated?.args !== true) {
26
+ toolInputs.set(message.call_id, message.args);
27
+ }
28
+ return [];
29
+ }
30
+ const cachedInput = toolInputs.get(message.call_id);
31
+ const hasCachedInput = toolInputs.has(message.call_id);
32
+ toolInputs.delete(message.call_id);
33
+ if (message.truncated?.args === true && !hasCachedInput) {
34
+ return [
35
+ {
36
+ type: "failed",
37
+ error: {
38
+ kind: "agent-run-failed",
39
+ detail: `Cursor truncated the input for tool call ${message.call_id}.`,
40
+ },
41
+ },
42
+ ];
43
+ }
44
+ if (message.truncated?.result === true) {
45
+ return [
46
+ {
47
+ type: "failed",
48
+ error: {
49
+ kind: "agent-run-failed",
50
+ detail: `Cursor truncated the result for tool call ${message.call_id}.`,
51
+ },
52
+ },
53
+ ];
54
+ }
55
+ const input = message.truncated?.args === true || message.args === undefined ? cachedInput : message.args;
56
+ const tool = nativeTool(message.name, input);
57
+ const call = {
58
+ type: "tool-call",
59
+ id: message.call_id,
60
+ name: tool.name,
61
+ input: tool.input,
62
+ };
63
+ const result = cursorResult(message.result);
64
+ return [
65
+ call,
66
+ {
67
+ type: "tool-result",
68
+ id: message.call_id,
69
+ name: tool.name,
70
+ result: resultOutput(tool.name, result.value),
71
+ isError: message.status === "error" || result.isError,
72
+ },
73
+ ];
74
+ }
75
+ case "usage":
76
+ return [
77
+ {
78
+ type: "usage",
79
+ input: message.usage.inputTokens ?? 0,
80
+ output: message.usage.outputTokens ?? 0,
81
+ cacheRead: message.usage.cacheReadTokens ?? 0,
82
+ cacheWrite: message.usage.cacheWriteTokens ?? 0,
83
+ reasoning: message.usage.reasoningTokens ?? 0,
84
+ total: message.usage.totalTokens ?? 0,
85
+ },
86
+ ];
87
+ case "status":
88
+ return [];
89
+ case "system":
90
+ case "user":
91
+ case "request":
92
+ case "task":
93
+ return [];
94
+ default: {
95
+ const _exhaustive = message;
96
+ return _exhaustive;
97
+ }
98
+ }
99
+ }
100
+ function nativeTool(name, value) {
101
+ const input = jsonValue(value);
102
+ if (!isJsonObject(input))
103
+ return { name, input };
104
+ if (name === "read" || name === "edit" || name === "write") {
105
+ return { name, input: rename(input, "path", "filePath") };
106
+ }
107
+ if (name === "ls")
108
+ return { name: "list", input };
109
+ if (name === "glob") {
110
+ return { name, input: rename(rename(input, "globPattern", "pattern"), "targetDirectory", "path") };
111
+ }
112
+ if (name === "grep") {
113
+ let mapped = rename(input, "glob", "include");
114
+ mapped = rename(mapped, "headLimit", "limit");
115
+ if (typeof mapped.caseInsensitive === "boolean") {
116
+ mapped = { ...mapped, caseSensitive: !mapped.caseInsensitive };
117
+ delete mapped.caseInsensitive;
118
+ }
119
+ return { name, input: mapped };
120
+ }
121
+ if (name === "updateTodos")
122
+ return { name: "todowrite", input };
123
+ if (name === "task")
124
+ return { name, input: rename(input, "subagentType", "subagent_type") };
125
+ return { name, input };
126
+ }
127
+ function rename(input, from, to) {
128
+ if (!(from in input))
129
+ return input;
130
+ const result = { ...input, [to]: input[from] ?? null };
131
+ delete result[from];
132
+ return result;
133
+ }
134
+ function cursorResult(value) {
135
+ const result = jsonValue(value);
136
+ if (!isJsonObject(result) || typeof result.status !== "string")
137
+ return { value: result, isError: false };
138
+ const isError = result.status !== "success";
139
+ if ("value" in result)
140
+ return { value: result.value ?? null, isError };
141
+ if (isError && "error" in result)
142
+ return { value: result.error ?? "Tool call failed", isError: true };
143
+ return { value: result, isError };
144
+ }
145
+ function resultOutput(name, value) {
146
+ return textOutput(name, value);
147
+ }
148
+ function textOutput(name, value) {
149
+ if (typeof value === "string")
150
+ return value;
151
+ if (isJsonObject(value)) {
152
+ if (name === "shell") {
153
+ const stdout = typeof value.stdout === "string" ? value.stdout : "";
154
+ const stderr = typeof value.stderr === "string" ? value.stderr : "";
155
+ const text = stdout + stderr;
156
+ return text.length > 0 ? text : "(no output)";
157
+ }
158
+ if (name === "read" && typeof value.content === "string")
159
+ return value.content;
160
+ if (name === "edit" && typeof value.diffString === "string")
161
+ return value.diffString;
162
+ if (name === "glob" && Array.isArray(value.files))
163
+ return value.files.filter(isString).join("\n");
164
+ if (name === "task" && typeof value.resultSuffix === "string")
165
+ return value.resultSuffix;
166
+ }
167
+ return JSON.stringify(value) ?? "null";
168
+ }
169
+ function isJsonObject(value) {
170
+ return typeof value === "object" && value !== null && !Array.isArray(value);
171
+ }
172
+ function isString(value) {
173
+ return typeof value === "string";
174
+ }
175
+ function jsonValue(value, seen = new WeakSet()) {
176
+ if (value === null || typeof value === "string" || typeof value === "boolean")
177
+ return value;
178
+ if (typeof value === "number")
179
+ return Number.isFinite(value) ? value : String(value);
180
+ if (typeof value !== "object")
181
+ return value === undefined ? null : String(value);
182
+ if (seen.has(value))
183
+ return "[Circular]";
184
+ seen.add(value);
185
+ if (Array.isArray(value))
186
+ return value.map((item) => jsonValue(item, seen));
187
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonValue(item, seen)]));
188
+ }
@@ -0,0 +1,29 @@
1
+ import { type AgentModeOption, type ModelParameterValue } from "@cursor/sdk";
2
+ import type { CursorLink } from "../auth/link.ts";
3
+ import { type CursorModelDescriptor } from "../catalog/catalog.ts";
4
+ import { type CatalogModelID } from "../ids.ts";
5
+ import type { CursorAgentOptions } from "../model/provider-options.ts";
6
+ import { openCursorAgent } from "./agent.ts";
7
+ import { type BindingStore, type TurnScope } from "./binding.ts";
8
+ import { type Conversation } from "./conversation.ts";
9
+ import type { KeyedLock } from "./lock.ts";
10
+ import { type TurnEvent } from "./translate.ts";
11
+ export interface TurnRequest {
12
+ readonly modelID: CatalogModelID;
13
+ readonly scope: TurnScope | undefined;
14
+ readonly conversation: Conversation;
15
+ readonly params?: readonly ModelParameterValue[];
16
+ readonly mode?: AgentModeOption;
17
+ readonly agentOptions?: CursorAgentOptions;
18
+ readonly includeRawChunks?: boolean;
19
+ readonly signal?: AbortSignal;
20
+ }
21
+ export interface TurnRunnerContext {
22
+ readonly link: CursorLink;
23
+ readonly models: () => readonly CursorModelDescriptor[];
24
+ readonly bindings: BindingStore;
25
+ readonly clock: () => number;
26
+ readonly lock: KeyedLock;
27
+ readonly openAgent?: typeof openCursorAgent;
28
+ }
29
+ export declare function createTurnRunner(ctx: TurnRunnerContext): (request: TurnRequest) => AsyncIterable<TurnEvent>;