@aparte/runner-ag-ui 1.5.0 → 1.7.0
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/README.md +5 -1
- package/dist/index.js +434 -75
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +7 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/state.d.ts +6 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/subagent-history.d.ts +56 -0
- package/dist/subagent-history.d.ts.map +1 -0
- package/dist/wire.d.ts +14 -3
- package/dist/wire.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -210,24 +210,139 @@ function step(doc, op) {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
function applyStateDelta(state, delta, warn) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
return applyPatch(state, delta, warn, { event: "STATE_DELTA", field: "delta", what: "state" });
|
|
214
|
+
}
|
|
215
|
+
function applyActivityDelta(content, patch, warn) {
|
|
216
|
+
return applyPatch(content, patch, warn, { event: "ACTIVITY_DELTA", field: "patch", what: "activity" });
|
|
217
|
+
}
|
|
218
|
+
function applyPatch(doc, patch, warn, names) {
|
|
219
|
+
const { event, field, what } = names;
|
|
220
|
+
if (!Array.isArray(patch)) {
|
|
221
|
+
warn(`Failed to apply ${what} patch: ${event} carried no list of operations; the ${what} is left as it was.`);
|
|
222
|
+
return doc;
|
|
216
223
|
}
|
|
217
224
|
const ops = [];
|
|
218
|
-
|
|
225
|
+
patch.forEach((op, i) => {
|
|
219
226
|
if (isContainer(op) && !Array.isArray(op) && OPS.has(String(op["op"]))) ops.push(op);
|
|
220
|
-
else warn(`dropped
|
|
227
|
+
else warn(`dropped /${field}/${i}: ${JSON.stringify(op)} is not a JSON Patch operation.`);
|
|
221
228
|
});
|
|
222
229
|
try {
|
|
223
|
-
let
|
|
224
|
-
for (const op of ops)
|
|
225
|
-
return
|
|
230
|
+
let next = structuredClone(doc);
|
|
231
|
+
for (const op of ops) next = step(next, op);
|
|
232
|
+
return next;
|
|
226
233
|
} catch (error) {
|
|
227
|
-
warn(`Failed to apply
|
|
228
|
-
return
|
|
234
|
+
warn(`Failed to apply ${what} patch: ${error.message}; the ${what} is left as it was.`);
|
|
235
|
+
return doc;
|
|
229
236
|
}
|
|
230
237
|
}
|
|
238
|
+
function createSubagentHistory(from = []) {
|
|
239
|
+
const records = new Map(from.map((r) => [r.id, JSON.parse(JSON.stringify(r))]));
|
|
240
|
+
const record = (id) => {
|
|
241
|
+
let at = records.get(id);
|
|
242
|
+
if (!at) {
|
|
243
|
+
at = { id, name: "subagent", status: "running", messages: [] };
|
|
244
|
+
records.set(id, at);
|
|
245
|
+
}
|
|
246
|
+
return at;
|
|
247
|
+
};
|
|
248
|
+
const assistantCall = (owner, toolCallId) => {
|
|
249
|
+
for (const m of record(owner).messages) {
|
|
250
|
+
if (m.role !== "assistant") continue;
|
|
251
|
+
const call = m.toolCalls?.find((c) => c.id === toolCallId);
|
|
252
|
+
if (call) return call;
|
|
253
|
+
}
|
|
254
|
+
return void 0;
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
/** Announced (again, by a resumed run: the same invocation going on). */
|
|
258
|
+
start(id, info) {
|
|
259
|
+
const at = record(id);
|
|
260
|
+
Object.assign(at, info);
|
|
261
|
+
at.status = "running";
|
|
262
|
+
delete at.interruptIds;
|
|
263
|
+
},
|
|
264
|
+
end(id, status, interruptIds) {
|
|
265
|
+
const at = record(id);
|
|
266
|
+
at.status = status;
|
|
267
|
+
if (interruptIds?.length) at.interruptIds = interruptIds;
|
|
268
|
+
else delete at.interruptIds;
|
|
269
|
+
},
|
|
270
|
+
text(owner, messageId, delta) {
|
|
271
|
+
const messages = record(owner).messages;
|
|
272
|
+
let at = messages.find((m) => m.role === "assistant" && m.id === messageId);
|
|
273
|
+
if (!at) {
|
|
274
|
+
at = { id: messageId, role: "assistant", content: "" };
|
|
275
|
+
messages.push(at);
|
|
276
|
+
}
|
|
277
|
+
at.content = (at.content ?? "") + delta;
|
|
278
|
+
},
|
|
279
|
+
/** A call joins the assistant message that ends the transcript, as one model step writes text then calls. */
|
|
280
|
+
call(owner, toolCallId, name) {
|
|
281
|
+
const known = assistantCall(owner, toolCallId);
|
|
282
|
+
if (known) {
|
|
283
|
+
if (name) known.function.name = name;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const messages = record(owner).messages;
|
|
287
|
+
const last = messages.at(-1);
|
|
288
|
+
let host;
|
|
289
|
+
if (last?.role === "assistant") {
|
|
290
|
+
host = last;
|
|
291
|
+
} else {
|
|
292
|
+
host = { id: `${toolCallId}:call`, role: "assistant" };
|
|
293
|
+
messages.push(host);
|
|
294
|
+
}
|
|
295
|
+
host.toolCalls = [...host.toolCalls ?? [], { id: toolCallId, type: "function", function: { name, arguments: "" } }];
|
|
296
|
+
},
|
|
297
|
+
args(owner, toolCallId, delta) {
|
|
298
|
+
const call = assistantCall(owner, toolCallId);
|
|
299
|
+
if (call) call.function.arguments += delta;
|
|
300
|
+
},
|
|
301
|
+
result(owner, messageId, toolCallId, content) {
|
|
302
|
+
record(owner).messages.push({ id: messageId ?? `${toolCallId}:result`, role: "tool", toolCallId, content });
|
|
303
|
+
},
|
|
304
|
+
/** The run's sub-agents, `tanstack` set on each when the run was TanStack AI's. */
|
|
305
|
+
records(tanstack = false) {
|
|
306
|
+
return [...records.values()].map((r) => tanstack ? { ...r, tanstack: true } : r);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function subagentMessages(records, answers = /* @__PURE__ */ new Map()) {
|
|
311
|
+
return records.filter((r) => r.tanstack).flatMap((r) => withAnswers(r.messages, answers).map((m, i) => {
|
|
312
|
+
if (m.role !== "assistant" && m.role !== "tool") return m;
|
|
313
|
+
const card = i === 0 ? {
|
|
314
|
+
metadata: {
|
|
315
|
+
tanstack: {
|
|
316
|
+
subagent: {
|
|
317
|
+
name: r.name,
|
|
318
|
+
status: r.status,
|
|
319
|
+
...r.description !== void 0 ? { description: r.description } : {},
|
|
320
|
+
...r.parentToolCallId !== void 0 ? { parentToolCallId: r.parentToolCallId } : {},
|
|
321
|
+
...r.parentSubagentRunId !== void 0 ? { parentSubagentRunId: r.parentSubagentRunId } : {},
|
|
322
|
+
...r.interruptIds ? { interruptIds: r.interruptIds } : {},
|
|
323
|
+
...r.metadata ? { metadata: r.metadata } : {}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} : {};
|
|
328
|
+
return { ...m, subagentRunId: r.id, ...card };
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
function withAnswers(messages, answers) {
|
|
332
|
+
if (answers.size === 0) return [...messages];
|
|
333
|
+
const out = [];
|
|
334
|
+
for (let i = 0; i < messages.length; i++) {
|
|
335
|
+
const m = messages[i];
|
|
336
|
+
out.push(m);
|
|
337
|
+
if (m.role !== "assistant" || !m.toolCalls?.length) continue;
|
|
338
|
+
while (messages[i + 1]?.role === "tool") out.push(messages[++i]);
|
|
339
|
+
for (const call of m.toolCalls) {
|
|
340
|
+
const answer = answers.get(call.id);
|
|
341
|
+
if (answer) out.push(answer);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
231
346
|
function compare(a, b) {
|
|
232
347
|
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
233
348
|
const d = (a[i] ?? 0) - (b[i] ?? 0);
|
|
@@ -357,10 +472,10 @@ function createVerifier(relaxed) {
|
|
|
357
472
|
};
|
|
358
473
|
const stillOpen = () => [...text.keys()].map((id) => `text message "${id}"`).concat([...reasoning.keys()].map((id) => `reasoning message "${id}"`)).concat([...tools.keys()].map((id) => `tool call "${id}"`)).concat([...steps].map((key) => `step "${key.split("\0")[1]}"`)).concat([...subagents].map((id) => `subagent "${id}"`));
|
|
359
474
|
const agrees = (id, opened, event) => {
|
|
360
|
-
const
|
|
361
|
-
if (
|
|
475
|
+
const by2 = event["subagentRunId"];
|
|
476
|
+
if (by2 === void 0) return;
|
|
362
477
|
const at = opened.get(id);
|
|
363
|
-
if (at !== void 0 && at !==
|
|
478
|
+
if (at !== void 0 && at !== by2) throw malformed(`${event.type} for "${id}" is attributed to "${String(by2)}", but "${id}" started attributed to "${String(at)}"`);
|
|
364
479
|
};
|
|
365
480
|
const verify = ((event) => {
|
|
366
481
|
const type = event.type;
|
|
@@ -573,14 +688,25 @@ function readTicket(token) {
|
|
|
573
688
|
return void 0;
|
|
574
689
|
}
|
|
575
690
|
}
|
|
691
|
+
const PARENT = "";
|
|
692
|
+
function by(owner) {
|
|
693
|
+
return owner ? { agentId: owner } : {};
|
|
694
|
+
}
|
|
695
|
+
function routerPlanOf(event) {
|
|
696
|
+
const tanstack = event["metadata"]?.tanstack;
|
|
697
|
+
const steps = tanstack?.subagentPlan?.steps;
|
|
698
|
+
if (!Array.isArray(steps)) return void 0;
|
|
699
|
+
const names = steps.map((step2) => Array.isArray(step2?.names) ? step2.names.filter((n) => typeof n === "string" && n !== "") : []).filter((step2) => step2.length > 0);
|
|
700
|
+
return names.length ? names : void 0;
|
|
701
|
+
}
|
|
576
702
|
function interruptKind(interrupt) {
|
|
577
703
|
if (interrupt.reason === "tool_call" || interrupt.reason === "confirmation" || interrupt.reason === "approval_required") return "approval";
|
|
578
704
|
if (/client_tool/.test(interrupt.reason)) return "external";
|
|
579
705
|
return void 0;
|
|
580
706
|
}
|
|
581
|
-
function readUsage(value) {
|
|
707
|
+
function readUsage(value, pick = "last") {
|
|
582
708
|
const entries = (Array.isArray(value) ? value : value && typeof value === "object" ? [value] : []).filter((u) => Boolean(u) && typeof u === "object");
|
|
583
|
-
const last = entries.at(-1);
|
|
709
|
+
const last = pick === "first" ? entries[0] : entries.at(-1);
|
|
584
710
|
if (!last) return void 0;
|
|
585
711
|
const num = (key) => typeof last[key] === "number" ? last[key] : 0;
|
|
586
712
|
const cached = num("cachedInputTokens");
|
|
@@ -658,16 +784,23 @@ function createAgUiRunner(options) {
|
|
|
658
784
|
const runId = `aparte-run-${uuid()}`;
|
|
659
785
|
const interrupted = new Set((ticket?.interrupts ?? []).map((i) => i.toolCallId));
|
|
660
786
|
const messages = toAgUiMessages(request.messages, flatten, warn);
|
|
787
|
+
const pausedAgents = Array.isArray(ticket?.subagents) ? ticket.subagents : [];
|
|
788
|
+
const sentBack = new Set(pausedAgents.filter((r) => r.tanstack).map((r) => r.id));
|
|
789
|
+
const answers = /* @__PURE__ */ new Map();
|
|
661
790
|
for (const [toolCallId, outcome] of Object.entries(outcomes)) {
|
|
662
791
|
if (interrupted.has(toolCallId)) continue;
|
|
663
792
|
if (outcome.kind === "decision" && outcome.approved) continue;
|
|
664
|
-
|
|
793
|
+
const answer = {
|
|
665
794
|
id: `aparte-result-${toolCallId}`,
|
|
666
795
|
role: "tool",
|
|
667
796
|
toolCallId,
|
|
668
797
|
content: outcome.kind === "failed" ? `Error: ${outcome.error}` : outcome.kind === "decision" ? rejectionText(outcome) : outcome.content
|
|
669
|
-
}
|
|
798
|
+
};
|
|
799
|
+
const owner = ticket?.agents?.[toolCallId];
|
|
800
|
+
if (owner !== void 0 && sentBack.has(owner)) answers.set(toolCallId, answer);
|
|
801
|
+
else messages.push(answer);
|
|
670
802
|
}
|
|
803
|
+
messages.push(...subagentMessages(pausedAgents, answers));
|
|
671
804
|
const left = ticket ? [] : unanswered.get(threadId) ?? [];
|
|
672
805
|
unanswered.delete(threadId);
|
|
673
806
|
const resume = ticket?.interrupts.length ? ticket.interrupts.map((interrupt) => resumeItem(interrupt, interrupt.kind === "unanswerable" ? void 0 : outcomes[interrupt.toolCallId])) : left.length ? left.map((interruptId) => ({ interruptId, status: "cancelled" })) : void 0;
|
|
@@ -708,32 +841,112 @@ function createAgUiRunner(options) {
|
|
|
708
841
|
const answered = /* @__PURE__ */ new Set();
|
|
709
842
|
const rejected = /* @__PURE__ */ new Set();
|
|
710
843
|
const settledByPage = /* @__PURE__ */ new Set();
|
|
711
|
-
let textOpen = false;
|
|
712
844
|
let runError;
|
|
713
845
|
let usage;
|
|
714
846
|
let finished;
|
|
715
847
|
const emit = opts.emitter;
|
|
848
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
849
|
+
const lane = (owner) => {
|
|
850
|
+
let at = lanes.get(owner);
|
|
851
|
+
if (!at) {
|
|
852
|
+
at = { textOpen: false, stepEnded: false };
|
|
853
|
+
lanes.set(owner, at);
|
|
854
|
+
}
|
|
855
|
+
return at;
|
|
856
|
+
};
|
|
857
|
+
const messageOwners = /* @__PURE__ */ new Map();
|
|
858
|
+
const chunkMessages = /* @__PURE__ */ new Map();
|
|
859
|
+
const lastCall = /* @__PURE__ */ new Map();
|
|
860
|
+
const ticketAgents = new Map(Object.entries(ticket?.agents ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
861
|
+
const agents = /* @__PURE__ */ new Map();
|
|
862
|
+
const transcripts = createSubagentHistory(pausedAgents);
|
|
863
|
+
let tanstackRun = false;
|
|
864
|
+
let ownStep = false;
|
|
865
|
+
const tagOf = (event) => typeof event["subagentRunId"] === "string" ? event["subagentRunId"] : void 0;
|
|
866
|
+
const messageOwner = (event) => {
|
|
867
|
+
let id = typeof event["messageId"] === "string" ? event["messageId"] : void 0;
|
|
868
|
+
if (event.type.endsWith("_CHUNK")) {
|
|
869
|
+
const key = `${event.type}\0${tagOf(event) ?? PARENT}`;
|
|
870
|
+
if (id !== void 0) chunkMessages.set(key, id);
|
|
871
|
+
else id = chunkMessages.get(key);
|
|
872
|
+
}
|
|
873
|
+
const opened = id !== void 0 ? messageOwners.get(id) : void 0;
|
|
874
|
+
if (opened !== void 0) return opened;
|
|
875
|
+
const owner = tagOf(event) ?? PARENT;
|
|
876
|
+
if (id !== void 0) messageOwners.set(id, owner);
|
|
877
|
+
return owner;
|
|
878
|
+
};
|
|
879
|
+
const newCallOwner = (event) => tagOf(event) ?? (typeof event["parentMessageId"] === "string" ? messageOwners.get(event["parentMessageId"]) : void 0) ?? PARENT;
|
|
880
|
+
const callOwner = (toolCallId) => calls.get(toolCallId)?.owner ?? ticketAgents.get(toolCallId);
|
|
881
|
+
const flush = (owner) => {
|
|
882
|
+
const at = lanes.get(owner);
|
|
883
|
+
if (at?.textOpen) {
|
|
884
|
+
emit({ type: "text-flush", ...by(owner) });
|
|
885
|
+
at.textOpen = false;
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
const meet = (agentId) => {
|
|
889
|
+
if (agents.has(agentId)) return;
|
|
890
|
+
agents.set(agentId, { open: true });
|
|
891
|
+
emit({ type: "agent-start", agentId });
|
|
892
|
+
};
|
|
893
|
+
const openSteps = [];
|
|
894
|
+
const closeSteps = (owner) => {
|
|
895
|
+
for (let i = openSteps.length - 1; i >= 0; i -= 1) {
|
|
896
|
+
const step2 = openSteps[i];
|
|
897
|
+
if (owner !== void 0 && step2.owner !== owner) continue;
|
|
898
|
+
openSteps.splice(i, 1);
|
|
899
|
+
emit({ type: "step-end", name: step2.name, ...by(step2.owner) });
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
track.closeSteps = closeSteps;
|
|
903
|
+
const activities = /* @__PURE__ */ new Map();
|
|
904
|
+
const showActivity = (id, at) => {
|
|
905
|
+
activities.set(id, at);
|
|
906
|
+
emit({ type: "activity", id, activityType: at.activityType, content: at.content, ...by(at.owner) });
|
|
907
|
+
};
|
|
908
|
+
const paused = ticket?.plan;
|
|
909
|
+
let routerPlan = paused && Array.isArray(paused.steps) ? { steps: paused.steps, status: new Map(Object.entries(paused.status ?? {})) } : void 0;
|
|
910
|
+
const planId = typeof paused?.id === "string" ? paused.id : `router-plan:${runId}`;
|
|
911
|
+
const tickRouterPlan = (name, status) => {
|
|
912
|
+
const at = name === void 0 ? -1 : routerPlan?.steps.findIndex((step2) => step2.includes(name)) ?? -1;
|
|
913
|
+
if (!routerPlan || at < 0) return;
|
|
914
|
+
if (status === "in-progress") {
|
|
915
|
+
for (const before of routerPlan.steps.slice(0, at).flat()) if (routerPlan.status.get(before) !== "failed") routerPlan.status.set(before, "done");
|
|
916
|
+
}
|
|
917
|
+
routerPlan.status.set(name, status);
|
|
918
|
+
const steps = routerPlan.steps.flat().map((title) => ({ title, status: routerPlan.status.get(title) ?? "pending" }));
|
|
919
|
+
showActivity(planId, { activityType: "plan", content: { steps }, owner: PARENT });
|
|
920
|
+
};
|
|
921
|
+
const endAgent = (agentId, status, detail = {}) => {
|
|
922
|
+
closeSteps(agentId);
|
|
923
|
+
const agent = agents.get(agentId);
|
|
924
|
+
if (agent) agent.open = false;
|
|
925
|
+
emit({ type: "agent-end", agentId, status, ...detail, ...agent?.parent ? { parentAgentId: agent.parent } : {} });
|
|
926
|
+
if (agent && !agent.parent) tickRouterPlan(agent.name, status === "waiting" ? "blocked" : status);
|
|
927
|
+
};
|
|
928
|
+
const failOpenAgents = (error) => {
|
|
929
|
+
for (const [agentId, agent] of [...agents].reverse()) {
|
|
930
|
+
if (!agent.open) continue;
|
|
931
|
+
flush(agentId);
|
|
932
|
+
endAgent(agentId, "failed", { error });
|
|
933
|
+
}
|
|
934
|
+
};
|
|
716
935
|
const startTool = (toolCallId) => {
|
|
717
936
|
const call = calls.get(toolCallId);
|
|
718
937
|
if (!call || started.has(toolCallId) || known.has(toolCallId)) return;
|
|
719
938
|
const input = parseArgs(call.args);
|
|
720
|
-
started.set(toolCallId, { name: call.name, input });
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
emit({ type: "tool-start", toolCallId, name: call.name, input });
|
|
726
|
-
stepEnded = true;
|
|
939
|
+
started.set(toolCallId, { name: call.name, input, owner: call.owner });
|
|
940
|
+
flush(call.owner);
|
|
941
|
+
emit({ type: "tool-start", toolCallId, name: call.name, input, ...by(call.owner) });
|
|
942
|
+
lane(call.owner).stepEnded = true;
|
|
727
943
|
};
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (!stepEnded) return;
|
|
731
|
-
stepEnded = false;
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
textOpen = false;
|
|
735
|
-
}
|
|
736
|
-
emit({ type: "turn-start" });
|
|
944
|
+
const nextStep = (owner) => {
|
|
945
|
+
const at = lane(owner);
|
|
946
|
+
if (!at.stepEnded) return;
|
|
947
|
+
at.stepEnded = false;
|
|
948
|
+
flush(owner);
|
|
949
|
+
emit({ type: "turn-start", ...by(owner) });
|
|
737
950
|
};
|
|
738
951
|
const callOf = (interrupt) => {
|
|
739
952
|
const here = started.get(interrupt.toolCallId) ?? history.get(interrupt.toolCallId);
|
|
@@ -751,25 +964,28 @@ function createAgUiRunner(options) {
|
|
|
751
964
|
emit({ type: "run-start" });
|
|
752
965
|
emit({ type: "turn-start" });
|
|
753
966
|
for (const [toolCallId, outcome] of Object.entries(outcomes)) {
|
|
967
|
+
const owner = ticketAgents.get(toolCallId) ?? PARENT;
|
|
968
|
+
if (owner) lane(owner).stepEnded = true;
|
|
754
969
|
if (outcome.kind === "result") {
|
|
755
970
|
emit({
|
|
756
971
|
type: "tool-resolved",
|
|
757
972
|
toolCallId,
|
|
758
973
|
result: outcome.content,
|
|
759
|
-
...outcome.structuredContent !== void 0 ? { structuredResult: outcome.structuredContent } : {}
|
|
974
|
+
...outcome.structuredContent !== void 0 ? { structuredResult: outcome.structuredContent } : {},
|
|
975
|
+
...by(owner)
|
|
760
976
|
});
|
|
761
977
|
settledByPage.add(toolCallId);
|
|
762
978
|
continue;
|
|
763
979
|
}
|
|
764
980
|
if (outcome.kind === "failed") {
|
|
765
|
-
emit({ type: "tool-failed", toolCallId, error: outcome.error });
|
|
981
|
+
emit({ type: "tool-failed", toolCallId, error: outcome.error, ...by(owner) });
|
|
766
982
|
settledByPage.add(toolCallId);
|
|
767
983
|
continue;
|
|
768
984
|
}
|
|
769
985
|
if (outcome.approved) {
|
|
770
|
-
emit({ type: "tool-approved", toolCallId });
|
|
986
|
+
emit({ type: "tool-approved", toolCallId, ...by(owner) });
|
|
771
987
|
} else {
|
|
772
|
-
emit({ type: "tool-rejected", toolCallId, ...toolRefusal(outcome) });
|
|
988
|
+
emit({ type: "tool-rejected", toolCallId, ...toolRefusal(outcome), ...by(owner) });
|
|
773
989
|
rejected.add(toolCallId);
|
|
774
990
|
}
|
|
775
991
|
}
|
|
@@ -788,15 +1004,20 @@ function createAgUiRunner(options) {
|
|
|
788
1004
|
verify(event);
|
|
789
1005
|
begin();
|
|
790
1006
|
if (/^(TEXT_MESSAGE|REASONING|TOOL_CALL)_/.test(event.type)) endedOnFinish = false;
|
|
1007
|
+
const tag = tagOf(event);
|
|
1008
|
+
if (tag !== void 0 && !event.type.startsWith("SUBAGENT_")) meet(tag);
|
|
1009
|
+
const metadata = event.metadata;
|
|
1010
|
+
if (metadata && typeof metadata === "object" && typeof metadata.tanstack === "object") tanstackRun = true;
|
|
791
1011
|
switch (event.type) {
|
|
792
1012
|
case "RUN_STARTED": {
|
|
793
1013
|
if (runError) {
|
|
794
1014
|
warn(`the agent's run failed ("${runError.message}") and the stream ran it again; the reply shows the new run.`);
|
|
795
|
-
for (const [toolCallId] of started) {
|
|
1015
|
+
for (const [toolCallId, call] of started) {
|
|
796
1016
|
if (answered.has(toolCallId)) continue;
|
|
797
|
-
emit({ type: "tool-aborted", toolCallId });
|
|
1017
|
+
emit({ type: "tool-aborted", toolCallId, ...by(call.owner) });
|
|
798
1018
|
answered.add(toolCallId);
|
|
799
1019
|
}
|
|
1020
|
+
failOpenAgents(runError.message);
|
|
800
1021
|
runError = void 0;
|
|
801
1022
|
}
|
|
802
1023
|
const declared = event["protocolVersion"];
|
|
@@ -809,52 +1030,75 @@ function createAgUiRunner(options) {
|
|
|
809
1030
|
}
|
|
810
1031
|
case "TEXT_MESSAGE_START":
|
|
811
1032
|
case "REASONING_START":
|
|
812
|
-
case "REASONING_MESSAGE_START":
|
|
813
|
-
|
|
1033
|
+
case "REASONING_MESSAGE_START": {
|
|
1034
|
+
const owner = messageOwner(event);
|
|
1035
|
+
if (owner === PARENT) ownStep = true;
|
|
1036
|
+
nextStep(owner);
|
|
814
1037
|
break;
|
|
1038
|
+
}
|
|
815
1039
|
case "TEXT_MESSAGE_CONTENT":
|
|
816
1040
|
case "TEXT_MESSAGE_CHUNK": {
|
|
1041
|
+
const owner = messageOwner(event);
|
|
817
1042
|
const delta = typeof event["delta"] === "string" ? event["delta"] : "";
|
|
1043
|
+
if (owner === PARENT) ownStep = true;
|
|
818
1044
|
if (delta) {
|
|
819
|
-
nextStep();
|
|
820
|
-
emit({ type: "text-delta", delta });
|
|
821
|
-
textOpen = true;
|
|
1045
|
+
nextStep(owner);
|
|
1046
|
+
emit({ type: "text-delta", delta, ...by(owner) });
|
|
1047
|
+
lane(owner).textOpen = true;
|
|
822
1048
|
}
|
|
1049
|
+
const id = typeof event["messageId"] === "string" ? event["messageId"] : chunkMessages.get(`${event.type}\0${tag ?? PARENT}`);
|
|
1050
|
+
if (owner !== PARENT && delta && id !== void 0) transcripts.text(owner, id, delta);
|
|
823
1051
|
break;
|
|
824
1052
|
}
|
|
825
|
-
case "TEXT_MESSAGE_END":
|
|
826
|
-
|
|
827
|
-
emit({ type: "text-flush" });
|
|
828
|
-
textOpen = false;
|
|
829
|
-
}
|
|
1053
|
+
case "TEXT_MESSAGE_END":
|
|
1054
|
+
flush(messageOwner(event));
|
|
830
1055
|
break;
|
|
831
|
-
}
|
|
832
1056
|
case "REASONING_MESSAGE_CONTENT":
|
|
833
1057
|
case "REASONING_MESSAGE_CHUNK": {
|
|
1058
|
+
const owner = messageOwner(event);
|
|
834
1059
|
const delta = typeof event["delta"] === "string" ? event["delta"] : "";
|
|
835
1060
|
if (delta) {
|
|
836
|
-
nextStep();
|
|
837
|
-
emit({ type: "thinking-delta", delta });
|
|
1061
|
+
nextStep(owner);
|
|
1062
|
+
emit({ type: "thinking-delta", delta, ...by(owner) });
|
|
838
1063
|
}
|
|
839
1064
|
break;
|
|
840
1065
|
}
|
|
841
1066
|
case "TOOL_CALL_START": {
|
|
842
1067
|
const id = String(event["toolCallId"]);
|
|
843
|
-
calls.
|
|
1068
|
+
if (!calls.has(id)) lastCall.set(tag ?? PARENT, id);
|
|
1069
|
+
const owner = newCallOwner(event);
|
|
1070
|
+
const name = String(event["toolCallName"] ?? "");
|
|
1071
|
+
calls.set(id, { name, args: "", owner });
|
|
1072
|
+
if (owner === PARENT) ownStep = true;
|
|
1073
|
+
else transcripts.call(owner, id, name);
|
|
844
1074
|
break;
|
|
845
1075
|
}
|
|
846
1076
|
case "TOOL_CALL_ARGS": {
|
|
847
|
-
const
|
|
848
|
-
|
|
1077
|
+
const id = String(event["toolCallId"]);
|
|
1078
|
+
const call = calls.get(id);
|
|
1079
|
+
if (call && typeof event["delta"] === "string") {
|
|
1080
|
+
call.args += event["delta"];
|
|
1081
|
+
if (call.owner !== PARENT) transcripts.args(call.owner, id, event["delta"]);
|
|
1082
|
+
}
|
|
849
1083
|
break;
|
|
850
1084
|
}
|
|
851
1085
|
case "TOOL_CALL_CHUNK": {
|
|
852
|
-
const id = typeof event["toolCallId"] === "string" ? event["toolCallId"] :
|
|
1086
|
+
const id = typeof event["toolCallId"] === "string" ? event["toolCallId"] : lastCall.get(tag ?? PARENT);
|
|
853
1087
|
if (!id) break;
|
|
854
|
-
|
|
1088
|
+
let call = calls.get(id);
|
|
1089
|
+
if (!call) {
|
|
1090
|
+
call = { name: "", args: "", owner: newCallOwner(event) };
|
|
1091
|
+
calls.set(id, call);
|
|
1092
|
+
lastCall.set(tag ?? PARENT, id);
|
|
1093
|
+
}
|
|
855
1094
|
if (typeof event["toolCallName"] === "string" && event["toolCallName"]) call.name = event["toolCallName"];
|
|
856
1095
|
if (typeof event["delta"] === "string") call.args += event["delta"];
|
|
857
|
-
|
|
1096
|
+
if (call.owner === PARENT) {
|
|
1097
|
+
ownStep = true;
|
|
1098
|
+
} else {
|
|
1099
|
+
transcripts.call(call.owner, id, call.name);
|
|
1100
|
+
if (typeof event["delta"] === "string") transcripts.args(call.owner, id, event["delta"]);
|
|
1101
|
+
}
|
|
858
1102
|
break;
|
|
859
1103
|
}
|
|
860
1104
|
case "TOOL_CALL_END": {
|
|
@@ -866,13 +1110,66 @@ function createAgUiRunner(options) {
|
|
|
866
1110
|
startTool(toolCallId);
|
|
867
1111
|
if (rejected.has(toolCallId) || settledByPage.has(toolCallId)) break;
|
|
868
1112
|
answered.add(toolCallId);
|
|
1113
|
+
const owner = callOwner(toolCallId) ?? tag ?? PARENT;
|
|
869
1114
|
const content = resultText(event["content"]);
|
|
1115
|
+
if (owner !== PARENT) transcripts.result(owner, typeof event["messageId"] === "string" ? event["messageId"] : void 0, toolCallId, content);
|
|
870
1116
|
const meta = event["metadata"];
|
|
871
1117
|
if (meta?.tanstack?.state === "output-error") {
|
|
872
|
-
emit({ type: "tool-failed", toolCallId, error: content });
|
|
1118
|
+
emit({ type: "tool-failed", toolCallId, error: content, ...by(owner) });
|
|
873
1119
|
} else {
|
|
874
1120
|
const parts = Array.isArray(event["content"]) ? { structuredResult: event["content"] } : {};
|
|
875
|
-
emit({ type: "tool-resolved", toolCallId, result: content, ...parts });
|
|
1121
|
+
emit({ type: "tool-resolved", toolCallId, result: content, ...parts, ...by(owner) });
|
|
1122
|
+
}
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
case "SUBAGENT_STARTED": {
|
|
1126
|
+
const agentId = String(event["subagentRunId"]);
|
|
1127
|
+
const toolCallId = typeof event["parentToolCallId"] === "string" ? event["parentToolCallId"] : void 0;
|
|
1128
|
+
const parent = typeof event["parentSubagentRunId"] === "string" ? event["parentSubagentRunId"] : void 0;
|
|
1129
|
+
if (toolCallId) startTool(toolCallId);
|
|
1130
|
+
const name = String(event["name"]);
|
|
1131
|
+
agents.set(agentId, { open: true, name, ...parent ? { parent } : {} });
|
|
1132
|
+
const metadata2 = event["metadata"];
|
|
1133
|
+
transcripts.start(agentId, {
|
|
1134
|
+
name,
|
|
1135
|
+
...typeof event["description"] === "string" ? { description: event["description"] } : {},
|
|
1136
|
+
...toolCallId ? { parentToolCallId: toolCallId } : {},
|
|
1137
|
+
...parent ? { parentSubagentRunId: parent } : {},
|
|
1138
|
+
...metadata2 && typeof metadata2 === "object" && !Array.isArray(metadata2) ? { metadata: metadata2 } : {}
|
|
1139
|
+
});
|
|
1140
|
+
if (!parent) {
|
|
1141
|
+
if (!routerPlan) {
|
|
1142
|
+
const steps = routerPlanOf(event);
|
|
1143
|
+
if (steps) routerPlan = { steps, status: /* @__PURE__ */ new Map() };
|
|
1144
|
+
}
|
|
1145
|
+
tickRouterPlan(name, "in-progress");
|
|
1146
|
+
}
|
|
1147
|
+
emit({
|
|
1148
|
+
type: "agent-start",
|
|
1149
|
+
agentId,
|
|
1150
|
+
name,
|
|
1151
|
+
...typeof event["description"] === "string" ? { description: event["description"] } : {},
|
|
1152
|
+
...toolCallId ? { toolCallId } : {},
|
|
1153
|
+
...parent ? { parentAgentId: parent } : {}
|
|
1154
|
+
});
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
case "SUBAGENT_FINISHED":
|
|
1158
|
+
case "SUBAGENT_ERROR": {
|
|
1159
|
+
const agentId = String(event["subagentRunId"]);
|
|
1160
|
+
flush(agentId);
|
|
1161
|
+
for (const [id, call] of calls) if (call.owner === agentId) startTool(id);
|
|
1162
|
+
const outcome = event["outcome"];
|
|
1163
|
+
if (event.type === "SUBAGENT_ERROR") {
|
|
1164
|
+
transcripts.end(agentId, "error");
|
|
1165
|
+
endAgent(agentId, "failed", { error: typeof event["message"] === "string" && event["message"] ? event["message"] : "The sub-agent failed." });
|
|
1166
|
+
} else if (outcome?.type === "suspended") {
|
|
1167
|
+
const ids = Array.isArray(outcome.interruptIds) ? outcome.interruptIds.filter((id) => typeof id === "string") : [];
|
|
1168
|
+
transcripts.end(agentId, "suspended", ids);
|
|
1169
|
+
endAgent(agentId, "waiting");
|
|
1170
|
+
} else {
|
|
1171
|
+
transcripts.end(agentId, "finished");
|
|
1172
|
+
endAgent(agentId, "done", event["result"] !== void 0 ? { result: resultText(event["result"]) } : {});
|
|
876
1173
|
}
|
|
877
1174
|
break;
|
|
878
1175
|
}
|
|
@@ -884,6 +1181,9 @@ function createAgUiRunner(options) {
|
|
|
884
1181
|
);
|
|
885
1182
|
break;
|
|
886
1183
|
}
|
|
1184
|
+
// State is run-scoped in AG-UI 1.0: a sub-agent's STATE_* updates the run's state
|
|
1185
|
+
// exactly as the parent's does — its `subagentRunId` is provenance, and the
|
|
1186
|
+
// protocol forbids withholding the update because a sub-agent sent it.
|
|
887
1187
|
case "STATE_SNAPSHOT":
|
|
888
1188
|
states.set(threadId, event["snapshot"]);
|
|
889
1189
|
break;
|
|
@@ -892,12 +1192,12 @@ function createAgUiRunner(options) {
|
|
|
892
1192
|
break;
|
|
893
1193
|
case "RUN_FINISHED": {
|
|
894
1194
|
for (const id of calls.keys()) startTool(id);
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
stepEnded = true;
|
|
1195
|
+
for (const owner of lanes.keys()) flush(owner);
|
|
1196
|
+
if (agents.size === 0) usage = readUsage(event["usage"]) ?? usage;
|
|
1197
|
+
else if (ownStep) usage = readUsage(event["usage"], "first") ?? usage;
|
|
1198
|
+
ownStep = false;
|
|
1199
|
+
lane(PARENT).stepEnded = true;
|
|
1200
|
+
for (const at of lanes.values()) at.stepEnded = true;
|
|
901
1201
|
endedOnFinish = true;
|
|
902
1202
|
finished = {
|
|
903
1203
|
runId: typeof event["runId"] === "string" ? event["runId"] : runId,
|
|
@@ -905,12 +1205,57 @@ function createAgUiRunner(options) {
|
|
|
905
1205
|
};
|
|
906
1206
|
break;
|
|
907
1207
|
}
|
|
1208
|
+
// A step is a name with a start and an end, its owner's: shown while it lasts
|
|
1209
|
+
// (the waiting indicator, a sub-agent's line). Steps may nest and overlap, and
|
|
1210
|
+
// one ends the latest open step of its name under the same owner.
|
|
1211
|
+
case "STEP_STARTED": {
|
|
1212
|
+
const owner = tag ?? PARENT;
|
|
1213
|
+
const name = String(event["stepName"] ?? "");
|
|
1214
|
+
openSteps.push({ owner, name });
|
|
1215
|
+
emit({ type: "step-start", name, ...by(owner) });
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
case "STEP_FINISHED": {
|
|
1219
|
+
const owner = tag ?? PARENT;
|
|
1220
|
+
const name = String(event["stepName"] ?? "");
|
|
1221
|
+
for (let i = openSteps.length - 1; i >= 0; i -= 1) {
|
|
1222
|
+
if (openSteps[i].owner !== owner || openSteps[i].name !== name) continue;
|
|
1223
|
+
openSteps.splice(i, 1);
|
|
1224
|
+
emit({ type: "step-end", name, ...by(owner) });
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
// An activity is structured progress, drawn by the renderer the app registered for
|
|
1230
|
+
// its type. A snapshot overwrites it unless it says `replace: false` (AG-UI 1.0:
|
|
1231
|
+
// "only an explicit false asks a consumer to leave what is already there") — its
|
|
1232
|
+
// type included; a delta patches what is held. Either way the page gets it whole.
|
|
1233
|
+
case "ACTIVITY_SNAPSHOT": {
|
|
1234
|
+
const id = String(event["messageId"] ?? "");
|
|
1235
|
+
const held = activities.get(id);
|
|
1236
|
+
if (held && event["replace"] === false) break;
|
|
1237
|
+
showActivity(id, { activityType: String(event["activityType"] ?? ""), content: event["content"], owner: held?.owner ?? tag ?? PARENT });
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "ACTIVITY_DELTA": {
|
|
1241
|
+
const id = String(event["messageId"] ?? "");
|
|
1242
|
+
const held = activities.get(id);
|
|
1243
|
+
if (!held) {
|
|
1244
|
+
warn(`ACTIVITY_DELTA for "${id}", an activity no ACTIVITY_SNAPSHOT created: skipped.`);
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
const content = applyActivityDelta(held.content, event["patch"], warn);
|
|
1248
|
+
if (content !== held.content) showActivity(id, { ...held, content });
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
908
1251
|
}
|
|
909
1252
|
}
|
|
1253
|
+
closeSteps();
|
|
910
1254
|
if (opts.signal.aborted) return usage;
|
|
911
1255
|
if (runError) {
|
|
912
|
-
for (const [toolCallId] of started) if (!answered.has(toolCallId)) emit({ type: "tool-aborted", toolCallId });
|
|
913
|
-
|
|
1256
|
+
for (const [toolCallId, call] of started) if (!answered.has(toolCallId)) emit({ type: "tool-aborted", toolCallId, ...by(call.owner) });
|
|
1257
|
+
for (const owner of lanes.keys()) flush(owner);
|
|
1258
|
+
failOpenAgents(runError.message);
|
|
914
1259
|
throw runError;
|
|
915
1260
|
}
|
|
916
1261
|
if (!finished) {
|
|
@@ -920,7 +1265,7 @@ function createAgUiRunner(options) {
|
|
|
920
1265
|
throw new AparteError("The agent stream was cut: it ended in the middle of a step, after a RUN_FINISHED.", AparteErrorCode.PROVIDER_ERROR);
|
|
921
1266
|
}
|
|
922
1267
|
verify.end();
|
|
923
|
-
|
|
1268
|
+
for (const owner of lanes.keys()) flush(owner);
|
|
924
1269
|
const pending = [];
|
|
925
1270
|
const interrupts = [];
|
|
926
1271
|
const unanswerable = [];
|
|
@@ -932,11 +1277,12 @@ function createAgUiRunner(options) {
|
|
|
932
1277
|
continue;
|
|
933
1278
|
}
|
|
934
1279
|
const call = callOf(interrupt);
|
|
1280
|
+
const owner = interrupt.subagentRunId ?? callOwner(interrupt.toolCallId) ?? PARENT;
|
|
935
1281
|
if (kind === "approval") {
|
|
936
|
-
emit({ type: "tool-awaiting-approval", toolCallId: interrupt.toolCallId, name: call.name, input: call.input });
|
|
1282
|
+
emit({ type: "tool-awaiting-approval", toolCallId: interrupt.toolCallId, name: call.name, input: call.input, ...by(owner) });
|
|
937
1283
|
}
|
|
938
1284
|
interrupts.push({ id: interrupt.id, toolCallId: interrupt.toolCallId, kind });
|
|
939
|
-
pending.push({ toolCallId: interrupt.toolCallId, name: call.name, input: call.input, reason: kind });
|
|
1285
|
+
pending.push({ toolCallId: interrupt.toolCallId, name: call.name, input: call.input, reason: kind, ...by(owner) });
|
|
940
1286
|
}
|
|
941
1287
|
} else {
|
|
942
1288
|
const outcome = finished.outcome;
|
|
@@ -944,13 +1290,25 @@ function createAgUiRunner(options) {
|
|
|
944
1290
|
for (const [toolCallId, call] of started) {
|
|
945
1291
|
if (answered.has(toolCallId)) continue;
|
|
946
1292
|
const forPage = outcome?.type !== "cancelled" && (named ? named.has(toolCallId) : browserTools.has(call.name));
|
|
947
|
-
if (forPage) pending.push({ toolCallId, name: call.name, input: call.input, reason: "external" });
|
|
948
|
-
else emit({ type: "tool-aborted", toolCallId });
|
|
1293
|
+
if (forPage) pending.push({ toolCallId, name: call.name, input: call.input, reason: "external", ...by(call.owner) });
|
|
1294
|
+
else emit({ type: "tool-aborted", toolCallId, ...by(call.owner) });
|
|
949
1295
|
}
|
|
950
1296
|
}
|
|
1297
|
+
for (const [agentId, agent] of [...agents].reverse()) {
|
|
1298
|
+
if (agent.open) endAgent(agentId, pending.some((p) => p.agentId === agentId) ? "waiting" : "done");
|
|
1299
|
+
}
|
|
951
1300
|
if (pending.length > 0) {
|
|
952
1301
|
const cancelled = unanswerable.map((id) => ({ id, toolCallId: "", kind: "unanswerable" }));
|
|
953
|
-
const
|
|
1302
|
+
const owned = pending.filter((p) => p.agentId).map((p) => [p.toolCallId, p.agentId]);
|
|
1303
|
+
const next = {
|
|
1304
|
+
v: 1,
|
|
1305
|
+
threadId,
|
|
1306
|
+
runId: finished.runId,
|
|
1307
|
+
interrupts: [...interrupts, ...cancelled],
|
|
1308
|
+
...owned.length ? { agents: Object.fromEntries(owned) } : {},
|
|
1309
|
+
...transcripts.records().length ? { subagents: transcripts.records(tanstackRun) } : {},
|
|
1310
|
+
...routerPlan ? { plan: { id: planId, steps: routerPlan.steps, status: Object.fromEntries(routerPlan.status) } } : {}
|
|
1311
|
+
};
|
|
954
1312
|
track.suspended = true;
|
|
955
1313
|
emit({ type: "run-suspended", pending, ...usage ? { usage } : {}, ticket: JSON.stringify(next) });
|
|
956
1314
|
} else {
|
|
@@ -965,6 +1323,7 @@ function createAgUiRunner(options) {
|
|
|
965
1323
|
return await runOnce(opts, track);
|
|
966
1324
|
} finally {
|
|
967
1325
|
if (track.threadId && !stableThread && !track.suspended) states.delete(track.threadId);
|
|
1326
|
+
track.closeSteps?.();
|
|
968
1327
|
}
|
|
969
1328
|
};
|
|
970
1329
|
}
|