pi-opencode-cli-bridge 0.2.10 → 0.2.12

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.
package/README.md CHANGED
@@ -33,7 +33,7 @@ The original MIT copyright notice is preserved in [LICENSE](LICENSE). The new pa
33
33
 
34
34
  - Node.js **22.19.0 or newer**.
35
35
  - Pi Coding Agent **1.2.0 or newer**; the development SDK is pinned to 1.2.0.
36
- - OpenCode installed on the same machine and available on `PATH`, or selected with `OPENCODE_PI_BIN`. Supports OpenCode up to **2.0.31**. Real-CLI integration tests cover **1.19.0** and **2.0.31**; intermediate releases are not individually verified.
36
+ - OpenCode installed on the same machine and available on `PATH`, or selected with `OPENCODE_PI_BIN`. Supports OpenCode up to **2.1.2**. Real-CLI integration tests cover **1.19.0** and **2.1.2**; intermediate releases are not individually verified.
37
37
 
38
38
  Check OpenCode before starting Pi:
39
39
 
@@ -119,7 +119,7 @@ Without an override, discovery selects active `opencode/` models whose reported
119
119
  2. It replays Pi's transcript system messages to recover the current system prompt and tool declarations, including later changes.
120
120
  3. It creates a temporary OpenCode project containing a `pi-model` agent whose permissions are all `ask`. Non-interactive `opencode run` rejects those permission requests automatically, while native tool declarations remain on the model request. On 1.x, a generated `tool.execute.before` plugin provides a second guard; 2.x relies on the permission guard, not the legacy plugin.
121
121
  4. It sends the prompt over standard input to `opencode run --format json`. On 2.x, it uses a private `--standalone` server, sets the subprocess working directory instead of the removed `--dir` flag, and selects reasoning variants with `provider/model#variant` instead of `--variant`. User and tool-result images are written to temporary files and attached only when the model supports images.
122
- 5. If the model calls a native OpenCode tool anyway, the call is still blocked. When every native call in that step has a Pi equivalent in the current tool list (`bash` or 2.x `shell`, `read`, `write`, `edit` with a single replacement, `grep`, and `glob` as `find`), the bridge stops OpenCode and returns them as Pi tool calls. Paths inside the temporary project become relative to Pi's working directory. Otherwise OpenCode continues and the model can correct itself. If it ends without recovering, the bridge makes one fresh attempt with the original transcript, the blocked native calls and their arguments, and a correction referring to the available Pi tool descriptions and schemas. The model is asked to choose tools that can perform the required operation, not merely tools with similar names, and generate arguments for their Pi schemas. This is a model-directed fallback, not an automatic conversion or a guarantee of functional equivalence. If no suitable tool exists, including when Pi exposes no tools, the model is asked to explain the limitation and request the missing content or access (for example, the text of an unreadable web page). Native tools stay blocked on both attempts, which share the turn timeout. A second failure produces an error rather than retrying indefinitely.
122
+ 5. If the model calls a native OpenCode tool anyway, the call is still blocked. When every native call in that step has a Pi equivalent in the current tool list (`bash` or 2.x `shell`, `read`, `write`, `edit` with a single replacement, `grep`, and `glob` as `find`), the bridge stops OpenCode and returns them as Pi tool calls. Paths inside the temporary project become relative to Pi's working directory. Otherwise OpenCode continues and the model can correct itself. If it ends without recovering — with no usable Pi tool call, whether it replies with prose (when Pi exposes tools) or stays silent — the bridge makes one fresh attempt with the original transcript, the blocked native calls and their arguments, and a correction referring to the available Pi tool descriptions and schemas. The model is asked to choose tools that can perform the required operation, not merely tools with similar names, and generate arguments for their Pi schemas. This is a model-directed fallback, not an automatic conversion or a guarantee of functional equivalence. If no suitable tool exists, including when Pi exposes no tools, the model is asked to explain the limitation and request the missing content or access (for example, the text of an unreadable web page). Native tools stay blocked on both attempts, which share the turn timeout. A second failure produces an error rather than retrying indefinitely.
123
123
  6. Text markers such as `<pi_tool_call>{"name":"read","arguments":{"path":"example.txt"}}</pi_tool_call>` become Pi tool calls. Tool names must be in the current Pi tool list; execution then follows Pi's normal validation and permission pipeline. DeepSeek models sometimes write their native DSML tool-call blocks (`<|DSML|invoke name="...">`) as text instead; complete blocks are converted the same way, with incomplete blocks rejected rather than executed.
124
124
  7. Successful continuation turns reuse an OpenCode session and send only the new transcript. Changes to the model, prompt, tools or transcript prefix start a fresh session.
125
125
 
@@ -140,11 +140,11 @@ Reasoning variants, image support and token limits follow CLI discovery. Token u
140
140
 
141
141
  - Prompts, system instructions, tool declarations, conversation text and attached images pass to OpenCode and its model provider. Do not send confidential data unless that provider's policies are suitable.
142
142
  - The extension does not directly extract credentials or call model HTTP endpoints. The OpenCode subprocess still runs as your user and can use its own configuration, credentials and inherited environment.
143
- - The native-tool guard is a guard rail, **not a sandbox**. Integration tests run the real OpenCode 1.19.0 and 2.0.31 CLIs against a local model fixture that requests native `write` and `bash`/`shell` calls, and check that neither runs with the legacy plugin intact, missing, or failing at startup. Other OpenCode versions, tools contributed by your own global OpenCode configuration (for example MCP servers), and a hostile local process are not covered.
143
+ - The native-tool guard is a guard rail, **not a sandbox**. Integration tests run the real OpenCode 1.19.0 and 2.1.2 CLIs against a local model fixture that requests native `write` and `bash`/`shell` calls, and check that neither runs with the legacy plugin intact, missing, or failing at startup. Other OpenCode versions, tools contributed by your own global OpenCode configuration (for example MCP servers), and a hostile local process are not covered.
144
144
  - Pi and OpenCode can both retain local conversation data. Best-effort cleanup is not a guarantee of secure deletion.
145
145
  - Free-tier model availability and accepted request shapes can change. This fork makes no guarantee of continued access or compliance with a hosted provider's terms; review the applicable terms yourself.
146
- - Live tests with DeepSeek v4.1 Flash Free used thinking off. The first high-thinking test timed out; no default thinking setting is changed by this package.
147
- - At startup Pi warns when your OpenCode version exceeds the supported maximum, **2.0.31**, or its version cannot be determined.
146
+ - Live tests on OpenCode 2.1.2 with DeepSeek v4.1 Flash Free verified text replies, a Pi `read` call and tool-result continuation with thinking off. High thinking was not reverified on 2.1.2; an earlier test timed out. No default thinking setting is changed by this package.
147
+ - At startup Pi warns when your OpenCode version exceeds the supported maximum, **2.1.2**, or its version cannot be determined.
148
148
  - Timeouts and cancellation escalate from `SIGTERM` to `SIGKILL` on the CLI's process group after 250 ms; on Windows the whole process tree is ended with `taskkill /T /F` (not tested on Windows). If OpenCode quits before reading the prompt, the turn shows OpenCode's own error rather than a broken-pipe error. Malformed or unexpected CLI events end the turn with an error instead of crashing Pi. Shutdown waits for session cleanup.
149
149
 
150
150
  See [SECURITY.md](SECURITY.md) for the trust boundaries and [docs/code-review.md](https://github.com/Gybra/pi-opencode-cli-bridge/blob/main/docs/code-review.md) for what was fixed and what remains. This is an experimental, unaudited fork.
@@ -159,7 +159,7 @@ OPENCODE_PI_INTEGRATION_BIN="$(which opencode)" npm run test:integration
159
159
  npm pack --dry-run
160
160
  ```
161
161
 
162
- `npm test` uses a fake OpenCode executable. `npm run test:integration` needs the real OpenCode CLI at version 1.19.0 or 2.0.31 (on `PATH` or in `OPENCODE_PI_INTEGRATION_BIN`), runs it with isolated home and data directories against a local model fixture, and makes no network request. Neither suite needs API credentials. Development SDKs are pinned; Pi supplies those packages at runtime. The npm archive includes the extension source, README, security policy and license, not tests or installed dependencies.
162
+ `npm test` uses a fake OpenCode executable. `npm run test:integration` needs the real OpenCode CLI at version 1.19.0 or 2.1.2 (on `PATH` or in `OPENCODE_PI_INTEGRATION_BIN`), runs it with isolated home and data directories against a local model fixture, and makes no network request. Neither suite needs API credentials. Development SDKs are pinned; Pi supplies those packages at runtime. The npm archive includes the extension source, README, security policy and license, not tests or installed dependencies.
163
163
 
164
164
  ## License
165
165
 
package/SECURITY.md CHANGED
@@ -10,7 +10,7 @@ Never include prompts, conversation history, images, API keys, auth files or ses
10
10
  - Requests are delegated to the real OpenCode CLI. The extension does not directly read credentials, but the subprocess inherits the environment and uses OpenCode's own configuration and authentication.
11
11
  - System instructions, tool declarations, transcript text and image attachments can reach the selected model provider. Provider retention and training policies apply.
12
12
  - The current Pi tool list is checked before marker responses become Pi tool calls. Pi remains responsible for argument validation and tool permission checks.
13
- - Native OpenCode tools are guarded by an agent permission of `ask` for everything, which non-interactive `opencode run` rejects. On 1.19.0 a generated `tool.execute.before` plugin provides a second guard; on 2.0.31 the bridge relies on permissions, not that legacy plugin. Native declarations remain visible to the model. Real-CLI integration tests on both releases request `write` and `bash`/`shell` side effects and verify that neither executes with the legacy plugin intact, missing, or failing. These guards are not a security boundary against a hostile local process, your own OpenCode configuration (it is loaded and may add tools or agent overrides), or other OpenCode versions.
13
+ - Native OpenCode tools are guarded by an agent permission of `ask` for everything, which non-interactive `opencode run` rejects. On 1.19.0 a generated `tool.execute.before` plugin provides a second guard; on 2.1.2 the bridge relies on permissions, not that legacy plugin. Native declarations remain visible to the model. Real-CLI integration tests on both releases request `write` and `bash`/`shell` side effects and verify that neither executes with the legacy plugin intact, missing, or failing. These guards are not a security boundary against a hostile local process, your own OpenCode configuration (it is loaded and may add tools or agent overrides), or other OpenCode versions.
14
14
  - Temporary project directories and OpenCode session records are removed best-effort. Pi's own conversation history is not deleted by the extension.
15
15
 
16
16
  ## Known gaps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-opencode-cli-bridge",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Bridge the OpenCode CLI into Pi Coding Agent",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/index.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  const PROVIDER_ID = "opencode-cli";
30
30
  const API_ID = "opencode-cli-runner";
31
31
  const AGENT_ID = "pi-model";
32
- const MAX_SUPPORTED_OPENCODE_VERSION = "2.0.31";
32
+ const MAX_SUPPORTED_OPENCODE_VERSION = "2.1.2";
33
33
  const cliVersions = new Map<string, string | undefined>();
34
34
 
35
35
  async function usesV2Cli(): Promise<boolean> {
@@ -1770,13 +1770,6 @@ function streamOpenCodeAttempt(
1770
1770
  }
1771
1771
  }
1772
1772
  if (opencodeError) throw new Error(opencodeError);
1773
- if (opencodeToolUse && !nativeCalls && !accumulatedText.slice(nativeTextMark).trim()) {
1774
- recoverableFailure = new Error(
1775
- `OpenCode could not use the available Pi tools${allowRecovery ? "" : " after one recovery attempt"} (blocked native tool: ${JSON.stringify(opencodeToolUse)}). Retry the request or select another OpenCode model.`,
1776
- );
1777
- throw recoverableFailure;
1778
- }
1779
-
1780
1773
  const toolCallResult = parseToolCallResponse(
1781
1774
  accumulatedText,
1782
1775
  allowedToolNames,
@@ -1810,6 +1803,19 @@ function streamOpenCodeAttempt(
1810
1803
  }
1811
1804
  textCalls = reasoningResult.calls;
1812
1805
  }
1806
+ // A blocked native call with no usable Pi call recovers: nothing after it
1807
+ // to answer with, or prose while Pi exposes tools to try (e.g. "please run
1808
+ // a curl"). A valid call in text or reasoning is used instead of retrying;
1809
+ // with no Pi tools the prose is the model's explanation and stays final.
1810
+ if (
1811
+ opencodeToolUse && !nativeCalls && textCalls.length === 0 &&
1812
+ (!accumulatedText.slice(nativeTextMark).trim() || allowedToolNames.size > 0)
1813
+ ) {
1814
+ recoverableFailure = new Error(
1815
+ `OpenCode could not use the available Pi tools${allowRecovery ? "" : " after one recovery attempt"} (blocked native tool: ${JSON.stringify(opencodeToolUse)}). Retry the request or select another OpenCode model.`,
1816
+ );
1817
+ throw recoverableFailure;
1818
+ }
1813
1819
  const toolCalls = [...textCalls, ...(nativeCalls ?? [])];
1814
1820
  if (toolCalls.length === 0 && displayText.trim()) {
1815
1821
  // Check if the model tried to use XML-style tool-use instead of