agentic-ui-kit 0.2.1 → 0.3.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 +109 -2
- package/dist/core.js +96 -1
- package/dist/index.js +1413 -286
- package/dist/styles.css +1 -1
- package/dist/tailwind.css +30 -2
- package/dist/types/core/src/index.d.ts +8 -1
- package/dist/types/core/src/partial-json.d.ts +8 -0
- package/dist/types/core/src/types.d.ts +5 -0
- package/dist/types/core/src/ui-markup.d.ts +92 -0
- package/dist/types/core/src/ui-tree.d.ts +110 -0
- package/dist/types/core/src/visualize.d.ts +60 -0
- package/dist/types/react/src/components/chat-notice.d.ts +11 -0
- package/dist/types/react/src/components/markdown.d.ts +19 -1
- package/dist/types/react/src/components/message-list.d.ts +5 -1
- package/dist/types/react/src/components/messages.d.ts +3 -2
- package/dist/types/react/src/components/tools.d.ts +20 -7
- package/dist/types/react/src/components/ui-tree.d.ts +87 -0
- package/dist/types/react/src/components/widget-frame.d.ts +97 -0
- package/dist/types/react/src/index.d.ts +15 -3
- package/dist/types/react/src/primitives/chat-theme.d.ts +13 -1
- package/dist/types/react/src/primitives/icons.d.ts +95 -0
- package/dist/types/react/src/primitives/labels.d.ts +193 -0
- package/dist/types/react/src/views/chat-transcript.d.ts +27 -0
- package/dist/types/react/src/views/chat-view.d.ts +15 -2
- package/dist/ui-markup-6xbP-myu.js +1082 -0
- package/package.json +1 -1
- package/dist/sse-BA_ebMcH.js +0 -408
package/package.json
CHANGED
package/dist/sse-BA_ebMcH.js
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
// ../core/src/partial-json.ts
|
|
2
|
-
const tryParse = (text) => {
|
|
3
|
-
try {
|
|
4
|
-
return JSON.parse(text);
|
|
5
|
-
} catch {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Best-effort value of JSON that is still streaming, so a tool call's fields can show while the model
|
|
11
|
-
* writes them: `{"title":"Stand-up","day":"Tu` gives `{ title: "Stand-up", day: "Tu" }`. Open strings are
|
|
12
|
-
* cut where the text ends, open objects and arrays are closed, and a key without its value yet is left
|
|
13
|
-
* out. Returns undefined when nothing usable has arrived. One pass over the text.
|
|
14
|
-
*/
|
|
15
|
-
function parsePartialJson(text) {
|
|
16
|
-
const whole = tryParse(text);
|
|
17
|
-
if (whole !== void 0) return whole;
|
|
18
|
-
const stack = [];
|
|
19
|
-
const closers = () => stack.map((f) => f.close).reverse().join("");
|
|
20
|
-
let safe = 0;
|
|
21
|
-
let safeClose = "";
|
|
22
|
-
const valueDone = (end) => {
|
|
23
|
-
const top = stack.at(-1);
|
|
24
|
-
if (top) top.expect = "comma";
|
|
25
|
-
safe = end;
|
|
26
|
-
safeClose = closers();
|
|
27
|
-
};
|
|
28
|
-
let i = 0;
|
|
29
|
-
const n = text.length;
|
|
30
|
-
while (i < n) {
|
|
31
|
-
const ch = text[i];
|
|
32
|
-
if (ch === " " || ch === "\n" || ch === "\r" || ch === " ") {
|
|
33
|
-
i++;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
const top = stack.at(-1);
|
|
37
|
-
if (ch === "\"") {
|
|
38
|
-
let j = i + 1;
|
|
39
|
-
let escaped = false;
|
|
40
|
-
for (; j < n; j++) {
|
|
41
|
-
const c = text[j];
|
|
42
|
-
if (escaped) escaped = false;
|
|
43
|
-
else if (c === "\\") escaped = true;
|
|
44
|
-
else if (c === "\"") break;
|
|
45
|
-
}
|
|
46
|
-
const isKey = top?.close === "}" && top.expect === "key";
|
|
47
|
-
if (j >= n) {
|
|
48
|
-
if (isKey) break;
|
|
49
|
-
const body = text.slice(i, n).replace(/\\(u[0-9a-fA-F]{0,3})?$/, "");
|
|
50
|
-
return tryParse(text.slice(0, i) + body + "\"" + closers()) ?? tryParse(text.slice(0, safe) + safeClose);
|
|
51
|
-
}
|
|
52
|
-
i = j + 1;
|
|
53
|
-
if (isKey) top.expect = "colon";
|
|
54
|
-
else valueDone(i);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if (ch === ":") {
|
|
58
|
-
if (top) top.expect = "value";
|
|
59
|
-
i++;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (ch === ",") {
|
|
63
|
-
if (top) top.expect = top.close === "}" ? "key" : "value";
|
|
64
|
-
i++;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (ch === "{" || ch === "[") {
|
|
68
|
-
stack.push({
|
|
69
|
-
close: ch === "{" ? "}" : "]",
|
|
70
|
-
expect: ch === "{" ? "key" : "value"
|
|
71
|
-
});
|
|
72
|
-
i++;
|
|
73
|
-
safe = i;
|
|
74
|
-
safeClose = closers();
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (ch === "}" || ch === "]") {
|
|
78
|
-
stack.pop();
|
|
79
|
-
i++;
|
|
80
|
-
valueDone(i);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
let j = i;
|
|
84
|
-
while (j < n && !" \n\r ,]}:".includes(text[j])) j++;
|
|
85
|
-
if (j >= n) {
|
|
86
|
-
if (/^-?\d+(\.\d+)?$/.test(text.slice(i, j))) return tryParse(text.slice(0, j) + closers()) ?? tryParse(text.slice(0, safe) + safeClose);
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
i = j;
|
|
90
|
-
valueDone(i);
|
|
91
|
-
}
|
|
92
|
-
return safe === 0 ? void 0 : tryParse(text.slice(0, safe) + safeClose);
|
|
93
|
-
}
|
|
94
|
-
// ../core/src/reduce.ts
|
|
95
|
-
/**
|
|
96
|
-
* Applies one stream event to the message it builds. Pure and immutable: returns the same object when the
|
|
97
|
-
* event changes nothing, so React can skip renders.
|
|
98
|
-
*
|
|
99
|
-
* Events with a seq at or below `message.lastSeq` are dropped, so replaying from an earlier seq after a
|
|
100
|
-
* reconnect is safe. Events other than run.start need the message to exist; without it they are dropped.
|
|
101
|
-
*/
|
|
102
|
-
function applyEvent(message, event) {
|
|
103
|
-
if (event.type === "run.start") {
|
|
104
|
-
if (message?.runId === event.runId && (message.lastSeq ?? -1) >= event.seq) return message;
|
|
105
|
-
if (message?.id === event.messageId) return {
|
|
106
|
-
...message,
|
|
107
|
-
status: "streaming",
|
|
108
|
-
runId: event.runId,
|
|
109
|
-
lastSeq: event.seq
|
|
110
|
-
};
|
|
111
|
-
return {
|
|
112
|
-
id: event.messageId,
|
|
113
|
-
parentId: event.parentId,
|
|
114
|
-
role: "assistant",
|
|
115
|
-
status: "streaming",
|
|
116
|
-
parts: [],
|
|
117
|
-
createdAt: event.createdAt,
|
|
118
|
-
runId: event.runId,
|
|
119
|
-
lastSeq: event.seq,
|
|
120
|
-
...event.model !== void 0 && { model: event.model },
|
|
121
|
-
...event.effort !== void 0 && { effort: event.effort }
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (!message || message.runId !== event.runId) return message;
|
|
125
|
-
if ((message.lastSeq ?? -1) >= event.seq) return message;
|
|
126
|
-
const seen = {
|
|
127
|
-
...message,
|
|
128
|
-
lastSeq: event.seq
|
|
129
|
-
};
|
|
130
|
-
switch (event.type) {
|
|
131
|
-
case "part.start":
|
|
132
|
-
if (message.parts.some((p) => p.id === event.part.id)) return seen;
|
|
133
|
-
return {
|
|
134
|
-
...seen,
|
|
135
|
-
parts: [...message.parts, event.part]
|
|
136
|
-
};
|
|
137
|
-
case "part.delta": return updatePart(seen, event.partId, (part) => appendDelta(part, event.text, event.inputJson));
|
|
138
|
-
case "part.update": return updatePart(seen, event.partId, (part) => applyFields(part, event));
|
|
139
|
-
case "part.end": return updatePart(seen, event.partId, (part) => closePart(applyFields(part, event)));
|
|
140
|
-
case "run.usage": return {
|
|
141
|
-
...seen,
|
|
142
|
-
usage: event.usage
|
|
143
|
-
};
|
|
144
|
-
case "run.end": return {
|
|
145
|
-
...seen,
|
|
146
|
-
status: event.status,
|
|
147
|
-
parts: message.parts.map((p) => closeOpenPart(p, event.status)),
|
|
148
|
-
...event.usage !== void 0 && { usage: event.usage },
|
|
149
|
-
...event.error !== void 0 && { error: event.error }
|
|
150
|
-
};
|
|
151
|
-
case "title": return seen;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/** Builds a message from a full event list, e.g. a stored run or a test fixture. */
|
|
155
|
-
function reduceEvents(events, initial) {
|
|
156
|
-
let message = initial;
|
|
157
|
-
for (const event of events) message = applyEvent(message, event);
|
|
158
|
-
return message;
|
|
159
|
-
}
|
|
160
|
-
function updatePart(message, partId, update) {
|
|
161
|
-
const index = message.parts.findIndex((p) => p.id === partId);
|
|
162
|
-
if (index === -1) return message;
|
|
163
|
-
const parts = message.parts.slice();
|
|
164
|
-
parts[index] = update(parts[index]);
|
|
165
|
-
return {
|
|
166
|
-
...message,
|
|
167
|
-
parts
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
function appendDelta(part, text = "", inputJson = "") {
|
|
171
|
-
switch (part.type) {
|
|
172
|
-
case "text":
|
|
173
|
-
case "thinking": return text ? {
|
|
174
|
-
...part,
|
|
175
|
-
text: part.text + text
|
|
176
|
-
} : part;
|
|
177
|
-
case "custom": return text ? {
|
|
178
|
-
...part,
|
|
179
|
-
text: (part.text ?? "") + text
|
|
180
|
-
} : part;
|
|
181
|
-
case "widget": return text ? {
|
|
182
|
-
...part,
|
|
183
|
-
html: (part.html ?? "") + text
|
|
184
|
-
} : part;
|
|
185
|
-
case "tool_call":
|
|
186
|
-
if (!inputJson) return part;
|
|
187
|
-
return {
|
|
188
|
-
...part,
|
|
189
|
-
inputJson: part.inputJson + inputJson,
|
|
190
|
-
partialInput: parsePartialJson(part.inputJson + inputJson)
|
|
191
|
-
};
|
|
192
|
-
case "attachment": return part;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/** Copies the fields from `fields` that are valid for the part's type. */
|
|
196
|
-
function applyFields(part, fields) {
|
|
197
|
-
const pick = (...keys) => {
|
|
198
|
-
const out = {};
|
|
199
|
-
for (const key of keys) if (fields[key] !== void 0) out[key] = fields[key];
|
|
200
|
-
return out;
|
|
201
|
-
};
|
|
202
|
-
const status = pick("status");
|
|
203
|
-
switch (part.type) {
|
|
204
|
-
case "text": return {
|
|
205
|
-
...part,
|
|
206
|
-
...status
|
|
207
|
-
};
|
|
208
|
-
case "thinking": return {
|
|
209
|
-
...part,
|
|
210
|
-
...status,
|
|
211
|
-
...pick("title", "durationMs")
|
|
212
|
-
};
|
|
213
|
-
case "tool_call": {
|
|
214
|
-
const next = {
|
|
215
|
-
...part,
|
|
216
|
-
...status,
|
|
217
|
-
...pick("title", "subtitle", "durationMs", "input", "result", "error")
|
|
218
|
-
};
|
|
219
|
-
return next.status === "input" || next.status === "streaming" ? next : withParsedInput(next);
|
|
220
|
-
}
|
|
221
|
-
case "attachment": return {
|
|
222
|
-
...part,
|
|
223
|
-
...status,
|
|
224
|
-
...pick("url")
|
|
225
|
-
};
|
|
226
|
-
case "widget": return {
|
|
227
|
-
...part,
|
|
228
|
-
...status,
|
|
229
|
-
...pick("title", "resourceUri")
|
|
230
|
-
};
|
|
231
|
-
case "custom": return {
|
|
232
|
-
...part,
|
|
233
|
-
...status,
|
|
234
|
-
...pick("data")
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function closePart(part) {
|
|
239
|
-
return withParsedInput(part.status === "error" ? part : {
|
|
240
|
-
...part,
|
|
241
|
-
status: "done"
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
/** Parses a tool call's streamed input if it is complete JSON and not parsed yet. */
|
|
245
|
-
function withParsedInput(part) {
|
|
246
|
-
if (part.type !== "tool_call") return part;
|
|
247
|
-
const { partialInput: _, ...rest } = part;
|
|
248
|
-
if (part.input !== void 0 || !part.inputJson) return "partialInput" in part ? rest : part;
|
|
249
|
-
const input = parseJson(part.inputJson);
|
|
250
|
-
if (input !== void 0) return {
|
|
251
|
-
...rest,
|
|
252
|
-
input
|
|
253
|
-
};
|
|
254
|
-
return part.status === "error" ? rest : {
|
|
255
|
-
...rest,
|
|
256
|
-
status: "error",
|
|
257
|
-
error: part.error ?? "The tool input was cut off or is not valid JSON"
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
/** At the end of a run, parts still open are closed. A tool call cut off by a stop or an error is an error. */
|
|
261
|
-
function closeOpenPart(part, runStatus) {
|
|
262
|
-
if (part.status === "done" || part.status === "error") return part;
|
|
263
|
-
if (part.type === "tool_call" && part.status === "awaiting" && runStatus === "done") return withParsedInput(part);
|
|
264
|
-
if (part.type === "tool_call" && runStatus !== "done") {
|
|
265
|
-
const error = part.error ?? (runStatus === "stopped" ? "Stopped" : "Failed");
|
|
266
|
-
return withParsedInput({
|
|
267
|
-
...part,
|
|
268
|
-
status: "error",
|
|
269
|
-
error
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
return closePart(part);
|
|
273
|
-
}
|
|
274
|
-
function parseJson(text) {
|
|
275
|
-
try {
|
|
276
|
-
return JSON.parse(text);
|
|
277
|
-
} catch {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
/** Rough token count of text, about four characters per token. For live counters, not billing. */
|
|
282
|
-
function estimateTokens(text) {
|
|
283
|
-
return Math.ceil(text.length / 4);
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Tokens the model spent inside an activity group: its reasoning and the tool inputs it wrote. Tool
|
|
287
|
-
* results are not model output, so they do not count. An estimate; per-step counts are not reported.
|
|
288
|
-
*/
|
|
289
|
-
function activityTokens(parts) {
|
|
290
|
-
let chars = 0;
|
|
291
|
-
for (const part of parts) if (part.type === "thinking") chars += part.text.length;
|
|
292
|
-
else if (part.type === "tool_call") chars += part.inputJson.length;
|
|
293
|
-
return Math.ceil(chars / 4);
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Output tokens of a message so far: the backend's count when it has sent one, else an estimate from
|
|
297
|
-
* everything the model has written (text, thinking, tool input, widget html).
|
|
298
|
-
*/
|
|
299
|
-
function outputTokens(message) {
|
|
300
|
-
if (message.usage?.outputTokens !== void 0) return message.usage.outputTokens;
|
|
301
|
-
let chars = 0;
|
|
302
|
-
for (const part of message.parts) if (part.type === "text" || part.type === "thinking") chars += part.text.length;
|
|
303
|
-
else if (part.type === "tool_call") chars += part.inputJson.length;
|
|
304
|
-
else if (part.type === "widget") chars += part.html?.length ?? 0;
|
|
305
|
-
else if (part.type === "custom") chars += part.text?.length ?? 0;
|
|
306
|
-
return Math.ceil(chars / 4);
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* The effort to send with a model: the chosen one if the model offers it, else the model's default, else
|
|
310
|
-
* none. Models without effort levels never get one.
|
|
311
|
-
*/
|
|
312
|
-
function resolveEffort(model, effort) {
|
|
313
|
-
const levels = model?.efforts;
|
|
314
|
-
if (!levels?.length) return void 0;
|
|
315
|
-
if (effort && levels.some((e) => e.id === effort)) return effort;
|
|
316
|
-
return model?.defaultEffort ?? levels[0].id;
|
|
317
|
-
}
|
|
318
|
-
/** Tool calls the client has to act on: run them (client tools) or ask the user (approvals). */
|
|
319
|
-
function awaitingToolCalls(message) {
|
|
320
|
-
return message.parts.filter((p) => p.type === "tool_call" && p.status === "awaiting");
|
|
321
|
-
}
|
|
322
|
-
/** Sets fields on one tool call locally, e.g. a client tool's result or the user's approval. */
|
|
323
|
-
function settleToolCall(message, partId, fields) {
|
|
324
|
-
return {
|
|
325
|
-
...message,
|
|
326
|
-
parts: message.parts.map((p) => p.id === partId && p.type === "tool_call" ? {
|
|
327
|
-
...p,
|
|
328
|
-
...fields
|
|
329
|
-
} : p)
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
// ../core/src/blocks.ts
|
|
333
|
-
function isActivityPart(part) {
|
|
334
|
-
return part.type === "thinking" || part.type === "tool_call";
|
|
335
|
-
}
|
|
336
|
-
function toBlocks(parts, standalone) {
|
|
337
|
-
const blocks = [];
|
|
338
|
-
for (const part of parts) {
|
|
339
|
-
if (part.type === "tool_call" && standalone?.(part)) {
|
|
340
|
-
blocks.push({
|
|
341
|
-
kind: "tool",
|
|
342
|
-
id: part.id,
|
|
343
|
-
part
|
|
344
|
-
});
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (!isActivityPart(part)) {
|
|
348
|
-
blocks.push({
|
|
349
|
-
kind: "part",
|
|
350
|
-
id: part.id,
|
|
351
|
-
part
|
|
352
|
-
});
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
const last = blocks.at(-1);
|
|
356
|
-
if (last?.kind === "activity") last.parts.push(part);
|
|
357
|
-
else blocks.push({
|
|
358
|
-
kind: "activity",
|
|
359
|
-
id: part.id,
|
|
360
|
-
parts: [part]
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
return blocks;
|
|
364
|
-
}
|
|
365
|
-
// ../core/src/sse.ts
|
|
366
|
-
/**
|
|
367
|
-
* Reads a server-sent event stream of `StreamEvent`s. Each SSE message carries one JSON event in `data`;
|
|
368
|
-
* comments and messages without data (keep-alives) are skipped.
|
|
369
|
-
*/
|
|
370
|
-
async function* readEventStream(body) {
|
|
371
|
-
const reader = body.getReader();
|
|
372
|
-
const decoder = new TextDecoder();
|
|
373
|
-
let buffer = "";
|
|
374
|
-
try {
|
|
375
|
-
for (;;) {
|
|
376
|
-
const { value, done } = await reader.read();
|
|
377
|
-
if (done) break;
|
|
378
|
-
buffer += decoder.decode(value, { stream: true });
|
|
379
|
-
let end;
|
|
380
|
-
while ((end = messageEnd(buffer)) !== -1) {
|
|
381
|
-
const event = parseMessage(buffer.slice(0, end));
|
|
382
|
-
buffer = buffer.slice(end).replace(BLANK_LINE_START, "");
|
|
383
|
-
if (event) yield event;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
const event = parseMessage(buffer + decoder.decode());
|
|
387
|
-
if (event) yield event;
|
|
388
|
-
} finally {
|
|
389
|
-
reader.releaseLock();
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/** Formats one event as an SSE message, for servers written in TS. */
|
|
393
|
-
function formatEvent(event) {
|
|
394
|
-
return `id: ${event.seq}\ndata: ${JSON.stringify(event)}\n\n`;
|
|
395
|
-
}
|
|
396
|
-
const BLANK_LINE = /\r\n\r\n|\n\n|\r\r/;
|
|
397
|
-
const BLANK_LINE_START = /^(\r\n\r\n|\n\n|\r\r)/;
|
|
398
|
-
function messageEnd(buffer) {
|
|
399
|
-
const match = BLANK_LINE.exec(buffer);
|
|
400
|
-
return match ? match.index : -1;
|
|
401
|
-
}
|
|
402
|
-
function parseMessage(text) {
|
|
403
|
-
const data = [];
|
|
404
|
-
for (const line of text.split(/\r\n|\r|\n/)) if (line.startsWith("data:")) data.push(line.slice(line.startsWith("data: ") ? 6 : 5));
|
|
405
|
-
if (data.length === 0) return void 0;
|
|
406
|
-
return JSON.parse(data.join("\n"));
|
|
407
|
-
}
|
|
408
|
-
export { activityTokens as a, estimateTokens as c, resolveEffort as d, settleToolCall as f, toBlocks as i, outputTokens as l, readEventStream as n, applyEvent as o, parsePartialJson as p, isActivityPart as r, awaitingToolCalls as s, formatEvent as t, reduceEvents as u };
|