cursor-opencode-provider 0.6.7 → 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 (42) hide show
  1. package/README.md +52 -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 +10 -1
  7. package/dist/language-model.js +253 -41
  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/messages.js +14 -1
  26. package/dist/protocol/switch-mode.d.ts +5 -4
  27. package/dist/protocol/switch-mode.js +12 -4
  28. package/dist/protocol/tool-call-bridge.d.ts +29 -1
  29. package/dist/protocol/tool-call-bridge.js +187 -7
  30. package/dist/protocol/tools.d.ts +30 -4
  31. package/dist/protocol/tools.js +185 -58
  32. package/dist/session.d.ts +13 -1
  33. package/dist/session.js +1 -0
  34. package/dist/shared.d.ts +3 -1
  35. package/dist/shared.js +3 -1
  36. package/dist/shell-timeout.d.ts +13 -0
  37. package/dist/shell-timeout.js +38 -0
  38. package/dist/todo-store.d.ts +29 -0
  39. package/dist/todo-store.js +78 -0
  40. package/dist/web-tools.d.ts +13 -0
  41. package/dist/web-tools.js +83 -5
  42. package/package.json +6 -3
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Per-session todo list used when the host does not ship `todowrite` /
3
+ * `todoread` (OpenCode 2 dropped those builtins; OpenCode 1.x still has
4
+ * `todowrite`).
5
+ *
6
+ * In-memory only — same lifetime as the provider process. A restart clears
7
+ * the list until the next write/read, matching the mirrored Cursor-todo
8
+ * snapshot in `language-model.ts`.
9
+ */
10
+ export declare const TODOWRITE_TOOL = "todowrite";
11
+ export declare const TODOREAD_TOOL = "todoread";
12
+ export declare const TODO_STATUSES: readonly ["pending", "in_progress", "completed", "cancelled"];
13
+ export type TodoStatus = (typeof TODO_STATUSES)[number];
14
+ export declare const TODO_PRIORITIES: readonly ["high", "medium", "low"];
15
+ export type TodoPriority = (typeof TODO_PRIORITIES)[number];
16
+ export type SessionTodo = {
17
+ id: string;
18
+ content: string;
19
+ status: TodoStatus;
20
+ priority: TodoPriority;
21
+ };
22
+ /** Normalize one host/Cursor todo item. Drops entries with empty content. */
23
+ export declare function normalizeSessionTodo(value: unknown, index: number): SessionTodo | undefined;
24
+ export declare function normalizeSessionTodos(value: unknown): SessionTodo[];
25
+ export declare function getSessionTodos(sessionID: string): SessionTodo[];
26
+ export declare function setSessionTodos(sessionID: string, todos: unknown): SessionTodo[];
27
+ export declare function clearSessionTodos(sessionID: string): void;
28
+ /** Test-only: drop every session list. */
29
+ export declare function clearAllSessionTodos(): void;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Per-session todo list used when the host does not ship `todowrite` /
3
+ * `todoread` (OpenCode 2 dropped those builtins; OpenCode 1.x still has
4
+ * `todowrite`).
5
+ *
6
+ * In-memory only — same lifetime as the provider process. A restart clears
7
+ * the list until the next write/read, matching the mirrored Cursor-todo
8
+ * snapshot in `language-model.ts`.
9
+ */
10
+ export const TODOWRITE_TOOL = "todowrite";
11
+ export const TODOREAD_TOOL = "todoread";
12
+ export const TODO_STATUSES = ["pending", "in_progress", "completed", "cancelled"];
13
+ export const TODO_PRIORITIES = ["high", "medium", "low"];
14
+ const lists = new Map();
15
+ const MAX_TRACKED_SESSIONS = 256;
16
+ function isStatus(value) {
17
+ return typeof value === "string" && TODO_STATUSES.includes(value);
18
+ }
19
+ function isPriority(value) {
20
+ return typeof value === "string" && TODO_PRIORITIES.includes(value);
21
+ }
22
+ /** Normalize one host/Cursor todo item. Drops entries with empty content. */
23
+ export function normalizeSessionTodo(value, index) {
24
+ if (!value || typeof value !== "object" || Array.isArray(value))
25
+ return undefined;
26
+ const record = value;
27
+ const content = typeof record.content === "string" ? record.content : "";
28
+ if (!content.trim())
29
+ return undefined;
30
+ const id = typeof record.id === "string" && record.id.trim()
31
+ ? record.id.trim()
32
+ : String(index + 1);
33
+ return {
34
+ id,
35
+ content,
36
+ status: isStatus(record.status) ? record.status : "pending",
37
+ priority: isPriority(record.priority) ? record.priority : "medium",
38
+ };
39
+ }
40
+ export function normalizeSessionTodos(value) {
41
+ if (!Array.isArray(value))
42
+ return [];
43
+ const out = [];
44
+ const seen = new Set();
45
+ for (const item of value) {
46
+ const todo = normalizeSessionTodo(item, out.length);
47
+ if (!todo)
48
+ continue;
49
+ let id = todo.id;
50
+ if (seen.has(id))
51
+ id = `${id}-${out.length + 1}`;
52
+ seen.add(id);
53
+ out.push(id === todo.id ? todo : { ...todo, id });
54
+ }
55
+ return out;
56
+ }
57
+ export function getSessionTodos(sessionID) {
58
+ return lists.get(sessionID)?.map((todo) => ({ ...todo })) ?? [];
59
+ }
60
+ export function setSessionTodos(sessionID, todos) {
61
+ const normalized = normalizeSessionTodos(todos);
62
+ lists.delete(sessionID);
63
+ lists.set(sessionID, normalized.map((todo) => ({ ...todo })));
64
+ while (lists.size > MAX_TRACKED_SESSIONS) {
65
+ const oldest = lists.keys().next().value;
66
+ if (oldest === undefined)
67
+ break;
68
+ lists.delete(oldest);
69
+ }
70
+ return getSessionTodos(sessionID);
71
+ }
72
+ export function clearSessionTodos(sessionID) {
73
+ lists.delete(sessionID);
74
+ }
75
+ /** Test-only: drop every session list. */
76
+ export function clearAllSessionTodos() {
77
+ lists.clear();
78
+ }
@@ -14,5 +14,18 @@ export declare function parseOpenCodeWebSearchResponse(raw: string): string | un
14
14
  * OpenCode 2.0 plugin's tool registration share one implementation — 2.0's
15
15
  * ToolContext has no `ask`, permissions being handled by the host instead.
16
16
  */
17
+ export type OpenCode2WebSearchResult = {
18
+ url: string;
19
+ title?: string;
20
+ content?: string;
21
+ time: {
22
+ published?: number;
23
+ };
24
+ };
25
+ /**
26
+ * Turn an Exa MCP text blob into OpenCode 2.0 `websearch` results
27
+ * (`{ url, title?, content?, time }`). Unknown shapes yield an empty list.
28
+ */
29
+ export declare function parseExaWebSearchResults(raw: string): OpenCode2WebSearchResult[];
17
30
  export declare function fetchOpenCodeWebSearchText(args: OpenCodeWebSearchArgs, signal: AbortSignal | undefined, fetchImpl?: typeof fetch): Promise<string>;
18
31
  export declare function executeOpenCodeWebSearch(args: OpenCodeWebSearchArgs, context: ToolContext, fetchImpl?: typeof fetch): Promise<ToolResult>;
package/dist/web-tools.js CHANGED
@@ -53,13 +53,91 @@ export function parseOpenCodeWebSearchResponse(raw) {
53
53
  }
54
54
  return undefined;
55
55
  }
56
+ function tryParseJson(raw) {
57
+ try {
58
+ return JSON.parse(raw);
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ function publishedMs(value) {
65
+ if (typeof value === "number" && Number.isFinite(value)) {
66
+ return value < 1e12 ? Math.floor(value * 1000) : Math.floor(value);
67
+ }
68
+ if (typeof value === "string" && value.trim()) {
69
+ const parsed = Date.parse(value);
70
+ return Number.isFinite(parsed) ? parsed : undefined;
71
+ }
72
+ return undefined;
73
+ }
74
+ function asWebSearchResult(row) {
75
+ if (!row || typeof row !== "object")
76
+ return undefined;
77
+ const record = row;
78
+ const url = typeof record.url === "string" ? record.url
79
+ : typeof record.href === "string" ? record.href
80
+ : undefined;
81
+ if (!url)
82
+ return undefined;
83
+ const title = typeof record.title === "string" ? record.title : undefined;
84
+ const content = typeof record.content === "string" ? record.content
85
+ : typeof record.text === "string" ? record.text
86
+ : typeof record.snippet === "string" ? record.snippet
87
+ : undefined;
88
+ const published = publishedMs(record.publishedDate ?? record.published ?? record.date);
89
+ return {
90
+ url,
91
+ ...(title ? { title } : {}),
92
+ ...(content ? { content } : {}),
93
+ time: published !== undefined ? { published } : {},
94
+ };
95
+ }
96
+ function parseExaTextResults(text) {
97
+ return text.split(/\n\n---\n\n/).flatMap((block) => {
98
+ const url = block.match(/^URL:\s*(.+)$/m)?.[1]?.trim();
99
+ if (!url)
100
+ return [];
101
+ const title = block.match(/^Title:\s*(.+)$/m)?.[1]?.trim();
102
+ const publishedText = block.match(/^Published:\s*(.+)$/m)?.[1]?.trim();
103
+ const published = publishedText && publishedText !== "N/A"
104
+ ? publishedMs(publishedText)
105
+ : undefined;
106
+ const content = block.match(/^(?:Highlights|Text):\s*\n?([\s\S]*)$/m)?.[1]?.trim();
107
+ return [{
108
+ url,
109
+ ...(title && title !== "N/A" ? { title } : {}),
110
+ ...(content ? { content } : {}),
111
+ time: published === undefined ? {} : { published },
112
+ }];
113
+ });
114
+ }
56
115
  /**
57
- * Raw Exa web-search call: no host tool context, no permission prompt.
58
- *
59
- * Split out so both the classic plugin's `custom_websearch` tool and the
60
- * OpenCode 2.0 plugin's tool registration share one implementation — 2.0's
61
- * ToolContext has no `ask`, permissions being handled by the host instead.
116
+ * Turn an Exa MCP text blob into OpenCode 2.0 `websearch` results
117
+ * (`{ url, title?, content?, time }`). Unknown shapes yield an empty list.
62
118
  */
119
+ export function parseExaWebSearchResults(raw) {
120
+ const text = parseOpenCodeWebSearchResponse(raw) ?? raw.trim();
121
+ if (!text)
122
+ return [];
123
+ const parsed = tryParseJson(text);
124
+ const rows = Array.isArray(parsed)
125
+ ? parsed
126
+ : parsed && typeof parsed === "object"
127
+ ? Array.isArray(parsed.results)
128
+ ? parsed.results
129
+ : Array.isArray(parsed.data)
130
+ ? parsed.data
131
+ : []
132
+ : [];
133
+ if (rows.length > 0) {
134
+ return rows.flatMap((row) => {
135
+ const result = asWebSearchResult(row);
136
+ return result ? [result] : [];
137
+ });
138
+ }
139
+ return parseExaTextResults(text);
140
+ }
63
141
  export async function fetchOpenCodeWebSearchText(args, signal, fetchImpl = fetch) {
64
142
  const controller = new AbortController();
65
143
  const abort = () => controller.abort(signal?.reason);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-opencode-provider",
3
- "version": "0.6.7",
3
+ "version": "0.7.0",
4
4
  "description": "Use Cursor subscription models from OpenCode via Cursor's Connect-RPC agent protocol",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,10 @@
40
40
  "types": "./dist/plugin-opencode2.d.ts",
41
41
  "import": "./dist/plugin-opencode2.js"
42
42
  },
43
+ "./server": {
44
+ "types": "./dist/plugin-opencode2.d.ts",
45
+ "import": "./dist/plugin-opencode2.js"
46
+ },
43
47
  "./errors": {
44
48
  "types": "./dist/errors.d.ts",
45
49
  "import": "./dist/errors.js"
@@ -70,7 +74,7 @@
70
74
  ],
71
75
  "scripts": {
72
76
  "build": "tsc",
73
- "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
77
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
74
78
  "test": "bun test",
75
79
  "test:node-http2": "bun run build && node test/node-http2-cleanup.mjs",
76
80
  "test:watch": "bun test --watch",
@@ -93,7 +97,6 @@
93
97
  },
94
98
  "devDependencies": {
95
99
  "@opencode-ai/plugin": "^1.18.16",
96
- "@opencode-ai/plugin-next": "npm:@opencode-ai/plugin@0.0.0-next-17155",
97
100
  "@tsconfig/node22": "^22.0.5",
98
101
  "@types/bun": "^1.3.0",
99
102
  "@types/node": "^22.20.1",