Show in-progress tool calls

This commit is contained in:
2026-06-05 22:20:56 -07:00
parent f71b69ca8b
commit fccc8110f4
14 changed files with 382 additions and 177 deletions

View File

@@ -292,15 +292,17 @@ type ToolAwareCompletionParams = {
};
};
export type ToolExecutionStatus = "initiated" | "completed" | "failed";
export type ToolExecutionEvent = {
toolCallId: string;
name: string;
status: "completed" | "failed";
status: ToolExecutionStatus;
summary: string;
args: Record<string, unknown>;
startedAt: string;
completedAt: string;
durationMs: number;
completedAt?: string;
durationMs?: number;
error?: string;
resultPreview?: string;
};
@@ -328,10 +330,13 @@ function toSingleLine(value: string, maxLength = 220) {
);
}
function buildToolSummary(name: string, args: Record<string, unknown>, status: "completed" | "failed", error?: string) {
function buildToolSummary(name: string, args: Record<string, unknown>, status: ToolExecutionStatus, error?: string) {
const errSuffix = status === "failed" && error ? ` Error: ${toSingleLine(error, 140)}` : "";
if (name === "web_search") {
const query = typeof args.query === "string" ? args.query.trim() : "";
if (status === "initiated") {
return query ? `Searching web for '${toSingleLine(query, 100)}'.` : "Searching web.";
}
if (status === "completed") {
return query ? `Performed web search for '${toSingleLine(query, 100)}'.` : "Performed web search.";
}
@@ -340,6 +345,9 @@ function buildToolSummary(name: string, args: Record<string, unknown>, status: "
if (name === "fetch_url") {
const url = typeof args.url === "string" ? args.url.trim() : "";
if (status === "initiated") {
return url ? `Fetching URL ${toSingleLine(url, 140)}.` : "Fetching URL.";
}
if (status === "completed") {
return url ? `Fetched URL ${toSingleLine(url, 140)}.` : "Fetched URL.";
}
@@ -348,6 +356,9 @@ function buildToolSummary(name: string, args: Record<string, unknown>, status: "
if (name === "codex_exec") {
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
if (status === "initiated") {
return prompt ? `Running Codex task: '${toSingleLine(prompt, 120)}'.` : "Running Codex task.";
}
if (status === "completed") {
return prompt ? `Ran Codex task: '${toSingleLine(prompt, 120)}'.` : "Ran Codex task.";
}
@@ -356,6 +367,9 @@ function buildToolSummary(name: string, args: Record<string, unknown>, status: "
if (name === "shell_exec") {
const command = typeof args.command === "string" ? args.command.trim() : "";
if (status === "initiated") {
return command ? `Running devbox shell command: '${toSingleLine(command, 120)}'.` : "Running devbox shell command.";
}
if (status === "completed") {
return command ? `Ran devbox shell command: '${toSingleLine(command, 120)}'.` : "Ran devbox shell command.";
}
@@ -364,6 +378,9 @@ function buildToolSummary(name: string, args: Record<string, unknown>, status: "
: `Devbox shell command failed.${errSuffix}`;
}
if (status === "initiated") {
return `Running tool '${name}'.`;
}
if (status === "completed") {
return `Ran tool '${name}'.`;
}
@@ -969,17 +986,55 @@ function normalizeModelToolCalls(toolCalls: any[], round: number): NormalizedToo
}));
}
async function executeToolCallAndBuildEvent(
call: NormalizedToolCall,
params: ToolAwareCompletionParams
): Promise<{ event: ToolExecutionEvent; toolResult: ToolRunOutcome }> {
type PreparedToolCallExecution = {
startedAtMs: number;
startedAt: string;
parsedArgs: Record<string, unknown>;
eventArgs: Record<string, unknown>;
parseError?: unknown;
};
function prepareToolCallExecution(call: NormalizedToolCall): { event: ToolExecutionEvent; execution: PreparedToolCallExecution } {
const startedAtMs = Date.now();
const startedAt = new Date(startedAtMs).toISOString();
let toolResult: ToolRunOutcome;
let parsedArgs: Record<string, unknown> = {};
let parseError: unknown;
try {
parsedArgs = toRecord(parseToolArgs(call.arguments));
toolResult = await executeTool(call.name, parsedArgs);
} catch (err) {
parseError = err;
}
const eventArgs = buildEventArgs(call.name, parsedArgs);
return {
event: {
toolCallId: call.id,
name: call.name,
status: "initiated",
summary: buildToolSummary(call.name, eventArgs, "initiated"),
args: eventArgs,
startedAt,
},
execution: {
startedAtMs,
startedAt,
parsedArgs,
eventArgs,
parseError,
},
};
}
async function executeToolCallAndBuildEvent(
call: NormalizedToolCall,
execution: PreparedToolCallExecution,
params: ToolAwareCompletionParams
): Promise<{ event: ToolExecutionEvent; toolResult: ToolRunOutcome }> {
let toolResult: ToolRunOutcome;
try {
if (execution.parseError) throw execution.parseError;
toolResult = await executeTool(call.name, execution.parsedArgs);
} catch (err: any) {
toolResult = {
ok: false,
@@ -996,16 +1051,15 @@ async function executeToolCallAndBuildEvent(
: undefined;
const completedAtMs = Date.now();
const eventArgs = buildEventArgs(call.name, parsedArgs);
const event: ToolExecutionEvent = {
toolCallId: call.id,
name: call.name,
status,
summary: buildToolSummary(call.name, eventArgs, status, error),
args: eventArgs,
startedAt,
summary: buildToolSummary(call.name, execution.eventArgs, status, error),
args: execution.eventArgs,
startedAt: execution.startedAt,
completedAt: new Date(completedAtMs).toISOString(),
durationMs: completedAtMs - startedAtMs,
durationMs: completedAtMs - execution.startedAtMs,
error,
resultPreview: buildResultPreview(toolResult),
};
@@ -1068,7 +1122,8 @@ export async function runToolAwareOpenAIChat(params: ToolAwareCompletionParams):
input.push(...outputItems);
for (const call of normalizedToolCalls) {
const { event, toolResult } = await executeToolCallAndBuildEvent(call, params);
const { execution } = prepareToolCallExecution(call);
const { event, toolResult } = await executeToolCallAndBuildEvent(call, execution, params);
toolEvents.push(event);
input.push({
@@ -1155,7 +1210,8 @@ export async function runToolAwareChatCompletions(params: ToolAwareCompletionPar
conversation.push(assistantToolCallMessage);
for (const call of normalizedToolCalls) {
const { event, toolResult } = await executeToolCallAndBuildEvent(call, params);
const { execution } = prepareToolCallExecution(call);
const { event, toolResult } = await executeToolCallAndBuildEvent(call, execution, params);
toolEvents.push(event);
conversation.push({
@@ -1299,7 +1355,9 @@ export async function* runToolAwareOpenAIChatStream(
input.push(...responseOutputItems);
for (const call of normalizedToolCalls) {
const { event, toolResult } = await executeToolCallAndBuildEvent(call, params);
const { event: initiatedEvent, execution } = prepareToolCallExecution(call);
yield { type: "tool_call", event: initiatedEvent };
const { event, toolResult } = await executeToolCallAndBuildEvent(call, execution, params);
toolEvents.push(event);
yield { type: "tool_call", event };
input.push({
@@ -1436,7 +1494,9 @@ export async function* runToolAwareChatCompletionsStream(
conversation.push(assistantToolCallMessage);
for (const call of normalizedToolCalls) {
const { event, toolResult } = await executeToolCallAndBuildEvent(call, params);
const { event: initiatedEvent, execution } = prepareToolCallExecution(call);
yield { type: "tool_call", event: initiatedEvent };
const { event, toolResult } = await executeToolCallAndBuildEvent(call, execution, params);
toolEvents.push(event);
yield { type: "tool_call", event };
conversation.push({