pi-opencode-cli-bridge 0.2.6 → 0.2.7
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 +3 -1
- package/package.json +1 -1
- package/src/index.ts +17 -9
package/README.md
CHANGED
|
@@ -123,6 +123,8 @@ Without an override, discovery selects active `opencode/` models whose reported
|
|
|
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, and an incomplete one fails the turn with a diagnostic instead of ending it silently.
|
|
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
|
|
|
126
|
+
A turn containing only reasoning, with no answer or usable Pi tool call, also gets one fresh recovery attempt instead of silently ending the conversation. The model is asked to answer or request an available Pi tool; normal text answers and valid calls found in reasoning are unchanged. Reasoning-only and blocked-native failures share the same single retry and remaining turn timeout. If recovery fails, Pi receives an explicit error, not another thinking-only message.
|
|
127
|
+
|
|
126
128
|
Pi's provider instrumentation hooks follow the CLI transport, not OpenCode's internal HTTP traffic:
|
|
127
129
|
|
|
128
130
|
- `onPayload` receives `{ prompt }`, the exact text written to the CLI's standard input. Returning `{ prompt: "..." }` replaces it; any other non-`undefined` value fails the turn. A turn with a payload hook always starts a fresh OpenCode session.
|
|
@@ -130,7 +132,7 @@ Pi's provider instrumentation hooks follow the CLI transport, not OpenCode's int
|
|
|
130
132
|
- `onProviderStreamEvent` receives each validated `--format json` event in order, and is awaited before the event is processed.
|
|
131
133
|
- Provider-scoped `env` values are passed to the subprocess. The bridge's own configuration directory and update-check settings always take precedence.
|
|
132
134
|
|
|
133
|
-
Reasoning variants, image support and token limits follow CLI discovery. Token usage is taken from CLI events when available, otherwise estimated, and includes both attempts when
|
|
135
|
+
Reasoning variants, image support and token limits follow CLI discovery. Token usage is taken from CLI events when available, otherwise estimated, and includes both attempts when recovery is needed. Session records and temporary project directories are cleaned up on a best-effort basis.
|
|
134
136
|
|
|
135
137
|
## Security, privacy and limits
|
|
136
138
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1518,7 +1518,7 @@ function streamOpenCodeAttempt(
|
|
|
1518
1518
|
model: Model<Api>,
|
|
1519
1519
|
context: Context,
|
|
1520
1520
|
options: SimpleStreamOptions | undefined,
|
|
1521
|
-
|
|
1521
|
+
allowRecovery: boolean,
|
|
1522
1522
|
): AssistantMessageEventStream {
|
|
1523
1523
|
const stream = createAssistantMessageEventStream();
|
|
1524
1524
|
const deadline = Date.now() + resolveTurnTimeoutMs(options?.timeoutMs);
|
|
@@ -1543,7 +1543,7 @@ function streamOpenCodeAttempt(
|
|
|
1543
1543
|
let stdoutRemainder = "";
|
|
1544
1544
|
let capturedSessionId: string | undefined;
|
|
1545
1545
|
let opencodeToolUse: string | undefined;
|
|
1546
|
-
let
|
|
1546
|
+
let recoverableFailure: Error | undefined;
|
|
1547
1547
|
// Text after the last blocked native call shows whether the model recovered in-run.
|
|
1548
1548
|
let nativeTextMark = 0;
|
|
1549
1549
|
let stepNativeCalls: (ParsedToolCall | undefined)[] = [];
|
|
@@ -1767,10 +1767,10 @@ function streamOpenCodeAttempt(
|
|
|
1767
1767
|
}
|
|
1768
1768
|
if (opencodeError) throw new Error(opencodeError);
|
|
1769
1769
|
if (opencodeToolUse && !nativeCalls && !accumulatedText.slice(nativeTextMark).trim()) {
|
|
1770
|
-
|
|
1771
|
-
`OpenCode could not use the available Pi tools${
|
|
1770
|
+
recoverableFailure = new Error(
|
|
1771
|
+
`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.`,
|
|
1772
1772
|
);
|
|
1773
|
-
throw
|
|
1773
|
+
throw recoverableFailure;
|
|
1774
1774
|
}
|
|
1775
1775
|
|
|
1776
1776
|
const toolCallResult = parseToolCallResponse(
|
|
@@ -1815,6 +1815,12 @@ function streamOpenCodeAttempt(
|
|
|
1815
1815
|
);
|
|
1816
1816
|
}
|
|
1817
1817
|
}
|
|
1818
|
+
if (toolCalls.length === 0 && !displayText.trim() && accumulatedThinking.trim()) {
|
|
1819
|
+
recoverableFailure = new Error(
|
|
1820
|
+
`OpenCode returned only reasoning without an answer or Pi tool call${allowRecovery ? "" : " after one recovery attempt"}. Retry the request or select another OpenCode model.`,
|
|
1821
|
+
);
|
|
1822
|
+
throw recoverableFailure;
|
|
1823
|
+
}
|
|
1818
1824
|
if (
|
|
1819
1825
|
toolCalls.length === 0 &&
|
|
1820
1826
|
!displayText.trim() &&
|
|
@@ -1943,12 +1949,14 @@ function streamOpenCodeAttempt(
|
|
|
1943
1949
|
deleteOpenCodeSession(capturedSessionId);
|
|
1944
1950
|
if (piState) dropOpencodeSession(piState);
|
|
1945
1951
|
const remainingMs = deadline - Date.now();
|
|
1946
|
-
if (
|
|
1947
|
-
!options?.signal?.aborted && remainingMs > 0 && context.tools?.length) {
|
|
1952
|
+
if (recoverableFailure && error === recoverableFailure && allowRecovery &&
|
|
1953
|
+
!options?.signal?.aborted && remainingMs > 0 && (!opencodeToolUse || context.tools?.length)) {
|
|
1948
1954
|
setEstimatedUsage(model, output, prompt, accumulatedText + accumulatedThinking);
|
|
1949
1955
|
const correction = [
|
|
1950
|
-
|
|
1951
|
-
|
|
1956
|
+
opencodeToolUse
|
|
1957
|
+
? `Your previous attempt used the disabled OpenCode native tool ${JSON.stringify(opencodeToolUse)}.`
|
|
1958
|
+
: "Your previous attempt ended with reasoning only, without an answer or Pi tool call. Provide a final answer or request a Pi tool, not just a plan to act.",
|
|
1959
|
+
`Available Pi tools: ${context.tools?.map(tool => tool.name).join(", ") || "none"}.`,
|
|
1952
1960
|
"Complete the original request using only those Pi tools. Do not call any OpenCode native tool.",
|
|
1953
1961
|
'Request Pi tools as <pi_tool_call>{"name":"tool_name","arguments":{}}</pi_tool_call> markers, or answer normally if no tool is needed.',
|
|
1954
1962
|
"If Pi has no grep tool, request a search through Pi bash when it is available instead.",
|