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
package/README.md CHANGED
@@ -126,7 +126,7 @@ For OpenCode builds that use the Effect/Promise **v2** plugin API (`plugins` fie
126
126
 
127
127
  Local clone equivalent: `"file:///absolute/path/to/cursor-opencode-provider/dist/plugin-v2.js"`.
128
128
 
129
- That entry registers the provider via `ctx.aisdk.sdk` / `ctx.aisdk.language`. Keep the classic `plugin` entry for auth.
129
+ That entry registers the provider via `ctx.aisdk.sdk` / `ctx.aisdk.language`. Keep the classic `plugin` entry for auth **and** plugin tools (notably `cursor_image_save` / `custom_websearch`): the 1.18 `/v2/promise` API has no `tool` domain, so a v2-only load silently disables Cursor image generation.
130
130
 
131
131
  ### OpenCode 2.0 beta (`opencode2`)
132
132
 
@@ -243,23 +243,21 @@ Choose the **cursor** provider, then one of:
243
243
  | **Cursor account (browser login)** | PKCE OAuth — opens cursor.com to sign in |
244
244
  | **API key** | Paste a key from [cursor.com/settings](https://cursor.com/settings) (`crsr_...`) |
245
245
 
246
- After login, the plugin fetches your available models and writes them to `<host-cache>/cursor-models.json` (default `~/.cache/opencode/`). On later startups, a missing, empty, expired, or old-schema cache is refreshed during config load when Cursor auth is available; an existing stale cache remains usable if refresh fails. Completed conversation state is stored separately under `<host-cache>/cursor-conversations/`, with one independently replaced gzip-compressed protobuf snapshot per stable OpenCode session. Snapshots are refreshed at each successful Cursor `TurnEnded` and restored after provider restarts; records not updated for more than 24 hours are removed when the provider starts. Cursor's checkpoint is an index of content-addressed KV blobs rather than a self-contained transcript, so the snapshot retains the blobs reachable from the latest checkpoint and discards superseded blobs. This private Cursor state is not a copy of OpenCode's session database.
246
+ After login, the plugin fetches your available models and writes them to `<host-cache>/cursor-models.json`. On later startups, a missing, empty, expired, or old-schema cache is refreshed during config load when Cursor auth is available; an existing stale cache remains usable if refresh fails. Completed conversation state is stored separately under `<host-cache>/cursor-conversations/`, with one independently replaced gzip-compressed protobuf snapshot per stable host session. Snapshots are refreshed at each successful Cursor `TurnEnded` and restored after provider restarts; records not updated for more than 24 hours are removed when the provider starts. Cursor's checkpoint is an index of content-addressed KV blobs rather than a self-contained transcript, so the snapshot retains the blobs reachable from the latest checkpoint and discards superseded blobs. This private Cursor state is not a copy of the host's session database.
247
247
 
248
248
  ### Paths (host cache)
249
249
 
250
250
  Model/version caches and Cursor project metadata live under a **host cache root**, resolved in this order:
251
251
 
252
- 1. Explicit `createCursor({ cacheDir })` / Effect v2 `Path.cache`
253
- 2. Optional `@opencode-compat/profile` `detect()` when it reports strong environment, binary, or package identity
254
- 3. Explicit `$MIMOCODE_HOME` / `KILO_CONFIG_DIR`, then the provider's host-named install path under `$XDG_CACHE_HOME`
255
- 4. Default `~/.cache/opencode/`
252
+ 1. Explicit `createCursor({ cacheDir })` / host API cache override
253
+ 2. A structural host path bridge installed before the provider loads
254
+ 3. Native OpenCode: `$XDG_CACHE_HOME/opencode`, otherwise `~/.cache/opencode`
256
255
 
257
- Config-directory presence is deliberately ignored: having MiMo or Kilo installed
258
- must not redirect a native OpenCode process into that host's cache.
256
+ The provider never detects or names fork hosts. Compatibility layers own host identity and may inject cache, data, and config roots through the structural bridge; without one, standalone OpenCode behavior is unchanged.
259
257
 
260
258
  | Kind | Default (OpenCode) | Notes |
261
259
  |------|--------------------|-------|
262
- | Model, version, and 24-hour conversation **cache** | `~/.cache/opencode/` | MiMo: `$MIMOCODE_HOME/cache` or `~/.cache/mimocode/`; Kilo: `~/.cache/kilo/` |
260
+ | Model, version, and 24-hour conversation **cache** | `~/.cache/opencode/` | A host bridge may replace the root |
263
261
  | Cursor **project metadata** (`agent-tools`, terminals, …) | `~/.cache/opencode/projects/<slug>/` | under `<host-cache>/projects/` |
264
262
  | OpenCode **auth** (`auth.json`) | `~/.local/share/opencode/` | `$XDG_DATA_HOME/opencode/` when set |
265
263
  | OpenCode **config** (AGENTS, skills, …) | `~/.config/opencode/` | still OpenCode-named for rule discovery |
@@ -331,7 +329,7 @@ const cursor = createCursor({
331
329
  // apiBaseURL: "https://api2.cursor.sh",
332
330
  // agentBaseURL: "https://agentn.us.api5.cursor.sh", // explicit Run host override
333
331
  // telemetryEnabled: true, // opt in to GetServerConfig telemetry
334
- // cacheDir: "/path/to/host/cache", // optional; else host heuristic / ~/.cache/opencode
332
+ // cacheDir: "/path/to/host/cache", // optional; else active-host heuristic
335
333
  // retry: { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 8_000 },
336
334
  // continuation: { heartbeatMs: 5_000, semanticIdleMs: 120_000, hardCapMs: 600_000 },
337
335
  })
@@ -340,7 +338,7 @@ const model = cursor.languageModel("composer-2.5")
340
338
  // model implements AI SDK LanguageModelV3 (doStream / doGenerate)
341
339
  ```
342
340
 
343
- Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `crsr_...` key). Optional: `apiBaseURL`, `agentBaseURL`, `cacheDir`, `headers`, `telemetryEnabled`, `retry`, and `continuation`. `cacheDir` pins the host cache root for model/version caches and Cursor project metadata; when omitted, the provider uses OCP `@opencode-compat/profile` detect (if installed) or the local MiMo/Kilo/OpenCode heuristic described in [Paths](#paths-host-cache). Transient failures resume from the latest checkpoint produced by that Run, matching Cursor CLI; without an eligible checkpoint, retries remain limited to replay-safe attempts so completed text or tool work is not duplicated. Pending-tool inactivity is renewed by OpenCode activity from the session or its descendants. The older `baseURL` option is still accepted as a legacy alias for `agentBaseURL`.
341
+ Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `crsr_...` key). Optional: `apiBaseURL`, `agentBaseURL`, `cacheDir`, `headers`, `telemetryEnabled`, `retry`, and `continuation`. `cacheDir` pins the host cache root for model/version caches and Cursor project metadata; when omitted, the provider uses an injected structural host path bridge or the native OpenCode XDG cache described in [Paths](#paths-host-cache). Transient failures resume from the latest checkpoint produced by that Run, matching Cursor CLI; without an eligible checkpoint, retries remain limited to replay-safe attempts so completed text or tool work is not duplicated. Pending-tool inactivity is renewed by OpenCode activity from the session or its descendants. The older `baseURL` option is still accepted as a legacy alias for `agentBaseURL`.
344
342
 
345
343
  ## Environment variables
346
344
 
@@ -352,7 +350,9 @@ Pass either `accessToken` (JWT from OAuth or key exchange) or `apiKey` (raw `crs
352
350
  | `CURSOR_PROVIDER_DEBUG` | Set to `1` or `true` to enable wire-level debug logging |
353
351
  | `CURSOR_PROVIDER_DEBUG_FILE` | Debug log path (default: `debug-<pid>.log` under `$TMPDIR/cursor-provider-logs-<uid>/`) |
354
352
  | `CURSOR_OPENCODE2_DEV_ENTRY` | **Local OpenCode 2.0 only.** Absolute path to a built entry file (usually `dist/index.js`). Rewrites the catalog's AI SDK package to `aisdk:file://…` so the daemon imports your local build instead of `npm install`-ing the published package. Export it **before** `opencode2 service start`, then restart after rebuilds. Unset in production. |
355
- | `XDG_CACHE_HOME` | Base for host cache dirs (`$XDG_CACHE_HOME/opencode/`, `…/mimocode/`, or `…/kilo/`) when no explicit `cacheDir` / OCP detect override |
353
+ | `XDG_CACHE_HOME` | Base for host cache dirs (`$XDG_CACHE_HOME/opencode/`, `…/mimocode/`, `…/kilo/`, or OMP's migrated `…/omp/`) when no explicit `cacheDir` / OCP detect override |
354
+ | `PI_CODING_AGENT_DIR` | Custom Pi/OMP agent root; provider stores its private cache below `<agent-root>/cache/cursor-opencode/` |
355
+ | `PI_CONFIG_DIR` | Custom Pi/OMP config root name used when deriving the default agent root |
356
356
  | `MIMOCODE_HOME` | When set, host cache is `$MIMOCODE_HOME/cache` (MiMo) |
357
357
  | `KILO_CONFIG_DIR` | When set, host cache is `$XDG_CACHE_HOME/kilo` |
358
358
  | `XDG_DATA_HOME` | When set, OpenCode `auth.json` is read from `$XDG_DATA_HOME/opencode/` instead of `~/.local/share/opencode/` |
@@ -446,6 +446,7 @@ Kilo `scout` is reserved for external documentation, dependency repositories, an
446
446
  | `cursor-opencode-provider/plugin/v2` | OpenCode 1.18 Effect/Promise v2 plugin (`ctx.aisdk.*`) |
447
447
  | `cursor-opencode-provider/plugin/opencode2` | OpenCode 2.0 beta plugin (self-registering: catalog + auth + tools) |
448
448
  | `cursor-opencode-provider/errors` | Structured provider error classes |
449
+ | `cursor-opencode-provider/image-save` | Host-neutral `executeCursorImageSave` (pi-bridge / non-plugin hosts) |
449
450
 
450
451
  The package root intentionally stays plugin-safe for OpenCode's classic loader. `CursorPluginV2` and non-plugin runtime APIs are **not** re-exported from the package root; load them through their dedicated subpaths.
451
452
 
@@ -482,7 +483,11 @@ Project `instructions` may reference absolute or `~/` paths (OpenCode parity). S
482
483
  - **Host web tools use collision-safe aliases** — Cursor sees `custom_websearch` / `custom_webfetch`, and the held Run maps each alias back to an executable OpenCode tool with its schema, permission check, and correlated result intact. The plugin registers its search fallback directly as `custom_websearch`, avoiding OpenCode's reserved `websearch` id filter for third-party providers and taking precedence over MCP search providers such as Brave without host environment configuration. Exact host `webfetch` is translated the same way. Cursor's UI-bound native web interactions stay disabled because their approval replies cannot carry OpenCode tool results.
483
484
  - **MCP resource tools (`list_mcp_resources` / `read_mcp_resource`) also use collision-safe aliases, and Cursor's native resource exec is always answered** — OpenCode's resource tools (advertised only when a connected MCP server declares `capabilities.resources`) share bare names with two of Cursor's own native tool-call variants. Left unaliased, Cursor's client can route a call onto its native exec channel instead of the ordinary MCP tool path, which this provider does not translate — confirmed live: reading a resource was dispatched natively and killed the Run before this fix. The provider now aliases both names to `custom_list_mcp_resources` / `custom_read_mcp_resource` (same mechanism as web search/fetch) so normal calls execute through OpenCode's tools, and separately answers Cursor's native resource-exec fields directly on the protocol floor — an empty list, or a "server not found" error for read — so an unsolicited native request from Cursor can never fail the Run. Downloads (`download_path`) have no OpenCode equivalent and are always refused; binary resource content is subject to the same [50 KB / attachment-type limits](#known-limitations) as other tool results.
484
485
  - **Background shells are non-interactive** — Cursor's native background-shell spawn and soft-background shell-stream timeouts are bridged through OpenCode's foreground-only `bash` tool. With bash/zsh, the classic plugin keeps the original permission/UI command and executes the wrapper through `shell.env` (`BASH_ENV` / `ZDOTDIR`); OpenCode's non-interactive sh/dash argv ignores those startup variables, so the plugin uses a short `exec /bin/sh '<wrapper-file>'` command backed by the same private wrapper. Native background-spawn requests also carry a self-contained marker-producing fallback when the classic hooks are absent. Spawn/soft-bg wrappers detach with `nohup`, redirect output under `${TMPDIR:-/tmp}/cursor-opencode-{bg,shell}.*`, and return the real PID (or typed timeout/exit) to Cursor. Private markers and OpenCode's `<shell_metadata>` envelope are stripped before storage/render, with a short still-running / started / timed-out status line left for the bash bubble. Requests that require `write_shell_stdin` are rejected explicitly because OpenCode does not expose an interactive background-process lifecycle through its AI SDK tool interface. The POSIX wrap path is not implemented for native Windows PowerShell/`cmd`.
485
- - **Cursor-native interaction queries remain headless** — Cursor UI/approval *queries* (as distinct from display tool calls) still cannot be surfaced through the AI SDK provider interface. The normal system prompt redirects questions, planning, plan-mode transitions, and available web capabilities to executable host tools (`question`, `todowrite`, `plan_enter` / `plan_exit`, `custom_websearch`, `custom_webfetch`); native web/PR/MCP/image/SCM requests are declined so they remain behind host tool permissions. Separately from display `create_plan_tool_call` → `todowrite` mirroring, interaction `create_plan_request_query` is auto-acked (CLI headless parity) with success and an empty `plan_uri`, so Cursor may treat the plan as accepted without an OpenCode UI confirm. Compaction prompts are unchanged. Unknown future interaction variants fail the turn explicitly instead of hanging the Run stream.
486
+ - **Cursor-native AskQuestion reaches you through OpenCode's question tool** — when Cursor asks a clarifying question, the provider translates the interaction into the host `question` tool, so the prompt appears in OpenCode with its real options and your answer is mapped back to Cursor's option ids (an answer you type yourself becomes Cursor's freeform text). Questions Cursor marks as non-blocking are acknowledged immediately and answered later, matching Cursor CLI. If the current agent does not offer `question` — most subagents deny it, and compaction turns have no tools — the request is declined with that reason and the model asks in its reply instead.
487
+ - **Cursor-native SwitchMode reaches you through OpenCode plan enter/exit** — when Cursor raises `switch_mode_request_query`, the provider translates it into the host `plan_enter` or `plan_exit` tool (when advertised) and keeps Cursor waiting until you approve or reject, matching Cursor CLI. `plan`/`spec` enter plan mode; every other non-empty target (including `agent`, `build`, `chat`, `debug`, `edit`, `background`, `multitask`, `triage`, `project`, and unknowns) leaves plan via `plan_exit`. After approval, the provider injects a Cursor CLI-shaped `<system_reminder>` for that mode on later turns so the agent gets the same behavioral instructions the CLI would send.
488
+ - **Cursor image generation saves through OpenCode's permission** — Cursor generates the image on its servers and then writes it to you like any other file. Because that write carries raw bytes, which OpenCode's text `write` tool cannot represent, the provider holds the bytes and asks OpenCode for the same permissions a normal write uses — the external-directory prompt when the target sits outside your project, then `edit` — naming the target file. Approve it and the image is written byte-for-byte to its path; deny it and nothing reaches disk — Cursor is told the write was denied, using its own permission-denied result, so the model knows the file is not there. Generation is only approved when the save path is available, so Cursor quota is never spent on an image that could not be saved. By default images land in the Cursor project folder under `assets/`, kept out of your git tree as agent artifacts. Ask for a specific path and the image is saved there instead. Editing an existing image with `reference_image_paths` is not supported yet. The approach is adapted from [`jkalasas/opencode-antigravity-image`](https://github.com/jkalasas/opencode-antigravity-image) (MIT) by jkalasas, which established writing image bytes from inside an OpenCode plugin tool.
489
+ - **Cursor-native CreatePlan writes a host plan file, then asks before executing it** — when Cursor raises `create_plan_request_query`, the provider writes plain markdown to your host's own plans directory (`<hostGlobalDataDir>/plans/`, never your repository and never Cursor's `~/.cursor/plans`), with no Cursor YAML frontmatter. Writing the plan does not ask; running it does. If you are in plan mode, the provider prints the plan into the conversation — Cursor sends it over a side channel that would otherwise never display it — and then asks *"Plan at &lt;path&gt; is complete. Would you like to switch to the build agent and start implementing?"* — the same prompt OpenCode's own `plan_exit` uses — and keeps Cursor waiting for your answer. Answer **Yes** and the plan is reported as approved and implementation begins; answer **No** (or dismiss it) and Cursor is told the plan was not accepted, so the model refines it and proposes again instead of starting work. Hosts that advertise their own plan-staging tool run their native review UI instead, and it reports the same approve/refine outcome. Empty args still get the CLI empty-`plan_uri` success ack. Display `create_plan_tool_call` continues to mirror into `todowrite` separately.
490
+ - **Other Cursor-native interaction queries remain headless** — remaining UI/approval *queries* (as distinct from display tool calls and the AskQuestion / SwitchMode / CreatePlan / GenerateImage bridges) still cannot be surfaced through the AI SDK provider interface. The normal system prompt redirects available web capabilities to executable host tools (`custom_websearch`, `custom_webfetch`); native web/PR/MCP/SCM requests are declined so they remain behind host tool permissions. Compaction prompts are unchanged. Unknown future interaction variants fail the turn explicitly instead of hanging the Run stream.
486
491
  - **Compaction resets Cursor conversation state without resetting stable workspace context** — the classic plugin marks OpenCode's `compaction` agent explicitly. On those turns the provider mints an isolated Cursor `conversation_id`, drops the prior checkpoint + KV blobs, preserves real tool outputs as OpenCode-host observations in the seed history, and re-advertises the session's last tool catalog while refusing execution during the summary itself. The first normal turn then rebases once more onto a fresh conversation seeded with OpenCode's compacted prompt and normal system instructions, so the summary-agent checkpoint cannot suppress later tool calls. Across both id rotations, the frozen workspace/rules/environment base and its prior encoded-context comparison seed are transferred instead of rebuilt; tools, skills, agents, plugins, and MCP state are still rediscovered, and the complete context is reused only when its bytes remain identical. Ordinary no-tool / `toolChoice:none` calls do not reset conversation state. Debug logs include system-prompt and encoded RequestContext hashes for cache-prefix verification.
487
492
  - **Conversation bindings survive provider restarts** — after each successful `TurnEnded`, the OpenCode-session binding, latest checkpoint, checkpoint-reachable KV blob graph, frozen request-context base, last real tool catalog, and pending post-compaction rebase marker are atomically replaced in that session's private `<host-cache>/cursor-conversations/*.pb.gz` snapshot. The protobuf stores checkpoints, blob ids/data, and request context as bytes without JSON/base64 expansion, then gzip compresses the complete record. A checkpoint alone is insufficient: Cursor requests its referenced hashes through `get_blob` when the next Run starts. Reachability follows Cursor CLI's export traversal (turns, messages, steps, images, summaries, todos, prompts, and nested subagents), pruning superseded blobs; decode failure or a missing referenced hash conservatively retains the complete blob set. The stable OpenCode session id selects the file; Cursor conversation ids may rotate during compaction and remain inside the snapshot. Independent files prevent concurrent OpenCode processes from losing unrelated sessions by replacing a shared aggregate cache. Startup restores records updated within 24 hours and safely flushes older ones without deleting a concurrently published fresh replacement. Experimental legacy JSON snapshots are discarded rather than migrated. Debug logs report hydration as `restored`, `missing`, `invalid`, or `expired`, plus the compressed and protobuf snapshot sizes. The restored catalog is a fallback only for lifecycle turns such as compaction; an ordinary restricted/no-tool turn never resurrects it. Process-global bindings and compaction catalogs still use a 256-session LRU bound; memory eviction drops the live checkpoint, blobs, and frozen context, which can be rehydrated from the restart cache on later use.
488
493
  - **Oversized checkpoint graphs rebase instead of flooding the Run stream** — before reusing a checkpoint, the provider measures its reachable KV graph. State above 100 MiB, or an incomplete graph with a missing/undecodable reference, rotates to a fresh Cursor conversation seeded from OpenCode's authoritative history. KV, request-context, MCP-state, interaction, and heartbeat writes are ordered per Run and await HTTP/2 drain, so a large blob reply cannot create an unbounded queue later misreported as heartbeat backpressure. Debug logs distinguish checkpoint-envelope bytes, reachable blob bytes, seed size, and encoded Run-request estimate; raw blob bytes are never presented as tokens.
@@ -5,7 +5,7 @@ export type CollectedAgent = {
5
5
  prompt: string;
6
6
  };
7
7
  /**
8
- * Custom agents from the active OpenCode-compatible config roots. OCP may
8
+ * Custom agents from the active OpenCode-compatible config roots. A host bridge may
9
9
  * install a host-neutral bridge before loading this module; without one this
10
10
  * remains the direct OpenCode discovery path.
11
11
  */
@@ -120,7 +120,7 @@ async function scanAgentRoot(root, out) {
120
120
  await scanDir(path.join(root, "agents"), "agents");
121
121
  }
122
122
  /**
123
- * Custom agents from the active OpenCode-compatible config roots. OCP may
123
+ * Custom agents from the active OpenCode-compatible config roots. A host bridge may
124
124
  * install a host-neutral bridge before loading this module; without one this
125
125
  * remains the direct OpenCode discovery path.
126
126
  */
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { opencodeGlobalDataDir } from "./paths.js";
3
+ import { hostGlobalDataDir } from "./paths.js";
4
4
  function debugEnabled() {
5
5
  return process.env.CURSOR_PROVIDER_DEBUG === "1" || process.env.CURSOR_PROVIDER_DEBUG === "true";
6
6
  }
@@ -26,7 +26,7 @@ export async function readStoredAuth(providerId) {
26
26
  return undefined;
27
27
  }
28
28
  }
29
- const filePath = path.join(opencodeGlobalDataDir(), "auth.json");
29
+ const filePath = path.join(hostGlobalDataDir(), "auth.json");
30
30
  try {
31
31
  const raw = await readFile(filePath, "utf-8");
32
32
  try {
@@ -9,7 +9,9 @@ export type DynamicRequestContextKey = typeof DYNAMIC_REQUEST_CONTEXT_KEYS[numbe
9
9
  /**
10
10
  * Full RequestContext payload for live UMA + exec #10 reply.
11
11
  * Sourced from OpenCode discovery (and .claude/.agents skill fallbacks).
12
- * Honors `instructions` globs the same way OpenCode does (including `.cursor/` if listed).
12
+ * Honors `instructions` globs the same way OpenCode does. The provider never
13
+ * looks in Cursor's own directories; a `.cursor/` path is read only when the
14
+ * user's own OpenCode config lists it, exactly as the host would read it.
13
15
  */
14
16
  export declare function buildRequestContext(input: BuildRequestContextInput): Promise<Record<string, unknown>>;
15
17
  /** Rediscover only capability/plugin sections that may change during a chat. */
@@ -36,7 +36,7 @@ const DEFAULT_HOST_SUBAGENTS = [
36
36
  /**
37
37
  * Convert the raw host executor catalog into the exact subagent list advertised
38
38
  * to Cursor. `hostSubagents.complete` answers a narrow question: did the host's
39
- * task/actor tool itself expose an exhaustive recipient list (schema enum or
39
+ * task tool itself exposes an exhaustive recipient list (schema enum or
40
40
  * catalog marker)? OpenCode's task tool intentionally uses a plain string, so
41
41
  * that raw flag is false even though we can still advertise a complete Cursor
42
42
  * catalog by adding built-in defaults and discovered agent files.
@@ -60,7 +60,9 @@ function buildAdvertisedSubagentCatalog(hostSubagents, discoveredAgents) {
60
60
  /**
61
61
  * Full RequestContext payload for live UMA + exec #10 reply.
62
62
  * Sourced from OpenCode discovery (and .claude/.agents skill fallbacks).
63
- * Honors `instructions` globs the same way OpenCode does (including `.cursor/` if listed).
63
+ * Honors `instructions` globs the same way OpenCode does. The provider never
64
+ * looks in Cursor's own directories; a `.cursor/` path is read only when the
65
+ * user's own OpenCode config lists it, exactly as the host would read it.
64
66
  */
65
67
  export async function buildRequestContext(input) {
66
68
  const workspaceRoot = path.resolve(input.workspaceRoot || process.cwd());
@@ -1,3 +1,3 @@
1
1
  export { buildDynamicRequestContext, buildRequestContext, DYNAMIC_REQUEST_CONTEXT_KEYS, materializeRequestContext, requestContextBase, type BuildRequestContextInput, } from "./build.js";
2
2
  export { clearFrozenRequestContext, getFrozenRequestContext, getOrBuildRequestContext, MAX_FROZEN_REQUEST_CONTEXTS, resetFrozenRequestContextsForTests, setFrozenRequestContext, transferFrozenRequestContext, } from "./frozen.js";
3
- export { adoptCompatHostCacheDir, getHostCacheDirOverride, opencodeGlobalCacheDir, opencodeGlobalConfigDir, opencodeGlobalDataDir, resolveHostCacheDir, setHostCacheDirOverride, } from "./paths.js";
3
+ export { HOST_PATH_BRIDGE, getHostCacheDirOverride, opencodeGlobalCacheDir, opencodeGlobalConfigDir, opencodeGlobalDataDir, hostGlobalDataDir, resolveHostCacheDir, setHostCacheDirOverride, } from "./paths.js";
@@ -1,3 +1,3 @@
1
1
  export { buildDynamicRequestContext, buildRequestContext, DYNAMIC_REQUEST_CONTEXT_KEYS, materializeRequestContext, requestContextBase, } from "./build.js";
2
2
  export { clearFrozenRequestContext, getFrozenRequestContext, getOrBuildRequestContext, MAX_FROZEN_REQUEST_CONTEXTS, resetFrozenRequestContextsForTests, setFrozenRequestContext, transferFrozenRequestContext, } from "./frozen.js";
3
- export { adoptCompatHostCacheDir, getHostCacheDirOverride, opencodeGlobalCacheDir, opencodeGlobalConfigDir, opencodeGlobalDataDir, resolveHostCacheDir, setHostCacheDirOverride, } from "./paths.js";
3
+ export { HOST_PATH_BRIDGE, getHostCacheDirOverride, opencodeGlobalCacheDir, opencodeGlobalConfigDir, opencodeGlobalDataDir, hostGlobalDataDir, resolveHostCacheDir, setHostCacheDirOverride, } from "./paths.js";
@@ -1,79 +1,61 @@
1
1
  export type HostPathEnv = NodeJS.ProcessEnv;
2
- /** Host-neutral bridge installed by OCP before an unchanged provider is loaded. */
3
- export declare const OPENCODE_PATH_BRIDGE: unique symbol;
2
+ /** Structural host-path capability installed before an unchanged provider loads. */
3
+ export declare const HOST_PATH_BRIDGE: unique symbol;
4
4
  export type OpenCodePathBridge = {
5
5
  projectConfigDirs: (workspaceRoot: string) => string[];
6
6
  globalConfigDirs: () => string[];
7
+ /** Optional host-owned durable data root; absent means native OpenCode defaults. */
8
+ globalDataDir?: () => string;
9
+ /** Optional host-owned cache root; absent means native OpenCode defaults. */
10
+ globalCacheDir?: () => string;
7
11
  configFileNames?: string[];
8
12
  };
9
13
  export declare function opencodeProjectConfigDirs(workspaceRoot: string): string[];
10
14
  export declare function opencodeGlobalConfigDirs(): string[];
11
15
  export declare function opencodeConfigFileNames(): string[];
12
- type CompatDetectResult = {
13
- id: string;
14
- supported: boolean;
15
- source?: string;
16
- profile: {
17
- paths: {
18
- cacheDir: string;
19
- };
20
- };
21
- };
22
- type CompatDetector = () => CompatDetectResult;
23
16
  /**
24
17
  * Pin the process-wide cache root. Highest precedence for {@link opencodeGlobalCacheDir}.
25
18
  * Use for host-injected `Path.cache` or an explicit `createCursor({ cacheDir })`.
26
19
  */
27
20
  export declare function setHostCacheDirOverride(dir: string | undefined): void;
28
21
  export declare function getHostCacheDirOverride(): string | undefined;
29
- declare const HOST_CACHE_DIRS: readonly ["mimocode", "kilo", "opencode"];
30
- export type HostCacheDir = (typeof HOST_CACHE_DIRS)[number];
31
- /**
32
- * Host cache-dir name inferred from the running binary (`argv[0]` /
33
- * `process.execPath`). A source-checkout provider can't be located by install
34
- * path, but the binary that loaded it is authoritative: when this process IS
35
- * `mimo` / `kilocode`, the cache root is that host's, not OpenCode's. Mirrors
36
- * OCP `detect()` binaryHint semantics and tolerates the leading-dot Kilo binary
37
- * (`.kilo`). Only `argv[0]` is inspected — later argv entries are arguments,
38
- * not binary identity, and could otherwise cause false positives (e.g. an
39
- * `--opencode-config=…` flag under a fork).
40
- */
41
- export declare function hostCacheDirFromProcess(argv?: readonly string[], execPath?: string): HostCacheDir | undefined;
42
- /**
43
- * Resolve the host cache directory without an override.
44
- *
45
- * Explicit host environment wins. Otherwise, an installed provider inherits
46
- * the host-named cache containing its module; a source checkout is attributed
47
- * by the running binary identity (see {@link hostCacheDirFromProcess}). Merely
48
- * having another host's config directory installed is not evidence that it
49
- * owns this process.
50
- */
51
- export declare function resolveHostCacheDir(env?: HostPathEnv, moduleUrl?: string, processIdentity?: {
52
- argv?: readonly string[];
53
- execPath?: string;
54
- }): string;
55
- /**
56
- * Best-effort: if `@opencode-compat/profile` is installed, adopt `detect().profile.paths.cacheDir`
57
- * when the host is supported. No-op when OCP is absent or detection fails.
58
- */
59
- export declare function adoptCompatHostCacheDir(detector?: CompatDetector): Promise<string | undefined>;
60
- /** OpenCode / host global config dir (`~/.config/<app>`). Still OpenCode-named for rule discovery. */
22
+ /** Resolve the native OpenCode cache root when no host bridge is installed. */
23
+ export declare function resolveHostCacheDir(env?: HostPathEnv): string;
24
+ /** Native OpenCode global config dir. */
61
25
  export declare function opencodeGlobalConfigDir(): string;
62
26
  /**
63
27
  * Host global cache dir for Cursor project metadata + model/version caches.
64
28
  *
65
29
  * Precedence:
66
30
  * 1. {@link setHostCacheDirOverride} / `createCursor({ cacheDir })` (host `Path.cache`)
67
- * 2. Strong OCP `detect()` identity when {@link adoptCompatHostCacheDir} ran successfully
68
- * 3. Explicit host environment / provider install path ({@link resolveHostCacheDir})
31
+ * 2. An injected structural host path bridge
32
+ * 3. Native OpenCode XDG defaults ({@link resolveHostCacheDir})
69
33
  */
70
34
  export declare function opencodeGlobalCacheDir(): string;
35
+ /** Native OpenCode global data root. */
36
+ export declare function opencodeGlobalDataDir(env?: HostPathEnv): string;
37
+ /** Host-portable durable data root; falls back to native OpenCode. */
38
+ export declare function hostGlobalDataDir(env?: HostPathEnv): string;
71
39
  /**
72
- * OpenCode global data dir (`~/.local/share/opencode`).
73
- * Uses `$XDG_DATA_HOME/opencode` when set, otherwise `$HOME/.local/share/opencode`.
74
- * Auth credentials live here in `auth.json`.
40
+ * Directory for host plan files — always `<hostGlobalDataDir()>/plans`, and
41
+ * never inside the user's repository.
42
+ *
43
+ * OpenCode's own `Session.plan` branches on VCS and puts plans in the worktree
44
+ * (`<worktree>/.opencode/plans`) for a git project. The provider deliberately
45
+ * does *not* mirror that branch. Writing there means a throwaway plan lands in
46
+ * the user's tree untracked-but-unignored, and — because OpenCode installs
47
+ * `@opencode-ai/plugin` into every `.opencode` directory it discovers walking up
48
+ * from the cwd — creating that directory also bootstraps a project-local
49
+ * `node_modules`. The provider must add nothing to a repository it did not
50
+ * already contain.
51
+ *
52
+ * The global-data location is not a degraded fallback: it is the branch OpenCode
53
+ * itself uses when there is no VCS, and its plan agent allow-lists that path for
54
+ * `edit` / `external_directory` alongside the in-worktree one. {@link
55
+ * hostGlobalDataDir} carries an optional injected host translation; without a
56
+ * bridge it is the native OpenCode data root.
75
57
  */
76
- export declare function opencodeGlobalDataDir(): string;
58
+ export declare function hostPlansDir(_workspaceRoot?: string): string;
77
59
  /**
78
60
  * Cursor-compatible path slug (`/Users/a/b` → `Users-a-b`).
79
61
  * Used for per-workspace metadata under the host cache.
@@ -81,7 +63,7 @@ export declare function opencodeGlobalDataDir(): string;
81
63
  export declare function slugifyWorkspacePath(workspaceRoot: string): string;
82
64
  /**
83
65
  * Cursor-style project metadata root for a workspace.
84
- * Lives at `<host-cache>/projects/<slug>/` (OpenCode / MiMo / Kilo cache root).
66
+ * Lives at `<host-cache>/projects/<slug>/` under the resolved OpenCode/host cache root.
85
67
  *
86
68
  * This is what Cursor's RequestContextEnv.project_folder / MCP
87
69
  * workspace_project_dir point at — agent-tools, terminals, transcripts, etc.
@@ -91,4 +73,3 @@ export declare function opencodeProjectDir(workspaceRoot: string): string;
91
73
  /** Ensure {@link opencodeProjectDir} exists (mode 0o700) and return it. */
92
74
  export declare function ensureOpencodeProjectDir(workspaceRoot: string): string;
93
75
  export declare function resolveHomeRelative(p: string): string;
94
- export {};
@@ -2,12 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import { mkdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
5
  import { trace } from "../debug.js";
7
- /** Host-neutral bridge installed by OCP before an unchanged provider is loaded. */
8
- export const OPENCODE_PATH_BRIDGE = Symbol.for("opencode.compat.path-bridge");
6
+ /** Structural host-path capability installed before an unchanged provider loads. */
7
+ export const HOST_PATH_BRIDGE = Symbol.for("opencode.host.path-bridge");
9
8
  function pathBridge() {
10
- const value = globalThis[OPENCODE_PATH_BRIDGE];
9
+ const value = globalThis[HOST_PATH_BRIDGE];
11
10
  if (!value || typeof value !== "object")
12
11
  return undefined;
13
12
  const bridge = value;
@@ -15,6 +14,23 @@ function pathBridge() {
15
14
  ? bridge
16
15
  : undefined;
17
16
  }
17
+ function openCodeGlobalDataDir(env = process.env) {
18
+ if (env.XDG_DATA_HOME && env.XDG_DATA_HOME.length > 0) {
19
+ return path.join(env.XDG_DATA_HOME, "opencode");
20
+ }
21
+ return path.join(resolveHome(env), ".local", "share", "opencode");
22
+ }
23
+ function openCodeGlobalCacheDir(env = process.env) {
24
+ return path.join(xdgCacheHome(env), "opencode");
25
+ }
26
+ function bridgeGlobalDataDir() {
27
+ const value = pathBridge()?.globalDataDir?.();
28
+ return typeof value === "string" && value.length > 0 ? path.resolve(value) : undefined;
29
+ }
30
+ function bridgeGlobalCacheDir() {
31
+ const value = pathBridge()?.globalCacheDir?.();
32
+ return typeof value === "string" && value.length > 0 ? path.resolve(value) : undefined;
33
+ }
18
34
  export function opencodeProjectConfigDirs(workspaceRoot) {
19
35
  return pathBridge()?.projectConfigDirs(path.resolve(workspaceRoot)) ?? [
20
36
  path.join(path.resolve(workspaceRoot), ".opencode"),
@@ -48,110 +64,11 @@ export function setHostCacheDirOverride(dir) {
48
64
  export function getHostCacheDirOverride() {
49
65
  return hostCacheDirOverride;
50
66
  }
51
- const HOST_CACHE_DIRS = ["mimocode", "kilo", "opencode"];
52
- /**
53
- * Host cache-dir name inferred from the running binary (`argv[0]` /
54
- * `process.execPath`). A source-checkout provider can't be located by install
55
- * path, but the binary that loaded it is authoritative: when this process IS
56
- * `mimo` / `kilocode`, the cache root is that host's, not OpenCode's. Mirrors
57
- * OCP `detect()` binaryHint semantics and tolerates the leading-dot Kilo binary
58
- * (`.kilo`). Only `argv[0]` is inspected — later argv entries are arguments,
59
- * not binary identity, and could otherwise cause false positives (e.g. an
60
- * `--opencode-config=…` flag under a fork).
61
- */
62
- export function hostCacheDirFromProcess(argv = process.argv, execPath = process.execPath) {
63
- for (const raw of [argv[0], execPath]) {
64
- if (!raw)
65
- continue;
66
- const name = path.basename(String(raw)).toLowerCase().replace(/^\.+/, "");
67
- if (name === "mimo" ||
68
- name === "mimocode" ||
69
- name.startsWith("mimo-") ||
70
- name.includes("mimocode")) {
71
- return "mimocode";
72
- }
73
- if (name === "kilo" ||
74
- name === "kilocode" ||
75
- name.startsWith("kilo-") ||
76
- name.includes("kilocode")) {
77
- return "kilo";
78
- }
79
- if (name === "opencode" ||
80
- name.startsWith("opencode-") ||
81
- name.includes("opencode")) {
82
- return "opencode";
83
- }
84
- }
85
- return undefined;
86
- }
87
- /**
88
- * Resolve the host cache directory without an override.
89
- *
90
- * Explicit host environment wins. Otherwise, an installed provider inherits
91
- * the host-named cache containing its module; a source checkout is attributed
92
- * by the running binary identity (see {@link hostCacheDirFromProcess}). Merely
93
- * having another host's config directory installed is not evidence that it
94
- * owns this process.
95
- */
96
- export function resolveHostCacheDir(env = process.env, moduleUrl = import.meta.url, processIdentity = {}) {
97
- const mimoHome = env.MIMOCODE_HOME;
98
- if (mimoHome && mimoHome.length > 0) {
99
- return path.join(mimoHome, "cache");
100
- }
101
- const cacheHome = xdgCacheHome(env);
102
- const kiloConfig = env.KILO_CONFIG_DIR;
103
- if (kiloConfig && kiloConfig.length > 0) {
104
- return path.join(cacheHome, "kilo");
105
- }
106
- let modulePath;
107
- try {
108
- modulePath = moduleUrl.startsWith("file:") ? fileURLToPath(moduleUrl) : path.resolve(moduleUrl);
109
- }
110
- catch {
111
- modulePath = undefined;
112
- }
113
- if (modulePath) {
114
- for (const host of HOST_CACHE_DIRS) {
115
- const root = path.resolve(cacheHome, host);
116
- if (modulePath === root || modulePath.startsWith(`${root}${path.sep}`))
117
- return root;
118
- }
119
- }
120
- // Source checkout: the module path can't name the host, but the binary that
121
- // loaded us can (running as `mimo` / `kilocode` / `opencode`).
122
- const host = hostCacheDirFromProcess(processIdentity.argv ?? process.argv, processIdentity.execPath ?? process.execPath);
123
- if (host)
124
- return path.join(cacheHome, host);
125
- return path.join(cacheHome, "opencode");
126
- }
127
- /**
128
- * Best-effort: if `@opencode-compat/profile` is installed, adopt `detect().profile.paths.cacheDir`
129
- * when the host is supported. No-op when OCP is absent or detection fails.
130
- */
131
- export async function adoptCompatHostCacheDir(detector) {
132
- if (hostCacheDirOverride)
133
- return hostCacheDirOverride;
134
- try {
135
- const detect = detector ?? (await import("@opencode-compat/profile")).detect;
136
- const result = detect();
137
- if (!result.supported || result.id === "unknown")
138
- return undefined;
139
- if (!result.source || !["env", "binary", "package"].includes(result.source)) {
140
- trace(`host-cache: ignored weak OCP detect host=${result.id} source=${result.source ?? "unknown"}`);
141
- return undefined;
142
- }
143
- const cacheDir = result.profile.paths.cacheDir;
144
- if (!cacheDir || cacheDir.length === 0)
145
- return undefined;
146
- setHostCacheDirOverride(cacheDir);
147
- trace(`host-cache: adopted OCP detect cacheDir=${cacheDir} host=${result.id}`);
148
- return cacheDir;
149
- }
150
- catch {
151
- return undefined;
152
- }
67
+ /** Resolve the native OpenCode cache root when no host bridge is installed. */
68
+ export function resolveHostCacheDir(env = process.env) {
69
+ return bridgeGlobalCacheDir() ?? openCodeGlobalCacheDir(env);
153
70
  }
154
- /** OpenCode / host global config dir (`~/.config/<app>`). Still OpenCode-named for rule discovery. */
71
+ /** Native OpenCode global config dir. */
155
72
  export function opencodeGlobalConfigDir() {
156
73
  return path.join(resolveHome(), ".config", "opencode");
157
74
  }
@@ -160,24 +77,43 @@ export function opencodeGlobalConfigDir() {
160
77
  *
161
78
  * Precedence:
162
79
  * 1. {@link setHostCacheDirOverride} / `createCursor({ cacheDir })` (host `Path.cache`)
163
- * 2. Strong OCP `detect()` identity when {@link adoptCompatHostCacheDir} ran successfully
164
- * 3. Explicit host environment / provider install path ({@link resolveHostCacheDir})
80
+ * 2. An injected structural host path bridge
81
+ * 3. Native OpenCode XDG defaults ({@link resolveHostCacheDir})
165
82
  */
166
83
  export function opencodeGlobalCacheDir() {
167
84
  if (hostCacheDirOverride)
168
85
  return hostCacheDirOverride;
169
86
  return resolveHostCacheDir();
170
87
  }
88
+ /** Native OpenCode global data root. */
89
+ export function opencodeGlobalDataDir(env = process.env) {
90
+ return openCodeGlobalDataDir(env);
91
+ }
92
+ /** Host-portable durable data root; falls back to native OpenCode. */
93
+ export function hostGlobalDataDir(env = process.env) {
94
+ return bridgeGlobalDataDir() ?? openCodeGlobalDataDir(env);
95
+ }
171
96
  /**
172
- * OpenCode global data dir (`~/.local/share/opencode`).
173
- * Uses `$XDG_DATA_HOME/opencode` when set, otherwise `$HOME/.local/share/opencode`.
174
- * Auth credentials live here in `auth.json`.
97
+ * Directory for host plan files — always `<hostGlobalDataDir()>/plans`, and
98
+ * never inside the user's repository.
99
+ *
100
+ * OpenCode's own `Session.plan` branches on VCS and puts plans in the worktree
101
+ * (`<worktree>/.opencode/plans`) for a git project. The provider deliberately
102
+ * does *not* mirror that branch. Writing there means a throwaway plan lands in
103
+ * the user's tree untracked-but-unignored, and — because OpenCode installs
104
+ * `@opencode-ai/plugin` into every `.opencode` directory it discovers walking up
105
+ * from the cwd — creating that directory also bootstraps a project-local
106
+ * `node_modules`. The provider must add nothing to a repository it did not
107
+ * already contain.
108
+ *
109
+ * The global-data location is not a degraded fallback: it is the branch OpenCode
110
+ * itself uses when there is no VCS, and its plan agent allow-lists that path for
111
+ * `edit` / `external_directory` alongside the in-worktree one. {@link
112
+ * hostGlobalDataDir} carries an optional injected host translation; without a
113
+ * bridge it is the native OpenCode data root.
175
114
  */
176
- export function opencodeGlobalDataDir() {
177
- if (process.env.XDG_DATA_HOME) {
178
- return path.join(process.env.XDG_DATA_HOME, "opencode");
179
- }
180
- return path.join(resolveHome(), ".local", "share", "opencode");
115
+ export function hostPlansDir(_workspaceRoot) {
116
+ return path.join(hostGlobalDataDir(), "plans");
181
117
  }
182
118
  /**
183
119
  * Cursor-compatible path slug (`/Users/a/b` → `Users-a-b`).
@@ -193,7 +129,7 @@ export function slugifyWorkspacePath(workspaceRoot) {
193
129
  }
194
130
  /**
195
131
  * Cursor-style project metadata root for a workspace.
196
- * Lives at `<host-cache>/projects/<slug>/` (OpenCode / MiMo / Kilo cache root).
132
+ * Lives at `<host-cache>/projects/<slug>/` under the resolved OpenCode/host cache root.
197
133
  *
198
134
  * This is what Cursor's RequestContextEnv.project_folder / MCP
199
135
  * workspace_project_dir point at — agent-tools, terminals, transcripts, etc.
@@ -167,7 +167,7 @@ export async function loadMergedConfig(workspaceRoot) {
167
167
  if (isProjectConfigDisabled())
168
168
  return mergeConfig({}, globalConfig);
169
169
  // The bridge supplies native project config roots for an unchanged plugin:
170
- // .opencode on OpenCode, .mimocode on MiMo, and .kilo/.kilocode on Kilo.
170
+ // The active host's project config directories are supplied by the path bridge; OpenCode defaults to .opencode.
171
171
  // Later roots have higher precedence, matching the host's native ordering.
172
172
  let projectConfig = await readJsonConfig(workspaceRoot);
173
173
  for (const configDir of opencodeProjectConfigDirs(workspaceRoot)) {
@@ -0,0 +1,19 @@
1
+ import { executeCursorImageSave } from "./image-save.js";
2
+ /** Build the classic OpenCode image-save tool with the host's own Zod helper. */
3
+ export declare function createCursorImageSaveTool(factory: {
4
+ tool: (input: Record<string, unknown>) => Record<string, unknown>;
5
+ schema: {
6
+ string: () => any;
7
+ };
8
+ }): Record<string, unknown>;
9
+ /** Host-neutral JSON-schema fallback for hosts with no classic helper. */
10
+ export declare const cursorImageSaveTool: {
11
+ description: string;
12
+ args: {
13
+ image_id: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ };
18
+ execute: typeof executeCursorImageSave;
19
+ };
@@ -0,0 +1,25 @@
1
+ import { executeCursorImageSave } from "./image-save.js";
2
+ /** Build the classic OpenCode image-save tool with the host's own Zod helper. */
3
+ export function createCursorImageSaveTool(factory) {
4
+ return factory.tool({
5
+ description: "Save an image that Cursor generated during this session to its target path. "
6
+ + "Takes only the id of an already-generated image — it cannot write arbitrary "
7
+ + "files, and it is not a general-purpose file writer. You do not normally call "
8
+ + "this: the Cursor provider issues it after an image is generated.",
9
+ args: {
10
+ image_id: factory.schema.string().describe("Id of the pending Cursor-generated image to save"),
11
+ },
12
+ execute: executeCursorImageSave,
13
+ });
14
+ }
15
+ /** Host-neutral JSON-schema fallback for hosts with no classic helper. */
16
+ export const cursorImageSaveTool = {
17
+ description: "Save an image that Cursor generated during this session to its target path. "
18
+ + "Takes only the id of an already-generated image — it cannot write arbitrary "
19
+ + "files, and it is not a general-purpose file writer. You do not normally call "
20
+ + "this: the Cursor provider issues it after an image is generated.",
21
+ args: {
22
+ image_id: { type: "string", description: "Id of the pending Cursor-generated image to save" },
23
+ },
24
+ execute: executeCursorImageSave,
25
+ };