@saadnvd1/agent-os 0.3.738 → 0.3.742
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Chat/Activity.test.ts +2 -1
- package/components/Chat/Activity.tsx +102 -64
- package/components/Chat/ChatPanel.tsx +4 -0
- package/components/Chat/LiveOutput.test.ts +36 -0
- package/components/Chat/Tools.tsx +42 -5
- package/data/chat/useChat.ts +10 -1
- package/lib/chat/drivers/claude-mapper.ts +10 -0
- package/lib/chat/drivers/claude.ts +9 -1
- package/lib/chat/drivers/edit-guard.ts +311 -0
- package/lib/chat/drivers/opencode-config.ts +111 -0
- package/lib/chat/drivers/opencode-mapper.ts +125 -13
- package/lib/chat/drivers/opencode-plugin.ts +78 -0
- package/lib/chat/drivers/opencode-server.ts +7 -2
- package/lib/chat/drivers/opencode.ts +108 -9
- package/lib/chat/drivers/pi-edit-guard.ts +87 -0
- package/lib/chat/drivers/pi-mapper.ts +74 -1
- package/lib/chat/drivers/pi.ts +82 -3
- package/lib/chat/drivers/transient-retry.ts +87 -0
- package/lib/chat/drivers/turn-items.ts +74 -1
- package/lib/chat/drivers/turn-outcome.ts +48 -1
- package/lib/chat/events.ts +15 -1
- package/lib/chat/model-wait-live.ts +19 -0
- package/lib/chat/model-wait.ts +88 -0
- package/lib/chat/progress.ts +169 -0
- package/lib/chat/registry.ts +5 -0
- package/lib/chat/runner-watch.ts +68 -0
- package/lib/chat/runner.ts +4 -70
- package/lib/chat/tools.ts +14 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ describe("currentActivity while compacting", () => {
|
|
|
5
5
|
it("says so, with the size going in and when it began", () => {
|
|
6
6
|
expect(
|
|
7
7
|
currentActivity([], "running", { since: 5, before: 182_000 })
|
|
8
|
-
).
|
|
8
|
+
).toMatchObject({
|
|
9
9
|
label: "Compacting context… 182k tokens",
|
|
10
10
|
since: 5,
|
|
11
11
|
compacting: true,
|
|
@@ -18,5 +18,6 @@ describe("currentActivity while compacting", () => {
|
|
|
18
18
|
it("is plain work without it, and nothing once the turn is over", () => {
|
|
19
19
|
expect(currentActivity([], "running")?.compacting).toBeUndefined();
|
|
20
20
|
expect(currentActivity([], "idle", { since: 5 })).toBeNull();
|
|
21
|
+
expect(currentActivity([], "running")?.label).toBe("Waiting for the model");
|
|
21
22
|
});
|
|
22
23
|
});
|
|
@@ -1,91 +1,129 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
AlertTriangle,
|
|
5
|
+
Loader2,
|
|
6
|
+
MessageCircleQuestion,
|
|
7
|
+
Shrink,
|
|
8
|
+
Square,
|
|
9
|
+
} from "lucide-react";
|
|
10
|
+
import type {
|
|
11
|
+
ChatItem,
|
|
12
|
+
ChatState,
|
|
13
|
+
Compacting,
|
|
14
|
+
ModelWait,
|
|
15
|
+
} from "@/lib/chat/events";
|
|
5
16
|
import { formatTokens } from "@/lib/chat/context";
|
|
6
17
|
import { formatElapsed } from "@/lib/chat/elapsed";
|
|
18
|
+
import { currentActivity, type Phase } from "@/lib/chat/progress";
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
7
20
|
import { useNow } from "./useNow";
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
export { currentActivity };
|
|
10
23
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
): { label: string; since?: number; compacting?: boolean } | null {
|
|
18
|
-
if (state === "waiting") return { label: "Waiting for your answer" };
|
|
19
|
-
if (state !== "running") return null;
|
|
20
|
-
if (compacting)
|
|
21
|
-
return {
|
|
22
|
-
label: `Compacting context…${compacting.before ? ` ${formatTokens(compacting.before)} tokens` : ""}`,
|
|
23
|
-
since: compacting.since,
|
|
24
|
-
compacting: true,
|
|
25
|
-
};
|
|
26
|
-
const lastUser = items.findLast((i) => i.kind === "user");
|
|
27
|
-
const tool = items.findLast(
|
|
28
|
-
(i): i is Tool => i.kind === "tool" && i.status === "running"
|
|
29
|
-
);
|
|
30
|
-
if (tool) return { label: tool.title, since: tool.createdAt };
|
|
31
|
-
const last = items.at(-1);
|
|
32
|
-
const writing =
|
|
33
|
-
last && (last.kind === "assistant" || last.kind === "reasoning");
|
|
34
|
-
return {
|
|
35
|
-
label:
|
|
36
|
-
writing && last.streaming
|
|
37
|
-
? last.kind === "assistant"
|
|
38
|
-
? "Writing"
|
|
39
|
-
: "Thinking"
|
|
40
|
-
: "Working",
|
|
41
|
-
since: lastUser?.createdAt,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
24
|
+
const PHASE: Record<Phase, string> = {
|
|
25
|
+
planning: "Planning",
|
|
26
|
+
coding: "Coding",
|
|
27
|
+
testing: "Testing",
|
|
28
|
+
error: "Error",
|
|
29
|
+
};
|
|
44
30
|
|
|
45
|
-
//
|
|
46
|
-
// looks like nothing is happening
|
|
31
|
+
// Up to three lines above the composer while a turn runs, so a long wait
|
|
32
|
+
// never looks like nothing is happening: what it's doing and for how long,
|
|
33
|
+
// the phase and step count, and a hint when a model call drags on.
|
|
47
34
|
export function ActivityLine({
|
|
48
35
|
items,
|
|
49
36
|
state,
|
|
50
37
|
compacting = null,
|
|
38
|
+
modelWait = null,
|
|
39
|
+
model,
|
|
51
40
|
onStop,
|
|
52
41
|
}: {
|
|
53
42
|
items: ChatItem[];
|
|
54
43
|
state: ChatState;
|
|
55
44
|
compacting?: Compacting | null;
|
|
45
|
+
modelWait?: ModelWait | null;
|
|
46
|
+
model?: string;
|
|
56
47
|
onStop: () => void;
|
|
57
48
|
}) {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
49
|
+
const now = useNow(state === "running");
|
|
50
|
+
const activity = currentActivity(
|
|
51
|
+
items,
|
|
52
|
+
state,
|
|
53
|
+
compacting,
|
|
54
|
+
modelWait,
|
|
55
|
+
model,
|
|
56
|
+
now
|
|
57
|
+
);
|
|
60
58
|
if (!activity) return null;
|
|
61
59
|
const waiting = state === "waiting";
|
|
60
|
+
const failed = activity.phase === "error" && activity.kind === "waiting";
|
|
62
61
|
return (
|
|
63
|
-
<div className="text-muted-foreground
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{formatElapsed(now - activity.since)}
|
|
62
|
+
<div className="text-muted-foreground px-2 text-xs">
|
|
63
|
+
<div className="flex min-h-9 items-center gap-2">
|
|
64
|
+
{waiting ? (
|
|
65
|
+
<MessageCircleQuestion className="text-primary h-3.5 w-3.5 shrink-0" />
|
|
66
|
+
) : activity.compacting ? (
|
|
67
|
+
<Shrink className="h-3.5 w-3.5 shrink-0 animate-pulse text-blue-600 dark:text-blue-400" />
|
|
68
|
+
) : failed ? (
|
|
69
|
+
<AlertTriangle className="text-destructive h-3.5 w-3.5 shrink-0" />
|
|
70
|
+
) : (
|
|
71
|
+
<Loader2 className="text-primary h-3.5 w-3.5 shrink-0 animate-spin" />
|
|
72
|
+
)}
|
|
73
|
+
<span className="text-foreground/80 min-w-0 flex-1 truncate">
|
|
74
|
+
{activity.label}
|
|
77
75
|
</span>
|
|
76
|
+
{activity.tokens && (
|
|
77
|
+
<span className="shrink-0 whitespace-nowrap">
|
|
78
|
+
{formatTokens(activity.tokens)}+ tokens
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
{activity.since && (
|
|
82
|
+
<span className="shrink-0 font-mono tabular-nums">
|
|
83
|
+
{formatElapsed(now - activity.since)}
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
{!waiting && (
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={onStop}
|
|
90
|
+
aria-label="Stop"
|
|
91
|
+
className="hover:text-foreground hover:bg-foreground/[0.06] -my-1 flex h-11 shrink-0 items-center gap-1 rounded-md px-2 md:my-0 md:h-7"
|
|
92
|
+
>
|
|
93
|
+
<Square className="h-3 w-3 fill-current" />
|
|
94
|
+
Stop
|
|
95
|
+
</button>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
{!waiting && activity.steps > 0 && activity.phase && (
|
|
99
|
+
<div className="flex items-center gap-1.5 pb-1 pl-5 text-[11px]">
|
|
100
|
+
<span
|
|
101
|
+
className={cn(
|
|
102
|
+
"shrink-0 font-medium",
|
|
103
|
+
activity.phase === "error"
|
|
104
|
+
? "text-destructive"
|
|
105
|
+
: "text-foreground/70"
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
{PHASE[activity.phase]}
|
|
109
|
+
</span>
|
|
110
|
+
<span className="shrink-0">
|
|
111
|
+
· {activity.steps} {activity.steps === 1 ? "step" : "steps"}
|
|
112
|
+
</span>
|
|
113
|
+
{activity.lastStep && (
|
|
114
|
+
<span className="min-w-0 flex-1 truncate">
|
|
115
|
+
· {activity.lastStep}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
118
|
+
{activity.turnSince && (
|
|
119
|
+
<span className="ml-auto shrink-0 pl-2 font-mono tabular-nums">
|
|
120
|
+
turn {formatElapsed(now - activity.turnSince)}
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
78
124
|
)}
|
|
79
|
-
{
|
|
80
|
-
<
|
|
81
|
-
type="button"
|
|
82
|
-
onClick={onStop}
|
|
83
|
-
aria-label="Stop"
|
|
84
|
-
className="hover:text-foreground hover:bg-foreground/[0.06] -my-1 flex h-11 shrink-0 items-center gap-1 rounded-md px-2 md:my-0 md:h-7"
|
|
85
|
-
>
|
|
86
|
-
<Square className="h-3 w-3 fill-current" />
|
|
87
|
-
Stop
|
|
88
|
-
</button>
|
|
125
|
+
{activity.hint && (
|
|
126
|
+
<p className="pb-1.5 pl-5 text-[11px]">{activity.hint}</p>
|
|
89
127
|
)}
|
|
90
128
|
</div>
|
|
91
129
|
);
|
|
@@ -33,6 +33,7 @@ import { sentPrompts } from "@/lib/chat/history";
|
|
|
33
33
|
import { quoteInsert, quoteMarkdown } from "@/lib/chat/quote";
|
|
34
34
|
import { OrchestratorBar } from "@/components/Orchestrator/OrchestratorBar";
|
|
35
35
|
import { ActivityLine } from "./Activity";
|
|
36
|
+
import { modelLabel as pickedModelLabel } from "./ModelPicker";
|
|
36
37
|
import { Approval } from "./Approval";
|
|
37
38
|
import { BackgroundTasks } from "./BackgroundTasks";
|
|
38
39
|
import { SubagentCard } from "./Subagent";
|
|
@@ -319,6 +320,7 @@ export function ChatPanel({
|
|
|
319
320
|
requestFiles,
|
|
320
321
|
search,
|
|
321
322
|
compacting,
|
|
323
|
+
modelWait,
|
|
322
324
|
loadedFor,
|
|
323
325
|
} = useChat(sessionId, (text) => setPrefill({ text, at: Date.now() }));
|
|
324
326
|
const startFresh = useFreshStart();
|
|
@@ -796,6 +798,8 @@ export function ChatPanel({
|
|
|
796
798
|
items={items}
|
|
797
799
|
state={state}
|
|
798
800
|
compacting={compacting}
|
|
801
|
+
modelWait={modelWait}
|
|
802
|
+
model={pickedModelLabel(models, model, modelLabel)}
|
|
799
803
|
onStop={interrupt}
|
|
800
804
|
/>
|
|
801
805
|
<BackgroundTasks
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { ToolItem } from "@/lib/chat/group";
|
|
5
|
+
import { ToolGroup } from "./Tools";
|
|
6
|
+
|
|
7
|
+
const tool = (over: Partial<ToolItem>): ToolItem => ({
|
|
8
|
+
id: "t1",
|
|
9
|
+
kind: "tool",
|
|
10
|
+
name: "Bash",
|
|
11
|
+
title: "npm test",
|
|
12
|
+
input: { command: "npm test" },
|
|
13
|
+
status: "running",
|
|
14
|
+
createdAt: Date.now(),
|
|
15
|
+
...over,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const html = (t: ToolItem) =>
|
|
19
|
+
renderToStaticMarkup(createElement(ToolGroup, { tools: [t] }));
|
|
20
|
+
|
|
21
|
+
describe("a running step", () => {
|
|
22
|
+
it("shows its output so far without being opened", () => {
|
|
23
|
+
const out = html(tool({ output: "PASS a.test.ts\nRUNS b.test.ts" }));
|
|
24
|
+
expect(out).toContain('data-testid="tool-output"');
|
|
25
|
+
expect(out).toContain("RUNS b.test.ts");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("shows no output block before it prints anything", () => {
|
|
29
|
+
expect(html(tool({}))).not.toContain("tool-output");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("keeps a finished step collapsed", () => {
|
|
33
|
+
const out = html(tool({ status: "done", output: "ok" }));
|
|
34
|
+
expect(out).not.toContain("tool-output");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { createContext, useContext, useEffect } from "react";
|
|
3
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
4
4
|
import { Check, ChevronRight, Layers, Loader2, Square, X } from "lucide-react";
|
|
5
5
|
import type { FileDiff, ToolBody } from "@/lib/chat/events";
|
|
6
6
|
import type { ToolItem } from "@/lib/chat/group";
|
|
@@ -111,6 +111,36 @@ export function ToolPreview({
|
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// A step's output. While it runs the view follows the end, unless the
|
|
115
|
+
// reader scrolled up to read.
|
|
116
|
+
export function LiveOutput({
|
|
117
|
+
text,
|
|
118
|
+
running,
|
|
119
|
+
}: {
|
|
120
|
+
text: string;
|
|
121
|
+
running: boolean;
|
|
122
|
+
}) {
|
|
123
|
+
const ref = useRef<HTMLPreElement>(null);
|
|
124
|
+
const pinned = useRef(true);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const el = ref.current;
|
|
127
|
+
if (running && el && pinned.current) el.scrollTop = el.scrollHeight;
|
|
128
|
+
}, [text, running]);
|
|
129
|
+
return (
|
|
130
|
+
<pre
|
|
131
|
+
ref={ref}
|
|
132
|
+
data-testid="tool-output"
|
|
133
|
+
onScroll={(e) => {
|
|
134
|
+
const el = e.currentTarget;
|
|
135
|
+
pinned.current = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
|
136
|
+
}}
|
|
137
|
+
className="text-muted-foreground max-h-64 overflow-auto rounded-lg px-2 font-mono text-[11px] whitespace-pre-wrap"
|
|
138
|
+
>
|
|
139
|
+
{text}
|
|
140
|
+
</pre>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
function ToolDetail({ tool }: { tool: ToolItem }) {
|
|
115
145
|
const body = useToolBody(tool);
|
|
116
146
|
if (body === undefined && tool.hasDiff)
|
|
@@ -123,9 +153,7 @@ function ToolDetail({ tool }: { tool: ToolItem }) {
|
|
|
123
153
|
<div className="space-y-2 pb-2 pl-6">
|
|
124
154
|
<ToolPreview name={tool.name} input={tool.input} diff={body?.diff} />
|
|
125
155
|
{body?.output && !body.diff && (
|
|
126
|
-
<
|
|
127
|
-
{body.output}
|
|
128
|
-
</pre>
|
|
156
|
+
<LiveOutput text={body.output} running={tool.status === "running"} />
|
|
129
157
|
)}
|
|
130
158
|
{body === undefined && tool.hasOutput && (
|
|
131
159
|
<Loader2 className="text-muted-foreground h-3.5 w-3.5 animate-spin" />
|
|
@@ -148,6 +176,10 @@ function Elapsed({ tool }: { tool: ToolItem }) {
|
|
|
148
176
|
);
|
|
149
177
|
}
|
|
150
178
|
|
|
179
|
+
// A running step that has printed something: its output shows as it comes.
|
|
180
|
+
const streaming = (tool: ToolItem) =>
|
|
181
|
+
tool.status === "running" && !!tool.output && !tool.deferred;
|
|
182
|
+
|
|
151
183
|
function ToolRow({ tool }: { tool: ToolItem }) {
|
|
152
184
|
const [open, setOpen] = useRowState(
|
|
153
185
|
`tool:${tool.id}`,
|
|
@@ -186,7 +218,7 @@ function ToolRow({ tool }: { tool: ToolItem }) {
|
|
|
186
218
|
<BackgroundButton tool={tool} />
|
|
187
219
|
<BookmarkButton itemId={tool.id} />
|
|
188
220
|
</div>
|
|
189
|
-
{open && <ToolDetail tool={tool} />}
|
|
221
|
+
{(open || streaming(tool)) && <ToolDetail tool={tool} />}
|
|
190
222
|
</div>
|
|
191
223
|
);
|
|
192
224
|
}
|
|
@@ -244,6 +276,11 @@ export function ToolGroup({ tools }: { tools: ToolItem[] }) {
|
|
|
244
276
|
{!open && !running && (
|
|
245
277
|
<StepOutcomes tools={tools} onOpen={() => setOpen(true)} />
|
|
246
278
|
)}
|
|
279
|
+
{!open && streaming(latest) && (
|
|
280
|
+
<div className="pb-1">
|
|
281
|
+
<ToolDetail tool={latest} />
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
247
284
|
{open && (
|
|
248
285
|
<div className="pb-1">
|
|
249
286
|
{tools.map((t) => (
|
package/data/chat/useChat.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ChatServerMessage,
|
|
17
17
|
ChatState,
|
|
18
18
|
Compacting,
|
|
19
|
+
ModelWait,
|
|
19
20
|
FileSuggestion,
|
|
20
21
|
QueuedMessage,
|
|
21
22
|
ToolBody,
|
|
@@ -164,6 +165,7 @@ export function useChat(
|
|
|
164
165
|
}, []);
|
|
165
166
|
const [state, setState] = useState<ChatState>("idle");
|
|
166
167
|
const [compacting, setCompacting] = useState<Compacting | null>(null);
|
|
168
|
+
const [modelWait, setModelWait] = useState<ModelWait | null>(null);
|
|
167
169
|
const [connected, setConnected] = useState(false);
|
|
168
170
|
// The chat whose snapshot the items are, once one has landed.
|
|
169
171
|
const [loadedFor, setLoadedFor] = useState<string | null>(null);
|
|
@@ -271,6 +273,7 @@ export function useChat(
|
|
|
271
273
|
setLoadingOlder(false);
|
|
272
274
|
setState(m.state);
|
|
273
275
|
setCompacting(m.compacting ?? null);
|
|
276
|
+
setModelWait(m.modelWait ?? null);
|
|
274
277
|
setQueue(m.queue ?? []);
|
|
275
278
|
setSuggestion(m.suggestion ?? null);
|
|
276
279
|
setLoadedFor(sessionId);
|
|
@@ -295,9 +298,14 @@ export function useChat(
|
|
|
295
298
|
searchRequests.current.delete(m.reqId);
|
|
296
299
|
} else if (m.type === "compacting") {
|
|
297
300
|
setCompacting(m.compacting);
|
|
301
|
+
} else if (m.type === "model_wait") {
|
|
302
|
+
setModelWait(m.wait);
|
|
298
303
|
} else if (m.type === "state") {
|
|
299
304
|
setState(m.state);
|
|
300
|
-
if (m.state === "idle" || m.state === "error")
|
|
305
|
+
if (m.state === "idle" || m.state === "error") {
|
|
306
|
+
setCompacting(null);
|
|
307
|
+
setModelWait(null);
|
|
308
|
+
}
|
|
301
309
|
} else if (m.type === "capabilities") {
|
|
302
310
|
setRefreshingCommands(false);
|
|
303
311
|
setCommands(m.commands);
|
|
@@ -596,6 +604,7 @@ export function useChat(
|
|
|
596
604
|
pinToolBody,
|
|
597
605
|
state,
|
|
598
606
|
compacting,
|
|
607
|
+
modelWait,
|
|
599
608
|
connected,
|
|
600
609
|
commands,
|
|
601
610
|
refreshCommands,
|
|
@@ -233,6 +233,16 @@ export class ClaudeMapper {
|
|
|
233
233
|
case "task_progress":
|
|
234
234
|
case "task_notification":
|
|
235
235
|
return this.task(m);
|
|
236
|
+
case "api_retry": {
|
|
237
|
+
const r = m as { attempt?: number; error_status?: number | null };
|
|
238
|
+
return [
|
|
239
|
+
{
|
|
240
|
+
type: "model_retry",
|
|
241
|
+
attempt: Number(r.attempt) || 1,
|
|
242
|
+
message: r.error_status ? `HTTP ${r.error_status}` : undefined,
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
}
|
|
236
246
|
case "status": {
|
|
237
247
|
// "compacting" while the context is summarised, null once it's done.
|
|
238
248
|
const status = (m as { status?: string | null }).status;
|
|
@@ -454,5 +454,13 @@ export function usageEvents(
|
|
|
454
454
|
? "review"
|
|
455
455
|
: "sub";
|
|
456
456
|
const call = callFromAssistant(m.message, kind, agentType, at);
|
|
457
|
-
|
|
457
|
+
if (!call) return [];
|
|
458
|
+
const prompt =
|
|
459
|
+
call.input + call.cacheRead + call.cacheWrite5m + call.cacheWrite1h;
|
|
460
|
+
return [
|
|
461
|
+
{ type: "call_usage", call },
|
|
462
|
+
...(kind === "main" && prompt > 0
|
|
463
|
+
? [{ type: "prompt_tokens" as const, tokens: prompt }]
|
|
464
|
+
: []),
|
|
465
|
+
];
|
|
458
466
|
}
|