Skip to content

Commit f22283b

Browse files
committed
Distinguish OpenAI function calls in logs
1 parent 9bdaeb5 commit f22283b

2 files changed

Lines changed: 73 additions & 9 deletions

File tree

web/src/components/EventLogModal.tsx

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,9 @@ function StructuredContentPart({ part, index }: { part: Record<string, unknown>;
10321032
if (type === "tool_use") {
10331033
return <ToolUsePart part={part} index={index} />;
10341034
}
1035+
if (type === "function_call" || type === "custom_tool_call") {
1036+
return <FunctionCallPart part={part} type={type} />;
1037+
}
10351038
if (type === "tool_result") {
10361039
return <ToolResultPart part={part} />;
10371040
}
@@ -1097,6 +1100,24 @@ function ToolUsePart({ part, index }: { part: Record<string, unknown>; index: nu
10971100
);
10981101
}
10991102

1103+
function FunctionCallPart({ part, type }: { part: Record<string, unknown>; type: string }) {
1104+
const name = stringRecordValue(part.name) || "function";
1105+
const id = stringRecordValue(part.call_id) || stringRecordValue(part.id);
1106+
const input = type === "custom_tool_call" ? part.input : part.arguments;
1107+
return (
1108+
<div className="tool-use-card">
1109+
<div className="tool-use-header">
1110+
<div className="tool-use-title">
1111+
<span className="part-type-badge">{type}</span>
1112+
<span className="tool-name">{name}</span>
1113+
</div>
1114+
{id && <span className="tool-id">{id}</span>}
1115+
</div>
1116+
<JsonValueBlock label={type === "custom_tool_call" ? "Input" : "Arguments"} value={input ?? {}} />
1117+
</div>
1118+
);
1119+
}
1120+
11001121
function ToolResultPart({ part }: { part: Record<string, unknown> }) {
11011122
const id = stringRecordValue(part.tool_use_id) || stringRecordValue(part.id) || stringRecordValue(part.call_id);
11021123
return (
@@ -1187,13 +1208,41 @@ function JsonValueBlock({ label, value }: { label: string; value: unknown }) {
11871208

11881209
function visualContentParts(turn: Turn): Record<string, unknown>[] | null {
11891210
const raw = isRecord(turn.raw) ? turn.raw : null;
1211+
const rawType = stringRecordValue(raw?.type);
1212+
if (raw && isStandaloneVisualPart(rawType)) return [raw];
1213+
11901214
const content = raw?.content;
1191-
if (!Array.isArray(content)) return null;
1192-
const parts = content.filter(isRecord);
1215+
const output = raw?.output;
1216+
const parts = Array.isArray(content)
1217+
? content.filter(isRecord)
1218+
: Array.isArray(output)
1219+
? output.flatMap(responsesOutputVisualParts)
1220+
: [];
11931221
if (parts.length === 0) return null;
11941222
return parts.some((part) => !isPlainTextPart(part)) ? parts : null;
11951223
}
11961224

1225+
function responsesOutputVisualParts(item: unknown): Record<string, unknown>[] {
1226+
if (!isRecord(item)) return [];
1227+
const type = stringRecordValue(item.type);
1228+
if (type === "message" && Array.isArray(item.content)) {
1229+
return item.content.filter(isRecord);
1230+
}
1231+
return isStandaloneVisualPart(type) ? [item] : [];
1232+
}
1233+
1234+
function isStandaloneVisualPart(type: string): boolean {
1235+
return (
1236+
type === "function_call" ||
1237+
type === "custom_tool_call" ||
1238+
type === "function_call_output" ||
1239+
type === "custom_tool_call_output" ||
1240+
type === "image_generation_call" ||
1241+
type === "web_search_call" ||
1242+
type === "reasoning"
1243+
);
1244+
}
1245+
11971246
function isPlainTextPart(part: Record<string, unknown>): boolean {
11981247
const type = stringRecordValue(part.type);
11991248
return (type === "text" || type === "input_text" || type === "output_text") && typeof part.text === "string";
@@ -1293,15 +1342,20 @@ function startsWithCollapsibleBlock(text: string): boolean {
12931342
}
12941343

12951344
function startsWithToolBlock(text: string): boolean {
1296-
return text.startsWith("**[tool_use ") || text.startsWith("**[tool_result");
1345+
return (
1346+
text.startsWith("**[tool_use ") ||
1347+
text.startsWith("**[tool_result") ||
1348+
text.startsWith("**[function_call ") ||
1349+
text.startsWith("**[custom_tool_call ")
1350+
);
12971351
}
12981352

12991353
function startsWithWebSearchCallBlock(text: string): boolean {
13001354
return text.startsWith("**[web_search_call ");
13011355
}
13021356

13031357
function chatPreview(turn: Turn): string {
1304-
const text = turnText(turn).replace(/[`*_#>\[\]()]/g, "").replace(/\s+/g, " ").trim();
1358+
const text = turnText(turn).replace(/[`*#>\[\]()]/g, "").replace(/\s+/g, " ").trim();
13051359
if (!text) return "(empty)";
13061360
return text.length > 160 ? text.slice(0, 160) + "..." : text;
13071361
}

web/src/lib/protocol.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ function responsesInputToTurn(raw: unknown): Turn {
430430
const json = formatPartialJSON(args);
431431
return {
432432
role: "assistant",
433-
text: toolUseMarkdown(name, m.call_id, json, "json"),
433+
text: functionCallMarkdown("function_call", name, m.call_id, json, "json"),
434434
raw: m,
435435
};
436436
}
@@ -439,7 +439,7 @@ function responsesInputToTurn(raw: unknown): Turn {
439439
const input = typeof m.input === "string" ? m.input : JSON.stringify(m.input ?? "", null, 2);
440440
return {
441441
role: "assistant",
442-
text: toolUseMarkdown(name, m.call_id, input, customToolLanguage(name, input)),
442+
text: functionCallMarkdown("custom_tool_call", name, m.call_id, input, customToolLanguage(name, input)),
443443
raw: m,
444444
};
445445
}
@@ -560,6 +560,16 @@ function toolUseMarkdown(name: string, rawCallID: unknown, input: string, langua
560560
return `**[tool_use ${name}${callIDSuffix(rawCallID)}]**\n\n${codeFence(input, language)}`;
561561
}
562562

563+
function functionCallMarkdown(
564+
type: "function_call" | "custom_tool_call",
565+
name: string,
566+
rawCallID: unknown,
567+
input: string,
568+
language = "",
569+
): string {
570+
return `**[${type} ${name}${callIDSuffix(rawCallID)}]**\n\n${codeFence(input, language)}`;
571+
}
572+
563573
function toolResultMarkdown(rawCallID: unknown, output: string): string {
564574
return `**[tool_result${callIDSuffix(rawCallID)}]**\n\n${codeFence(output)}`;
565575
}
@@ -978,7 +988,7 @@ export function extractResponseStream(text: string): StreamExtraction {
978988
const language = c.custom ? customToolLanguage(c.name, input) : "json";
979989
responseItems.push({
980990
order: c.order,
981-
markdown: toolUseMarkdown(c.name, c.callID, input, language),
991+
markdown: functionCallMarkdown(c.custom ? "custom_tool_call" : "function_call", c.name, c.callID, input, language),
982992
});
983993
}
984994
responseItems.sort((a, b) => a.order - b.order);
@@ -1168,15 +1178,15 @@ export function extractResponseJSON(rawJson: string): StreamExtraction | null {
11681178
const args = typeof it.arguments === "string" ? (it.arguments as string) : JSON.stringify(it.arguments ?? {});
11691179
const json = formatPartialJSON(args);
11701180
const sep = out.content ? "\n\n" : "";
1171-
out.content += sep + toolUseMarkdown(name, it.call_id, json, "json");
1181+
out.content += sep + functionCallMarkdown("function_call", name, it.call_id, json, "json");
11721182
out.detected = true;
11731183
continue;
11741184
}
11751185
if (it.type === "custom_tool_call") {
11761186
const name = String(it.name || "tool");
11771187
const input = typeof it.input === "string" ? it.input : JSON.stringify(it.input ?? "", null, 2);
11781188
const sep = out.content ? "\n\n" : "";
1179-
out.content += sep + toolUseMarkdown(name, it.call_id, input, customToolLanguage(name, input));
1189+
out.content += sep + functionCallMarkdown("custom_tool_call", name, it.call_id, input, customToolLanguage(name, input));
11801190
out.detected = true;
11811191
continue;
11821192
}

0 commit comments

Comments
 (0)