Show in-progress tool calls
This commit is contained in:
@@ -32,7 +32,7 @@ type ToolLogMetadata = {
|
||||
kind: "tool_call";
|
||||
toolCallId?: string;
|
||||
toolName?: string;
|
||||
status?: "completed" | "failed";
|
||||
status?: "initiated" | "completed" | "failed";
|
||||
summary?: string;
|
||||
args?: Record<string, unknown>;
|
||||
startedAt?: string;
|
||||
@@ -171,28 +171,47 @@ function isToolCallLogMessage(message: Message) {
|
||||
}
|
||||
|
||||
function buildOptimisticToolMessage(event: ToolCallEvent): Message {
|
||||
const metadata: ToolLogMetadata = {
|
||||
kind: "tool_call",
|
||||
toolCallId: event.toolCallId,
|
||||
toolName: event.name,
|
||||
status: event.status,
|
||||
summary: event.summary,
|
||||
args: event.args,
|
||||
startedAt: event.startedAt,
|
||||
error: event.error ?? null,
|
||||
resultPreview: event.resultPreview ?? null,
|
||||
};
|
||||
|
||||
if (event.completedAt) metadata.completedAt = event.completedAt;
|
||||
if (typeof event.durationMs === "number") metadata.durationMs = event.durationMs;
|
||||
|
||||
return {
|
||||
id: `temp-tool-${event.toolCallId}`,
|
||||
createdAt: event.completedAt ?? new Date().toISOString(),
|
||||
createdAt: event.completedAt ?? event.startedAt ?? new Date().toISOString(),
|
||||
role: "tool",
|
||||
content: event.summary,
|
||||
name: event.name,
|
||||
metadata: {
|
||||
kind: "tool_call",
|
||||
toolCallId: event.toolCallId,
|
||||
toolName: event.name,
|
||||
status: event.status,
|
||||
summary: event.summary,
|
||||
args: event.args,
|
||||
startedAt: event.startedAt,
|
||||
completedAt: event.completedAt,
|
||||
durationMs: event.durationMs,
|
||||
error: event.error ?? null,
|
||||
resultPreview: event.resultPreview ?? null,
|
||||
} satisfies ToolLogMetadata,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
function upsertOptimisticToolMessage(messages: Message[], event: ToolCallEvent) {
|
||||
const toolMessage = buildOptimisticToolMessage(event);
|
||||
const existingIndex = messages.findIndex(
|
||||
(message) => asToolLogMetadata(message.metadata)?.toolCallId === event.toolCallId || message.id === `temp-tool-${event.toolCallId}`
|
||||
);
|
||||
if (existingIndex >= 0) {
|
||||
return messages.map((message, index) => (index === existingIndex ? { ...toolMessage, id: message.id } : message));
|
||||
}
|
||||
|
||||
const assistantIndex = messages.findIndex(
|
||||
(message, index, all) => index === all.length - 1 && message.id.startsWith("temp-assistant-")
|
||||
);
|
||||
if (assistantIndex < 0) return messages.concat(toolMessage);
|
||||
return [...messages.slice(0, assistantIndex), toolMessage, ...messages.slice(assistantIndex)];
|
||||
}
|
||||
|
||||
function getModelOptions(catalog: ModelCatalogResponse["providers"], provider: Provider) {
|
||||
const providerModels = catalog[provider]?.models ?? [];
|
||||
if (providerModels.length) return providerModels;
|
||||
@@ -602,7 +621,12 @@ async function main() {
|
||||
for (const message of messages) {
|
||||
const toolMeta = asToolLogMetadata(message.metadata);
|
||||
if (message.role === "tool" && toolMeta) {
|
||||
const prefix = toolMeta.status === "failed" ? "{red-fg}[tool failed]{/red-fg}" : "{cyan-fg}[tool]{/cyan-fg}";
|
||||
const prefix =
|
||||
toolMeta.status === "failed"
|
||||
? "{red-fg}[tool failed]{/red-fg}"
|
||||
: toolMeta.status === "initiated"
|
||||
? "{yellow-fg}[tool running]{/yellow-fg}"
|
||||
: "{cyan-fg}[tool]{/cyan-fg}";
|
||||
const summary = toolMeta.summary?.trim() || message.content.trim() || "Tool call executed.";
|
||||
parts.push(`${prefix} ${escapeTags(summary)}`);
|
||||
continue;
|
||||
@@ -1083,29 +1107,7 @@ async function main() {
|
||||
},
|
||||
onToolCall: (payload) => {
|
||||
if (!pendingChatState) return;
|
||||
const alreadyPresent = pendingChatState.messages.some(
|
||||
(message) =>
|
||||
asToolLogMetadata(message.metadata)?.toolCallId === payload.toolCallId || message.id === `temp-tool-${payload.toolCallId}`
|
||||
);
|
||||
if (alreadyPresent) return;
|
||||
|
||||
const toolMessage = buildOptimisticToolMessage(payload);
|
||||
const assistantIndex = pendingChatState.messages.findIndex(
|
||||
(message, index, all) => index === all.length - 1 && message.id.startsWith("temp-assistant-")
|
||||
);
|
||||
|
||||
if (assistantIndex < 0) {
|
||||
pendingChatState = { ...pendingChatState, messages: pendingChatState.messages.concat(toolMessage) };
|
||||
} else {
|
||||
pendingChatState = {
|
||||
...pendingChatState,
|
||||
messages: [
|
||||
...pendingChatState.messages.slice(0, assistantIndex),
|
||||
toolMessage,
|
||||
...pendingChatState.messages.slice(assistantIndex),
|
||||
],
|
||||
};
|
||||
}
|
||||
pendingChatState = { ...pendingChatState, messages: upsertOptimisticToolMessage(pendingChatState.messages, payload) };
|
||||
|
||||
queueTranscriptScrollToBottomIfFollowing();
|
||||
updateUI();
|
||||
|
||||
@@ -55,12 +55,12 @@ export type Message = {
|
||||
export type ToolCallEvent = {
|
||||
toolCallId: string;
|
||||
name: string;
|
||||
status: "completed" | "failed";
|
||||
status: "initiated" | "completed" | "failed";
|
||||
summary: string;
|
||||
args: Record<string, unknown>;
|
||||
startedAt: string;
|
||||
completedAt: string;
|
||||
durationMs: number;
|
||||
completedAt?: string;
|
||||
durationMs?: number;
|
||||
error?: string;
|
||||
resultPreview?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user