Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function matchMedia() {
let MessagesTimeline: typeof import("./MessagesTimeline").MessagesTimeline;
let buildToolCallExpandedBody: typeof import("./MessagesTimeline").buildToolCallExpandedBody;
let WorkEntryExpandedBody: typeof import("./MessagesTimeline").WorkEntryExpandedBody;
let splitPreviewJsonPayload: typeof import("./MessagesTimeline").splitPreviewJsonPayload;

beforeAll(async () => {
const classList = {
Expand Down Expand Up @@ -177,7 +178,7 @@ beforeAll(async () => {
},
});

({ MessagesTimeline, WorkEntryExpandedBody, buildToolCallExpandedBody } =
({ MessagesTimeline, WorkEntryExpandedBody, buildToolCallExpandedBody, splitPreviewJsonPayload } =
await import("./MessagesTimeline"));
}, 30_000);

Expand Down Expand Up @@ -236,6 +237,33 @@ function buildUserTimelineEntry(text: string) {
};
}

describe("splitPreviewJsonPayload", () => {
it("splits labeled tool-call summaries into label and JSON payload", () => {
expect(splitPreviewJsonPayload('Skill: {"skill":"codex-first","args":"Implement"}')).toEqual({
label: "Skill:",
payload: '{"skill":"codex-first","args":"Implement"}',
});
});

it("accepts payloads truncated mid-JSON and bare payloads without a label", () => {
expect(splitPreviewJsonPayload('ToolSearch: {"query":"select:Re...')).toEqual({
label: "ToolSearch:",
payload: '{"query":"select:Re...',
});
expect(splitPreviewJsonPayload('{"query":"x"}')).toEqual({
label: null,
payload: '{"query":"x"}',
});
expect(splitPreviewJsonPayload('["a","b"]')).toEqual({ label: null, payload: '["a","b"]' });
});

it("leaves plain-text previews untouched", () => {
expect(splitPreviewJsonPayload("Read src/index.ts")).toBeNull();
expect(splitPreviewJsonPayload("Note: {braces} in prose")).toBeNull();
expect(splitPreviewJsonPayload("See: [link](https://example.com)")).toBeNull();
});
});

describe("MessagesTimeline", () => {
it("extracts full provider tool output for expanded work rows", () => {
const makeEntry = (toolData: unknown) => ({
Expand Down
25 changes: 24 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2050,6 +2050,21 @@ function workEntryDisplayText(workEntry: TimelineWorkEntry, workspaceRoot: strin
};
}

// Dynamic tool-call summaries arrive as "ToolName: {…json…}" (see the provider
// adapters' summarizeToolRequest); the payload may be truncated mid-JSON, so
// detect it by shape rather than parsing.
export function splitPreviewJsonPayload(
preview: string,
): { label: string | null; payload: string } | null {
const separator = preview.indexOf(": ");
const label = separator > 0 ? preview.slice(0, separator + 1) : null;
const payload = separator > 0 ? preview.slice(separator + 2) : preview;
if (!/^(?:\{["}]|\[["{\]])/.test(payload)) {
return null;
}
return { label, payload };
}

function workEntryRawCommand(
workEntry: Pick<TimelineWorkEntry, "command" | "rawCommand">,
): string | null {
Expand Down Expand Up @@ -2503,6 +2518,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
const entryIconName = showWarningIndicator ? "x" : workEntryIconName(workEntry);
const { heading, preview, displayText } = workEntryDisplayText(workEntry, workspaceRoot);
const previewIsCommand = preview !== null && preview === workEntry.command;
const previewJson = !previewIsCommand && preview ? splitPreviewJsonPayload(preview) : null;
const expandedBody = useMemo(
() => buildToolCallExpandedBody(workEntry, workspaceRoot),
[workEntry, workspaceRoot],
Expand Down Expand Up @@ -2596,7 +2612,14 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
: "work-entry-preview-clip overflow-hidden whitespace-nowrap",
)}
>
{preview}
{previewJson ? (
<>
{previewJson.label ? <>{previewJson.label} </> : null}
<span className="font-mono text-[11px]">{previewJson.payload}</span>
</>
) : (
preview
)}
</span>
)}
</p>
Expand Down
Loading