cursor-opencode-provider 0.6.8 → 0.7.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 (39) hide show
  1. package/README.md +50 -180
  2. package/dist/compaction-marker.d.ts +3 -2
  3. package/dist/compaction-marker.js +3 -2
  4. package/dist/host-agent-mode.d.ts +32 -0
  5. package/dist/host-agent-mode.js +92 -0
  6. package/dist/language-model.d.ts +6 -1
  7. package/dist/language-model.js +170 -34
  8. package/dist/models.d.ts +12 -1
  9. package/dist/models.js +16 -6
  10. package/dist/opencode2/catalog.d.ts +46 -11
  11. package/dist/opencode2/catalog.js +60 -45
  12. package/dist/opencode2/todo-tools.d.ts +64 -0
  13. package/dist/opencode2/todo-tools.js +165 -0
  14. package/dist/opencode2/types.d.ts +138 -83
  15. package/dist/opencode2/types.js +8 -10
  16. package/dist/plan-execution-kickoff.d.ts +2 -0
  17. package/dist/plan-execution-kickoff.js +25 -1
  18. package/dist/plugin-opencode2.d.ts +4 -1
  19. package/dist/plugin-opencode2.js +335 -154
  20. package/dist/plugin.js +5 -1
  21. package/dist/protocol/conversation-persistence.d.ts +2 -0
  22. package/dist/protocol/conversation-persistence.js +19 -1
  23. package/dist/protocol/conversation-state.d.ts +4 -0
  24. package/dist/protocol/conversation-state.js +4 -0
  25. package/dist/protocol/switch-mode.d.ts +5 -4
  26. package/dist/protocol/switch-mode.js +12 -4
  27. package/dist/protocol/tool-call-bridge.js +1 -1
  28. package/dist/protocol/tools.d.ts +30 -4
  29. package/dist/protocol/tools.js +185 -58
  30. package/dist/session.d.ts +7 -1
  31. package/dist/shared.d.ts +3 -1
  32. package/dist/shared.js +3 -1
  33. package/dist/shell-timeout.d.ts +13 -0
  34. package/dist/shell-timeout.js +38 -0
  35. package/dist/todo-store.d.ts +29 -0
  36. package/dist/todo-store.js +78 -0
  37. package/dist/web-tools.d.ts +13 -0
  38. package/dist/web-tools.js +83 -5
  39. package/package.json +6 -3
package/dist/plugin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { pathToFileURL } from "node:url";
3
- import { CURSOR_COMPACTION_OPTION, CURSOR_PROVIDER_ID, CURSOR_WEBSITE_HOST, CURSOR_API_HOST } from "./shared.js";
3
+ import { CURSOR_API_HOST, CURSOR_COMPACTION_OPTION, CURSOR_HOST_AGENT_OPTION, CURSOR_PROVIDER_ID, CURSOR_WEBSITE_HOST, } from "./shared.js";
4
4
  import { cursorApiBaseURL, cursorGetServerConfigTelemetryEnabled } from "./plugin-core.js";
5
5
  import { pollForTokens, exchangeApiKey, refreshAccessToken, isExpiringSoon, generatePkceParams, generatePkceChallenge, buildLoginUrl, decodeJwtExpiryMs } from "./auth.js";
6
6
  import { readCache, discoverModels, isCacheFresh } from "./models.js";
@@ -254,6 +254,10 @@ export async function CursorPlugin(input) {
254
254
  async "chat.params"(hookInput, output) {
255
255
  if (hookInput.model.providerID !== CURSOR_PROVIDER_ID)
256
256
  return;
257
+ // Agent changes can replace the host system prompt and mode contract.
258
+ // Carry the canonical OpenCode id so an incompatible Cursor checkpoint
259
+ // is rotated instead of resuming the prior agent's prompt.
260
+ output.options[CURSOR_HOST_AGENT_OPTION] = hookInput.agent;
257
261
  // OpenCode's compaction pipeline invokes the LLM with agent="compaction".
258
262
  // Carry that stable runtime fact into LanguageModelV3 providerOptions so
259
263
  // the provider never has to guess from an empty tool list.
@@ -9,6 +9,8 @@ export type PersistedConversation = {
9
9
  requestContext: Record<string, unknown>;
10
10
  toolCatalog: OpencodeToolDef[];
11
11
  postCompactionRebase: boolean;
12
+ hostAgent?: string;
13
+ systemPromptHash?: string;
12
14
  };
13
15
  export type ConversationLoadStatus = "restored" | "missing" | "invalid" | "expired";
14
16
  export type PersistedConversationLoad = {
@@ -47,7 +47,7 @@ function cloneConversation(value) {
47
47
  *
48
48
  * ConversationCache: schema_version=1, session_key=2, conversation_id=3,
49
49
  * updated_at=4, checkpoint=5, blobs=6, request_context=7, tool_catalog=8,
50
- * post_compaction_rebase=9.
50
+ * post_compaction_rebase=9, host_agent=10, system_prompt_hash=11.
51
51
  * Blob: id=1, data=2. Tool: name=1, description=2,
52
52
  * input_schema_json=3, source_name=4.
53
53
  *
@@ -172,6 +172,10 @@ function encodeCacheFile(value) {
172
172
  }
173
173
  if (value.postCompactionRebase)
174
174
  writer.uint32(fieldTag(9, 0)).bool(true);
175
+ if (value.hostAgent)
176
+ writer.uint32(fieldTag(10, 2)).string(value.hostAgent);
177
+ if (value.systemPromptHash)
178
+ writer.uint32(fieldTag(11, 2)).string(value.systemPromptHash);
175
179
  return { protobufBytes: writer.finish(), requestContextBytes: requestContext.length };
176
180
  }
177
181
  function decodeProtobuf(data) {
@@ -185,6 +189,8 @@ function decodeProtobuf(data) {
185
189
  let requestContextBytes;
186
190
  const toolCatalog = [];
187
191
  let postCompactionRebase = false;
192
+ let hostAgent;
193
+ let systemPromptHash;
188
194
  while (reader.pos < reader.len) {
189
195
  const tag = reader.uint32();
190
196
  const wireType = tag & 7;
@@ -234,6 +240,16 @@ function decodeProtobuf(data) {
234
240
  throw new Error("invalid compaction marker");
235
241
  postCompactionRebase = reader.bool();
236
242
  break;
243
+ case 10:
244
+ if (wireType !== 2)
245
+ throw new Error("invalid host agent");
246
+ hostAgent = reader.string();
247
+ break;
248
+ case 11:
249
+ if (wireType !== 2)
250
+ throw new Error("invalid system prompt hash");
251
+ systemPromptHash = reader.string();
252
+ break;
237
253
  default:
238
254
  reader.skipType(wireType);
239
255
  }
@@ -252,6 +268,8 @@ function decodeProtobuf(data) {
252
268
  requestContext,
253
269
  toolCatalog,
254
270
  postCompactionRebase,
271
+ ...(hostAgent ? { hostAgent } : {}),
272
+ ...(systemPromptHash ? { systemPromptHash } : {}),
255
273
  };
256
274
  }
257
275
  function decodeCacheFile(compressed, expectedSessionKey) {
@@ -4,6 +4,8 @@ export declare function hydrateConversationState(cacheDir: string, sessionKey: s
4
4
  conversationId: string;
5
5
  postCompactionRebase: boolean;
6
6
  toolCatalog: OpencodeToolDef[];
7
+ hostAgent?: string;
8
+ systemPromptHash?: string;
7
9
  } | undefined>;
8
10
  /** Persist the complete resumable state only after Cursor confirms TurnEnded. */
9
11
  export declare function persistConversationState(cacheDir: string, input: {
@@ -12,5 +14,7 @@ export declare function persistConversationState(cacheDir: string, input: {
12
14
  requestContext: Record<string, unknown>;
13
15
  toolCatalog?: OpencodeToolDef[];
14
16
  postCompactionRebase?: boolean;
17
+ hostAgent?: string;
18
+ systemPromptHash?: string;
15
19
  }): Promise<void>;
16
20
  export declare function clearPersistedConversationState(cacheDir: string, sessionKey: string, expectedConversationId?: string): Promise<void>;
@@ -27,6 +27,8 @@ export async function hydrateConversationState(cacheDir, sessionKey) {
27
27
  conversationId: persisted.conversationId,
28
28
  postCompactionRebase: persisted.postCompactionRebase,
29
29
  toolCatalog: structuredClone(persisted.toolCatalog),
30
+ ...(persisted.hostAgent ? { hostAgent: persisted.hostAgent } : {}),
31
+ ...(persisted.systemPromptHash ? { systemPromptHash: persisted.systemPromptHash } : {}),
30
32
  };
31
33
  }
32
34
  /** Persist the complete resumable state only after Cursor confirms TurnEnded. */
@@ -50,6 +52,8 @@ export async function persistConversationState(cacheDir, input) {
50
52
  requestContext,
51
53
  toolCatalog: structuredClone(input.toolCatalog ?? []),
52
54
  postCompactionRebase: input.postCompactionRebase,
55
+ hostAgent: input.hostAgent,
56
+ systemPromptHash: input.systemPromptHash,
53
57
  });
54
58
  trace(`conversation persistence: saved sessionKey=${input.sessionKey} ` +
55
59
  `conversationId=${input.conversationId} checkpoint=${checkpoint?.length ?? 0}B ` +
@@ -8,7 +8,7 @@
8
8
  * Display `switch_mode_tool_call` (#25) is a separate transcript record and is
9
9
  * not replayed as a host tool.
10
10
  *
11
- * Host mapping (advertisement-gated):
11
+ * OpenCode 1.x mapping (advertisement-gated):
12
12
  * - plan, spec → plan_enter
13
13
  * - every other non-empty target → plan_exit (leave plan for OpenCode build),
14
14
  * then inject the Cursor CLI-shaped mode reminder for that target
@@ -19,9 +19,10 @@
19
19
  * `setAgentModel` primary-agent switch (opencode `tool/plan.ts`,
20
20
  * `session/prompt.ts`). A provider cannot reach `setAgentModel`, but the two
21
21
  * observable halves — the approval gate and the behavioural contract — are both
22
- * reachable, so the bridge degrades instead of refusing when the host tool is
23
- * missing (`plan_enter` is commented out upstream and `plan_exit` is gated
24
- * behind OPENCODE_EXPERIMENTAL_PLAN_MODE + CLI client):
22
+ * reachable, so the protocol bridge degrades instead of refusing when the host
23
+ * tool is missing. OpenCode 2 additionally exposes `session.switchAgent`; its
24
+ * entrypoint installs a structural callback that selects the native `plan` or
25
+ * `build` primary agent after the owning Cursor Run becomes terminal:
25
26
  *
26
27
  * - entering plan/spec needs no host tool at all — approve immediately and let
27
28
  * the injected <system_reminder> carry the contract, exactly as Cursor CLI's
@@ -8,7 +8,7 @@
8
8
  * Display `switch_mode_tool_call` (#25) is a separate transcript record and is
9
9
  * not replayed as a host tool.
10
10
  *
11
- * Host mapping (advertisement-gated):
11
+ * OpenCode 1.x mapping (advertisement-gated):
12
12
  * - plan, spec → plan_enter
13
13
  * - every other non-empty target → plan_exit (leave plan for OpenCode build),
14
14
  * then inject the Cursor CLI-shaped mode reminder for that target
@@ -19,9 +19,10 @@
19
19
  * `setAgentModel` primary-agent switch (opencode `tool/plan.ts`,
20
20
  * `session/prompt.ts`). A provider cannot reach `setAgentModel`, but the two
21
21
  * observable halves — the approval gate and the behavioural contract — are both
22
- * reachable, so the bridge degrades instead of refusing when the host tool is
23
- * missing (`plan_enter` is commented out upstream and `plan_exit` is gated
24
- * behind OPENCODE_EXPERIMENTAL_PLAN_MODE + CLI client):
22
+ * reachable, so the protocol bridge degrades instead of refusing when the host
23
+ * tool is missing. OpenCode 2 additionally exposes `session.switchAgent`; its
24
+ * entrypoint installs a structural callback that selects the native `plan` or
25
+ * `build` primary agent after the owning Cursor Run becomes terminal:
25
26
  *
26
27
  * - entering plan/spec needs no host tool at all — approve immediately and let
27
28
  * the injected <system_reminder> carry the contract, exactly as Cursor CLI's
@@ -68,6 +69,7 @@ export const SWITCH_MODE_EXIT_HEADER = "Build Agent";
68
69
  const SWITCH_MODE_EXIT_YES = "Yes";
69
70
  const SWITCH_MODE_EXIT_NO = "No";
70
71
  const activeCursorModeBySession = new Map();
72
+ const MAX_ACTIVE_CURSOR_MODES = 256;
71
73
  function asRecord(value) {
72
74
  return value && typeof value === "object" && !Array.isArray(value)
73
75
  ? value
@@ -248,6 +250,12 @@ export function setActiveCursorMode(sessionKey, targetModeId, options = {}) {
248
250
  firstTurn: true,
249
251
  bridgedPlanEntered: options.bridgedPlanEntered === true,
250
252
  });
253
+ while (activeCursorModeBySession.size > MAX_ACTIVE_CURSOR_MODES) {
254
+ const oldest = activeCursorModeBySession.keys().next().value;
255
+ if (!oldest)
256
+ break;
257
+ activeCursorModeBySession.delete(oldest);
258
+ }
251
259
  }
252
260
  export function getActiveCursorMode(sessionKey) {
253
261
  if (!sessionKey)
@@ -1,4 +1,4 @@
1
- import { mapCursorArgsToOpencode, mapCursorSubagentTypeToOpenCode, mcpRealToolName } from "./tools.js";
1
+ import { mapCursorArgsToOpencode, mapCursorSubagentTypeToOpenCode, mcpRealToolName, } from "./tools.js";
2
2
  import { decodeStructEntriesToJson } from "./struct.js";
3
3
  import { mapSwitchModeTarget } from "./switch-mode.js";
4
4
  const TODO_STATUS = {
@@ -8,6 +8,25 @@ export type OpencodeToolDef = {
8
8
  /** Original flattened identity when `name` is a Cursor-facing alias. */
9
9
  sourceName?: string;
10
10
  };
11
+ /** Host file-tool argument key. OpenCode 1.x uses `filePath`; 2.0 uses `path`. */
12
+ export type HostFilePathKey = "path" | "filePath";
13
+ /** Host shell tool id. OpenCode 1.x uses `bash`; 2.0 uses `shell`. */
14
+ export type HostShellTool = "bash" | "shell";
15
+ export type HostToolDialect = {
16
+ filePathKey: HostFilePathKey;
17
+ shellTool: HostShellTool;
18
+ };
19
+ export declare const OPENCODE_1_TOOL_DIALECT: HostToolDialect;
20
+ /** Prefer `filePath`, then `path` / `file_path` — Cursor and both OpenCode majors. */
21
+ export declare function opencodePathArg(args: Record<string, unknown> | undefined): string | undefined;
22
+ /**
23
+ * Infer the host tool dialect from advertised AI SDK schemas.
24
+ * OpenCode 2.0 read/edit/write require `path` and rename bash → `shell`.
25
+ */
26
+ export declare function hostToolDialectFromTools(tools: readonly {
27
+ name?: string;
28
+ inputSchema?: unknown;
29
+ }[]): HostToolDialect;
11
30
  export declare const CUSTOM_WEBSEARCH_TOOL = "custom_websearch";
12
31
  export declare const CUSTOM_WEBFETCH_TOOL = "custom_webfetch";
13
32
  export declare const CUSTOM_LIST_MCP_RESOURCES_TOOL = "custom_list_mcp_resources";
@@ -72,9 +91,9 @@ export type HostSubagentDefinition = {
72
91
  description?: string;
73
92
  };
74
93
  export type HostSubagentCatalog = {
75
- executor?: "task";
94
+ executor?: "task" | "subagent";
76
95
  agents: HostSubagentDefinition[];
77
- /** True when the OpenCode task tool supplied its complete, permission-filtered catalog. */
96
+ /** True when the OpenCode task/subagent tool supplied its complete, permission-filtered catalog. */
78
97
  complete: boolean;
79
98
  };
80
99
  /** Extract the current host's permission-filtered Task/Actor recipient catalog. */
@@ -143,13 +162,13 @@ export type ParsedExecRequest = {
143
162
  * this provider's warning text.
144
163
  */
145
164
  export declare function rejectPartialReadMutation(parsed: ParsedExecRequest): void;
146
- export declare function parseExecServerMessage(msg: Record<string, unknown>): ParsedExecRequest | undefined;
165
+ export declare function parseExecServerMessage(msg: Record<string, unknown>, dialect?: HostToolDialect): ParsedExecRequest | undefined;
147
166
  /**
148
167
  * Remap Cursor exec / MCP arg shapes onto OpenCode's Effect Schema keys.
149
168
  * Without this, OpenCode rejects calls with InvalidArgumentsError
150
169
  * (e.g. `path` instead of `filePath`, `file_text` instead of `content`).
151
170
  */
152
- export declare function mapCursorArgsToOpencode(toolName: string, raw: Record<string, unknown>, execVariant?: string): {
171
+ export declare function mapCursorArgsToOpencode(toolName: string, raw: Record<string, unknown>, execVariant?: string, dialect?: HostToolDialect): {
153
172
  toolName: string;
154
173
  args: Record<string, unknown>;
155
174
  binaryBytes?: Uint8Array;
@@ -174,6 +193,13 @@ export declare function binaryWritePayload(parsed: ParsedExecRequest): {
174
193
  path: string;
175
194
  data: Uint8Array;
176
195
  } | undefined;
196
+ /**
197
+ * Replace the prompt-derived task title with Cursor's TaskToolCall description
198
+ * when the display call is correlated via tool_call_id. SubagentArgs itself has
199
+ * no description field, so without this OpenCode shows a cut-off first-five-words
200
+ * slice of the prompt.
201
+ */
202
+ export declare function preferCorrelatedTaskDescription(parsed: ParsedExecRequest, description: string | undefined): void;
177
203
  /**
178
204
  * Map Cursor McpArgs back to the OpenCode tool id.
179
205
  * Prefers provider_identifier + bare tool_name (github + create_pull_request