feat(agent): enable parallel tool execution via pi-agent-core patch
Replace sequential for+await tool dispatch with Promise.allSettled for parallel execution. All tool_execution_start events emit immediately, tools run concurrently, results are processed in original order. Also fix run-log toolStartTimes to key by toolCallId instead of toolName to prevent collisions with parallel same-name tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c012bff246
commit
a254daff01
4 changed files with 110 additions and 20 deletions
|
|
@ -44,6 +44,9 @@
|
|||
"overrides": {
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@mariozechner/pi-agent-core@0.52.9": "patches/@mariozechner__pi-agent-core@0.52.9.patch"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ function extractRunLogResultDetails(result: unknown): Record<string, unknown> |
|
|||
|
||||
function formatRunLogToolSummary(tool: string, details: Record<string, unknown> | null): string | undefined {
|
||||
if (!details) return undefined;
|
||||
if (details.error) return `error: ${details.message || details.error}`;
|
||||
if (details.error) return `error: ${details.code || details.message || details.error}`;
|
||||
switch (tool) {
|
||||
case "web_search": return `${details.count ?? 0} results`;
|
||||
case "web_fetch": {
|
||||
|
|
@ -780,17 +780,19 @@ export class Agent {
|
|||
|
||||
private handleRunLogEvent(event: AgentEvent) {
|
||||
if (event.type === "tool_execution_start") {
|
||||
const toolCallId = (event as any).toolCallId ?? "unknown";
|
||||
const toolName = (event as any).toolName ?? "unknown";
|
||||
this.toolStartTimes.set(toolName, Date.now());
|
||||
this.toolStartTimes.set(toolCallId, Date.now());
|
||||
this.runLog.log("tool_start", {
|
||||
tool: toolName,
|
||||
args: JSON.stringify((event as any).args ?? {}).slice(0, 500),
|
||||
});
|
||||
} else if (event.type === "tool_execution_end") {
|
||||
const toolCallId = (event as any).toolCallId ?? "unknown";
|
||||
const toolName = (event as any).toolName ?? "unknown";
|
||||
const startTime = this.toolStartTimes.get(toolName);
|
||||
const startTime = this.toolStartTimes.get(toolCallId);
|
||||
const duration_ms = startTime ? Date.now() - startTime : undefined;
|
||||
this.toolStartTimes.delete(toolName);
|
||||
this.toolStartTimes.delete(toolCallId);
|
||||
|
||||
// Extract result metadata for run-log persistence (survives session compaction)
|
||||
const result = (event as any).result;
|
||||
|
|
@ -806,7 +808,7 @@ export class Agent {
|
|||
result_summary: formatRunLogToolSummary(toolName, details),
|
||||
};
|
||||
if (details?.error) {
|
||||
toolEndData.error_type = String(details.error);
|
||||
toolEndData.error_type = details.code ? String(details.code) : String(details.error);
|
||||
}
|
||||
this.runLog.log("tool_end", toolEndData);
|
||||
}
|
||||
|
|
|
|||
80
patches/@mariozechner__pi-agent-core@0.52.9.patch
Normal file
80
patches/@mariozechner__pi-agent-core@0.52.9.patch
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
diff --git a/dist/agent-loop.js b/dist/agent-loop.js
|
||||
index b26d45753809da14df6409354e8c537684b9acd7..931399cc12bafea940fe99ab74ae288d71506e52 100644
|
||||
--- a/dist/agent-loop.js
|
||||
+++ b/dist/agent-loop.js
|
||||
@@ -206,17 +206,18 @@ async function streamAssistantResponse(context, config, signal, stream, streamFn
|
||||
*/
|
||||
async function executeToolCalls(tools, assistantMessage, signal, stream, getSteeringMessages) {
|
||||
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
|
||||
- const results = [];
|
||||
- let steeringMessages;
|
||||
- for (let index = 0; index < toolCalls.length; index++) {
|
||||
- const toolCall = toolCalls[index];
|
||||
- const tool = tools?.find((t) => t.name === toolCall.name);
|
||||
+ // Emit all tool_execution_start events immediately
|
||||
+ for (const toolCall of toolCalls) {
|
||||
stream.push({
|
||||
type: "tool_execution_start",
|
||||
toolCallId: toolCall.id,
|
||||
toolName: toolCall.name,
|
||||
args: toolCall.arguments,
|
||||
});
|
||||
+ }
|
||||
+ // Execute all tools in parallel
|
||||
+ const settled = await Promise.allSettled(toolCalls.map(async (toolCall) => {
|
||||
+ const tool = tools?.find((t) => t.name === toolCall.name);
|
||||
let result;
|
||||
let isError = false;
|
||||
try {
|
||||
@@ -240,6 +241,27 @@ async function executeToolCalls(tools, assistantMessage, signal, stream, getStee
|
||||
};
|
||||
isError = true;
|
||||
}
|
||||
+ return { result, isError };
|
||||
+ }));
|
||||
+ // Process results IN ORIGINAL ORDER (critical for LLM context)
|
||||
+ const results = [];
|
||||
+ let steeringMessages;
|
||||
+ for (let i = 0; i < settled.length; i++) {
|
||||
+ const entry = settled[i];
|
||||
+ const toolCall = toolCalls[i];
|
||||
+ let result;
|
||||
+ let isError;
|
||||
+ if (entry.status === "fulfilled") {
|
||||
+ result = entry.value.result;
|
||||
+ isError = entry.value.isError;
|
||||
+ }
|
||||
+ else {
|
||||
+ result = {
|
||||
+ content: [{ type: "text", text: entry.reason instanceof Error ? entry.reason.message : String(entry.reason) }],
|
||||
+ details: {},
|
||||
+ };
|
||||
+ isError = true;
|
||||
+ }
|
||||
stream.push({
|
||||
type: "tool_execution_end",
|
||||
toolCallId: toolCall.id,
|
||||
@@ -259,17 +281,12 @@ async function executeToolCalls(tools, assistantMessage, signal, stream, getStee
|
||||
results.push(toolResultMessage);
|
||||
stream.push({ type: "message_start", message: toolResultMessage });
|
||||
stream.push({ type: "message_end", message: toolResultMessage });
|
||||
- // Check for steering messages - skip remaining tools if user interrupted
|
||||
- if (getSteeringMessages) {
|
||||
- const steering = await getSteeringMessages();
|
||||
- if (steering.length > 0) {
|
||||
- steeringMessages = steering;
|
||||
- const remainingCalls = toolCalls.slice(index + 1);
|
||||
- for (const skipped of remainingCalls) {
|
||||
- results.push(skipToolCall(skipped, stream));
|
||||
- }
|
||||
- break;
|
||||
- }
|
||||
+ }
|
||||
+ // Check steering messages once after all tools complete
|
||||
+ if (getSteeringMessages) {
|
||||
+ const steering = await getSteeringMessages();
|
||||
+ if (steering.length > 0) {
|
||||
+ steeringMessages = steering;
|
||||
}
|
||||
}
|
||||
return { toolResults: results, steeringMessages };
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
|
|
@ -68,13 +68,18 @@ overrides:
|
|||
'@types/react': ^19.2.0
|
||||
'@types/react-dom': ^19.2.0
|
||||
|
||||
patchedDependencies:
|
||||
'@mariozechner/pi-agent-core@0.52.9':
|
||||
hash: befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00
|
||||
path: patches/@mariozechner__pi-agent-core@0.52.9.patch
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
version: 0.52.9(patch_hash=befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00)(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
|
|
@ -591,7 +596,7 @@ importers:
|
|||
dependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
version: 0.52.9(patch_hash=befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00)(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
|
|
@ -690,7 +695,7 @@ importers:
|
|||
devDependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
version: 0.52.9(patch_hash=befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00)(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: 'catalog:'
|
||||
version: 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
|
|
@ -12498,7 +12503,7 @@ snapshots:
|
|||
std-env: 3.10.0
|
||||
yoctocolors: 2.1.2
|
||||
|
||||
'@mariozechner/pi-agent-core@0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
|
||||
'@mariozechner/pi-agent-core@0.52.9(patch_hash=befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00)(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@mariozechner/pi-ai': 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -12537,7 +12542,7 @@ snapshots:
|
|||
'@mariozechner/pi-coding-agent@0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@mariozechner/jiti': 2.6.5
|
||||
'@mariozechner/pi-agent-core': 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-agent-core': 0.52.9(patch_hash=befbe3b9fd1a6d3e13dfa4927d9f0addd2f3b3454bd9d407c16dacb9a71c0f00)(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.52.9(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6))(ws@8.19.0)(zod@4.3.6)
|
||||
'@mariozechner/pi-tui': 0.52.9
|
||||
'@silvia-odwyer/photon-node': 0.3.4
|
||||
|
|
@ -15944,9 +15949,9 @@ snapshots:
|
|||
'@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-expo: 1.0.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1))
|
||||
globals: 16.5.0
|
||||
|
|
@ -15961,8 +15966,8 @@ snapshots:
|
|||
'@next/eslint-plugin-next': 16.1.6
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||
|
|
@ -15984,7 +15989,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
|
|
@ -15995,18 +16000,18 @@ snapshots:
|
|||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -16019,7 +16024,7 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -16030,7 +16035,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue