@oh-my-pi/pi-agent-core 15.11.0 → 15.11.2
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 +9 -0
- package/dist/types/types.d.ts +2 -1
- package/package.json +6 -6
- package/src/agent-loop.ts +30 -2
- package/src/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.11.2] - 2026-06-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `AgentTool.concurrency` now also accepts a per-call resolver function `(args) => "shared" | "exclusive"`, letting tools pick the scheduling mode from the call's arguments (a throwing resolver falls back to `"exclusive"`)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed whitespace-only error tool results so Anthropic requests no longer 400 with `tool_result: content cannot be empty if is_error is true` and wedge the session on every subsequent turn
|
|
5
14
|
## [15.11.0] - 2026-06-10
|
|
6
15
|
### Breaking Changes
|
|
7
16
|
|
package/dist/types/types.d.ts
CHANGED
|
@@ -382,8 +382,9 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
382
382
|
* Concurrency mode for tool scheduling when multiple calls are in one turn.
|
|
383
383
|
* - "shared": can run alongside other shared tools (default)
|
|
384
384
|
* - "exclusive": runs alone; other tools wait until it finishes
|
|
385
|
+
* - function: resolved per call from the (raw, pre-validation) arguments
|
|
385
386
|
*/
|
|
386
|
-
concurrency?: "shared" | "exclusive";
|
|
387
|
+
concurrency?: "shared" | "exclusive" | ((args: Partial<Static<TParameters>>) => "shared" | "exclusive");
|
|
387
388
|
/** If true, argument validation errors are non-fatal: raw args are passed to execute() instead of returning an error to the LLM. */
|
|
388
389
|
lenientArgValidation?: boolean;
|
|
389
390
|
/**
|
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": "15.11.
|
|
4
|
+
"version": "15.11.2",
|
|
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,11 +35,11 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "15.11.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "15.11.
|
|
40
|
-
"@oh-my-pi/pi-natives": "15.11.
|
|
41
|
-
"@oh-my-pi/pi-utils": "15.11.
|
|
42
|
-
"@oh-my-pi/snapcompact": "15.11.
|
|
38
|
+
"@oh-my-pi/pi-ai": "15.11.2",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "15.11.2",
|
|
40
|
+
"@oh-my-pi/pi-natives": "15.11.2",
|
|
41
|
+
"@oh-my-pi/pi-utils": "15.11.2",
|
|
42
|
+
"@oh-my-pi/snapcompact": "15.11.2",
|
|
43
43
|
"@opentelemetry/api": "^1.9.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -148,6 +148,16 @@ function snapshotAssistantMessageEvent(event: AssistantMessageEvent): AssistantM
|
|
|
148
148
|
* (missing `content` array → crash on reload). We coerce at the single boundary where untyped
|
|
149
149
|
* results enter the agent loop, so every downstream consumer can rely on the type.
|
|
150
150
|
*/
|
|
151
|
+
const EMPTY_ERROR_TOOL_RESULT_TEXT = "Tool failed with no output.";
|
|
152
|
+
|
|
153
|
+
function hasSubstantiveToolResultContent(content: AgentToolResult["content"]): boolean {
|
|
154
|
+
for (const block of content) {
|
|
155
|
+
if (block.type === "image") return true;
|
|
156
|
+
if (block.type === "text" && block.text.trim().length > 0) return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
151
161
|
function coerceToolResult(raw: unknown): { result: AgentToolResult<unknown>; malformed: boolean } {
|
|
152
162
|
const rawObj = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : null;
|
|
153
163
|
const rawContent = rawObj?.content;
|
|
@@ -193,8 +203,14 @@ function coerceToolResult(raw: unknown): { result: AgentToolResult<unknown>; mal
|
|
|
193
203
|
text: `Tool returned an invalid result: ${invalidBlocks} content block${invalidBlocks === 1 ? "" : "s"} had an unsupported shape.`,
|
|
194
204
|
});
|
|
195
205
|
}
|
|
206
|
+
const isError = explicitError || invalidBlocks > 0;
|
|
207
|
+
// Anthropic rejects tool_result blocks with is_error: true and empty content.
|
|
208
|
+
if (isError && !hasSubstantiveToolResultContent(content)) {
|
|
209
|
+
content.length = 0;
|
|
210
|
+
content.push({ type: "text", text: EMPTY_ERROR_TOOL_RESULT_TEXT });
|
|
211
|
+
}
|
|
196
212
|
return {
|
|
197
|
-
result: { content, details, ...(
|
|
213
|
+
result: { content, details, ...(isError ? { isError: true } : {}) },
|
|
198
214
|
malformed: invalidBlocks > 0,
|
|
199
215
|
};
|
|
200
216
|
}
|
|
@@ -1547,7 +1563,19 @@ async function executeToolCalls(
|
|
|
1547
1563
|
|
|
1548
1564
|
for (let index = 0; index < records.length; index++) {
|
|
1549
1565
|
const record = records[index];
|
|
1550
|
-
const
|
|
1566
|
+
const concurrencyMode = record.tool?.concurrency;
|
|
1567
|
+
let concurrency: "shared" | "exclusive";
|
|
1568
|
+
if (typeof concurrencyMode === "function") {
|
|
1569
|
+
// Resolved from raw pre-validation args; a throwing resolver must not
|
|
1570
|
+
// take down the whole batch, so fall back to the safe (serial) mode.
|
|
1571
|
+
try {
|
|
1572
|
+
concurrency = concurrencyMode(record.args);
|
|
1573
|
+
} catch {
|
|
1574
|
+
concurrency = "exclusive";
|
|
1575
|
+
}
|
|
1576
|
+
} else {
|
|
1577
|
+
concurrency = concurrencyMode ?? "shared";
|
|
1578
|
+
}
|
|
1551
1579
|
const start = concurrency === "exclusive" ? Promise.all([lastExclusive, ...sharedTasks]) : lastExclusive;
|
|
1552
1580
|
const task = start.then(() => runTool(record, index));
|
|
1553
1581
|
tasks.push(task);
|
package/src/types.ts
CHANGED
|
@@ -456,8 +456,9 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
456
456
|
* Concurrency mode for tool scheduling when multiple calls are in one turn.
|
|
457
457
|
* - "shared": can run alongside other shared tools (default)
|
|
458
458
|
* - "exclusive": runs alone; other tools wait until it finishes
|
|
459
|
+
* - function: resolved per call from the (raw, pre-validation) arguments
|
|
459
460
|
*/
|
|
460
|
-
concurrency?: "shared" | "exclusive";
|
|
461
|
+
concurrency?: "shared" | "exclusive" | ((args: Partial<Static<TParameters>>) => "shared" | "exclusive");
|
|
461
462
|
/** If true, argument validation errors are non-fatal: raw args are passed to execute() instead of returning an error to the LLM. */
|
|
462
463
|
lenientArgValidation?: boolean;
|
|
463
464
|
/**
|