refactor tool-call detail contract for unknown tools

This commit is contained in:
Mohamed Boudra
2026-02-09 16:50:04 +07:00
parent ef29319baf
commit 7ec3f2d3cf
31 changed files with 228 additions and 177 deletions
@@ -106,23 +106,23 @@ function MergedToolCallItem({ item }: { item: MergedToolCall }) {
{isExpanded && (
<View style={stylesheet.toolContent}>
{item.rawInput && (
{item.input && (
<View style={stylesheet.section}>
<Text style={stylesheet.sectionTitle}>Input:</Text>
<Text style={stylesheet.code}>
{JSON.stringify(item.rawInput, null, 2)}
{JSON.stringify(item.input, null, 2)}
</Text>
</View>
)}
{item.rawOutput && (
{item.output && (
<View style={stylesheet.section}>
<Text style={stylesheet.sectionTitle}>Output:</Text>
<Text style={stylesheet.code}>
{JSON.stringify(item.rawOutput, null, 2)}
{JSON.stringify(item.output, null, 2)}
</Text>
</View>
)}
{!item.rawInput && !item.rawOutput && (
{!item.input && !item.output && (
<Text style={stylesheet.emptyText}>
No details available
</Text>
@@ -1101,8 +1101,8 @@ function PermissionRequestCard({
<ToolCallDetailsContent
detail={{
type: "unknown",
rawInput: request.input ?? null,
rawOutput: null,
input: request.input ?? null,
output: null,
}}
maxHeight={200}
/>
+6 -6
View File
@@ -1198,8 +1198,8 @@ export const ToolCall = memo(function ToolCall({
if (args !== undefined || result !== undefined) {
return {
type: "unknown",
rawInput: args ?? null,
rawOutput: result ?? null,
input: args ?? null,
output: result ?? null,
};
}
return undefined;
@@ -1208,8 +1208,8 @@ export const ToolCall = memo(function ToolCall({
const displayDetail =
effectiveDetail ?? {
type: "unknown",
rawInput: null,
rawOutput: null,
input: null,
output: null,
};
const displayModel = useMemo(
@@ -1235,8 +1235,8 @@ export const ToolCall = memo(function ToolCall({
Boolean(error) ||
(effectiveDetail
? effectiveDetail.type !== "unknown" ||
effectiveDetail.rawInput !== null ||
effectiveDetail.rawOutput !== null
effectiveDetail.input !== null ||
effectiveDetail.output !== null
: false);
const handleToggle = useCallback(() => {
@@ -139,8 +139,8 @@ export function ToolCallDetailsContent({
);
} else if (detail?.type === "unknown") {
const sectionsFromTopLevel = [
{ title: "Input", value: detail.rawInput },
{ title: "Output", value: detail.rawOutput },
{ title: "Input", value: detail.input },
{ title: "Output", value: detail.output },
].filter((entry) => entry.value !== null && entry.value !== undefined);
for (const section of sectionsFromTopLevel) {
+18 -18
View File
@@ -41,8 +41,8 @@ export interface ToolCall {
title: string;
status?: 'pending' | 'in_progress' | 'completed' | 'failed';
toolKind?: 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other';
rawInput?: Record<string, unknown>;
rawOutput?: Record<string, unknown>;
input?: Record<string, unknown>;
output?: Record<string, unknown>;
content?: unknown[];
locations?: unknown[];
}
@@ -53,8 +53,8 @@ export interface ToolCallUpdate {
title?: string | null;
status?: 'pending' | 'in_progress' | 'completed' | 'failed' | null;
toolKind?: 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other' | null;
rawInput?: Record<string, unknown>;
rawOutput?: Record<string, unknown>;
input?: Record<string, unknown>;
output?: Record<string, unknown>;
content?: unknown[] | null;
locations?: unknown[] | null;
}
@@ -109,8 +109,8 @@ export interface MergedToolCall {
title: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
toolKind?: 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other';
rawInput?: Record<string, unknown>;
rawOutput?: Record<string, unknown>;
input?: Record<string, unknown>;
output?: Record<string, unknown>;
content?: unknown[];
locations?: unknown[];
startTimestamp: Date;
@@ -137,8 +137,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
title: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
toolKind?: 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other';
rawInput?: Record<string, unknown>;
rawOutput?: Record<string, unknown>;
input?: Record<string, unknown>;
output?: Record<string, unknown>;
content?: unknown[];
locations?: unknown[];
startTimestamp: Date;
@@ -210,8 +210,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
title: update.title,
status: update.status || 'pending',
toolKind: update.toolKind,
rawInput: update.rawInput,
rawOutput: update.rawOutput,
input: update.input,
output: update.output,
content: update.content,
locations: update.locations,
startTimestamp: activity.timestamp,
@@ -223,8 +223,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
existing.title = update.title;
if (update.status) existing.status = update.status;
if (update.toolKind) existing.toolKind = update.toolKind;
if (update.rawInput) existing.rawInput = update.rawInput;
if (update.rawOutput) existing.rawOutput = update.rawOutput;
if (update.input) existing.input = update.input;
if (update.output) existing.output = update.output;
if (update.content) existing.content = update.content;
if (update.locations) existing.locations = update.locations;
existing.endTimestamp = activity.timestamp;
@@ -236,8 +236,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
if (update.title) existing.title = update.title;
if (update.status) existing.status = update.status;
if (update.toolKind) existing.toolKind = update.toolKind;
if (update.rawInput) existing.rawInput = { ...existing.rawInput, ...update.rawInput };
if (update.rawOutput) existing.rawOutput = { ...existing.rawOutput, ...update.rawOutput };
if (update.input) existing.input = { ...existing.input, ...update.input };
if (update.output) existing.output = { ...existing.output, ...update.output };
if (update.content) existing.content = update.content;
if (update.locations) existing.locations = update.locations;
existing.endTimestamp = activity.timestamp;
@@ -251,8 +251,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
title: update.title || 'Tool Call',
status: update.status || 'pending',
toolKind: update.toolKind || undefined,
rawInput: update.rawInput,
rawOutput: update.rawOutput,
input: update.input,
output: update.output,
content: update.content || undefined,
locations: update.locations || undefined,
startTimestamp: activity.timestamp,
@@ -280,8 +280,8 @@ export function groupActivities(activities: AgentActivity[]): Array<GroupedTextM
title: toolCall.title,
status: toolCall.status,
toolKind: toolCall.toolKind,
rawInput: toolCall.rawInput,
rawOutput: toolCall.rawOutput,
input: toolCall.input,
output: toolCall.output,
content: toolCall.content,
locations: toolCall.locations,
startTimestamp: toolCall.startTimestamp,
+2 -2
View File
@@ -28,8 +28,8 @@ const toolCallEvent = (): AgentStreamEventPayload => ({
status: "running",
detail: {
type: "unknown",
rawInput: { command: "echo hi" },
rawOutput: null,
input: { command: "echo hi" },
output: null,
},
error: null,
},
@@ -215,8 +215,8 @@ function buildToolEvent({
}): AgentStreamEventPayload {
const canonicalDetail: ToolCallDetail = detail ?? {
type: "unknown",
rawInput: input,
rawOutput: output,
input: input,
output: output,
};
const baseItem = {
+6 -6
View File
@@ -42,8 +42,8 @@ function canonicalToolTimeline(params: {
}): AgentStreamEventPayload {
const detail: ToolCallDetail = params.detail ?? {
type: "unknown",
rawInput: params.input ?? null,
rawOutput: params.output ?? null,
input: params.input ?? null,
output: params.output ?? null,
};
const baseItem = {
@@ -150,8 +150,8 @@ describe("stream reducer canonical tool calls", () => {
assert.strictEqual(tools[0].payload.data.status, "completed");
assert.deepStrictEqual(tools[0].payload.data.detail, {
type: "unknown",
rawInput: { command: "pwd" },
rawOutput: {
input: { command: "pwd" },
output: {
output: "/tmp/repo\n",
exitCode: 0,
},
@@ -282,8 +282,8 @@ describe("stream reducer canonical tool calls", () => {
assert.ok(tool);
assert.deepStrictEqual(tool.payload.data.detail, {
type: "unknown",
rawInput: { path: "README.md" },
rawOutput: { content: "hello" },
input: { path: "README.md" },
output: { content: "hello" },
});
assert.strictEqual(tool.payload.data.status, "completed");
});
+7 -7
View File
@@ -353,20 +353,20 @@ function mergeToolCallDetail(existing: ToolCallDetail, incoming: ToolCallDetail)
if (existing.type === "unknown" && incoming.type === "unknown") {
return {
type: "unknown",
rawInput: mergeUnknownValue(existing.rawInput, incoming.rawInput),
rawOutput: mergeUnknownValue(existing.rawOutput, incoming.rawOutput),
input: mergeUnknownValue(existing.input, incoming.input),
output: mergeUnknownValue(existing.output, incoming.output),
};
}
if (existing.type === incoming.type) {
return { ...existing, ...incoming };
return { ...existing, ...incoming } as ToolCallDetail;
}
return incoming;
}
function rawInputFromDetail(detail: ToolCallDetail): unknown | null {
return detail.type === "unknown" ? detail.rawInput : null;
function inputFromUnknownDetail(detail: ToolCallDetail): unknown | null {
return detail.type === "unknown" ? detail.input : null;
}
function mergeAgentToolCallStatus(
@@ -552,7 +552,7 @@ export function reduceStreamUpdate(
// as Tasks when possible and otherwise hide it to avoid a stuck loading tool call.
const tasks = extractTaskEntriesFromToolCall(
item.name,
rawInputFromDetail(item.detail)
inputFromUnknownDetail(item.detail)
);
if (tasks) {
nextState = appendTodoList(
@@ -570,7 +570,7 @@ export function reduceStreamUpdate(
const tasks = extractTaskEntriesFromToolCall(
item.name,
rawInputFromDetail(item.detail)
inputFromUnknownDetail(item.detail)
);
if (tasks) {
nextState = appendTodoList(
@@ -45,8 +45,8 @@ describe("tool-call-display", () => {
error: null,
detail: {
type: "unknown",
rawInput: null,
rawOutput: null,
input: null,
output: null,
},
metadata: {
subAgentActivity: "Running tests",
@@ -66,8 +66,8 @@ describe("tool-call-display", () => {
error: null,
detail: {
type: "unknown",
rawInput: null,
rawOutput: null,
input: null,
output: null,
},
});
@@ -83,8 +83,8 @@ describe("tool-call-display", () => {
error: null,
detail: {
type: "unknown",
rawInput: { command: "npm run test" },
rawOutput: null,
input: { command: "npm run test" },
output: null,
},
});
@@ -100,8 +100,8 @@ describe("tool-call-display", () => {
error: { message: "boom" },
detail: {
type: "unknown",
rawInput: { command: "false" },
rawOutput: null,
input: { command: "false" },
output: null,
},
});