@agentskit/chat 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1614 @@
1
+ // src/index.ts
2
+ import { ConfigError, ErrorCodes } from "@agentskit/core";
3
+ import { createStatechartInstance, defineStatechart, StatechartError, transitionStatechart } from "@agentskit/statechart";
4
+ import {
5
+ ComponentFallbackSchema,
6
+ ComponentKeySchema,
7
+ createInteractionEvent,
8
+ createSelectionEvent,
9
+ decodeComponentFrame as decodeComponentFrame2,
10
+ decodeSessionSnapshot,
11
+ SESSION_PROTOCOL,
12
+ SESSION_PROTOCOL_VERSION,
13
+ SessionSnapshotSchema
14
+ } from "@agentskit/chat-protocol";
15
+ import { z as z2 } from "zod";
16
+
17
+ // src/catalog.ts
18
+ import { z } from "zod";
19
+ var CHOICE_LIST_COMPONENT_KEY = "choice-list";
20
+ var IdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
21
+ var TextSchema = z.string().min(1).max(4096);
22
+ var LabelSchema = z.string().min(1).max(256);
23
+ var JsonRecordSchema = z.record(z.string().max(128), z.json());
24
+ var isPortableUrl = (value) => {
25
+ if (/[\u0000-\u001F\u007F\\]/u.test(value)) return false;
26
+ if (/^\/(?!\/)/.test(value)) return true;
27
+ try {
28
+ const url = new URL(value);
29
+ return ["http:", "https:"].includes(url.protocol) && url.username === "" && url.password === "";
30
+ } catch {
31
+ return false;
32
+ }
33
+ };
34
+ var PortableUrlSchema = z.string().max(2048).refine(isPortableUrl, "URL must be relative or use HTTP(S).");
35
+ var ActionSchema = z.object({ name: IdSchema, input: JsonRecordSchema }).strict().readonly();
36
+ var OptionSchema = z.object({ id: IdSchema, label: LabelSchema }).strict().readonly();
37
+ var itemIdsUnique = (items, context) => {
38
+ const ids = /* @__PURE__ */ new Set();
39
+ items.forEach((item, index) => {
40
+ if (ids.has(item.id)) context.addIssue({ code: "custom", path: [index, "id"], message: "Item ids must be unique." });
41
+ ids.add(item.id);
42
+ });
43
+ };
44
+ var ChoiceListPropsSchema = z.object({
45
+ prompt: TextSchema,
46
+ choices: z.array(z.object({ id: IdSchema, label: LabelSchema, description: TextSchema.optional(), action: ActionSchema.optional() }).strict().readonly()).min(1).max(20).superRefine(itemIdsUnique)
47
+ }).strict().readonly();
48
+ var ButtonGroupPropsSchema = z.object({
49
+ label: LabelSchema,
50
+ buttons: z.array(z.object({ id: IdSchema, label: LabelSchema, disabled: z.boolean().optional(), variant: z.enum(["primary", "secondary", "danger"]).optional() }).strict().readonly()).min(1).max(12).superRefine(itemIdsUnique)
51
+ }).strict().readonly();
52
+ var FormFieldSchema = z.object({
53
+ id: IdSchema,
54
+ label: LabelSchema,
55
+ type: z.enum(["text", "email", "number", "checkbox", "select"]),
56
+ required: z.boolean().optional(),
57
+ placeholder: z.string().max(256).optional(),
58
+ options: z.array(OptionSchema).min(1).max(50).optional()
59
+ }).strict().readonly().superRefine((field, context) => {
60
+ if (field.type === "select" && field.options === void 0) context.addIssue({ code: "custom", path: ["options"], message: "Select fields require options." });
61
+ if (field.type !== "select" && field.options !== void 0) context.addIssue({ code: "custom", path: ["options"], message: "Only select fields accept options." });
62
+ });
63
+ var FormPropsSchema = z.object({ title: LabelSchema.optional(), fields: z.array(FormFieldSchema).min(1).max(30).superRefine(itemIdsUnique), submitLabel: LabelSchema }).strict().readonly();
64
+ var ConfirmationPropsSchema = z.object({ title: LabelSchema, message: TextSchema, confirmLabel: LabelSchema, cancelLabel: LabelSchema }).strict().readonly();
65
+ var ProgressPropsSchema = z.object({ label: LabelSchema, value: z.number().finite().min(0).max(100), status: TextSchema.optional() }).strict().readonly();
66
+ var SourceListPropsSchema = z.object({ label: LabelSchema, sources: z.array(z.object({ id: IdSchema, title: LabelSchema, url: PortableUrlSchema.optional(), snippet: TextSchema.optional() }).strict().readonly()).min(1).max(50).superRefine(itemIdsUnique) }).strict().readonly();
67
+ var LinkCardPropsSchema = z.object({ title: LabelSchema, description: TextSchema.optional(), href: PortableUrlSchema, label: LabelSchema.optional() }).strict().readonly();
68
+ var ErrorNoticePropsSchema = z.object({ title: LabelSchema, message: TextSchema, code: z.string().max(128).optional(), retryLabel: LabelSchema.optional() }).strict().readonly();
69
+ var ToolCallPropsSchema = z.object({ name: LabelSchema, status: z.enum(["pending", "running", "complete", "error"]), arguments: JsonRecordSchema.optional(), result: z.json().optional() }).strict().readonly();
70
+ var ApprovalRequestPropsSchema = z.object({ title: LabelSchema, description: TextSchema, approveLabel: LabelSchema, denyLabel: LabelSchema }).strict().readonly();
71
+ var TableCellSchema = z.union([z.string().max(4096), z.number().finite(), z.boolean(), z.null()]);
72
+ var TablePropsSchema = z.object({ caption: LabelSchema, columns: z.array(z.object({ key: IdSchema, label: LabelSchema }).strict().readonly()).min(1).max(30).superRefine((columns, context) => itemIdsUnique(columns.map((column) => ({ id: column.key })), context)), rows: z.array(z.record(z.string(), TableCellSchema)).max(1e3) }).strict().readonly();
73
+ var FileAttachmentPropsSchema = z.object({ name: LabelSchema, mimeType: z.string().min(1).max(256), sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), url: PortableUrlSchema.optional() }).strict().readonly();
74
+ var define = (definition) => Object.freeze({
75
+ ...definition,
76
+ events: Object.freeze((definition.events ?? []).map((event) => Object.freeze({ ...event }))),
77
+ capabilities: Object.freeze([...definition.capabilities ?? []]),
78
+ ...definition.accessibility === void 0 ? {} : { accessibility: Object.freeze({ ...definition.accessibility }) }
79
+ });
80
+ var display = (role, live = "none") => ({ role, keyboard: false, live });
81
+ var interactive = (role) => ({ role, keyboard: true, live: "none" });
82
+ var ChoiceListComponent = define({ key: CHOICE_LIST_COMPONENT_KEY, propsSchema: ChoiceListPropsSchema, events: [{ name: "select", value: "id" }], accessibility: interactive("group"), capabilities: ["display", "selection"], fallback: (props) => `${props.prompt} ${props.choices.map((choice) => choice.label).join(", ")}.` });
83
+ var ButtonGroupComponent = define({ key: "button-group", propsSchema: ButtonGroupPropsSchema, events: [{ name: "select", value: "id" }], accessibility: interactive("group"), capabilities: ["display", "selection"], fallback: (props) => `${props.label}: ${props.buttons.map((button) => button.label).join(", ")}.` });
84
+ var FormComponent = define({ key: "form", propsSchema: FormPropsSchema, events: [{ name: "submit", value: "form" }], accessibility: interactive("form"), capabilities: ["display", "input"], fallback: (props) => `${props.title ?? "Form"}: ${props.fields.map((field) => field.label).join(", ")}.` });
85
+ var ConfirmationComponent = define({ key: "confirmation", propsSchema: ConfirmationPropsSchema, events: [{ name: "confirm", value: "none" }, { name: "cancel", value: "none" }], accessibility: interactive("group"), capabilities: ["display", "action"], fallback: (props) => `${props.title}: ${props.message}` });
86
+ var ProgressComponent = define({ key: "progress", propsSchema: ProgressPropsSchema, events: [], accessibility: display("progressbar", "polite"), capabilities: ["display", "progress"], fallback: (props) => `${props.label}: ${props.value}%.` });
87
+ var SourceListComponent = define({ key: "source-list", propsSchema: SourceListPropsSchema, events: [{ name: "open", value: "id" }], accessibility: interactive("list"), capabilities: ["display", "navigation"], fallback: (props) => `${props.label}: ${props.sources.map((source) => source.title).join(", ")}.` });
88
+ var LinkCardComponent = define({ key: "link-card", propsSchema: LinkCardPropsSchema, events: [{ name: "open", value: "url" }], accessibility: interactive("link"), capabilities: ["display", "navigation"], fallback: (props) => `${props.title}: ${props.description ?? props.href}` });
89
+ var ErrorNoticeComponent = define({ key: "error-notice", propsSchema: ErrorNoticePropsSchema, events: [{ name: "retry", value: "none" }], accessibility: interactive("alert"), capabilities: ["display", "action"], fallback: (props) => `${props.title}: ${props.message}` });
90
+ var ToolCallComponent = define({ key: "tool-call", propsSchema: ToolCallPropsSchema, events: [], accessibility: display("status", "polite"), capabilities: ["display"], fallback: (props) => `${props.name}: ${props.status}.` });
91
+ var ApprovalRequestComponent = define({ key: "approval-request", propsSchema: ApprovalRequestPropsSchema, events: [{ name: "approve", value: "none" }, { name: "deny", value: "none" }], accessibility: interactive("group"), capabilities: ["display", "action"], fallback: (props) => `${props.title}: ${props.description}` });
92
+ var TableComponent = define({ key: "table", propsSchema: TablePropsSchema, events: [], accessibility: display("table"), capabilities: ["display"], fallback: (props) => `${props.caption}: ${props.rows.length} rows.` });
93
+ var FileAttachmentComponent = define({ key: "file-attachment", propsSchema: FileAttachmentPropsSchema, events: [{ name: "open", value: "url" }], accessibility: interactive("link"), capabilities: ["display", "download"], fallback: (props) => `${props.name} (${props.mimeType}).` });
94
+ var StandardComponentCatalog = Object.freeze([
95
+ ButtonGroupComponent,
96
+ ChoiceListComponent,
97
+ FormComponent,
98
+ ConfirmationComponent,
99
+ ProgressComponent,
100
+ SourceListComponent,
101
+ LinkCardComponent,
102
+ ErrorNoticeComponent,
103
+ ToolCallComponent,
104
+ ApprovalRequestComponent,
105
+ TableComponent,
106
+ FileAttachmentComponent
107
+ ]);
108
+ var STANDARD_COMPONENT_KEYS = Object.freeze(StandardComponentCatalog.map((component) => component.key));
109
+ var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
110
+ var validateStandardComponentInteraction = (componentKey, props, event, value) => {
111
+ switch (componentKey) {
112
+ case "button-group": {
113
+ const parsed = ButtonGroupPropsSchema.safeParse(props);
114
+ return parsed.success && event === "select" && typeof value === "string" && parsed.data.buttons.some((button) => button.id === value && button.disabled !== true);
115
+ }
116
+ case "form": {
117
+ const parsed = FormPropsSchema.safeParse(props);
118
+ if (!parsed.success || event !== "submit" || !isRecord(value)) return false;
119
+ if (Object.keys(value).some((key) => !parsed.data.fields.some((field) => field.id === key))) return false;
120
+ return parsed.data.fields.every((field) => {
121
+ const fieldValue = value[field.id];
122
+ if (fieldValue === void 0 || fieldValue === "") return field.required !== true;
123
+ if (field.type === "checkbox") return typeof fieldValue === "boolean";
124
+ if (field.type === "number") return typeof fieldValue === "number" && Number.isFinite(fieldValue) || typeof fieldValue === "string" && fieldValue.trim() !== "" && Number.isFinite(Number(fieldValue));
125
+ if (field.type === "select") return typeof fieldValue === "string" && field.options?.some((option) => option.id === fieldValue) === true;
126
+ return typeof fieldValue === "string";
127
+ });
128
+ }
129
+ case "confirmation":
130
+ return (event === "confirm" || event === "cancel") && value === void 0;
131
+ case "source-list": {
132
+ const parsed = SourceListPropsSchema.safeParse(props);
133
+ return parsed.success && event === "open" && typeof value === "string" && parsed.data.sources.some((source) => source.id === value && source.url !== void 0);
134
+ }
135
+ case "link-card": {
136
+ const parsed = LinkCardPropsSchema.safeParse(props);
137
+ return parsed.success && event === "open" && value === parsed.data.href;
138
+ }
139
+ case "error-notice": {
140
+ const parsed = ErrorNoticePropsSchema.safeParse(props);
141
+ return parsed.success && event === "retry" && parsed.data.retryLabel !== void 0 && value === void 0;
142
+ }
143
+ case "approval-request":
144
+ return (event === "approve" || event === "deny") && value === void 0;
145
+ case "file-attachment": {
146
+ const parsed = FileAttachmentPropsSchema.safeParse(props);
147
+ return parsed.success && event === "open" && parsed.data.url !== void 0 && value === parsed.data.url;
148
+ }
149
+ default:
150
+ return false;
151
+ }
152
+ };
153
+
154
+ // src/ask.ts
155
+ import { createWebStorageMemory } from "@agentskit/memory/web-storage";
156
+ import {
157
+ ASK_EVENT_MAX_BYTES,
158
+ ASSISTANT_CONTENT_MAX_BYTES,
159
+ ASSISTANT_CONTENT_MAX_RECORDS,
160
+ ComponentRenderFrameSchema,
161
+ createAssistantContentEncoder,
162
+ decodeAskEvents,
163
+ decodeAssistantContent
164
+ } from "@agentskit/chat-protocol";
165
+ import { AskEventSchema as AskEventSchema2, decodeAskEvents as decodeAskEvents2 } from "@agentskit/chat-protocol";
166
+ var MAX_TEXT_CHARS = 16384;
167
+ var DEFAULT_CONNECTION_TIMEOUT_MS = 3e4;
168
+ var DEFAULT_ENDPOINT = "https://ask.agentskit.io/v1/ask";
169
+ var safeId = (value, fallback) => {
170
+ const normalized = value.replace(/[^A-Za-z0-9._:-]/g, "-").slice(0, 128);
171
+ return /^[A-Za-z0-9]/.test(normalized) ? normalized : fallback;
172
+ };
173
+ var safeHref = (path, anchor) => {
174
+ const fragment = anchor?.replace(/^#/, "");
175
+ const hasScheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.test(path);
176
+ if (!hasScheme && !path.startsWith("//")) {
177
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, "https://agentskit.invalid");
178
+ if (url.origin !== "https://agentskit.invalid") return void 0;
179
+ if (fragment !== void 0) url.hash = fragment;
180
+ const href = `${url.pathname}${url.search}${url.hash}`;
181
+ return href.length <= 2048 ? href : void 0;
182
+ }
183
+ try {
184
+ const url = new URL(path);
185
+ if (!["http:", "https:"].includes(url.protocol) || url.username !== "" || url.password !== "") return void 0;
186
+ if (fragment !== void 0) url.hash = fragment;
187
+ const href = url.toString();
188
+ return href.length <= 2048 ? href : void 0;
189
+ } catch {
190
+ return void 0;
191
+ }
192
+ };
193
+ var sourceListFrame = (event) => {
194
+ if (!Array.isArray(event.args.sources)) return void 0;
195
+ const sources = event.args.sources.flatMap((candidate, index) => {
196
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return [];
197
+ const source = candidate;
198
+ if (typeof source.title !== "string" || source.title.trim() === "" || typeof source.path !== "string") return [];
199
+ const url = safeHref(source.path, typeof source.anchor === "string" ? source.anchor : void 0);
200
+ return url === void 0 ? [] : [{ id: `source-${index + 1}`, title: source.title.slice(0, 256), url }];
201
+ }).slice(0, 50);
202
+ if (sources.length === 0) return void 0;
203
+ return ComponentRenderFrameSchema.parse({
204
+ protocol: "agentskit.chat.component",
205
+ version: 1,
206
+ type: "render",
207
+ componentKey: "source-list",
208
+ instanceId: safeId(event.id, "sources"),
209
+ props: { label: "Sources", sources },
210
+ fallback: {
211
+ kind: "source-list",
212
+ summary: `Sources: ${sources.map((source) => source.title).join(", ")}.`.slice(0, 4096)
213
+ }
214
+ });
215
+ };
216
+ function projectAskEvent(event, projectTool) {
217
+ if (event.type === "text") return event.delta === "" ? void 0 : { kind: "text", text: event.delta };
218
+ if (event.type === "error") return { kind: "error", message: event.message };
219
+ if (event.type === "done") return { kind: "done" };
220
+ if (event.name === "answer" && typeof event.args.markdown === "string") {
221
+ return event.args.markdown === "" ? void 0 : { kind: "text", text: event.args.markdown };
222
+ }
223
+ if (event.name === "cite") {
224
+ const frame = sourceListFrame(event);
225
+ return frame === void 0 ? void 0 : { kind: "component", frame };
226
+ }
227
+ try {
228
+ const custom = projectTool?.(event);
229
+ const parsed = ComponentRenderFrameSchema.safeParse(custom);
230
+ return parsed.success ? { kind: "component", frame: parsed.data } : void 0;
231
+ } catch {
232
+ return void 0;
233
+ }
234
+ }
235
+ function projectAskMessages(messages) {
236
+ const projected = [];
237
+ for (const message of messages) {
238
+ if (message.role !== "user" && message.role !== "assistant") continue;
239
+ if (message.role === "user") {
240
+ projected.push({ role: message.role, content: message.content });
241
+ continue;
242
+ }
243
+ const decoded = decodeAssistantContent(message.content);
244
+ const content = decoded.ok ? decoded.parts.map((part) => part.kind === "text" ? part.text : `[${part.frame.componentKey}]`).join("\n").trim() : message.content;
245
+ projected.push({ role: message.role, content });
246
+ }
247
+ return projected;
248
+ }
249
+ var endpointWithParams = ({ endpoint = DEFAULT_ENDPOINT, corpus, persona }) => {
250
+ const relative = endpoint.startsWith("/");
251
+ const origin = typeof globalThis.location === "undefined" ? "http://localhost" : globalThis.location.origin;
252
+ const url = new URL(endpoint, origin);
253
+ if (corpus?.trim()) url.searchParams.set("corpus", corpus);
254
+ if (persona?.trim()) url.searchParams.set("persona", persona);
255
+ return relative ? `${url.pathname}${url.search}${url.hash}` : url.toString();
256
+ };
257
+ var createBoundedAssistantEncoder = () => {
258
+ const encoder = createAssistantContentEncoder();
259
+ let bytes = 0;
260
+ let records = 0;
261
+ return (part) => {
262
+ if (records >= ASSISTANT_CONTENT_MAX_RECORDS) throw new Error("Ask response exceeded the assistant content record limit.");
263
+ const encoded = encoder.encode(part);
264
+ const nextBytes = bytes + new TextEncoder().encode(encoded).byteLength;
265
+ if (nextBytes > ASSISTANT_CONTENT_MAX_BYTES) throw new Error("Ask response exceeded the assistant content byte limit.");
266
+ bytes = nextBytes;
267
+ records += 1;
268
+ return encoded;
269
+ };
270
+ };
271
+ var encodeText = function* (encode, text) {
272
+ for (let offset = 0; offset < text.length; offset += MAX_TEXT_CHARS) {
273
+ yield { type: "text", content: encode({ kind: "text", text: text.slice(offset, offset + MAX_TEXT_CHARS) }) };
274
+ }
275
+ };
276
+ function createAskAdapter(options = {}) {
277
+ return {
278
+ capabilities: { streaming: true, structuredOutput: true },
279
+ createSource(request) {
280
+ const controller = new AbortController();
281
+ return {
282
+ abort: () => controller.abort(),
283
+ async *stream() {
284
+ const encode = createBoundedAssistantEncoder();
285
+ try {
286
+ const connection = new AbortController();
287
+ const timeout = setTimeout(() => connection.abort(), DEFAULT_CONNECTION_TIMEOUT_MS);
288
+ let response;
289
+ try {
290
+ response = await fetch(endpointWithParams(options), {
291
+ method: "POST",
292
+ signal: AbortSignal.any([controller.signal, connection.signal]),
293
+ headers: { "content-type": "application/json" },
294
+ body: JSON.stringify({ messages: projectAskMessages(request.messages) })
295
+ });
296
+ } finally {
297
+ clearTimeout(timeout);
298
+ }
299
+ if (!response.ok || response.body === null) throw new Error(`Ask request failed (${response.status}).`);
300
+ const reader = response.body.getReader();
301
+ const decoder = new TextDecoder();
302
+ let buffer = "";
303
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
304
+ let mode = contentType.includes("text/plain") ? "text" : contentType.includes("ndjson") ? "ndjson" : "unknown";
305
+ let discardingOversizedLine = false;
306
+ while (true) {
307
+ const result = await reader.read();
308
+ let incoming = decoder.decode(result.value, { stream: !result.done });
309
+ if (discardingOversizedLine) {
310
+ const lineEnd = incoming.indexOf("\n");
311
+ if (lineEnd < 0) {
312
+ if (result.done) break;
313
+ continue;
314
+ }
315
+ incoming = incoming.slice(lineEnd + 1);
316
+ discardingOversizedLine = false;
317
+ }
318
+ buffer += incoming;
319
+ if (mode === "unknown" && buffer.trimStart() !== "") {
320
+ if (!buffer.trimStart().startsWith("{")) mode = "text";
321
+ else {
322
+ const lineEnd = buffer.indexOf("\n");
323
+ if (lineEnd >= 0 || result.done) {
324
+ const firstLine = lineEnd >= 0 ? buffer.slice(0, lineEnd + 1) : `${buffer}
325
+ `;
326
+ mode = decodeAskEvents(firstLine).events.length > 0 ? "ndjson" : "text";
327
+ } else if (new TextEncoder().encode(buffer).byteLength > ASK_EVENT_MAX_BYTES) {
328
+ buffer = "";
329
+ discardingOversizedLine = true;
330
+ }
331
+ }
332
+ }
333
+ if (mode === "unknown" && !result.done) continue;
334
+ if (mode === "text") {
335
+ yield* encodeText(encode, buffer);
336
+ buffer = "";
337
+ if (result.done) break;
338
+ continue;
339
+ }
340
+ const decoded = decodeAskEvents(result.done ? `${buffer}
341
+ ` : buffer);
342
+ buffer = decoded.rest;
343
+ discardingOversizedLine = decoded.discardedPartial;
344
+ for (const event of decoded.events) {
345
+ const projected = projectAskEvent(event, options.projectTool);
346
+ if (projected?.kind === "error") {
347
+ void reader.cancel().catch(() => void 0);
348
+ yield { type: "error", content: projected.message };
349
+ return;
350
+ }
351
+ if (projected?.kind === "done") {
352
+ void reader.cancel().catch(() => void 0);
353
+ yield { type: "done" };
354
+ return;
355
+ }
356
+ if (projected?.kind === "text") yield* encodeText(encode, projected.text);
357
+ if (projected?.kind === "component") {
358
+ yield { type: "text", content: encode({ kind: "component", frame: projected.frame }) };
359
+ }
360
+ }
361
+ if (result.done) break;
362
+ }
363
+ yield { type: "done" };
364
+ } catch (cause) {
365
+ if (!controller.signal.aborted) {
366
+ yield { type: "error", content: cause instanceof Error ? cause.message : "Ask request failed." };
367
+ }
368
+ }
369
+ }
370
+ };
371
+ }
372
+ };
373
+ }
374
+ var legacyContent = (record, projectTool) => {
375
+ const direct = typeof record.content === "string" ? record.content : typeof record.text === "string" ? record.text : void 0;
376
+ if (direct !== void 0) return direct;
377
+ if (record.role !== "assistant" || !Array.isArray(record.parts)) return void 0;
378
+ const encode = createBoundedAssistantEncoder();
379
+ const encoded = [];
380
+ for (const candidate of record.parts) {
381
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
382
+ const part = candidate;
383
+ let projected;
384
+ if (part.kind === "text" && typeof part.text === "string") {
385
+ if (part.text === "") continue;
386
+ for (let offset = 0; offset < part.text.length; offset += MAX_TEXT_CHARS) {
387
+ try {
388
+ encoded.push(encode({ kind: "text", text: part.text.slice(offset, offset + MAX_TEXT_CHARS) }));
389
+ } catch {
390
+ return encoded.join("");
391
+ }
392
+ }
393
+ continue;
394
+ } else if (part.kind === "tool" && typeof part.id === "string" && typeof part.name === "string" && typeof part.args === "object" && part.args !== null && !Array.isArray(part.args)) {
395
+ const result = projectAskEvent({ type: "tool", id: part.id, name: part.name, args: part.args }, projectTool);
396
+ if (result?.kind === "text") {
397
+ for (let offset = 0; offset < result.text.length; offset += MAX_TEXT_CHARS) {
398
+ try {
399
+ encoded.push(encode({ kind: "text", text: result.text.slice(offset, offset + MAX_TEXT_CHARS) }));
400
+ } catch {
401
+ return encoded.length > 0 ? encoded.join("") : void 0;
402
+ }
403
+ }
404
+ continue;
405
+ }
406
+ if (result?.kind === "component") projected = { kind: "component", frame: result.frame };
407
+ } else continue;
408
+ if (projected !== void 0) {
409
+ try {
410
+ encoded.push(encode(projected));
411
+ } catch {
412
+ return encoded.length > 0 ? encoded.join("") : void 0;
413
+ }
414
+ }
415
+ }
416
+ return encoded.length > 0 || record.parts.length === 0 ? encoded.join("") : void 0;
417
+ };
418
+ var migrateLegacyMessages = (value, projectTool) => {
419
+ if (!Array.isArray(value)) return void 0;
420
+ const messages = [];
421
+ for (const [index, candidate] of value.entries()) {
422
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
423
+ const record = candidate;
424
+ if (record.role !== "user" && record.role !== "assistant") continue;
425
+ const content = legacyContent(record, projectTool);
426
+ if (content === void 0) continue;
427
+ messages.push({
428
+ id: safeId(typeof record.id === "string" ? record.id : "", `legacy-${index + 1}`),
429
+ role: record.role,
430
+ content,
431
+ status: "complete",
432
+ createdAt: new Date(index)
433
+ });
434
+ }
435
+ return messages.length > 0 || value.length === 0 ? messages : void 0;
436
+ };
437
+ function createAskSessionMemory({
438
+ key,
439
+ legacyKeys = [],
440
+ maxMessages = 20,
441
+ maxRecordBytes = ASK_EVENT_MAX_BYTES,
442
+ getStorage = () => {
443
+ try {
444
+ return typeof globalThis.sessionStorage === "undefined" ? void 0 : globalThis.sessionStorage;
445
+ } catch {
446
+ return void 0;
447
+ }
448
+ },
449
+ projectTool
450
+ }) {
451
+ return createWebStorageMemory({
452
+ key,
453
+ getStorage,
454
+ maxMessages,
455
+ maxRecordBytes,
456
+ migration: {
457
+ keys: legacyKeys,
458
+ read: (value) => migrateLegacyMessages(value, projectTool)
459
+ }
460
+ });
461
+ }
462
+
463
+ // src/deterministic.ts
464
+ import {
465
+ ANSWER_PROTOCOL,
466
+ ANSWER_PROTOCOL_VERSION,
467
+ AnswerCitationSchema,
468
+ AnswerResponseSchema,
469
+ COMPONENT_PROTOCOL,
470
+ COMPONENT_PROTOCOL_VERSION,
471
+ createAssistantContentEncoder as createAssistantContentEncoder2,
472
+ decodeAssistantContent as decodeAssistantContent2,
473
+ isAssistantContentCandidate,
474
+ verifyLocalKnowledgeArtifactSync,
475
+ normalizeKnowledgeKey
476
+ } from "@agentskit/chat-protocol";
477
+ var normalizeDeterministicQuery = normalizeKnowledgeKey;
478
+ var baseResponse = (query) => ({
479
+ protocol: ANSWER_PROTOCOL,
480
+ version: ANSWER_PROTOCOL_VERSION,
481
+ query: query.slice(0, 512),
482
+ normalizedQuery: normalizeDeterministicQuery(query.slice(0, 512)).slice(0, 512)
483
+ });
484
+ var escalation = (query, reason, message) => AnswerResponseSchema.parse({
485
+ ...baseResponse(query),
486
+ outcome: "escalation",
487
+ message,
488
+ reason,
489
+ confidence: { level: "low", basis: reason }
490
+ });
491
+ var createDeterministicAnswerResolver = (artifactInput, options) => {
492
+ const verified = verifyLocalKnowledgeArtifactSync(artifactInput, options);
493
+ if (!verified.ok) return Object.freeze({
494
+ resolve: (query) => escalation(query, "corrupt", "Local knowledge is corrupt. A backend answer is required."),
495
+ resolveChoice: (_choiceId, query) => escalation(query, "corrupt", "Local knowledge is corrupt. A backend answer is required.")
496
+ });
497
+ const artifact = verified.value;
498
+ const index = /* @__PURE__ */ new Map();
499
+ const entriesById = new Map(artifact.entries.map((entry) => [entry.id, entry]));
500
+ for (const entry of artifact.entries) {
501
+ for (const value of entry.match.values) {
502
+ const key = normalizeDeterministicQuery(value);
503
+ const entries = index.get(key) ?? [];
504
+ if (!entries.some((candidate) => candidate.id === entry.id)) entries.push(entry);
505
+ index.set(key, entries);
506
+ }
507
+ }
508
+ const now = options.now ?? Date.now;
509
+ const isStale = () => artifact.expiresAt !== void 0 && Date.parse(artifact.expiresAt) <= now();
510
+ const answerForEntry = (entry, query) => AnswerResponseSchema.parse({
511
+ ...baseResponse(query),
512
+ outcome: "answer",
513
+ answer: entry.answer,
514
+ provenance: { source: "local", artifactId: artifact.artifactId, contentHash: artifact.contentHash, entryIds: [entry.id] },
515
+ confidence: { level: "high", basis: "exact" }
516
+ });
517
+ const uniqueValue = (entry) => entry.match.values.find((value) => (index.get(normalizeDeterministicQuery(value))?.length ?? 0) === 1) ?? entry.match.values[0] ?? entry.id;
518
+ return Object.freeze({
519
+ resolve(query) {
520
+ if (isStale()) {
521
+ return escalation(query, "stale", "Local knowledge is stale. A backend answer is required.");
522
+ }
523
+ if (query.length > 512) {
524
+ return escalation(query, "miss", "No exact local answer was found. A backend answer is required.");
525
+ }
526
+ const normalizedQuery = normalizeDeterministicQuery(query);
527
+ if (normalizedQuery.length > 512) return escalation(query, "miss", "No exact local answer was found. A backend answer is required.");
528
+ const base = { ...baseResponse(query), normalizedQuery };
529
+ const entries = index.get(base.normalizedQuery) ?? [];
530
+ if (entries.length === 0) return escalation(query, "miss", "No exact local answer was found. A backend answer is required.");
531
+ if (entries.length > 1) {
532
+ return AnswerResponseSchema.parse({
533
+ ...base,
534
+ outcome: "choices",
535
+ message: "More than one exact local answer matches. Choose one to continue.",
536
+ suggestions: entries.slice(0, 8).map((entry2) => ({ id: entry2.id, label: entry2.label, value: uniqueValue(entry2) })),
537
+ provenance: { source: "local", artifactId: artifact.artifactId, contentHash: artifact.contentHash, entryIds: entries.slice(0, 8).map((entry2) => entry2.id) },
538
+ confidence: { level: "medium", basis: "ambiguous" }
539
+ });
540
+ }
541
+ const entry = entries[0];
542
+ if (entry === void 0) return escalation(query, "miss", "No exact local answer was found. A backend answer is required.");
543
+ return answerForEntry(entry, query);
544
+ },
545
+ resolveChoice(choiceId, query) {
546
+ if (isStale()) return escalation(query, "stale", "Local knowledge is stale. A backend answer is required.");
547
+ if (query.length > 512) return escalation(query, "miss", "The selected local answer is unavailable. A backend answer is required.");
548
+ const normalizedQuery = normalizeDeterministicQuery(query);
549
+ const candidates = normalizedQuery.length <= 512 ? (index.get(normalizedQuery) ?? []).slice(0, 8) : [];
550
+ const entry = candidates.some((candidate) => candidate.id === choiceId) ? entriesById.get(choiceId) : void 0;
551
+ return entry === void 0 ? escalation(query, "miss", "The selected local answer is unavailable. A backend answer is required.") : answerForEntry(entry, query);
552
+ }
553
+ });
554
+ };
555
+ var latestUserInput = (request) => {
556
+ const message = request.messages.filter((candidate) => candidate.role === "user").at(-1);
557
+ return { content: message?.content ?? "", messageId: message?.id ?? "deterministic" };
558
+ };
559
+ var safeInstanceId = (value, fallback) => {
560
+ const normalized = value.replace(/[^A-Za-z0-9._:-]/g, "-").slice(0, 128);
561
+ return /^[A-Za-z0-9]/.test(normalized) ? normalized : fallback;
562
+ };
563
+ var projectDecision = (decision, messageId, rememberChoices) => {
564
+ const encoder = createAssistantContentEncoder2();
565
+ const content = [];
566
+ if (decision.outcome === "answer") {
567
+ content.push(encoder.encode({ kind: "text", text: decision.answer.markdown }));
568
+ if (decision.answer.citations.length > 0) {
569
+ const props = SourceListPropsSchema.parse({
570
+ label: "Sources",
571
+ sources: decision.answer.citations.map((citation) => ({ id: citation.id, title: citation.title, url: citation.href }))
572
+ });
573
+ content.push(encoder.encode({ kind: "component", frame: {
574
+ protocol: COMPONENT_PROTOCOL,
575
+ version: COMPONENT_PROTOCOL_VERSION,
576
+ type: "render",
577
+ componentKey: "source-list",
578
+ instanceId: safeInstanceId(`sources-${messageId}`, "deterministic-sources"),
579
+ props,
580
+ fallback: { kind: "source-list", summary: `Sources: ${props.sources.map((source) => source.title).join(", ")}.` }
581
+ } }));
582
+ }
583
+ } else if (decision.outcome === "choices") {
584
+ content.push(encoder.encode({ kind: "text", text: decision.message }));
585
+ const props = ChoiceListPropsSchema.parse({
586
+ prompt: decision.message,
587
+ choices: decision.suggestions.map((suggestion) => ({ id: suggestion.id, label: suggestion.label, description: suggestion.value }))
588
+ });
589
+ const frame = {
590
+ protocol: COMPONENT_PROTOCOL,
591
+ version: COMPONENT_PROTOCOL_VERSION,
592
+ type: "render",
593
+ componentKey: "choice-list",
594
+ instanceId: safeInstanceId(`deterministic-choices-${messageId}`, "deterministic-choices"),
595
+ props,
596
+ fallback: { kind: "choice-list", summary: `${decision.message} ${props.choices.map((choice) => choice.label).join(", ")}.` }
597
+ };
598
+ rememberChoices(frame, decision);
599
+ content.push(encoder.encode({ kind: "component", frame }));
600
+ } else {
601
+ content.push(encoder.encode({ kind: "text", text: decision.message }));
602
+ }
603
+ return content.join("");
604
+ };
605
+ var localSource = (decision, messageId, rememberChoices) => {
606
+ let aborted = false;
607
+ return {
608
+ async *stream() {
609
+ if (aborted) return;
610
+ yield { type: "text", content: projectDecision(decision, messageId, rememberChoices), metadata: { answer: decision } };
611
+ if (!aborted) yield { type: "done" };
612
+ },
613
+ abort() {
614
+ aborted = true;
615
+ }
616
+ };
617
+ };
618
+ var observe = (observer, decision) => {
619
+ try {
620
+ void Promise.resolve(observer?.(decision)).catch(() => void 0);
621
+ } catch {
622
+ }
623
+ };
624
+ var backendAnswer = (query, content, backend) => {
625
+ const decoded = decodeAssistantContent2(content);
626
+ if (!decoded.ok && isAssistantContentCandidate(content)) {
627
+ return escalation(query, "corrupt", "Backend answer contained invalid ordered content.");
628
+ }
629
+ if (decoded.ok && !decoded.complete) {
630
+ return escalation(query, "corrupt", "Backend answer ended with incomplete ordered content.");
631
+ }
632
+ const markdown = (decoded.ok ? decoded.parts.flatMap((part) => part.kind === "text" ? [part.text] : []).join("") : content).trim();
633
+ if (markdown === "") return void 0;
634
+ if (markdown.length > 16384) {
635
+ return escalation(query, "corrupt", "Backend answer exceeded the unified response limit.");
636
+ }
637
+ const citations = decoded.ok ? decoded.parts.flatMap((part) => {
638
+ if (part.kind !== "component" || part.frame.componentKey !== "source-list") return [];
639
+ const sources = SourceListPropsSchema.safeParse(part.frame.props);
640
+ if (!sources.success) return [];
641
+ return sources.data.sources.flatMap((source) => {
642
+ if (source.url === void 0) return [];
643
+ const citation = AnswerCitationSchema.safeParse({ id: source.id, title: source.title, href: source.url });
644
+ return citation.success ? [citation.data] : [];
645
+ });
646
+ }).slice(0, 8) : [];
647
+ return AnswerResponseSchema.parse({
648
+ ...baseResponse(query),
649
+ outcome: "answer",
650
+ answer: { markdown, citations },
651
+ provenance: { source: "backend", ...backend },
652
+ confidence: { level: "high", basis: "backend" }
653
+ });
654
+ };
655
+ var observeBackendSource = (source, query, backend, observer) => ({
656
+ abort: () => source.abort(),
657
+ async *stream() {
658
+ let content = "";
659
+ let overflowed = false;
660
+ let completed = false;
661
+ let failed = false;
662
+ const finalize = () => overflowed ? escalation(query, "corrupt", "Backend stream exceeded the observation limit.") : backendAnswer(query, content, backend);
663
+ for await (const chunk of source.stream()) {
664
+ if (chunk.type === "text") {
665
+ const incoming = chunk.content ?? "";
666
+ const remaining = 262144 - content.length;
667
+ if (incoming.length > remaining) overflowed = true;
668
+ if (remaining > 0) content += incoming.slice(0, remaining);
669
+ }
670
+ if (chunk.type === "error") failed = true;
671
+ if (chunk.type !== "done") {
672
+ yield chunk;
673
+ continue;
674
+ }
675
+ completed = true;
676
+ if (failed) {
677
+ yield chunk;
678
+ continue;
679
+ }
680
+ const answer = finalize();
681
+ if (answer === void 0) {
682
+ yield chunk;
683
+ } else {
684
+ observe(observer, answer);
685
+ yield { ...chunk, metadata: { ...chunk.metadata, answer } };
686
+ }
687
+ }
688
+ if (!completed && !failed) {
689
+ const answer = finalize();
690
+ if (answer !== void 0) {
691
+ observe(observer, answer);
692
+ yield { type: "done", metadata: { answer } };
693
+ }
694
+ }
695
+ }
696
+ });
697
+ var createDeterministicAnswerAdapter = (options) => {
698
+ const resolver = createDeterministicAnswerResolver(options.artifact, options);
699
+ const MAX_CHOICE_SESSIONS = 256;
700
+ const MAX_SESSION_CHOICES = 256;
701
+ const choices = /* @__PURE__ */ new Map();
702
+ const rememberChoices = (sessionId, frame, decision) => {
703
+ let sessionChoices = choices.get(sessionId);
704
+ if (sessionChoices === void 0) {
705
+ if (choices.size >= MAX_CHOICE_SESSIONS) {
706
+ const evictable = [...choices].find(
707
+ ([, authorizations]) => [...authorizations.values()].every((authorization) => !authorization.claimed)
708
+ )?.[0];
709
+ if (evictable === void 0) return;
710
+ choices.delete(evictable);
711
+ }
712
+ sessionChoices = /* @__PURE__ */ new Map();
713
+ } else {
714
+ choices.delete(sessionId);
715
+ }
716
+ const previous = sessionChoices.get(frame.instanceId);
717
+ if (previous?.claimed) {
718
+ choices.set(sessionId, sessionChoices);
719
+ return;
720
+ }
721
+ if (previous === void 0 && sessionChoices.size >= MAX_SESSION_CHOICES) {
722
+ const evictable = [...sessionChoices].find(([, authorization]) => !authorization.claimed)?.[0];
723
+ if (evictable === void 0) {
724
+ choices.set(sessionId, sessionChoices);
725
+ return;
726
+ }
727
+ sessionChoices.delete(evictable);
728
+ }
729
+ sessionChoices.set(frame.instanceId, {
730
+ props: JSON.stringify(frame.props),
731
+ values: new Map(decision.suggestions.map((suggestion) => [suggestion.id, suggestion.value])),
732
+ claimed: false
733
+ });
734
+ choices.set(sessionId, sessionChoices);
735
+ };
736
+ const createSourceForSession = (request, sessionId) => {
737
+ const rememberSessionChoices = (frame, decision2) => rememberChoices(sessionId, frame, decision2);
738
+ const user = latestUserInput(request);
739
+ const decision = resolver.resolve(user.content);
740
+ if (decision.outcome !== "escalation") {
741
+ observe(options.onDecision, decision);
742
+ return localSource(decision, user.messageId, rememberSessionChoices);
743
+ }
744
+ if (options.fallbackMode === "disabled" || options.fallback === void 0) {
745
+ const unavailable = decision.reason === "miss" ? escalation(decision.query, "offline", "This question needs the backend, which is not available.") : decision;
746
+ observe(options.onDecision, unavailable);
747
+ return localSource(unavailable, user.messageId, rememberSessionChoices);
748
+ }
749
+ observe(options.onDecision, decision);
750
+ const source = options.fallback.createSource({
751
+ ...request,
752
+ context: {
753
+ ...request.context,
754
+ metadata: { ...request.context?.metadata, "agentskit.chat.escalation": decision }
755
+ }
756
+ });
757
+ return observeBackendSource(source, decision.query, options.backend, options.onDecision);
758
+ };
759
+ return {
760
+ capabilities: options.fallbackMode === "backend" ? options.fallback?.capabilities ?? { streaming: true, structuredOutput: true } : { streaming: true, structuredOutput: true },
761
+ createSource: (request) => createSourceForSession(request, "unscoped"),
762
+ createSourceForSession,
763
+ releaseChoiceSession: (sessionId) => {
764
+ choices.delete(sessionId);
765
+ },
766
+ resolveChoiceSubmission(frame, choiceId, context) {
767
+ const unavailable = () => frame.instanceId.startsWith("deterministic-choices-") ? { unavailable: true } : void 0;
768
+ const sessionChoices = choices.get(context.sessionId);
769
+ if (sessionChoices === void 0) return unavailable();
770
+ const remembered = sessionChoices.get(frame.instanceId);
771
+ if (remembered === void 0 || remembered.claimed || remembered.props !== JSON.stringify(frame.props)) return unavailable();
772
+ const value = remembered.values.get(choiceId);
773
+ if (value === void 0) return unavailable();
774
+ choices.delete(context.sessionId);
775
+ choices.set(context.sessionId, sessionChoices);
776
+ remembered.claimed = true;
777
+ let settled = false;
778
+ return {
779
+ value,
780
+ commit() {
781
+ if (settled) return;
782
+ settled = true;
783
+ sessionChoices.delete(frame.instanceId);
784
+ if (sessionChoices.size === 0 && choices.get(context.sessionId) === sessionChoices) choices.delete(context.sessionId);
785
+ },
786
+ release() {
787
+ if (settled) return;
788
+ settled = true;
789
+ remembered.claimed = false;
790
+ }
791
+ };
792
+ }
793
+ };
794
+ };
795
+
796
+ // src/presentation.ts
797
+ import {
798
+ decodeAssistantContent as decodeAssistantContent3,
799
+ decodeComponentFrame,
800
+ isAssistantContentCandidate as isAssistantContentCandidate2,
801
+ isComponentFrameCandidate
802
+ } from "@agentskit/chat-protocol";
803
+ var presentChatMessage = (message) => {
804
+ if (message.role === "assistant" && isAssistantContentCandidate2(message.content)) {
805
+ const decoded = decodeAssistantContent3(message.content);
806
+ if (!decoded.ok) return [{ kind: "diagnostic", code: decoded.diagnostic.code, message: decoded.diagnostic.message }];
807
+ const parts = [];
808
+ for (const part of decoded.parts) {
809
+ if (part.kind === "component") {
810
+ parts.push({ kind: "component", frame: part.frame });
811
+ continue;
812
+ }
813
+ const previous = parts.at(-1);
814
+ if (previous?.kind === "message") {
815
+ parts[parts.length - 1] = { kind: "message", message: { ...previous.message, content: previous.message.content + part.text } };
816
+ } else {
817
+ parts.push({ kind: "message", message: { ...message, content: part.text } });
818
+ }
819
+ }
820
+ return parts;
821
+ }
822
+ if (message.role === "assistant" && isComponentFrameCandidate(message.content)) {
823
+ const decoded = decodeComponentFrame(message.content);
824
+ return decoded.ok ? [{ kind: "component", frame: decoded.frame }] : [{ kind: "diagnostic", code: decoded.diagnostic.code, message: decoded.diagnostic.message }];
825
+ }
826
+ return [{ kind: "message", message }];
827
+ };
828
+
829
+ // src/index.ts
830
+ var ThemeColorSchema = z2.string().regex(/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, "Theme colors must use portable hex notation.");
831
+ var ThemeLengthSchema = z2.number().finite().nonnegative().max(1e3);
832
+ var ThemeFontFamilySchema = z2.string().regex(/^(?:system|[A-Za-z0-9][A-Za-z0-9 _-]{0,127})$/, "Theme fontFamily must be system or one portable family name.");
833
+ var ChatThemeColorsSchema = z2.object({
834
+ background: ThemeColorSchema,
835
+ surface: ThemeColorSchema,
836
+ border: ThemeColorSchema,
837
+ text: ThemeColorSchema,
838
+ muted: ThemeColorSchema,
839
+ accent: ThemeColorSchema,
840
+ onAccent: ThemeColorSchema,
841
+ danger: ThemeColorSchema
842
+ }).strict();
843
+ var ChatThemeSpacingSchema = z2.object({ small: ThemeLengthSchema, medium: ThemeLengthSchema, large: ThemeLengthSchema }).strict();
844
+ var ChatThemeRadiusSchema = z2.object({ medium: ThemeLengthSchema, large: ThemeLengthSchema }).strict();
845
+ var ChatThemeSchema = z2.object({
846
+ colors: ChatThemeColorsSchema.readonly(),
847
+ spacing: ChatThemeSpacingSchema.readonly(),
848
+ radius: ChatThemeRadiusSchema.readonly(),
849
+ fontFamily: ThemeFontFamilySchema
850
+ }).strict().readonly();
851
+ var defaultChatTheme = ChatThemeSchema.parse({
852
+ colors: { background: "#ffffff", surface: "#f3f4f6", border: "#d1d5db", text: "#111827", muted: "#6b7280", accent: "#2563eb", onAccent: "#ffffff", danger: "#dc2626" },
853
+ spacing: { small: 8, medium: 12, large: 16 },
854
+ radius: { medium: 8, large: 12 },
855
+ fontFamily: "system"
856
+ });
857
+ var resolveChatTheme = (input = {}) => {
858
+ const candidate = z2.object({
859
+ colors: ChatThemeColorsSchema.partial().strict().optional(),
860
+ spacing: ChatThemeSpacingSchema.partial().strict().optional(),
861
+ radius: ChatThemeRadiusSchema.partial().strict().optional(),
862
+ fontFamily: ThemeFontFamilySchema.optional()
863
+ }).strict().parse(input);
864
+ return ChatThemeSchema.parse({
865
+ colors: { ...defaultChatTheme.colors, ...candidate.colors },
866
+ spacing: { ...defaultChatTheme.spacing, ...candidate.spacing },
867
+ radius: { ...defaultChatTheme.radius, ...candidate.radius },
868
+ fontFamily: candidate.fontFamily ?? defaultChatTheme.fontFamily
869
+ });
870
+ };
871
+ var getLifecycleTargets = (messages) => {
872
+ const assistant = messages.at(-1);
873
+ if (assistant?.role !== "assistant") return { userId: void 0, assistantId: void 0 };
874
+ const userId = messages.slice(0, -1).reverse().find((message) => message.role === "user")?.id;
875
+ return userId === void 0 ? { userId: void 0, assistantId: void 0 } : { userId, assistantId: assistant.id };
876
+ };
877
+ var createCapabilityPolicy = ({ sessionId, getContext, requirements, onTrace, now = Date.now }) => {
878
+ if (sessionId.length === 0) invalidConfirmation("Action policy session is invalid.");
879
+ const required = new Map(Object.entries(requirements).map(([action, capabilities]) => {
880
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(action) || capabilities.some((capability) => !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(capability))) {
881
+ return invalidConfirmation("Action policy requirements are invalid.");
882
+ }
883
+ return [action, Object.freeze([...new Set(capabilities)])];
884
+ }));
885
+ const traces = [];
886
+ let sequence = 0;
887
+ const record = (call, phase, capabilities, reason) => {
888
+ const trace = Object.freeze({
889
+ id: `policy-${++sequence}`,
890
+ toolCallId: call.id,
891
+ action: call.name,
892
+ phase,
893
+ decision: reason === "allowed" ? "allow" : "deny",
894
+ reason,
895
+ requiredCapabilities: capabilities,
896
+ timestamp: now()
897
+ });
898
+ traces.push(trace);
899
+ try {
900
+ void Promise.resolve(onTrace?.(trace)).catch(() => void 0);
901
+ } catch {
902
+ }
903
+ return trace;
904
+ };
905
+ const decide = (call) => {
906
+ const capabilities = required.get(call.name);
907
+ let trusted;
908
+ try {
909
+ const candidate = getContext();
910
+ if (candidate !== void 0 && (typeof candidate.sessionId !== "string" || !Array.isArray(candidate.capabilities) || !candidate.capabilities.every((capability) => typeof capability === "string"))) throw new TypeError("Invalid trusted context");
911
+ trusted = candidate === void 0 ? void 0 : {
912
+ sessionId: candidate.sessionId,
913
+ capabilities: Object.freeze([...candidate.capabilities])
914
+ };
915
+ } catch {
916
+ return { capabilities: capabilities ?? Object.freeze([]), reason: "policy-failure" };
917
+ }
918
+ return {
919
+ capabilities: capabilities ?? Object.freeze([]),
920
+ reason: capabilities === void 0 ? "action-unregistered" : trusted === void 0 ? "missing-context" : trusted.sessionId !== sessionId ? "session-mismatch" : capabilities.some((capability) => !trusted.capabilities.includes(capability)) ? "missing-capability" : "allowed"
921
+ };
922
+ };
923
+ return {
924
+ async authorizeToolCall(call, context) {
925
+ const { capabilities, reason } = decide(call);
926
+ const trace = record(call, context.phase, capabilities, reason);
927
+ return { allowed: trace.decision === "allow", ...trace.decision === "deny" ? { reason: trace.reason } : {} };
928
+ },
929
+ compose: (authorizer) => async (call, context) => {
930
+ let local = decide(call);
931
+ let reason = local.reason;
932
+ if (reason === "allowed" && authorizer) {
933
+ try {
934
+ if (!(await authorizer(call, context)).allowed) reason = "composed-policy-denied";
935
+ else {
936
+ local = decide(call);
937
+ reason = local.reason;
938
+ }
939
+ } catch {
940
+ reason = "policy-failure";
941
+ }
942
+ }
943
+ const trace = record(call, context.phase, local.capabilities, reason);
944
+ return { allowed: trace.decision === "allow", ...trace.decision === "deny" ? { reason: trace.reason } : {} };
945
+ },
946
+ getTrace: () => Object.freeze([...traces])
947
+ };
948
+ };
949
+ var withActionPolicy = (chat, policy) => {
950
+ return {
951
+ ...chat,
952
+ authorizeToolCall: policy.compose(chat.authorizeToolCall)
953
+ };
954
+ };
955
+ var resolveChoiceAction = (frame, choiceId) => {
956
+ const props = ChoiceListPropsSchema.parse(frame.props);
957
+ return props.choices.find((choice) => choice.id === choiceId)?.action;
958
+ };
959
+ var invalidConfirmation = (message) => {
960
+ throw new ConfigError({
961
+ code: ErrorCodes.AK_CONFIG_INVALID,
962
+ message,
963
+ hint: "Use the pending confirmation token in the session that created it."
964
+ });
965
+ };
966
+ var freezeJson = (value) => {
967
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
968
+ for (const child of Object.values(value)) freezeJson(child);
969
+ Object.freeze(value);
970
+ }
971
+ return value;
972
+ };
973
+ var createActionConfirmation = ({
974
+ sessionId,
975
+ chat,
976
+ ttlMs = 3e5,
977
+ now = Date.now,
978
+ createId,
979
+ initialRecords = [],
980
+ onChange,
981
+ claimStatus
982
+ }) => {
983
+ if (sessionId.length === 0 || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) invalidConfirmation("Action confirmation options are invalid.");
984
+ let sequence = 0;
985
+ const nextId = createId ?? (() => `${(++sequence).toString(36)}-${now().toString(36)}`);
986
+ const records = /* @__PURE__ */ new Map();
987
+ const tokensByCall = /* @__PURE__ */ new Map();
988
+ const operations = /* @__PURE__ */ new Map();
989
+ const snapshot = (record, status) => Object.freeze({ ...record, status });
990
+ const requireRecord = (token, requestedSession) => {
991
+ const record = records.get(token);
992
+ if (!record || record.sessionId !== requestedSession) return invalidConfirmation("Action confirmation token is invalid for this session.");
993
+ return record;
994
+ };
995
+ const runOnce = (token, operation) => {
996
+ const current = operations.get(token);
997
+ if (current) return current;
998
+ const pending = operation().finally(() => operations.delete(token));
999
+ operations.set(token, pending);
1000
+ return pending;
1001
+ };
1002
+ const changed = async () => {
1003
+ await onChange?.(Object.freeze([...records.values()]));
1004
+ };
1005
+ const claim = async (record, status) => {
1006
+ const terminal = snapshot(record, status);
1007
+ if (claimStatus && !await claimStatus(record, status)) invalidConfirmation("Action confirmation was already resolved by another client.");
1008
+ records.set(record.token, terminal);
1009
+ if (!claimStatus) await changed();
1010
+ return terminal;
1011
+ };
1012
+ for (const persisted of initialRecords) {
1013
+ const record = freezeJson({ ...persisted, sessionId });
1014
+ records.set(record.token, record);
1015
+ tokensByCall.set(record.toolCallId, record.token);
1016
+ }
1017
+ return {
1018
+ async propose(action) {
1019
+ const suffix = nextId();
1020
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(suffix)) invalidConfirmation("Action confirmation id is invalid.");
1021
+ const call = await chat.proposeToolCall({ id: `app-${suffix}`, name: action.name, args: action.input });
1022
+ const token = `confirm-${suffix}`;
1023
+ const record = Object.freeze({
1024
+ token,
1025
+ sessionId,
1026
+ action: call.name,
1027
+ input: freezeJson(structuredClone(call.args)),
1028
+ toolCallId: call.id,
1029
+ expiresAt: now() + ttlMs,
1030
+ status: call.status === "requires_confirmation" ? "pending" : "rejected"
1031
+ });
1032
+ records.set(token, record);
1033
+ tokensByCall.set(call.id, token);
1034
+ await changed();
1035
+ return record;
1036
+ },
1037
+ approve(token, requestedSession) {
1038
+ return runOnce(token, async () => {
1039
+ const record = requireRecord(token, requestedSession);
1040
+ if (record.status !== "pending") return record;
1041
+ if (now() >= record.expiresAt) {
1042
+ const expiring = await claim(record, "expiring");
1043
+ try {
1044
+ await chat.deny(record.toolCallId, "confirmation expired");
1045
+ } catch (error) {
1046
+ if (!claimStatus) {
1047
+ records.set(token, record);
1048
+ await changed();
1049
+ }
1050
+ ;
1051
+ throw error;
1052
+ }
1053
+ return claim(expiring, "expired");
1054
+ }
1055
+ const approving = await claim(record, "approving");
1056
+ try {
1057
+ await chat.approve(record.toolCallId);
1058
+ } catch (error) {
1059
+ if (!claimStatus) {
1060
+ records.set(token, record);
1061
+ await changed();
1062
+ }
1063
+ ;
1064
+ throw error;
1065
+ }
1066
+ return claim(approving, "approved");
1067
+ });
1068
+ },
1069
+ reject(token, requestedSession, reason) {
1070
+ return runOnce(token, async () => {
1071
+ const record = requireRecord(token, requestedSession);
1072
+ if (record.status !== "pending") return record;
1073
+ const rejecting = await claim(record, "rejecting");
1074
+ try {
1075
+ await chat.deny(record.toolCallId, reason);
1076
+ } catch (error) {
1077
+ if (!claimStatus) {
1078
+ records.set(token, record);
1079
+ await changed();
1080
+ }
1081
+ ;
1082
+ throw error;
1083
+ }
1084
+ return claim(rejecting, "rejected");
1085
+ });
1086
+ },
1087
+ getByToolCall(toolCallId) {
1088
+ const token = tokensByCall.get(toolCallId);
1089
+ return token === void 0 ? void 0 : records.get(token);
1090
+ },
1091
+ getSnapshot: () => Object.freeze([...records.values()])
1092
+ };
1093
+ };
1094
+ var defineComponentManifest = (components) => {
1095
+ const manifest = /* @__PURE__ */ Object.create(null);
1096
+ for (const component of components) {
1097
+ const standardDefinition = StandardComponentCatalog.find((candidate) => candidate.key === component.key);
1098
+ if (!ComponentKeySchema.safeParse(component.key).success || Object.hasOwn(manifest, component.key) || standardDefinition !== void 0 && standardDefinition !== component) {
1099
+ throw new ConfigError({
1100
+ code: ErrorCodes.AK_CONFIG_INVALID,
1101
+ message: "Component manifest keys must be non-empty, unique, and must not override standard catalog keys.",
1102
+ hint: "Register each application component exactly once and use the canonical definition for standard keys."
1103
+ });
1104
+ }
1105
+ manifest[component.key] = component;
1106
+ }
1107
+ return manifest;
1108
+ };
1109
+ var resolveComponentFrame = (input, manifest) => {
1110
+ const decoded = decodeComponentFrame2(input);
1111
+ if (!decoded.ok) return decoded;
1112
+ if (!Object.hasOwn(manifest, decoded.frame.componentKey)) return {
1113
+ ok: false,
1114
+ diagnostic: { code: "COMPONENT_UNKNOWN", message: "Component is not registered.", retryable: false }
1115
+ };
1116
+ const component = manifest[decoded.frame.componentKey];
1117
+ const props = component.propsSchema.safeParse(decoded.frame.props);
1118
+ return props.success ? { ok: true, frame: decoded.frame, props: props.data } : {
1119
+ ok: false,
1120
+ diagnostic: { code: "COMPONENT_INVALID_PROPS", message: "Component props are invalid.", retryable: false }
1121
+ };
1122
+ };
1123
+ var createComponentInteraction = (frame, manifest, event, value) => {
1124
+ const resolved = resolveComponentFrame(frame, manifest);
1125
+ const definition = resolved.ok ? manifest[resolved.frame.componentKey] : void 0;
1126
+ const declared = definition?.events?.find((candidate) => candidate.name === event);
1127
+ const isStandard = STANDARD_COMPONENT_KEYS.includes(frame.componentKey);
1128
+ const validValue = isStandard ? resolved.ok && validateStandardComponentInteraction(frame.componentKey, resolved.props, event, value) : declared?.value === "none" ? value === void 0 : declared?.value === "id" ? typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value) : declared?.value === "url" ? typeof value === "string" && (/^\/(?!\/)/.test(value) || (() => {
1129
+ try {
1130
+ const url = new URL(value);
1131
+ return ["http:", "https:"].includes(url.protocol) && url.username === "" && url.password === "";
1132
+ } catch {
1133
+ return false;
1134
+ }
1135
+ })()) : declared?.value === "form" ? value !== null && typeof value === "object" && !Array.isArray(value) : false;
1136
+ if (!resolved.ok || !declared || !validValue) {
1137
+ throw new ConfigError({
1138
+ code: ErrorCodes.AK_CONFIG_INVALID,
1139
+ message: "Component interaction is not declared by the resolved component.",
1140
+ hint: "Emit only catalog events with the declared value shape."
1141
+ });
1142
+ }
1143
+ return createInteractionEvent(frame, event, value);
1144
+ };
1145
+ var resolveComponentFallback = (input, manifest) => {
1146
+ const resolved = resolveComponentFrame(input, manifest);
1147
+ if (!resolved.ok) return void 0;
1148
+ return manifest[resolved.frame.componentKey]?.fallback?.(resolved.props);
1149
+ };
1150
+ var resolveChoiceListFrame = (input, manifest) => {
1151
+ const resolved = resolveComponentFrame(input, manifest);
1152
+ if (!resolved.ok) return resolved;
1153
+ if (resolved.frame.componentKey !== CHOICE_LIST_COMPONENT_KEY) return {
1154
+ ok: false,
1155
+ diagnostic: { code: "COMPONENT_UNKNOWN", message: "Component is not a ChoiceList.", retryable: false }
1156
+ };
1157
+ const props = ChoiceListPropsSchema.safeParse(resolved.props);
1158
+ return props.success ? { ok: true, frame: resolved.frame, props: props.data } : {
1159
+ ok: false,
1160
+ diagnostic: { code: "COMPONENT_INVALID_PROPS", message: "ChoiceList props are invalid.", retryable: false }
1161
+ };
1162
+ };
1163
+ var selectChoice = (frame, choiceId) => {
1164
+ if (frame.componentKey !== CHOICE_LIST_COMPONENT_KEY) {
1165
+ throw new ConfigError({
1166
+ code: ErrorCodes.AK_CONFIG_INVALID,
1167
+ message: "Selection frame is not a ChoiceList.",
1168
+ hint: "Resolve the frame through ChoiceListComponent before emitting a selection."
1169
+ });
1170
+ }
1171
+ const props = ChoiceListPropsSchema.parse(frame.props);
1172
+ if (!props.choices.some((choice) => choice.id === choiceId)) {
1173
+ throw new ConfigError({
1174
+ code: ErrorCodes.AK_CONFIG_INVALID,
1175
+ message: "Selected choice is not present in the ChoiceList.",
1176
+ hint: "Emit only choice ids declared by the validated render frame."
1177
+ });
1178
+ }
1179
+ return createSelectionEvent(frame, choiceId);
1180
+ };
1181
+ var defineChat = (definition) => definition;
1182
+ var SessionConflictError = class extends Error {
1183
+ constructor() {
1184
+ super("Session snapshot changed in another client.");
1185
+ this.name = "SessionConflictError";
1186
+ }
1187
+ };
1188
+ var invalidConversation = (message) => {
1189
+ throw new ConfigError({
1190
+ code: ErrorCodes.AK_CONFIG_INVALID,
1191
+ message,
1192
+ hint: "Define valid conversation states, routes, and transition targets."
1193
+ });
1194
+ };
1195
+ var compileConversation = (definitionId, revision, conversation) => {
1196
+ const stateNames = new Set(Object.keys(conversation.states));
1197
+ const routeIds = /* @__PURE__ */ new Set();
1198
+ for (const route of conversation.routes) {
1199
+ if (route.id.length === 0 || route.event.length === 0) invalidConversation("Conversation route identity is invalid.");
1200
+ if (routeIds.has(route.id)) invalidConversation("Conversation route ids must be unique.");
1201
+ routeIds.add(route.id);
1202
+ for (const state of route.states ?? []) {
1203
+ if (!stateNames.has(state)) invalidConversation("Conversation route state is unknown.");
1204
+ }
1205
+ }
1206
+ const states = {};
1207
+ for (const [stateName, state] of Object.entries(conversation.states)) {
1208
+ const on = {};
1209
+ for (const [event, target] of Object.entries(state.on ?? {})) on[event] = { target };
1210
+ states[stateName] = Object.keys(on).length > 0 ? { on } : {};
1211
+ }
1212
+ try {
1213
+ return defineStatechart({
1214
+ id: `${definitionId}.conversation`,
1215
+ version: String(revision),
1216
+ initial: conversation.initial,
1217
+ parseContext: (input) => {
1218
+ if (input === null || typeof input !== "object" || Array.isArray(input) || Object.keys(input).length > 0) {
1219
+ throw new TypeError("Conversation statechart context must be empty.");
1220
+ }
1221
+ return {};
1222
+ },
1223
+ states
1224
+ });
1225
+ } catch (error) {
1226
+ if (error instanceof StatechartError) invalidConversation(error.message);
1227
+ return invalidConversation("Conversation statechart definition is invalid.");
1228
+ }
1229
+ };
1230
+ var latestUserInput2 = (request) => request.messages.filter((message) => message.role === "user").at(-1)?.content ?? "";
1231
+ var deterministicSource = (content) => {
1232
+ let aborted = false;
1233
+ return {
1234
+ async *stream() {
1235
+ if (aborted) return;
1236
+ yield { type: "text", content };
1237
+ if (!aborted) yield { type: "done" };
1238
+ },
1239
+ abort() {
1240
+ aborted = true;
1241
+ }
1242
+ };
1243
+ };
1244
+ var routeErrorSource = () => ({
1245
+ async *stream() {
1246
+ yield { type: "error", content: "Deterministic route failed." };
1247
+ },
1248
+ abort() {
1249
+ }
1250
+ });
1251
+ var wrappedAdapters = /* @__PURE__ */ new WeakMap();
1252
+ var createChatSession = (definition, options = {}) => {
1253
+ const revision = definition.revision ?? 1;
1254
+ if (!Number.isSafeInteger(revision) || revision <= 0) invalidConversation("Chat definition revision is invalid.");
1255
+ const sessionId = options.sessionId ?? `${definition.id}:${Date.now().toString(36)}`;
1256
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(sessionId)) invalidConversation("Chat session identity is invalid.");
1257
+ const restored = options.snapshot;
1258
+ let confirmations = restored?.confirmations ?? [];
1259
+ let cursor = restored?.cursor ?? 0;
1260
+ let exists = restored !== void 0;
1261
+ let activeTurn = restored?.activeTurn;
1262
+ let terminalTurns = restored?.terminalTurns ?? [];
1263
+ let persistQueue = Promise.resolve();
1264
+ const conversation = definition.conversation;
1265
+ const machine = conversation ? compileConversation(definition.id, revision, conversation) : void 0;
1266
+ if (!conversation && restored?.conversation) invalidConversation("Session conversation metadata is incompatible with this chat definition.");
1267
+ const decisions = /* @__PURE__ */ new Map();
1268
+ for (const decision of restored?.conversation?.decisions ?? []) {
1269
+ decisions.set(decision.messageId, decision);
1270
+ }
1271
+ const statechartNow = () => (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
1272
+ const createInitialMachineInstance = () => {
1273
+ if (!machine) return void 0;
1274
+ try {
1275
+ return createStatechartInstance(machine, {}, { instanceId: sessionId, now: statechartNow() });
1276
+ } catch (error) {
1277
+ if (error instanceof StatechartError) invalidConversation(error.message);
1278
+ return invalidConversation("Conversation statechart instance is invalid.");
1279
+ }
1280
+ };
1281
+ const transitionDecision = (instance, decision) => {
1282
+ if (!conversation || !machine || decision.fromState !== instance.state) return void 0;
1283
+ const route = conversation.routes.find((candidate) => candidate.id === decision.routeId);
1284
+ if (!route || route.states !== void 0 && !route.states.includes(instance.state)) return void 0;
1285
+ const result = transitionStatechart(machine, instance, { type: route.event }, { now: statechartNow() });
1286
+ return result.status === "accepted" && result.to === decision.toState ? result.instance : void 0;
1287
+ };
1288
+ let machineInstance = createInitialMachineInstance();
1289
+ if (machineInstance && restored?.conversation) {
1290
+ let restoredInstance = machineInstance;
1291
+ for (const decision of restored.conversation.decisions) {
1292
+ const next = transitionDecision(restoredInstance, decision) ?? invalidConversation("Session conversation metadata is incompatible with this chat definition.");
1293
+ restoredInstance = next;
1294
+ }
1295
+ if (restoredInstance.state !== restored.conversation.state) {
1296
+ invalidConversation("Session conversation metadata is incompatible with this chat definition.");
1297
+ }
1298
+ machineInstance = restoredInstance;
1299
+ }
1300
+ const getMachineInstance = () => machineInstance ?? invalidConversation("Conversation statechart instance is missing.");
1301
+ const buildSnapshot = (nextCursor) => SessionSnapshotSchema.parse({
1302
+ protocol: SESSION_PROTOCOL,
1303
+ version: SESSION_PROTOCOL_VERSION,
1304
+ sessionId,
1305
+ definitionId: definition.id,
1306
+ definitionRevision: revision,
1307
+ updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
1308
+ cursor: nextCursor,
1309
+ ...activeTurn ? { activeTurn } : {},
1310
+ ...terminalTurns.length > 0 ? { terminalTurns } : {},
1311
+ ...conversation ? {
1312
+ conversation: {
1313
+ state: getMachineInstance().state,
1314
+ decisions: [...decisions].map(([messageId, decision]) => ({ messageId, ...decision }))
1315
+ }
1316
+ } : {},
1317
+ confirmations
1318
+ });
1319
+ const persist = (signal) => {
1320
+ if (!options.storage) {
1321
+ cursor += 1;
1322
+ return Promise.resolve();
1323
+ }
1324
+ persistQueue = persistQueue.catch(() => void 0).then(async () => {
1325
+ const expectedCursor = exists ? cursor : void 0;
1326
+ const snapshot = buildSnapshot(cursor + 1);
1327
+ if (!await options.storage.save(snapshot, expectedCursor, signal ?? options.signal)) throw new SessionConflictError();
1328
+ cursor = snapshot.cursor;
1329
+ exists = true;
1330
+ });
1331
+ return persistQueue;
1332
+ };
1333
+ const createConfirmation = (confirmationOptions) => createActionConfirmation({
1334
+ ...confirmationOptions,
1335
+ sessionId,
1336
+ initialRecords: confirmations,
1337
+ onChange: async (records) => {
1338
+ const previous = confirmations;
1339
+ confirmations = records.map(({ sessionId: _sessionId, ...record }) => record);
1340
+ try {
1341
+ await persist();
1342
+ } catch (error) {
1343
+ confirmations = previous;
1344
+ throw error;
1345
+ }
1346
+ try {
1347
+ void Promise.resolve(options.onConfirmationChange?.(records)).catch(() => void 0);
1348
+ } catch {
1349
+ }
1350
+ },
1351
+ ...options.storage ? { claimStatus: async (record, status) => {
1352
+ const previous = confirmations;
1353
+ confirmations = confirmations.map((candidate) => candidate.token === record.token ? { ...candidate, status } : candidate);
1354
+ try {
1355
+ await persist();
1356
+ const observed = Object.freeze(confirmations.map((record2) => Object.freeze({ ...record2, sessionId })));
1357
+ try {
1358
+ void Promise.resolve(options.onConfirmationChange?.(observed)).catch(() => void 0);
1359
+ } catch {
1360
+ }
1361
+ ;
1362
+ return true;
1363
+ } catch (error) {
1364
+ confirmations = previous;
1365
+ throw error;
1366
+ }
1367
+ } } : {}
1368
+ });
1369
+ const scopeAdapter = (adapter) => {
1370
+ const sessionAware = adapter;
1371
+ return {
1372
+ ...adapter,
1373
+ createSource: (request) => sessionAware.createSourceForSession?.(request, sessionId) ?? adapter.createSource(request)
1374
+ };
1375
+ };
1376
+ const claimTurn = async (turnId, leaseMs, signal) => {
1377
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(turnId) || !Number.isSafeInteger(leaseMs) || leaseMs <= 0) invalidConversation("Turn claim is invalid.");
1378
+ const now = (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
1379
+ if (terminalTurns.some((turn) => turn.turnId === turnId) || activeTurn && activeTurn.expiresAt > now) return false;
1380
+ activeTurn = { turnId, expiresAt: now + leaseMs };
1381
+ await persist(signal);
1382
+ return true;
1383
+ };
1384
+ const releaseTurn = async (turnId, outcome, signal) => {
1385
+ if (activeTurn?.turnId !== turnId) return;
1386
+ activeTurn = void 0;
1387
+ terminalTurns = [...terminalTurns.filter((candidate) => candidate.turnId !== turnId), { turnId, outcome }].slice(-64);
1388
+ await persist(signal);
1389
+ };
1390
+ if (!conversation) return {
1391
+ sessionId,
1392
+ definitionId: definition.id,
1393
+ definitionRevision: revision,
1394
+ chat: { ...definition.chat, adapter: scopeAdapter(definition.chat.adapter) },
1395
+ updateChat: (chat) => ({ ...chat, adapter: scopeAdapter(chat.adapter) }),
1396
+ getConversationSnapshot: () => void 0,
1397
+ createConfirmation,
1398
+ persist,
1399
+ getCursor: () => cursor,
1400
+ claimTurn,
1401
+ releaseTurn
1402
+ };
1403
+ const conversationMachine = machine ?? invalidConversation("Conversation statechart instance is missing.");
1404
+ machineInstance ??= invalidConversation("Conversation statechart instance is missing.");
1405
+ const emitTrace = (trace) => {
1406
+ try {
1407
+ void Promise.resolve(conversation.onTrace?.(trace)).catch(() => void 0);
1408
+ } catch {
1409
+ }
1410
+ };
1411
+ const rebuildState = (messages) => {
1412
+ let rebuilt = createInitialMachineInstance() ?? invalidConversation("Conversation statechart instance is missing.");
1413
+ for (const message of messages) {
1414
+ if (message.role !== "user") continue;
1415
+ const decision = decisions.get(message.id);
1416
+ if (decision?.input !== message.content) continue;
1417
+ const next = transitionDecision(rebuilt, decision);
1418
+ if (next) rebuilt = next;
1419
+ }
1420
+ machineInstance = rebuilt;
1421
+ };
1422
+ const getConversationSnapshot = () => {
1423
+ const instance = getMachineInstance();
1424
+ const state = conversation.states[instance.state];
1425
+ return {
1426
+ state: instance.state,
1427
+ events: Object.keys(state.on ?? {}),
1428
+ actions: [...state.actions ?? []]
1429
+ };
1430
+ };
1431
+ const schedulePersist = () => {
1432
+ void persist().catch(() => void 0);
1433
+ };
1434
+ const createSource = (adapter, request) => {
1435
+ const userMessages = request.messages.filter((message) => message.role === "user");
1436
+ const userIds = new Set(userMessages.map((message) => message.id));
1437
+ for (const id of decisions.keys()) {
1438
+ if (!userIds.has(id)) decisions.delete(id);
1439
+ }
1440
+ const userMessage = userMessages.at(-1);
1441
+ const input = latestUserInput2(request);
1442
+ rebuildState(userMessages.slice(0, -1));
1443
+ const replay = userMessage === void 0 ? void 0 : decisions.get(userMessage.id);
1444
+ if (userMessage !== void 0 && replay?.input === input && replay.fromState === getMachineInstance().state) {
1445
+ const next = transitionDecision(getMachineInstance(), replay);
1446
+ if (!next) {
1447
+ decisions.delete(userMessage.id);
1448
+ schedulePersist();
1449
+ return routeErrorSource();
1450
+ }
1451
+ machineInstance = next;
1452
+ emitTrace({ kind: replay.kind, routeId: replay.routeId, fromState: replay.fromState, toState: replay.toState });
1453
+ schedulePersist();
1454
+ return deterministicSource(replay.content);
1455
+ }
1456
+ if (userMessage !== void 0) decisions.delete(userMessage.id);
1457
+ const currentState = getMachineInstance().state;
1458
+ const state = conversationMachine.states[currentState];
1459
+ let route;
1460
+ try {
1461
+ route = conversation.routes.find(
1462
+ (candidate) => (candidate.states === void 0 || candidate.states.includes(currentState)) && state.on?.[candidate.event] !== void 0 && candidate.match(input)
1463
+ );
1464
+ } catch {
1465
+ schedulePersist();
1466
+ return routeErrorSource();
1467
+ }
1468
+ if (!route) {
1469
+ emitTrace({ kind: "agentic", fromState: currentState, toState: currentState });
1470
+ schedulePersist();
1471
+ return adapter.createSource(request);
1472
+ }
1473
+ const fromState = currentState;
1474
+ let content;
1475
+ try {
1476
+ content = route.response(input, { sessionId, ...userMessage ? { messageId: userMessage.id } : {} });
1477
+ } catch {
1478
+ schedulePersist();
1479
+ return routeErrorSource();
1480
+ }
1481
+ const transition = transitionStatechart(conversationMachine, getMachineInstance(), { type: route.event }, { now: statechartNow() });
1482
+ if (transition.status !== "accepted") {
1483
+ schedulePersist();
1484
+ return routeErrorSource();
1485
+ }
1486
+ machineInstance = transition.instance;
1487
+ const kind = route.traceKind ?? "deterministic";
1488
+ if (userMessage !== void 0) decisions.set(userMessage.id, {
1489
+ input,
1490
+ routeId: route.id,
1491
+ kind,
1492
+ content,
1493
+ fromState,
1494
+ toState: transition.to
1495
+ });
1496
+ emitTrace({
1497
+ kind,
1498
+ routeId: route.id,
1499
+ fromState,
1500
+ toState: transition.to
1501
+ });
1502
+ schedulePersist();
1503
+ return deterministicSource(content);
1504
+ };
1505
+ const updateChat = (chat) => {
1506
+ const adapter = wrappedAdapters.get(chat.adapter) ?? chat.adapter;
1507
+ const scoped = scopeAdapter(adapter);
1508
+ const wrapped = { ...adapter, createSource: (request) => createSource(scoped, request) };
1509
+ wrappedAdapters.set(wrapped, adapter);
1510
+ return { ...chat, adapter: wrapped };
1511
+ };
1512
+ return {
1513
+ sessionId,
1514
+ definitionId: definition.id,
1515
+ definitionRevision: revision,
1516
+ chat: updateChat(definition.chat),
1517
+ updateChat,
1518
+ getConversationSnapshot,
1519
+ createConfirmation,
1520
+ persist,
1521
+ getCursor: () => cursor,
1522
+ claimTurn,
1523
+ releaseTurn
1524
+ };
1525
+ };
1526
+ var resumeChatSession = async (definition, options) => {
1527
+ const input = await options.storage.load(options.sessionId, options.signal);
1528
+ if (input === void 0 || input === null) return createChatSession(definition, options);
1529
+ const decoded = decodeSessionSnapshot(input);
1530
+ if (!decoded.ok) return invalidConversation(decoded.diagnostic.message);
1531
+ const snapshot = decoded.snapshot;
1532
+ if (snapshot.sessionId !== options.sessionId || snapshot.definitionId !== definition.id || snapshot.definitionRevision !== (definition.revision ?? 1)) {
1533
+ invalidConversation("Session snapshot is incompatible with this chat definition.");
1534
+ }
1535
+ return createChatSession(definition, { ...options, snapshot });
1536
+ };
1537
+ var resolveChatSession = (definition, session) => {
1538
+ if (!session) return createChatSession(definition);
1539
+ if (session.definitionId !== definition.id || session.definitionRevision !== (definition.revision ?? 1)) {
1540
+ invalidConversation("Prepared session is incompatible with this chat definition.");
1541
+ }
1542
+ return session;
1543
+ };
1544
+ var commandRoute = (options) => {
1545
+ if (options.command.length === 0) invalidConversation("Conversation command cannot be empty.");
1546
+ const { command, ...route } = options;
1547
+ return { ...route, match: (input) => input === command };
1548
+ };
1549
+ var SemanticFallbackSchema = ComponentFallbackSchema;
1550
+ var parseSemanticFallback = (input) => SemanticFallbackSchema.parse(input);
1551
+ var formatSemanticFallback = (fallback) => `[unsupported visual: ${fallback.kind}] ${fallback.summary}`;
1552
+ export {
1553
+ ApprovalRequestComponent,
1554
+ ApprovalRequestPropsSchema,
1555
+ AskEventSchema2 as AskEventSchema,
1556
+ ButtonGroupComponent,
1557
+ ButtonGroupPropsSchema,
1558
+ CHOICE_LIST_COMPONENT_KEY,
1559
+ ChatThemeSchema,
1560
+ ChoiceListComponent,
1561
+ ChoiceListPropsSchema,
1562
+ ConfirmationComponent,
1563
+ ConfirmationPropsSchema,
1564
+ ErrorNoticeComponent,
1565
+ ErrorNoticePropsSchema,
1566
+ FileAttachmentComponent,
1567
+ FileAttachmentPropsSchema,
1568
+ FormComponent,
1569
+ FormPropsSchema,
1570
+ LinkCardComponent,
1571
+ LinkCardPropsSchema,
1572
+ ProgressComponent,
1573
+ ProgressPropsSchema,
1574
+ STANDARD_COMPONENT_KEYS,
1575
+ SemanticFallbackSchema,
1576
+ SessionConflictError,
1577
+ SourceListComponent,
1578
+ SourceListPropsSchema,
1579
+ StandardComponentCatalog,
1580
+ TableComponent,
1581
+ TablePropsSchema,
1582
+ ToolCallComponent,
1583
+ ToolCallPropsSchema,
1584
+ commandRoute,
1585
+ createActionConfirmation,
1586
+ createAskAdapter,
1587
+ createAskSessionMemory,
1588
+ createCapabilityPolicy,
1589
+ createChatSession,
1590
+ createComponentInteraction,
1591
+ createDeterministicAnswerAdapter,
1592
+ createDeterministicAnswerResolver,
1593
+ decodeAskEvents2 as decodeAskEvents,
1594
+ defaultChatTheme,
1595
+ defineChat,
1596
+ defineComponentManifest,
1597
+ formatSemanticFallback,
1598
+ getLifecycleTargets,
1599
+ normalizeDeterministicQuery,
1600
+ parseSemanticFallback,
1601
+ presentChatMessage,
1602
+ projectAskEvent,
1603
+ projectAskMessages,
1604
+ resolveChatSession,
1605
+ resolveChatTheme,
1606
+ resolveChoiceAction,
1607
+ resolveChoiceListFrame,
1608
+ resolveComponentFallback,
1609
+ resolveComponentFrame,
1610
+ resumeChatSession,
1611
+ selectChoice,
1612
+ validateStandardComponentInteraction,
1613
+ withActionPolicy
1614
+ };