cursor-opencode-provider 0.5.0 → 0.6.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 (48) hide show
  1. package/README.md +18 -13
  2. package/dist/context/agents.d.ts +1 -1
  3. package/dist/context/agents.js +1 -1
  4. package/dist/context/auth-store.js +2 -2
  5. package/dist/context/build.d.ts +3 -1
  6. package/dist/context/build.js +4 -2
  7. package/dist/context/index.d.ts +1 -1
  8. package/dist/context/index.js +1 -1
  9. package/dist/context/paths.d.ts +34 -53
  10. package/dist/context/paths.js +54 -118
  11. package/dist/context/rules.js +1 -1
  12. package/dist/image-save-tool.d.ts +19 -0
  13. package/dist/image-save-tool.js +25 -0
  14. package/dist/image-save.d.ts +58 -0
  15. package/dist/image-save.js +189 -0
  16. package/dist/image-staging.d.ts +59 -0
  17. package/dist/image-staging.js +91 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/language-model.d.ts +3 -2
  20. package/dist/language-model.js +572 -34
  21. package/dist/plan-execution-kickoff.d.ts +55 -0
  22. package/dist/plan-execution-kickoff.js +162 -0
  23. package/dist/plugin-opencode2.js +52 -3
  24. package/dist/plugin-v2.d.ts +11 -9
  25. package/dist/plugin-v2.js +6 -13
  26. package/dist/plugin.d.ts +9 -0
  27. package/dist/plugin.js +61 -5
  28. package/dist/pricing-data.d.ts +16 -4
  29. package/dist/pricing-data.js +16 -4
  30. package/dist/protocol/ask-question.d.ts +133 -0
  31. package/dist/protocol/ask-question.js +320 -0
  32. package/dist/protocol/create-plan.d.ts +141 -0
  33. package/dist/protocol/create-plan.js +373 -0
  34. package/dist/protocol/generate-image.d.ts +54 -0
  35. package/dist/protocol/generate-image.js +101 -0
  36. package/dist/protocol/interactions.d.ts +110 -8
  37. package/dist/protocol/interactions.js +295 -35
  38. package/dist/protocol/messages.js +118 -6
  39. package/dist/protocol/switch-mode.d.ts +178 -0
  40. package/dist/protocol/switch-mode.js +417 -0
  41. package/dist/protocol/tool-call-bridge.js +10 -5
  42. package/dist/protocol/tools.d.ts +27 -11
  43. package/dist/protocol/tools.js +95 -41
  44. package/dist/usage.d.ts +2 -2
  45. package/dist/usage.js +2 -2
  46. package/dist/web-search-tool.d.ts +22 -26
  47. package/dist/web-search-tool.js +25 -15
  48. package/package.json +7 -4
@@ -71,9 +71,9 @@ export type HostSubagentDefinition = {
71
71
  description?: string;
72
72
  };
73
73
  export type HostSubagentCatalog = {
74
- executor?: "task" | "actor";
74
+ executor?: "task";
75
75
  agents: HostSubagentDefinition[];
76
- /** True when the host tool supplied its complete, permission-filtered catalog. */
76
+ /** True when the OpenCode task tool supplied its complete, permission-filtered catalog. */
77
77
  complete: boolean;
78
78
  };
79
79
  /** Extract the current host's permission-filtered Task/Actor recipient catalog. */
@@ -151,7 +151,28 @@ export declare function parseExecServerMessage(msg: Record<string, unknown>): Pa
151
151
  export declare function mapCursorArgsToOpencode(toolName: string, raw: Record<string, unknown>, execVariant?: string): {
152
152
  toolName: string;
153
153
  args: Record<string, unknown>;
154
+ binaryBytes?: Uint8Array;
154
155
  };
156
+ /**
157
+ * Decode `WriteArgs.file_bytes`. `encoding_hint` names the file's original
158
+ * encoding; OpenCode's `write` takes a string, so an unknown encoding label
159
+ * falls back to UTF-8 rather than dropping the write.
160
+ *
161
+ * Returns undefined when the bytes are not text in that encoding. A non-fatal
162
+ * decode would replace every undecodable sequence with U+FFFD and hand the host
163
+ * a corrupted file that still reports success, so binary content must take the
164
+ * byte-preserving path instead (see `isBinaryWriteRequest`).
165
+ */
166
+ export declare function decodeWriteBytes(bytes: Uint8Array, encodingHint?: string): string | undefined;
167
+ /**
168
+ * True when a parsed `write` request carries bytes that are not text. Such a
169
+ * request cannot go to OpenCode's `write` tool at all — it takes a string, and
170
+ * additionally BOM-splits, diffs, and runs `Format.file()` on what it writes.
171
+ */
172
+ export declare function binaryWritePayload(parsed: ParsedExecRequest): {
173
+ path: string;
174
+ data: Uint8Array;
175
+ } | undefined;
155
176
  /**
156
177
  * Map Cursor McpArgs back to the OpenCode tool id.
157
178
  * Prefers provider_identifier + bare tool_name (github + create_pull_request
@@ -162,15 +183,10 @@ export declare function mcpRealToolName(mcpArgs: Record<string, unknown>, defaul
162
183
  * A read target addressed by URI scheme rather than by local filesystem path.
163
184
  *
164
185
  * Cursor's LocalReadExecutor only ever reads local files, so this provider
165
- * mirrors its pre-execution validation. Host read tools are not so limited:
166
- * oh-my-pi's `read` accepts "a path, URL, or internal URI", and mounts every
167
- * discoverable tool — including all MCP server tools — under `xd://<tool>`
168
- * device URLs that are read for their schema and written to for execution
169
- * (`tools.xdev`, on by default). Resolving those against the workspace root
170
- * mangles `xd://everything_echo` into `<root>/xd:/everything_echo`, which stats
171
- * as missing and gets refused as `file_not_found` — so the host's read tool is
172
- * never called and every xd://-mounted MCP tool is unreachable through this
173
- * provider. Such targets must be forwarded untouched and left to the host.
186
+ * mirrors its pre-execution validation. An advertised host read tool may also
187
+ * accept URLs or internal URI schemes. Resolving those against the workspace
188
+ * root mangles the URI and produces a false `file_not_found`, so URI targets
189
+ * are forwarded untouched and left to the advertised executor.
174
190
  *
175
191
  * Requires a scheme of two or more characters followed by `://`, so Windows
176
192
  * drive letters (`C:\src`, `C:/src`) stay local paths.
@@ -299,6 +299,10 @@ const PRESERVE_EMPTY_STRING_KEYS = new Set([
299
299
  "file_text",
300
300
  "fileText",
301
301
  "stream_content",
302
+ // Some advertised edit schemas use a single textual `input` payload. Preserve
303
+ // an empty value so the executor reports the real edit error rather than a
304
+ // misleading missing-property error.
305
+ "input",
302
306
  "oldString",
303
307
  "old_string",
304
308
  "newString",
@@ -396,18 +400,16 @@ function subagentTypeEnumValues(schema) {
396
400
  }
397
401
  /** Extract the current host's permission-filtered Task/Actor recipient catalog. */
398
402
  export function extractHostSubagentCatalog(tools) {
399
- const executorTool = tools.find((tool) => tool.name === "actor") ??
400
- tools.find((tool) => tool.name === "task");
403
+ const executorTool = tools.find((tool) => tool.name === "task");
401
404
  if (!executorTool)
402
405
  return { agents: [], complete: true };
403
406
  const described = parseSubagentDescriptionCatalog(executorTool.description);
404
407
  const enumNames = subagentTypeEnumValues(executorTool.inputSchema);
405
408
  const descriptions = new Map(described.agents.map((agent) => [agent.name, agent.description]));
406
- // MiMo's Actor enum is stricter than its prose catalog (`mode: subagent`
407
- // rather than every non-primary agent), so structured names are authoritative.
409
+ // A structured subagent_type enum is stricter than prose, so it is authoritative.
408
410
  const names = new Set(enumNames.length > 0 ? enumNames : described.agents.map((agent) => agent.name));
409
411
  return {
410
- executor: executorTool.name,
412
+ executor: "task",
411
413
  agents: [...names].map((name) => ({
412
414
  name,
413
415
  ...(descriptions.get(name) ? { description: descriptions.get(name) } : {}),
@@ -467,9 +469,9 @@ export function remapNativeSubagentForCatalog(parsed, advertisedToolNames, catal
467
469
  if (parsed.resultField !== "subagent_result" || parsed.toolName !== "task")
468
470
  return;
469
471
  const advertised = new Set(advertisedToolNames);
470
- const executor = advertised.has("actor") ? "actor" : advertised.has("task") ? "task" : undefined;
471
- if (!executor)
472
+ if (!advertised.has("task"))
472
473
  return;
474
+ const executor = "task";
473
475
  const description = str(parsed.args.description) ?? "";
474
476
  const prompt = str(parsed.args.prompt) ?? "";
475
477
  const cursorSubagentType = str(parsed.resultMetadata?.cursor_subagent_type) ??
@@ -484,24 +486,12 @@ export function remapNativeSubagentForCatalog(parsed, advertisedToolNames, catal
484
486
  }
485
487
  const resumeAgentId = str(parsed.args.task_id);
486
488
  parsed.toolName = executor;
487
- if (executor === "task") {
488
- parsed.args = {
489
- description,
490
- prompt,
491
- subagent_type: subagentType,
492
- ...(resumeAgentId ? { task_id: resumeAgentId } : {}),
493
- ...(parsed.args.background === true ? { background: true } : {}),
494
- };
495
- return;
496
- }
497
489
  parsed.args = {
498
- operation: {
499
- action: parsed.args.background === true ? "spawn" : "run",
500
- description,
501
- prompt,
502
- subagent_type: subagentType,
503
- ...(resumeAgentId ? { actor_id: resumeAgentId } : {}),
504
- },
490
+ description,
491
+ prompt,
492
+ subagent_type: subagentType,
493
+ ...(resumeAgentId ? { task_id: resumeAgentId } : {}),
494
+ ...(parsed.args.background === true ? { background: true } : {}),
505
495
  };
506
496
  }
507
497
  /** Matches Cursor's own pre-write read threshold (`local-exec` 52428800). */
@@ -682,6 +672,12 @@ export function remapCorrelatedEditWriteForCatalog(parsed, advertisedToolNames,
682
672
  export function remapEditToolsForCatalog(parsed, advertisedToolNames, workspaceRoot) {
683
673
  if (parsed.toolName !== "write" && parsed.toolName !== "edit")
684
674
  return;
675
+ // `apply_patch` envelopes are text. A binary write has no patch form at all,
676
+ // so it must stay a write and reach the byte-preserving path — converting it
677
+ // here would drop the bytes and report a patch-translation failure instead of
678
+ // the image Cursor asked to save.
679
+ if (binaryWritePayload(parsed))
680
+ return;
685
681
  const advertised = new Set(advertisedToolNames);
686
682
  if (advertised.has(parsed.toolName) || !advertised.has(APPLY_PATCH_TOOL))
687
683
  return;
@@ -940,7 +936,19 @@ export function parseExecServerMessage(msg) {
940
936
  toolName: mapped.toolName,
941
937
  args: mapped.args,
942
938
  resultField,
943
- ...(resultMetadata ? { resultMetadata } : {}),
939
+ ...(resultMetadata || mapped.binaryBytes
940
+ ? {
941
+ resultMetadata: {
942
+ ...resultMetadata,
943
+ ...(mapped.binaryBytes
944
+ ? {
945
+ binaryWriteBytes: mapped.binaryBytes,
946
+ path: str(rawArgs.path) ?? str(rawArgs.file_path) ?? "",
947
+ }
948
+ : {}),
949
+ },
950
+ }
951
+ : {}),
944
952
  };
945
953
  }
946
954
  function shellStreamResultMetadata(raw) {
@@ -1020,14 +1028,31 @@ export function mapCursorArgsToOpencode(toolName, raw, execVariant) {
1020
1028
  // non-empty and only falls back to `file_text`; mirror that order so a
1021
1029
  // byte-encoded write is not silently seen as empty content.
1022
1030
  const bytes = bytesValue(cleaned.file_bytes) ?? bytesValue(cleaned.fileBytes);
1023
- const content = bytes !== undefined
1024
- ? decodeWriteBytes(bytes, str(cleaned.encoding_hint) ?? str(cleaned.encodingHint))
1025
- : stringValue(cleaned.content) ?? stringValue(cleaned.file_text) ?? stringValue(cleaned.fileText);
1031
+ let content;
1032
+ if (bytes !== undefined) {
1033
+ content = decodeWriteBytes(bytes, str(cleaned.encoding_hint) ?? str(cleaned.encodingHint));
1034
+ if (content === undefined) {
1035
+ // Binary. Surface the raw bytes to the caller instead of a lossy
1036
+ // decode; `binaryWritePayload` picks them up after parsing.
1037
+ return { toolName: "write", args, binaryBytes: bytes };
1038
+ }
1039
+ }
1040
+ else {
1041
+ content = stringValue(cleaned.content)
1042
+ ?? stringValue(cleaned.file_text)
1043
+ ?? stringValue(cleaned.fileText);
1044
+ }
1026
1045
  if (content !== undefined)
1027
1046
  args.content = content;
1028
1047
  return { toolName: "write", args };
1029
1048
  }
1030
1049
  case "edit": {
1050
+ // Preserve an opaque textual edit payload when the advertised executor
1051
+ // accepts that generic shape; rebuilding only OpenCode's targeted fields
1052
+ // would silently turn a valid call into `{}`.
1053
+ if (typeof cleaned.input === "string") {
1054
+ return { toolName: "edit", args: { input: cleaned.input } };
1055
+ }
1031
1056
  const args = {};
1032
1057
  const filePath = str(cleaned.filePath) ?? str(cleaned.path) ?? str(cleaned.file_path);
1033
1058
  if (filePath)
@@ -1105,22 +1130,56 @@ function bytesValue(v) {
1105
1130
  }
1106
1131
  return undefined;
1107
1132
  }
1133
+ /** UTF-16/32 embed NUL bytes in ordinary text; 8-bit encodings do not. */
1134
+ const WIDE_TEXT_ENCODING = /^utf-?(16|32)/;
1108
1135
  /**
1109
1136
  * Decode `WriteArgs.file_bytes`. `encoding_hint` names the file's original
1110
- * encoding; OpenCode's `write` takes a string, so anything Node cannot decode
1137
+ * encoding; OpenCode's `write` takes a string, so an unknown encoding label
1111
1138
  * falls back to UTF-8 rather than dropping the write.
1139
+ *
1140
+ * Returns undefined when the bytes are not text in that encoding. A non-fatal
1141
+ * decode would replace every undecodable sequence with U+FFFD and hand the host
1142
+ * a corrupted file that still reports success, so binary content must take the
1143
+ * byte-preserving path instead (see `isBinaryWriteRequest`).
1112
1144
  */
1113
- function decodeWriteBytes(bytes, encodingHint) {
1145
+ export function decodeWriteBytes(bytes, encodingHint) {
1114
1146
  const encoding = encodingHint?.trim().toLowerCase().replace(/[_ ]/g, "-");
1147
+ let decoder;
1148
+ let label = "utf-8";
1115
1149
  if (encoding && encoding !== "utf-8" && encoding !== "utf8") {
1116
1150
  try {
1117
- return new TextDecoder(encoding, { fatal: false }).decode(bytes);
1151
+ decoder = new TextDecoder(encoding, { fatal: true });
1152
+ label = encoding;
1118
1153
  }
1119
1154
  catch {
1120
1155
  trace(`write: unsupported encoding_hint ${JSON.stringify(encodingHint)} — decoding as utf-8`);
1121
1156
  }
1122
1157
  }
1123
- return new TextDecoder("utf-8").decode(bytes);
1158
+ if (!decoder)
1159
+ decoder = new TextDecoder("utf-8", { fatal: true });
1160
+ // Single-byte encodings decode any byte sequence without error, so a decode
1161
+ // that cannot fail is not on its own evidence of text.
1162
+ if (!WIDE_TEXT_ENCODING.test(label) && bytes.includes(0))
1163
+ return undefined;
1164
+ try {
1165
+ return decoder.decode(bytes);
1166
+ }
1167
+ catch {
1168
+ return undefined;
1169
+ }
1170
+ }
1171
+ /**
1172
+ * True when a parsed `write` request carries bytes that are not text. Such a
1173
+ * request cannot go to OpenCode's `write` tool at all — it takes a string, and
1174
+ * additionally BOM-splits, diffs, and runs `Format.file()` on what it writes.
1175
+ */
1176
+ export function binaryWritePayload(parsed) {
1177
+ if (parsed.toolName !== "write")
1178
+ return undefined;
1179
+ const data = parsed.resultMetadata?.binaryWriteBytes;
1180
+ if (!(data instanceof Uint8Array))
1181
+ return undefined;
1182
+ return { path: str(parsed.resultMetadata?.path) ?? "", data };
1124
1183
  }
1125
1184
  function num(v) {
1126
1185
  if (typeof v === "number" && Number.isFinite(v))
@@ -1203,15 +1262,10 @@ function untildify(input) {
1203
1262
  * A read target addressed by URI scheme rather than by local filesystem path.
1204
1263
  *
1205
1264
  * Cursor's LocalReadExecutor only ever reads local files, so this provider
1206
- * mirrors its pre-execution validation. Host read tools are not so limited:
1207
- * oh-my-pi's `read` accepts "a path, URL, or internal URI", and mounts every
1208
- * discoverable tool — including all MCP server tools — under `xd://<tool>`
1209
- * device URLs that are read for their schema and written to for execution
1210
- * (`tools.xdev`, on by default). Resolving those against the workspace root
1211
- * mangles `xd://everything_echo` into `<root>/xd:/everything_echo`, which stats
1212
- * as missing and gets refused as `file_not_found` — so the host's read tool is
1213
- * never called and every xd://-mounted MCP tool is unreachable through this
1214
- * provider. Such targets must be forwarded untouched and left to the host.
1265
+ * mirrors its pre-execution validation. An advertised host read tool may also
1266
+ * accept URLs or internal URI schemes. Resolving those against the workspace
1267
+ * root mangles the URI and produces a false `file_not_found`, so URI targets
1268
+ * are forwarded untouched and left to the advertised executor.
1215
1269
  *
1216
1270
  * Requires a scheme of two or more characters followed by `://`, so Windows
1217
1271
  * drive letters (`C:\src`, `C:/src`) stay local paths.
package/dist/usage.d.ts CHANGED
@@ -49,8 +49,8 @@ export declare function formatCursorCacheDiagnostics(counters: CursorUsageCounte
49
49
  export declare function formatTurnUsageValidation(counters: CursorUsageCounters, usage: LanguageModelV3Usage, tokenDetails?: CursorConversationTokenDetails, contextSource?: CursorContextUsageSource): string;
50
50
  /** OpenCode requires a usage object at every step boundary. */
51
51
  export declare function emptyLanguageModelV3Usage(): LanguageModelV3Usage;
52
- /** Mirror Kilo `Session.getUsage` / AI SDK flat fields from V3 nested usage. */
53
- export declare function kiloShapedUsageFromV3(usage: LanguageModelV3Usage): {
52
+ /** Project nested V3 usage into the common flat AI-SDK counter shape. */
53
+ export declare function flatUsageFromV3(usage: LanguageModelV3Usage): {
54
54
  inputTokens: number;
55
55
  outputTokens: number;
56
56
  reasoningTokens: number;
package/dist/usage.js CHANGED
@@ -217,8 +217,8 @@ export function emptyLanguageModelV3Usage() {
217
217
  reasoningTokens: 0,
218
218
  });
219
219
  }
220
- /** Mirror Kilo `Session.getUsage` / AI SDK flat fields from V3 nested usage. */
221
- export function kiloShapedUsageFromV3(usage) {
220
+ /** Project nested V3 usage into the common flat AI-SDK counter shape. */
221
+ export function flatUsageFromV3(usage) {
222
222
  const input = usage.inputTokens;
223
223
  const output = usage.outputTokens;
224
224
  const noCache = input.noCache ?? 0;
@@ -1,33 +1,29 @@
1
+ import { executeOpenCodeWebSearch } from "./web-tools.js";
2
+ type ToolFactory = {
3
+ tool: (input: Record<string, unknown>) => Record<string, unknown>;
4
+ schema: {
5
+ string: () => any;
6
+ number: () => any;
7
+ enum: (values: readonly string[]) => any;
8
+ };
9
+ };
10
+ /** Build the classic OpenCode web-search tool with the host's own Zod helper. */
11
+ export declare function createOpenCodeWebSearchTool(factory: ToolFactory): Record<string, unknown>;
1
12
  /**
2
- * Classic-plugin web search tool registration.
3
- *
4
- * Kept apart from `./web-tools.ts` on purpose: this is a *value* import from
5
- * `@opencode-ai/plugin`, and the OpenCode 2.0 entrypoint shares the search
6
- * implementation in that module. 2.0's package root exports no `tool`, so
7
- * importing it from a module on the 2.0 graph would throw during plugin load.
8
- * Only `plugin.ts` (classic hooks) may import this file.
13
+ * Host-neutral JSON-schema fallback when the classic helper is unavailable.
14
+ * OpenCode's legacy schema adapter marks every listed property required, so
15
+ * expose only the genuinely required query rather than turning four optional
16
+ * tuning fields into mandatory inputs.
9
17
  */
10
18
  export declare const openCodeWebSearchTool: {
11
19
  description: string;
12
20
  args: {
13
- query: import("zod").ZodString;
14
- numResults: import("zod").ZodOptional<import("zod").ZodNumber>;
15
- livecrawl: import("zod").ZodOptional<import("zod").ZodEnum<{
16
- fallback: "fallback";
17
- preferred: "preferred";
18
- }>>;
19
- type: import("zod").ZodOptional<import("zod").ZodEnum<{
20
- fast: "fast";
21
- auto: "auto";
22
- deep: "deep";
23
- }>>;
24
- contextMaxCharacters: import("zod").ZodOptional<import("zod").ZodNumber>;
21
+ query: {
22
+ type: string;
23
+ description: string;
24
+ };
25
25
  };
26
- execute(args: {
27
- query: string;
28
- numResults?: number | undefined;
29
- livecrawl?: "fallback" | "preferred" | undefined;
30
- type?: "fast" | "auto" | "deep" | undefined;
31
- contextMaxCharacters?: number | undefined;
32
- }, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
26
+ execute: typeof executeOpenCodeWebSearch;
33
27
  };
28
+ export type { ToolFactory as OpenCodeToolFactory };
29
+ export declare function createOpenCodeWebSearchToolFromPlugin(pluginModule: any): Record<string, unknown>;
@@ -1,22 +1,32 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  import { executeOpenCodeWebSearch } from "./web-tools.js";
2
+ /** Build the classic OpenCode web-search tool with the host's own Zod helper. */
3
+ export function createOpenCodeWebSearchTool(factory) {
4
+ const schema = factory.schema;
5
+ return factory.tool({
6
+ description: "Search the web for current information using OpenCode's web search backend.",
7
+ args: {
8
+ query: schema.string().describe("Web search query"),
9
+ numResults: schema.number().int().min(1).max(20).optional(),
10
+ livecrawl: schema.enum(["fallback", "preferred"]).optional(),
11
+ type: schema.enum(["auto", "fast", "deep"]).optional(),
12
+ contextMaxCharacters: schema.number().int().positive().optional(),
13
+ },
14
+ execute: executeOpenCodeWebSearch,
15
+ });
16
+ }
3
17
  /**
4
- * Classic-plugin web search tool registration.
5
- *
6
- * Kept apart from `./web-tools.ts` on purpose: this is a *value* import from
7
- * `@opencode-ai/plugin`, and the OpenCode 2.0 entrypoint shares the search
8
- * implementation in that module. 2.0's package root exports no `tool`, so
9
- * importing it from a module on the 2.0 graph would throw during plugin load.
10
- * Only `plugin.ts` (classic hooks) may import this file.
18
+ * Host-neutral JSON-schema fallback when the classic helper is unavailable.
19
+ * OpenCode's legacy schema adapter marks every listed property required, so
20
+ * expose only the genuinely required query rather than turning four optional
21
+ * tuning fields into mandatory inputs.
11
22
  */
12
- export const openCodeWebSearchTool = tool({
23
+ export const openCodeWebSearchTool = {
13
24
  description: "Search the web for current information using OpenCode's web search backend.",
14
25
  args: {
15
- query: tool.schema.string().describe("Web search query"),
16
- numResults: tool.schema.number().int().min(1).max(20).optional(),
17
- livecrawl: tool.schema.enum(["fallback", "preferred"]).optional(),
18
- type: tool.schema.enum(["auto", "fast", "deep"]).optional(),
19
- contextMaxCharacters: tool.schema.number().int().positive().optional(),
26
+ query: { type: "string", description: "Web search query" },
20
27
  },
21
28
  execute: executeOpenCodeWebSearch,
22
- });
29
+ };
30
+ export function createOpenCodeWebSearchToolFromPlugin(pluginModule) {
31
+ return createOpenCodeWebSearchTool({ tool: pluginModule.tool, schema: pluginModule.tool.schema });
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-opencode-provider",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",
@@ -55,6 +55,10 @@
55
55
  "./auth": {
56
56
  "types": "./dist/auth.d.ts",
57
57
  "import": "./dist/auth.js"
58
+ },
59
+ "./image-save": {
60
+ "types": "./dist/image-save.d.ts",
61
+ "import": "./dist/image-save.js"
58
62
  }
59
63
  },
60
64
  "files": [
@@ -80,11 +84,10 @@
80
84
  "protobufjs": "^7.6.5"
81
85
  },
82
86
  "peerDependencies": {
83
- "@opencode-ai/plugin": "^1.17.13",
84
- "@opencode-compat/profile": ">=0.1.0"
87
+ "@opencode-ai/plugin": "^1.17.13"
85
88
  },
86
89
  "peerDependenciesMeta": {
87
- "@opencode-compat/profile": {
90
+ "@opencode-ai/plugin": {
88
91
  "optional": true
89
92
  }
90
93
  },