@oh-my-pi/pi-agent-core 17.0.6 → 17.0.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.0.8] - 2026-07-22
6
+
7
+ ### Fixed
8
+
9
+ - Improved resilience against transient stream JSON parse failures by recovering completed tool calls while safely preventing incomplete, unknown, refused, or sensitive calls from executing.
10
+
5
11
  ## [17.0.5] - 2026-07-18
6
12
 
7
13
  ### Added
@@ -111,6 +111,15 @@ export interface SyntheticToolResultDetails {
111
111
  executed: false;
112
112
  upstreamError?: string;
113
113
  }
114
+ /**
115
+ * Narrow an {@link AgentMessage} to a synthetic {@link ToolResultMessage} —
116
+ * a tool_result emitted for a tool call the assistant never invoked (see
117
+ * {@link SyntheticToolResultDetails}). Consumers use this to look past the
118
+ * placeholder pairing back to the assistant turn that produced it, e.g.
119
+ * `AgentSession.retry()` walking back over the synthetic results a
120
+ * stalled/aborted mid-tool-call turn leaves behind.
121
+ */
122
+ export declare function isSyntheticToolResultMessage(message: AgentMessage | undefined): message is ToolResultMessage<SyntheticToolResultDetails>;
114
123
  /**
115
124
  * Create the persisted synthetic result for a tool call that was emitted by
116
125
  * the assistant but never invoked locally.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-agent-core",
4
- "version": "17.0.6",
4
+ "version": "17.0.8",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -35,12 +35,12 @@
35
35
  "fmt": "biome format --write ."
36
36
  },
37
37
  "dependencies": {
38
- "@oh-my-pi/pi-ai": "17.0.6",
39
- "@oh-my-pi/pi-catalog": "17.0.6",
40
- "@oh-my-pi/pi-natives": "17.0.6",
41
- "@oh-my-pi/pi-utils": "17.0.6",
42
- "@oh-my-pi/pi-wire": "17.0.6",
43
- "@oh-my-pi/snapcompact": "17.0.6",
38
+ "@oh-my-pi/pi-ai": "17.0.8",
39
+ "@oh-my-pi/pi-catalog": "17.0.8",
40
+ "@oh-my-pi/pi-natives": "17.0.8",
41
+ "@oh-my-pi/pi-utils": "17.0.8",
42
+ "@oh-my-pi/pi-wire": "17.0.8",
43
+ "@oh-my-pi/snapcompact": "17.0.8",
44
44
  "@opentelemetry/api": "^1.9.1"
45
45
  },
46
46
  "devDependencies": {
package/src/agent-loop.ts CHANGED
@@ -1629,13 +1629,26 @@ function recoverTransientErrorToolTurn(
1629
1629
  if (message.stopReason !== "error") return message;
1630
1630
  const toolCalls = message.content.filter(block => block.type === "toolCall");
1631
1631
  if (toolCalls.length === 0) return message;
1632
+ const stopDetailType = message.stopDetails?.type;
1633
+ const stopDetailCategory = message.stopDetails?.category;
1634
+ if (
1635
+ stopDetailType === "refusal" ||
1636
+ stopDetailType === "sensitive" ||
1637
+ stopDetailCategory === "refusal" ||
1638
+ stopDetailCategory === "sensitive"
1639
+ )
1640
+ return message;
1632
1641
  const availableToolNames = new Set<string>();
1633
1642
  for (const tool of availableTools) {
1634
1643
  availableToolNames.add(tool.name);
1635
1644
  if (tool.customWireName !== undefined) availableToolNames.add(tool.customWireName);
1636
1645
  }
1637
1646
  if (!toolCalls.every(toolCall => availableToolNames.has(toolCall.name))) return message;
1638
- if (!AIError.isStreamReadErrorText(`${message.errorMessage ?? ""}\n${message.stopDetails?.explanation ?? ""}`))
1647
+ if (
1648
+ !AIError.isStreamReadErrorText(`${message.errorMessage ?? ""}\n${message.stopDetails?.explanation ?? ""}`) &&
1649
+ !AIError.isTransientStreamParseError(message.errorMessage) &&
1650
+ !AIError.isTransientStreamParseError(message.stopDetails?.explanation)
1651
+ )
1639
1652
  return message;
1640
1653
  return {
1641
1654
  ...message,
@@ -2285,6 +2298,23 @@ export interface SyntheticToolResultDetails {
2285
2298
  upstreamError?: string;
2286
2299
  }
2287
2300
 
2301
+ /**
2302
+ * Narrow an {@link AgentMessage} to a synthetic {@link ToolResultMessage} —
2303
+ * a tool_result emitted for a tool call the assistant never invoked (see
2304
+ * {@link SyntheticToolResultDetails}). Consumers use this to look past the
2305
+ * placeholder pairing back to the assistant turn that produced it, e.g.
2306
+ * `AgentSession.retry()` walking back over the synthetic results a
2307
+ * stalled/aborted mid-tool-call turn leaves behind.
2308
+ */
2309
+ export function isSyntheticToolResultMessage(
2310
+ message: AgentMessage | undefined,
2311
+ ): message is ToolResultMessage<SyntheticToolResultDetails> {
2312
+ return (
2313
+ message?.role === "toolResult" &&
2314
+ (message.details as SyntheticToolResultDetails | undefined)?.__synthetic === true
2315
+ );
2316
+ }
2317
+
2288
2318
  function syntheticDetailsFor(
2289
2319
  reason: "aborted" | "error" | "skipped" | "length",
2290
2320
  errorMessage: string | undefined,