@vellumai/assistant 0.10.0-dev.202606232234.a0ec2ee → 0.10.0-dev.202606232330.324e2b5
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/openapi.yaml +7 -0
- package/package.json +1 -1
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/subagent/manager.ts +9 -0
package/openapi.yaml
CHANGED
|
@@ -25994,6 +25994,13 @@ paths:
|
|
|
25994
25994
|
type: boolean
|
|
25995
25995
|
messageId:
|
|
25996
25996
|
type: string
|
|
25997
|
+
toolUseId:
|
|
25998
|
+
type: string
|
|
25999
|
+
input:
|
|
26000
|
+
type: object
|
|
26001
|
+
propertyNames:
|
|
26002
|
+
type: string
|
|
26003
|
+
additionalProperties: {}
|
|
25997
26004
|
required:
|
|
25998
26005
|
- type
|
|
25999
26006
|
- content
|
package/package.json
CHANGED
|
@@ -130,6 +130,33 @@ describe("parseSubagentMessages", () => {
|
|
|
130
130
|
expect(result.objective).toBe("Research vampire lore");
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test("emits toolUseId and raw input on tool_use, toolUseId on tool_result", () => {
|
|
134
|
+
const messages = [
|
|
135
|
+
msg("user", [{ type: "text", text: "Do something" }]),
|
|
136
|
+
msg("assistant", [
|
|
137
|
+
{
|
|
138
|
+
type: "tool_use",
|
|
139
|
+
id: "t-abc",
|
|
140
|
+
name: "bash",
|
|
141
|
+
input: { command: "ls -la" },
|
|
142
|
+
},
|
|
143
|
+
]),
|
|
144
|
+
msg("user", [
|
|
145
|
+
{ type: "tool_result", tool_use_id: "t-abc", content: "total 0" },
|
|
146
|
+
]),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const result = parseSubagentMessages("sub-1", messages);
|
|
150
|
+
const toolUse = result.events.find((e) => e.type === "tool_use");
|
|
151
|
+
expect(toolUse).toBeDefined();
|
|
152
|
+
expect(toolUse!.toolUseId).toBe("t-abc");
|
|
153
|
+
expect(toolUse!.input).toEqual({ command: "ls -la" });
|
|
154
|
+
|
|
155
|
+
const toolResult = result.events.find((e) => e.type === "tool_result");
|
|
156
|
+
expect(toolResult).toBeDefined();
|
|
157
|
+
expect(toolResult!.toolUseId).toBe("t-abc");
|
|
158
|
+
});
|
|
159
|
+
|
|
133
160
|
test("includes messageId on text events from assistant messages", () => {
|
|
134
161
|
const messages = [
|
|
135
162
|
msg("user", [{ type: "text", text: "Do something" }]),
|
|
@@ -326,3 +326,68 @@ describe("SubagentManager terminal disposal", () => {
|
|
|
326
326
|
asInternals(manager).stopSweep();
|
|
327
327
|
});
|
|
328
328
|
});
|
|
329
|
+
|
|
330
|
+
describe("SubagentManager.abort usage", () => {
|
|
331
|
+
test("emits the conversation's latest usage on abort, not zeros", () => {
|
|
332
|
+
const manager = new SubagentManager();
|
|
333
|
+
const sent: ServerMessage[] = [];
|
|
334
|
+
const sender = (msg: ServerMessage) => sent.push(msg);
|
|
335
|
+
|
|
336
|
+
const subagentId = "sa-abort-usage";
|
|
337
|
+
// state.usage starts at {0,0,0}; the live (fake) conversation has accrued
|
|
338
|
+
// usage (makeFakeConversation → {100, 50, 0.005}). Wire `sender` as the
|
|
339
|
+
// stored parent sender so `setStatus` routes the terminal event through it.
|
|
340
|
+
injectFakeSubagent(manager, subagentId, makeState(subagentId), sender);
|
|
341
|
+
|
|
342
|
+
const aborted = manager.abort(subagentId, sender, undefined, {
|
|
343
|
+
suppressNotification: true,
|
|
344
|
+
});
|
|
345
|
+
expect(aborted).toBe(true);
|
|
346
|
+
|
|
347
|
+
const statusMsg = sent.find(
|
|
348
|
+
(m): m is Extract<ServerMessage, { type: "subagent_status_changed" }> =>
|
|
349
|
+
m.type === "subagent_status_changed",
|
|
350
|
+
);
|
|
351
|
+
expect(statusMsg).toBeDefined();
|
|
352
|
+
expect(statusMsg!.status).toBe("aborted");
|
|
353
|
+
// The emitted usage is the conversation's accrued total — NOT the {0,0,0}
|
|
354
|
+
// init — so the client doesn't flush the token panel to zero on stop.
|
|
355
|
+
expect(statusMsg!.usage).toEqual({
|
|
356
|
+
inputTokens: 100,
|
|
357
|
+
outputTokens: 50,
|
|
358
|
+
estimatedCost: 0.005,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
asInternals(manager).stopSweep();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("keeps the last-known state.usage when the conversation was already released", () => {
|
|
365
|
+
const manager = new SubagentManager();
|
|
366
|
+
const sent: ServerMessage[] = [];
|
|
367
|
+
const sender = (msg: ServerMessage) => sent.push(msg);
|
|
368
|
+
|
|
369
|
+
const subagentId = "sa-abort-no-conv";
|
|
370
|
+
// No live conversation (released), but state carries a last-known usage —
|
|
371
|
+
// the abort must surface that, not overwrite it.
|
|
372
|
+
const state = makeState(subagentId, {
|
|
373
|
+
usage: { inputTokens: 320, outputTokens: 80, estimatedCost: 0.004 },
|
|
374
|
+
});
|
|
375
|
+
injectFakeSubagent(manager, subagentId, state, sender, null);
|
|
376
|
+
|
|
377
|
+
manager.abort(subagentId, sender, undefined, {
|
|
378
|
+
suppressNotification: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const statusMsg = sent.find(
|
|
382
|
+
(m): m is Extract<ServerMessage, { type: "subagent_status_changed" }> =>
|
|
383
|
+
m.type === "subagent_status_changed",
|
|
384
|
+
);
|
|
385
|
+
expect(statusMsg!.usage).toEqual({
|
|
386
|
+
inputTokens: 320,
|
|
387
|
+
outputTokens: 80,
|
|
388
|
+
estimatedCost: 0.004,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
asInternals(manager).stopSweep();
|
|
392
|
+
});
|
|
393
|
+
});
|
|
@@ -31,6 +31,23 @@ export const SubagentDetailEventSchema = z.object({
|
|
|
31
31
|
toolName: z.string().optional(),
|
|
32
32
|
isError: z.boolean().optional(),
|
|
33
33
|
messageId: z.string().optional(),
|
|
34
|
+
/**
|
|
35
|
+
* Tool-call id — the `tool_use.id` on a tool-call event and the referencing
|
|
36
|
+
* `tool_use_id` on its tool-result event, in the daemon's canonical
|
|
37
|
+
* content-block format. That format is provider-agnostic: every provider
|
|
38
|
+
* (Anthropic, OpenAI, Gemini, …) normalizes its native tool calls into these
|
|
39
|
+
* `tool_use`/`tool_result` blocks (see `providers/types.ts`), so this id is
|
|
40
|
+
* present regardless of which model produced the call. Lets the web client
|
|
41
|
+
* pair a result with its call and key the nested tool-detail view, so tool
|
|
42
|
+
* pills on reloaded/history subagents are clickable (not just live ones).
|
|
43
|
+
*/
|
|
44
|
+
toolUseId: z.string().optional(),
|
|
45
|
+
/**
|
|
46
|
+
* Raw tool input object on tool-call events. (`content` also carries a
|
|
47
|
+
* JSON-stringified copy for back-compat / label derivation.) Surfaced in the
|
|
48
|
+
* tool-detail view's input section.
|
|
49
|
+
*/
|
|
50
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
34
51
|
});
|
|
35
52
|
|
|
36
53
|
export type SubagentDetailEvent = z.infer<typeof SubagentDetailEventSchema>;
|
|
@@ -39,6 +39,8 @@ export interface SubagentDetailResult {
|
|
|
39
39
|
toolName?: string;
|
|
40
40
|
isError?: boolean;
|
|
41
41
|
messageId?: string;
|
|
42
|
+
toolUseId?: string;
|
|
43
|
+
input?: Record<string, unknown>;
|
|
42
44
|
}>;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -112,6 +114,8 @@ export function parseSubagentMessages(
|
|
|
112
114
|
type: "tool_use",
|
|
113
115
|
content: JSON.stringify(input),
|
|
114
116
|
toolName: name,
|
|
117
|
+
toolUseId: id || undefined,
|
|
118
|
+
input,
|
|
115
119
|
});
|
|
116
120
|
if (id) pendingTools.set(id, name);
|
|
117
121
|
} else if (
|
|
@@ -147,6 +151,7 @@ export function parseSubagentMessages(
|
|
|
147
151
|
content: resultContent,
|
|
148
152
|
toolName: toolName ?? "unknown",
|
|
149
153
|
isError,
|
|
154
|
+
toolUseId: toolUseId || undefined,
|
|
150
155
|
});
|
|
151
156
|
}
|
|
152
157
|
}
|
package/src/subagent/manager.ts
CHANGED
|
@@ -553,6 +553,15 @@ export class SubagentManager {
|
|
|
553
553
|
),
|
|
554
554
|
);
|
|
555
555
|
managed.state.completedAt = Date.now();
|
|
556
|
+
// Capture the conversation's latest usage before emitting the terminal
|
|
557
|
+
// status. `subagent_status_changed` ships `state.usage`, and the abort path
|
|
558
|
+
// (unlike the completion/failure paths, which sync at agent-loop exit) would
|
|
559
|
+
// otherwise send the {0,0,0} init usage — zeroing the client's token counts
|
|
560
|
+
// even though those tokens were already spent. `usageStats` accrues per LLM
|
|
561
|
+
// turn (see conversation-usage.ts), so this is the most recent total.
|
|
562
|
+
if (managed.conversation) {
|
|
563
|
+
managed.state.usage = { ...managed.conversation.usageStats };
|
|
564
|
+
}
|
|
556
565
|
if (parentSendToClient) {
|
|
557
566
|
// Route the status update through the stored parent sender so the
|
|
558
567
|
// owning conversation's UI chip updates, even when the abort comes from a
|