Skip to content

Commit c47684b

Browse files
committed
Format Task tool calls
1 parent 990925d commit c47684b

6 files changed

Lines changed: 146 additions & 0 deletions

File tree

.changeset/easy-lemons-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/format-claude-stream": patch
3+
---
4+
5+
Task tool calls are now formatted nicely instead of the raw JSON being dumped.

src/core/events/task-tool-call.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {Colorizer} from "../ports/colorizer.ts";
2+
import {ClaudeIOEvent} from "./claude-io-event.type.ts";
3+
4+
interface ConstructorParams {
5+
toolUseId: string;
6+
subagentType: string;
7+
description: string;
8+
prompt: string;
9+
}
10+
11+
export class TaskToolCall implements ClaudeIOEvent {
12+
toolUseId: string;
13+
subagentType: string;
14+
description: string;
15+
prompt: string;
16+
17+
constructor({
18+
toolUseId,
19+
subagentType,
20+
description,
21+
prompt,
22+
}: ConstructorParams) {
23+
this.toolUseId = toolUseId;
24+
this.subagentType = subagentType;
25+
this.description = description;
26+
this.prompt = prompt;
27+
}
28+
29+
format(colorizer: Colorizer): string {
30+
let headline = `Task (${this.subagentType}): ${this.description}`;
31+
return [colorizer.importantAction(headline), this.prompt].join("\n");
32+
}
33+
}

src/core/interpreter.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {MarkupColorizer} from "./ports/markup-colorizer.ts";
1313
import {Interpreter} from "./interpreter.ts";
1414
import {ToolUseSuccess} from "./events/tool-use-success.ts";
1515
import {ClaudeIOEvent} from "./events/claude-io-event.type.js";
16+
import {TaskToolCall} from "./events/task-tool-call.ts";
1617

1718
describe("Interpreter", () => {
1819
it("outputs a generic tool call event", async () => {
@@ -308,4 +309,40 @@ describe("Interpreter", () => {
308309
"$ echo hello\nYou don't have permission.\n",
309310
);
310311
});
312+
313+
it("formats a task tool call", async () => {
314+
const outputFake = new OutputFake();
315+
const interpreter = new Interpreter(outputFake, new NullColorizer());
316+
317+
await interpreter.process(
318+
new TaskToolCall({
319+
toolUseId: "1",
320+
subagentType: "Explore",
321+
description: "look around",
322+
prompt: "a prompt",
323+
}),
324+
);
325+
326+
expect(outputFake.value()).toBe(
327+
"Task (Explore): look around\na prompt\n",
328+
);
329+
});
330+
331+
it("colorizes a task tool call", async () => {
332+
const outputFake = new OutputFake();
333+
const interpreter = new Interpreter(outputFake, new MarkupColorizer());
334+
335+
await interpreter.process(
336+
new TaskToolCall({
337+
toolUseId: "1",
338+
subagentType: "Explore",
339+
description: "look around",
340+
prompt: "a prompt",
341+
}),
342+
);
343+
344+
expect(outputFake.value()).toBe(
345+
"[[importantAction Task (Explore): look around]]\na prompt\n",
346+
);
347+
});
311348
});

src/formats/parse-events.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {UnreachableCodeError} from "../lib/unreachable-code-error.ts";
1818
import {UnrecognizedJsonEvent} from "../core/events/unrecognized-json-event.ts";
1919
import {ToolUseSuccess} from "../core/events/tool-use-success.ts";
2020
import {ToolUseError} from "../core/events/tool-use-error.ts";
21+
import {TaskToolCall} from "../core/events/task-tool-call.ts";
2122

2223
export function parseEvents(data: unknown): ClaudeIOEvent[] {
2324
const parsed = StreamJsonLine.safeParse(data);
@@ -101,6 +102,13 @@ function parseToolCallEvent(
101102
toolCall.input.pattern,
102103
toolCall.input.path,
103104
);
105+
case "Task":
106+
return new TaskToolCall({
107+
toolUseId: toolCall.id,
108+
subagentType: toolCall.input.subagent_type,
109+
description: toolCall.input.description,
110+
prompt: toolCall.input.prompt,
111+
});
104112
default:
105113
throw new UnreachableCodeError(toolCall);
106114
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {ClaudeIOEvent} from "../../core/events/claude-io-event.type.ts";
2+
import {TaskToolCall} from "../../core/events/task-tool-call.ts";
3+
4+
export const data = {
5+
type: "assistant",
6+
message: {
7+
model: "claude-opus-4-6",
8+
id: "msg_019QCmPhUXrRHQCn4cUFr72U",
9+
type: "message",
10+
role: "assistant",
11+
content: [
12+
{
13+
type: "tool_use",
14+
id: "toolu_011C9YydUfQk5v4YnCdp4zUo",
15+
name: "Task",
16+
input: {
17+
subagent_type: "Explore",
18+
description: "Explore CLI and version context",
19+
prompt: "this is the prompt",
20+
},
21+
caller: {type: "direct"},
22+
},
23+
],
24+
stop_reason: null,
25+
stop_sequence: null,
26+
usage: {
27+
input_tokens: 1,
28+
cache_creation_input_tokens: 990,
29+
cache_read_input_tokens: 22524,
30+
cache_creation: {
31+
ephemeral_5m_input_tokens: 990,
32+
ephemeral_1h_input_tokens: 0,
33+
},
34+
output_tokens: 1,
35+
service_tier: "standard",
36+
inference_geo: "not_available",
37+
},
38+
context_management: null,
39+
},
40+
parent_tool_use_id: null,
41+
session_id: "918dfbf3-6251-4f36-b837-36d96efd3303",
42+
uuid: "45fcb4b7-527c-4449-b05f-51eba96f25f4",
43+
};
44+
45+
export const expected: ClaudeIOEvent[] = [
46+
new TaskToolCall({
47+
toolUseId: "toolu_011C9YydUfQk5v4YnCdp4zUo",
48+
subagentType: "Explore",
49+
description: "Explore CLI and version context",
50+
prompt: "this is the prompt",
51+
}),
52+
];

src/formats/tool-calls.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ export const GrepToolCall = z.looseObject({
3535
}),
3636
});
3737

38+
export const TaskToolCall = z.looseObject({
39+
name: z.literal("Task"),
40+
id: z.string(),
41+
input: z.looseObject({
42+
subagent_type: z.string(),
43+
description: z.string(),
44+
prompt: z.string(),
45+
}),
46+
});
47+
3848
export const UnrecognizedToolCall = z.looseObject({
3949
name: z.string(),
4050
id: z.string(),
@@ -46,4 +56,5 @@ export const ToolCall = z.discriminatedUnion("name", [
4656
EditToolCall,
4757
GrepToolCall,
4858
ReadToolCall,
59+
TaskToolCall,
4960
]);

0 commit comments

Comments
 (0)