cursor-opencode-provider 0.6.8 → 0.7.1

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 (41) hide show
  1. package/README.md +51 -181
  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 +15 -1
  7. package/dist/language-model.js +190 -36
  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 +36 -11
  29. package/dist/protocol/tools.js +747 -117
  30. package/dist/protocol/workspace-grounding.d.ts +17 -1
  31. package/dist/protocol/workspace-grounding.js +34 -5
  32. package/dist/session.d.ts +7 -1
  33. package/dist/shared.d.ts +3 -1
  34. package/dist/shared.js +3 -1
  35. package/dist/shell-timeout.d.ts +13 -0
  36. package/dist/shell-timeout.js +38 -0
  37. package/dist/todo-store.d.ts +29 -0
  38. package/dist/todo-store.js +78 -0
  39. package/dist/web-tools.d.ts +13 -0
  40. package/dist/web-tools.js +83 -5
  41. package/package.json +6 -3
@@ -4,8 +4,8 @@ import { CURSOR_WIRE_MODEL_ID_KEY } from "../models.js";
4
4
  import { modelsToConfig } from "../model-config.js";
5
5
  import { toOpenCode2Costs } from "../pricing.js";
6
6
  /**
7
- * Catalog registration for the OpenCode 2.0 plugin — the replacement for the
8
- * classic plugin's `config` hook.
7
+ * In-memory provider registration for the OpenCode 2.0 plugin — the replacement
8
+ * for the classic plugin's `config` hook.
9
9
  *
10
10
  * Model naming, thinking suffixes, and long-context tiering are NOT reimplemented
11
11
  * here: we run the shared `modelsToConfig` and translate its output into the 2.0
@@ -17,10 +17,10 @@ export const CURSOR_INTEGRATION_ID = CURSOR_PROVIDER_ID;
17
17
  * `aisdk:` selects OpenCode 2.0's AI SDK path, which is what surfaces the
18
18
  * `aisdk.hook("sdk")` / `("language")` extension points we supply the provider
19
19
  * through. The suffix is this package's npm name so the host's built-in
20
- * `DynamicProviderPlugin` can still resolve it if our own hook is ever
21
- * bypassed — that fallback runs `npm.add(pkg)` against the *published*
22
- * registry into `<host-cache>/packages/<pkg>/node_modules/<pkg>`, ignoring
23
- * any local `file://` plugin path this process was loaded from.
20
+ * fallback can still resolve it if our own hook is ever bypassed — that
21
+ * fallback runs `npm.add(pkg)` against the *published* registry into
22
+ * `<host-cache>/packages/<pkg>/node_modules/<pkg>`, ignoring any local
23
+ * `file://` plugin path this process was loaded from.
24
24
  *
25
25
  * `CURSOR_OPENCODE2_DEV_ENTRY` overrides the suffix with an `aisdk:file://…`
26
26
  * spec instead, pointed at a local built entry file (e.g. `dist/index.js`,
@@ -32,19 +32,8 @@ export const CURSOR_INTEGRATION_ID = CURSOR_PROVIDER_ID;
32
32
  export const CURSOR_AISDK_PACKAGE = process.env.CURSOR_OPENCODE2_DEV_ENTRY
33
33
  ? `aisdk:${pathToFileURL(process.env.CURSOR_OPENCODE2_DEV_ENTRY).href}`
34
34
  : "aisdk:cursor-opencode-provider";
35
- /** Register (or update) the Cursor provider entry. `update` is an upsert. */
36
- export function applyCursorProvider(draft) {
37
- draft.provider.update(CURSOR_PROVIDER_ID, (provider) => {
38
- provider.id = CURSOR_PROVIDER_ID;
39
- provider.name = "Cursor";
40
- provider.package = CURSOR_AISDK_PACKAGE;
41
- // Links the provider to the integration that stores its credentials, so
42
- // `connection.active(...)` resolves the token the user set up via /connect.
43
- provider.integrationID = CURSOR_INTEGRATION_ID;
44
- });
45
- }
46
35
  /** Translate one `modelsToConfig` entry into the 2.0 `Model.Info` shape. */
47
- function applyModelEntry(draft, id, entry) {
36
+ export function modelConfigEntryToInfo(id, entry) {
48
37
  const options = entry.options;
49
38
  // Long-context and Fast entries get synthetic OpenCode ids (`<id>-1m`,
50
39
  // `<id>-fast`, `<id>-1m-fast`) while still addressing the same Cursor model
@@ -54,38 +43,64 @@ function applyModelEntry(draft, id, entry) {
54
43
  ? options[CURSOR_WIRE_MODEL_ID_KEY]
55
44
  : id;
56
45
  const variants = Object.entries((entry.variants ?? {})).map(([variantId, settings]) => ({ id: variantId, settings: { ...settings } }));
57
- draft.model.update(CURSOR_PROVIDER_ID, id, (model) => {
58
- model.id = id;
59
- model.modelID = wireId;
60
- model.providerID = CURSOR_PROVIDER_ID;
61
- model.name = entry.name;
62
- const inputModalities = Array.isArray(entry.modalities?.input)
63
- ? entry.modalities.input.filter((modality) => typeof modality === "string")
64
- : ["text"];
65
- const outputModalities = Array.isArray(entry.modalities?.output)
66
- ? entry.modalities.output.filter((modality) => typeof modality === "string")
67
- : ["text"];
68
- model.capabilities = {
46
+ const inputModalities = Array.isArray(entry.modalities?.input)
47
+ ? entry.modalities.input.filter((modality) => typeof modality === "string")
48
+ : ["text"];
49
+ const outputModalities = Array.isArray(entry.modalities?.output)
50
+ ? entry.modalities.output.filter((modality) => typeof modality === "string")
51
+ : ["text"];
52
+ const info = {
53
+ id,
54
+ modelID: wireId,
55
+ providerID: CURSOR_PROVIDER_ID,
56
+ name: entry.name ?? id,
57
+ capabilities: {
69
58
  tools: entry.tool_call !== false,
70
59
  input: inputModalities,
71
60
  output: outputModalities,
72
- };
73
- model.limit = { context: entry.limit.context, output: entry.limit.output };
74
- model.variants = variants;
75
- model.status = "active";
76
- model.enabled = true;
77
- model.time = { released: 0 };
78
- model.cost = toOpenCode2Costs(entry.cost);
79
- // Carries the default variant parameters (and the wire id) through to
80
- // doStream as provider options, matching the classic plugin.
81
- if (options)
82
- model.settings = { ...options };
83
- });
61
+ },
62
+ limit: {
63
+ context: entry.limit?.context ?? 200_000,
64
+ output: entry.limit?.output ?? 8192,
65
+ },
66
+ variants,
67
+ status: "active",
68
+ enabled: true,
69
+ time: { released: 0 },
70
+ cost: toOpenCode2Costs(entry.cost),
71
+ };
72
+ if (options)
73
+ info.settings = { ...options };
74
+ return info;
84
75
  }
85
- /** Register every discovered Cursor model. Safe to re-run; `update` upserts. */
86
- export function applyCursorModels(draft, models) {
76
+ /** Full model map for the in-memory provider inventory. */
77
+ export function modelsToCatalogModelMap(models) {
87
78
  const config = modelsToConfig(models);
79
+ const out = {};
88
80
  for (const [id, entry] of Object.entries(config)) {
89
- applyModelEntry(draft, id, entry);
81
+ out[id] = modelConfigEntryToInfo(id, entry);
90
82
  }
83
+ return out;
84
+ }
85
+ /**
86
+ * Publish discovered Cursor models into the live provider inventory.
87
+ *
88
+ * Skip while empty (keeps the last successful inventory through a no-op
89
+ * transform on first register), replace the definition with `editor.add`,
90
+ * then `ctx.provider.reload()`.
91
+ */
92
+ export function applyCursorProviderInventory(editor, models, sourceConnection) {
93
+ if (models.length === 0)
94
+ return;
95
+ editor.add({
96
+ info: {
97
+ id: CURSOR_PROVIDER_ID,
98
+ name: "Cursor",
99
+ activation: "enabled",
100
+ package: CURSOR_AISDK_PACKAGE,
101
+ integrationID: CURSOR_INTEGRATION_ID,
102
+ },
103
+ models: Object.values(modelsToCatalogModelMap(models)),
104
+ ...(sourceConnection ? { sourceConnection } : {}),
105
+ });
91
106
  }
@@ -0,0 +1,64 @@
1
+ import type { ToolDraft } from "./types.js";
2
+ /**
3
+ * OpenCode 2 snapshot: only `codemode === false` tools join the AI SDK catalog.
4
+ * Everything else is Code Mode-only (`packages/core/src/tool.ts` direct vs
5
+ * `codeModeTools`). Plugin-owned todo tools must opt into the direct catalog so
6
+ * Cursor can execute them without going through Code Mode.
7
+ */
8
+ export declare const OPENCODE2_DIRECT_TOOL_OPTIONS: {
9
+ readonly codemode: false;
10
+ };
11
+ /**
12
+ * Force-enable plugin-owned `todowrite` / `todoread` on OpenCode 2.0.
13
+ * Default is off: the host has no TUI/desktop checklist, so advertising
14
+ * these in-memory tools is opt-in. OpenCode 1.x is unaffected (host builtin).
15
+ * Same truthy rule as `CURSOR_PROVIDER_DEBUG` (`1` or `true`).
16
+ */
17
+ export declare const CURSOR_OPENCODE2_TODOS_ENV = "CURSOR_OPENCODE2_TODOS";
18
+ export declare function isOpenCode2TodosEnabled(env?: NodeJS.ProcessEnv): boolean;
19
+ /**
20
+ * 1.x `todowrite` description, shortened. OpenCode 2 has no builtin todo
21
+ * tools; this is the catalog text the model sees when the 2.0 gate is on.
22
+ */
23
+ export declare const TODOWRITE_DESCRIPTION = "Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status to the user.\n\nUse proactively when the work is 3+ distinct steps, non-trivial, or the user lists multiple tasks. Skip single straightforward edits and purely informational questions.\n\nStates: pending, in_progress (exactly one at a time), completed, cancelled.\nUpdate status as you go. Mark completed only after the work \u2014 including verification \u2014 is actually done.";
24
+ export declare const TODOREAD_DESCRIPTION = "Read the current session todo list. Takes no arguments. Returns the full list as JSON.";
25
+ export declare const TODO_OUTPUT_SCHEMA: {
26
+ readonly type: "object";
27
+ readonly additionalProperties: false;
28
+ readonly properties: {
29
+ readonly todos: {
30
+ readonly type: "array";
31
+ readonly items: {
32
+ readonly type: "object";
33
+ readonly additionalProperties: false;
34
+ readonly properties: {
35
+ readonly id: {
36
+ readonly type: "string";
37
+ };
38
+ readonly content: {
39
+ readonly type: "string";
40
+ };
41
+ readonly status: {
42
+ readonly type: "string";
43
+ readonly enum: readonly ["pending", "in_progress", "completed", "cancelled"];
44
+ };
45
+ readonly priority: {
46
+ readonly type: "string";
47
+ readonly enum: readonly ["high", "medium", "low"];
48
+ };
49
+ };
50
+ readonly required: readonly ["id", "content", "status", "priority"];
51
+ };
52
+ };
53
+ };
54
+ readonly required: readonly ["todos"];
55
+ };
56
+ /** True when the host editor already owns this tool id. */
57
+ export declare function hostHasTool(draft: ToolDraft, name: string): boolean;
58
+ /**
59
+ * Register canonical `todowrite` / `todoread` when the 2.0 force-enable gate
60
+ * is on and the host catalog does not already advertise them. No-op when the
61
+ * gate is off (default), on OpenCode 1.x (this function is not called), and
62
+ * on any 2.x host that restores the tools.
63
+ */
64
+ export declare function registerTodoTools(draft: ToolDraft): void;
@@ -0,0 +1,165 @@
1
+ import { TODOREAD_TOOL, TODOWRITE_TOOL, getSessionTodos, setSessionTodos, } from "../todo-store.js";
2
+ /**
3
+ * OpenCode 2 snapshot: only `codemode === false` tools join the AI SDK catalog.
4
+ * Everything else is Code Mode-only (`packages/core/src/tool.ts` direct vs
5
+ * `codeModeTools`). Plugin-owned todo tools must opt into the direct catalog so
6
+ * Cursor can execute them without going through Code Mode.
7
+ */
8
+ export const OPENCODE2_DIRECT_TOOL_OPTIONS = { codemode: false };
9
+ /**
10
+ * Force-enable plugin-owned `todowrite` / `todoread` on OpenCode 2.0.
11
+ * Default is off: the host has no TUI/desktop checklist, so advertising
12
+ * these in-memory tools is opt-in. OpenCode 1.x is unaffected (host builtin).
13
+ * Same truthy rule as `CURSOR_PROVIDER_DEBUG` (`1` or `true`).
14
+ */
15
+ export const CURSOR_OPENCODE2_TODOS_ENV = "CURSOR_OPENCODE2_TODOS";
16
+ export function isOpenCode2TodosEnabled(env = process.env) {
17
+ const value = env[CURSOR_OPENCODE2_TODOS_ENV]?.toLowerCase();
18
+ return value === "1" || value === "true";
19
+ }
20
+ /**
21
+ * 1.x `todowrite` description, shortened. OpenCode 2 has no builtin todo
22
+ * tools; this is the catalog text the model sees when the 2.0 gate is on.
23
+ */
24
+ export const TODOWRITE_DESCRIPTION = `Create and maintain a structured task list for the current coding session. Tracks progress, organizes multi-step work, and surfaces status to the user.
25
+
26
+ Use proactively when the work is 3+ distinct steps, non-trivial, or the user lists multiple tasks. Skip single straightforward edits and purely informational questions.
27
+
28
+ States: pending, in_progress (exactly one at a time), completed, cancelled.
29
+ Update status as you go. Mark completed only after the work — including verification — is actually done.`;
30
+ export const TODOREAD_DESCRIPTION = "Read the current session todo list. Takes no arguments. Returns the full list as JSON.";
31
+ const TODO_ITEM_SCHEMA = {
32
+ type: "object",
33
+ additionalProperties: false,
34
+ properties: {
35
+ id: { type: "string", description: "Optional stable item id" },
36
+ content: { type: "string", description: "Brief description of the task" },
37
+ status: {
38
+ type: "string",
39
+ description: "pending, in_progress, completed, or cancelled",
40
+ enum: ["pending", "in_progress", "completed", "cancelled"],
41
+ },
42
+ priority: {
43
+ type: "string",
44
+ description: "high, medium, or low",
45
+ enum: ["high", "medium", "low"],
46
+ },
47
+ },
48
+ required: ["content"],
49
+ };
50
+ const TODO_OUTPUT_ITEM_SCHEMA = {
51
+ type: "object",
52
+ additionalProperties: false,
53
+ properties: {
54
+ id: { type: "string" },
55
+ content: { type: "string" },
56
+ status: {
57
+ type: "string",
58
+ enum: ["pending", "in_progress", "completed", "cancelled"],
59
+ },
60
+ priority: {
61
+ type: "string",
62
+ enum: ["high", "medium", "low"],
63
+ },
64
+ },
65
+ required: ["id", "content", "status", "priority"],
66
+ };
67
+ export const TODO_OUTPUT_SCHEMA = {
68
+ type: "object",
69
+ additionalProperties: false,
70
+ properties: {
71
+ todos: { type: "array", items: TODO_OUTPUT_ITEM_SCHEMA },
72
+ },
73
+ required: ["todos"],
74
+ };
75
+ function todoResult(todos) {
76
+ const content = JSON.stringify(todos, null, 2);
77
+ return {
78
+ output: { todos },
79
+ content,
80
+ metadata: { todos },
81
+ };
82
+ }
83
+ function sessionIDFromContext(context) {
84
+ if (!context || typeof context !== "object") {
85
+ throw new Error("OpenCode 2.0 todo tool context did not provide a sessionID");
86
+ }
87
+ const id = context.sessionID;
88
+ if (typeof id !== "string" || !id) {
89
+ throw new Error("OpenCode 2.0 todo tool context did not provide a sessionID");
90
+ }
91
+ return id;
92
+ }
93
+ /** True when the host editor already owns this tool id. */
94
+ export function hostHasTool(draft, name) {
95
+ if (typeof draft.get === "function") {
96
+ try {
97
+ if (draft.get(name))
98
+ return true;
99
+ }
100
+ catch {
101
+ // Host get() may throw for unknown ids; fall through to list().
102
+ }
103
+ }
104
+ if (typeof draft.list === "function") {
105
+ try {
106
+ return draft.list().some((tool) => tool.id === name || tool.name === name);
107
+ }
108
+ catch {
109
+ return false;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+ /**
115
+ * Register canonical `todowrite` / `todoread` when the 2.0 force-enable gate
116
+ * is on and the host catalog does not already advertise them. No-op when the
117
+ * gate is off (default), on OpenCode 1.x (this function is not called), and
118
+ * on any 2.x host that restores the tools.
119
+ */
120
+ export function registerTodoTools(draft) {
121
+ if (!isOpenCode2TodosEnabled())
122
+ return;
123
+ if (!hostHasTool(draft, TODOWRITE_TOOL)) {
124
+ draft.add({
125
+ name: TODOWRITE_TOOL,
126
+ description: TODOWRITE_DESCRIPTION,
127
+ input: {
128
+ type: "object",
129
+ additionalProperties: false,
130
+ properties: {
131
+ todos: {
132
+ type: "array",
133
+ description: "The updated todo list (replace-all snapshot)",
134
+ items: TODO_ITEM_SCHEMA,
135
+ },
136
+ },
137
+ required: ["todos"],
138
+ },
139
+ output: TODO_OUTPUT_SCHEMA,
140
+ options: OPENCODE2_DIRECT_TOOL_OPTIONS,
141
+ execute: async (input, context) => {
142
+ const sessionID = sessionIDFromContext(context);
143
+ const todos = setSessionTodos(sessionID, input?.todos);
144
+ return todoResult(todos);
145
+ },
146
+ });
147
+ }
148
+ if (!hostHasTool(draft, TODOREAD_TOOL)) {
149
+ draft.add({
150
+ name: TODOREAD_TOOL,
151
+ description: TODOREAD_DESCRIPTION,
152
+ input: {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ properties: {},
156
+ },
157
+ output: TODO_OUTPUT_SCHEMA,
158
+ options: OPENCODE2_DIRECT_TOOL_OPTIONS,
159
+ execute: async (_input, context) => {
160
+ const sessionID = sessionIDFromContext(context);
161
+ return todoResult(getSessionTodos(sessionID));
162
+ },
163
+ });
164
+ }
165
+ }