@alquimia-ai/tools 2.1.0 → 2.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.
Files changed (80) hide show
  1. package/dist/actions/index.d.mts +1 -0
  2. package/dist/actions/index.d.ts +1 -0
  3. package/dist/actions/index.js.map +1 -1
  4. package/dist/actions/index.mjs.map +1 -1
  5. package/dist/actions-BebMyGqF.d.ts +16 -0
  6. package/dist/actions-Ca3ro-hX.d.mts +16 -0
  7. package/dist/adapters/fetch.d.mts +2 -0
  8. package/dist/adapters/fetch.d.ts +2 -0
  9. package/dist/adapters/fetch.js +7 -1
  10. package/dist/adapters/fetch.js.map +1 -1
  11. package/dist/adapters/fetch.mjs +7 -1
  12. package/dist/adapters/fetch.mjs.map +1 -1
  13. package/dist/adapters/index.d.mts +1 -0
  14. package/dist/adapters/index.d.ts +1 -0
  15. package/dist/adapters/index.js.map +1 -1
  16. package/dist/adapters/next.d.mts +2 -0
  17. package/dist/adapters/next.d.ts +2 -0
  18. package/dist/adapters/next.js +7 -1
  19. package/dist/adapters/next.js.map +1 -1
  20. package/dist/adapters/next.mjs +7 -1
  21. package/dist/adapters/next.mjs.map +1 -1
  22. package/dist/genui/index.d.mts +3027 -0
  23. package/dist/genui/index.d.ts +3027 -0
  24. package/dist/genui/index.js +3344 -0
  25. package/dist/genui/index.js.map +1 -0
  26. package/dist/genui/index.mjs +3321 -0
  27. package/dist/genui/index.mjs.map +1 -0
  28. package/dist/hooks/index.d.mts +26 -1
  29. package/dist/hooks/index.d.ts +26 -1
  30. package/dist/hooks/index.js +650 -21
  31. package/dist/hooks/index.js.map +1 -1
  32. package/dist/hooks/index.mjs +632 -3
  33. package/dist/hooks/index.mjs.map +1 -1
  34. package/dist/next/index.d.mts +7 -0
  35. package/dist/next/index.d.ts +7 -0
  36. package/dist/next/index.js +50 -0
  37. package/dist/next/index.js.map +1 -1
  38. package/dist/next/index.mjs +50 -0
  39. package/dist/next/index.mjs.map +1 -1
  40. package/dist/providers/index.d.mts +3 -2
  41. package/dist/providers/index.d.ts +3 -2
  42. package/dist/providers/index.js.map +1 -1
  43. package/dist/providers/index.mjs.map +1 -1
  44. package/dist/proxy.d.mts +2 -0
  45. package/dist/proxy.d.ts +2 -0
  46. package/dist/proxy.js +41 -0
  47. package/dist/proxy.js.map +1 -1
  48. package/dist/proxy.mjs +41 -0
  49. package/dist/proxy.mjs.map +1 -1
  50. package/dist/registry-Bcu_ZYL1.d.mts +19 -0
  51. package/dist/registry-Bcu_ZYL1.d.ts +19 -0
  52. package/dist/sdk/index.d.mts +10 -0
  53. package/dist/sdk/index.d.ts +10 -0
  54. package/dist/sdk/index.js +26 -0
  55. package/dist/sdk/index.js.map +1 -1
  56. package/dist/sdk/index.mjs +26 -0
  57. package/dist/sdk/index.mjs.map +1 -1
  58. package/dist/services/index.d.mts +1 -0
  59. package/dist/services/index.d.ts +1 -0
  60. package/dist/types/index.d.mts +10 -1
  61. package/dist/types/index.d.ts +10 -1
  62. package/dist/types/index.js.map +1 -1
  63. package/dist/types/index.mjs.map +1 -1
  64. package/dist/types-DnfWUE9V.d.mts +56 -0
  65. package/dist/types-DnfWUE9V.d.ts +56 -0
  66. package/dist/types-Dvu9y4WM.d.mts +53 -0
  67. package/dist/types-Dvu9y4WM.d.ts +53 -0
  68. package/dist/utils/index.d.mts +6 -2
  69. package/dist/utils/index.d.ts +6 -2
  70. package/dist/utils/index.js +4 -0
  71. package/dist/utils/index.js.map +1 -1
  72. package/dist/utils/index.mjs +4 -0
  73. package/dist/utils/index.mjs.map +1 -1
  74. package/dist/worklog/index.d.mts +29 -0
  75. package/dist/worklog/index.d.ts +29 -0
  76. package/dist/worklog/index.js +218 -0
  77. package/dist/worklog/index.js.map +1 -0
  78. package/dist/worklog/index.mjs +195 -0
  79. package/dist/worklog/index.mjs.map +1 -0
  80. package/package.json +24 -7
@@ -36,7 +36,7 @@ __export(hooks_exports, {
36
36
  module.exports = __toCommonJS(hooks_exports);
37
37
 
38
38
  // src/hooks/alquimia.hook.tsx
39
- var import_react = require("react");
39
+ var import_react2 = require("react");
40
40
 
41
41
  // src/sdk/alquimia-sdk.ts
42
42
  var import_axios = __toESM(require("axios"));
@@ -176,6 +176,32 @@ var AlquimiaSDK = class {
176
176
  this.attachments = [];
177
177
  return this;
178
178
  }
179
+ /**
180
+ * Complete a pending client-tool execution (the missing "submit half" of the
181
+ * client-tool loop). POSTs a {@link ToolExecutionResponse} to the runtime's
182
+ * `/event/tool-completion`. For interactive `render_ui` surfaces `result` is
183
+ * the user's data; for display-only surfaces the SDK sends `{ shown: true }`.
184
+ * The pending call is correlated by `control_id` in the body + the
185
+ * `context-metadata` header (taskid + sessionid).
186
+ */
187
+ async submitToolResult(response) {
188
+ const url = this.adapter.resolveToolCompletionUrl?.();
189
+ if (!url) {
190
+ throw new Error(
191
+ "AlquimiaSDK: adapter does not support tool completion (resolveToolCompletionUrl missing)"
192
+ );
193
+ }
194
+ const adapterHeaders = this.adapter.getHeaders?.() ?? {};
195
+ const headers = {
196
+ "Content-Type": "application/json",
197
+ assistantid: this.assistantId,
198
+ ...this.streamId && { taskid: this.streamId },
199
+ ...this.conversationId && { sessionid: this.conversationId },
200
+ ...this.userId && { userid: this.userId },
201
+ ...adapterHeaders
202
+ };
203
+ await this.axiosInstance.post(url, response, { headers });
204
+ }
179
205
  async generateImage(query) {
180
206
  if (!this.stableDiffusionProvider) {
181
207
  throw new Error("Stable Diffusion provider not initialized");
@@ -337,10 +363,505 @@ function mergeToolEvents(initial, toolEvents) {
337
363
  initial
338
364
  );
339
365
  }
366
+ function resolveGenuiAllow(catalog, allow) {
367
+ return !allow || allow === "all" ? Object.keys(catalog.components) : allow;
368
+ }
369
+
370
+ // src/worklog/types.ts
371
+ function initialWorklogState(taskId = null) {
372
+ return { taskId, status: "idle", answer: null, nodes: [], index: {}, raw: [] };
373
+ }
374
+
375
+ // src/worklog/registry.ts
376
+ var asRecord = (data) => data && typeof data === "object" ? data : {};
377
+ var toolTitle = (r) => {
378
+ const name = asRecord(r.data).name;
379
+ return typeof name === "string" && name.length > 0 ? `Tool: ${name}` : "Tool";
380
+ };
381
+ var agentTitle = (r) => {
382
+ const name = asRecord(r.data).name;
383
+ return typeof name === "string" && name.length > 0 ? `Agent: ${name}` : "Agent-to-agent";
384
+ };
385
+ var constTitle = (label) => () => label;
386
+ var EVENT_REGISTRY = {
387
+ // Run envelope
388
+ AssistantInference: { kind: "answer", role: "run-open", title: constTitle("Inference started") },
389
+ AssistantInferenceResponse: { kind: "answer", role: "run-close", title: constTitle("Answer") },
390
+ // Safeguard
391
+ ShieldInference: { kind: "safeguard", role: "open", title: constTitle("Safeguard") },
392
+ ShieldInferenceResponse: { kind: "safeguard", role: "close", title: constTitle("Safeguard") },
393
+ // Reasoning
394
+ ResponseInference: { kind: "reasoning", role: "open", title: constTitle("Reasoning") },
395
+ ResponseInferenceResponse: { kind: "reasoning", role: "close", title: constTitle("Reasoning") },
396
+ // Tools (server + subclasses) and their responses
397
+ ServerToolExecution: { kind: "tool", role: "open", title: toolTitle },
398
+ BuiltinToolExecution: { kind: "tool", role: "open", title: toolTitle },
399
+ UnknownToolExecution: { kind: "tool", role: "open", title: toolTitle },
400
+ ClientToolExecution: { kind: "tool", role: "open", title: toolTitle, pending: true },
401
+ ToolExecutionResponse: { kind: "tool", role: "close", title: toolTitle },
402
+ BuiltinToolExecutionResponse: { kind: "tool", role: "close", title: toolTitle },
403
+ // Tool schema discovery
404
+ ToolSchema: { kind: "tool", role: "open", title: constTitle("Tool schema") },
405
+ ToolSchemaResponse: { kind: "tool", role: "close", title: constTitle("Tool schema") },
406
+ // Agent-to-agent
407
+ A2AInference: { kind: "a2a", role: "open", title: agentTitle },
408
+ AgentDiscovery: { kind: "a2a", role: "open", title: constTitle("Agent discovery") },
409
+ AgentDiscoveryResponse: { kind: "a2a", role: "close", title: constTitle("Agent discovery") },
410
+ // Memory / persistence
411
+ ContextPersistence: { kind: "memory", role: "open", title: constTitle("Memory persistence") },
412
+ ContextFlush: { kind: "memory", role: "open", title: constTitle("Memory flush") },
413
+ ContextFlushResponse: { kind: "memory", role: "close", title: constTitle("Memory flush") },
414
+ // Human approval
415
+ HumanApprovalRequired: {
416
+ kind: "tool",
417
+ role: "open",
418
+ title: constTitle("Human approval required"),
419
+ pending: true
420
+ },
421
+ HumanApprovalRequiredResponse: { kind: "tool", role: "close", title: constTitle("Human approval") }
422
+ };
423
+ function resolveInterpreter(eventClass) {
424
+ if (Object.prototype.hasOwnProperty.call(EVENT_REGISTRY, eventClass)) {
425
+ return EVENT_REGISTRY[eventClass];
426
+ }
427
+ return {
428
+ kind: "unknown",
429
+ role: "open",
430
+ title: (r) => r.event_class
431
+ };
432
+ }
433
+
434
+ // src/worklog/reducer.ts
435
+ var asString = (v) => typeof v === "string" ? v : v == null ? "" : JSON.stringify(v);
436
+ var nodeDepth = (record) => {
437
+ const d = record.data && typeof record.data === "object" ? record.data.depth : void 0;
438
+ return typeof d === "number" ? d : 0;
439
+ };
440
+ var makeOpenNode = (record, interp, id) => ({
441
+ id,
442
+ kind: interp.kind,
443
+ eventClass: record.event_class,
444
+ status: interp.pending ? "pending" : "running",
445
+ title: interp.title(record),
446
+ startedAt: record.date_submitted,
447
+ depth: nodeDepth(record),
448
+ children: [],
449
+ command: record.data,
450
+ raw: [record]
451
+ });
452
+ var makeClosedNode = (record, interp, id) => ({
453
+ id,
454
+ kind: interp.kind,
455
+ eventClass: record.event_class,
456
+ status: record.status === "error" ? "failed" : "completed",
457
+ title: interp.title(record),
458
+ startedAt: record.date_submitted,
459
+ endedAt: record.date_submitted,
460
+ depth: nodeDepth(record),
461
+ children: [],
462
+ response: record.data,
463
+ error: record.status === "error" ? asString(record.data) : void 0,
464
+ raw: [record]
465
+ });
466
+ function reduceWorklog(state, record) {
467
+ const interp = resolveInterpreter(record.event_class);
468
+ const next = {
469
+ ...state,
470
+ nodes: state.nodes.slice(),
471
+ index: { ...state.index },
472
+ raw: [...state.raw, record]
473
+ };
474
+ if (interp.role === "ignore") return next;
475
+ if (interp.role === "run-open") {
476
+ next.status = "running";
477
+ return next;
478
+ }
479
+ if (interp.role === "run-close") {
480
+ next.answer = asString(record.data) || null;
481
+ next.status = record.status === "error" ? "error" : "success";
482
+ return next;
483
+ }
484
+ const key = record.control_id ?? `${record.event_class}-${next.raw.length}`;
485
+ if (interp.role === "close") {
486
+ const pos2 = Object.prototype.hasOwnProperty.call(next.index, key) ? next.index[key] : void 0;
487
+ if (pos2 != null) {
488
+ const existing = next.nodes[pos2];
489
+ next.nodes[pos2] = {
490
+ ...existing,
491
+ status: record.status === "error" ? "failed" : "completed",
492
+ endedAt: record.date_submitted,
493
+ response: record.data,
494
+ error: record.status === "error" ? asString(record.data) : existing.error,
495
+ raw: [...existing.raw, record]
496
+ };
497
+ } else {
498
+ next.index[key] = next.nodes.length;
499
+ next.nodes.push(makeClosedNode(record, interp, key));
500
+ }
501
+ if (record.status === "error") next.status = "error";
502
+ return next;
503
+ }
504
+ const pos = Object.prototype.hasOwnProperty.call(next.index, key) ? next.index[key] : void 0;
505
+ if (pos != null) {
506
+ const existing = next.nodes[pos];
507
+ next.nodes[pos] = { ...existing, raw: [...existing.raw, record] };
508
+ } else {
509
+ next.index[key] = next.nodes.length;
510
+ next.nodes.push(makeOpenNode(record, interp, key));
511
+ }
512
+ return next;
513
+ }
514
+
515
+ // src/worklog/use-worklog.ts
516
+ var import_react = require("react");
517
+
518
+ // src/worklog/from-frame.ts
519
+ function frameToRecord(frame) {
520
+ const status = frame.status === "success" || frame.status === "error" ? frame.status : null;
521
+ return {
522
+ event_class: typeof frame.event_class === "string" && frame.event_class.length > 0 ? frame.event_class : "Unknown",
523
+ control_id: frame.control_id ?? null,
524
+ data: frame.data,
525
+ status,
526
+ date_submitted: typeof frame.date_submitted === "string" ? frame.date_submitted : ""
527
+ };
528
+ }
529
+
530
+ // src/hooks/pending-client-tool.ts
531
+ function extractPendingClientTool(frame) {
532
+ if (!frame || typeof frame !== "object") return null;
533
+ const f = frame;
534
+ if (f.event_class !== "ClientToolExecution") return null;
535
+ const name = f.data?.name;
536
+ const parameters = f.data?.parameters;
537
+ const controlId = f.control_id;
538
+ if (typeof name !== "string" || parameters === void 0 || typeof controlId !== "string") return null;
539
+ return { controlId, toolName: name, parameters };
540
+ }
541
+
542
+ // src/genui/style.ts
543
+ var import_zod = require("zod");
544
+ var TONES = ["default", "muted", "primary", "success", "warning", "danger"];
545
+ var SIZES = ["sm", "md", "lg"];
546
+ var EMPHASES = ["low", "medium", "high"];
547
+ var ALIGNS = ["start", "center", "end"];
548
+ var DENSITIES = ["compact", "comfortable"];
549
+ var styleSchemaShape = {
550
+ tone: import_zod.z.enum(TONES).optional(),
551
+ size: import_zod.z.enum(SIZES).optional(),
552
+ emphasis: import_zod.z.enum(EMPHASES).optional(),
553
+ align: import_zod.z.enum(ALIGNS).optional(),
554
+ density: import_zod.z.enum(DENSITIES).optional()
555
+ };
556
+
557
+ // src/genui/registry.ts
558
+ function resolveComponent(manifest, name) {
559
+ if (!name || typeof name !== "string") return null;
560
+ return manifest.components[name] ?? null;
561
+ }
562
+
563
+ // src/genui/catalog.ts
564
+ var import_zod2 = require("zod");
565
+ var CORE_CATALOG_ID = "https://alquimia.ai/catalogs/core/v1/catalog.json";
566
+ var def = (name, own, opts) => ({
567
+ name,
568
+ schema: import_zod2.z.object({
569
+ ...styleSchemaShape,
570
+ ...opts?.variant ? { variant: import_zod2.z.enum(opts.variant).optional() } : {},
571
+ ...own
572
+ }).passthrough(),
573
+ allowedActions: opts?.allowedActions
574
+ });
575
+ var option = import_zod2.z.object({ label: import_zod2.z.string(), value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]) });
576
+ var coreCatalog = {
577
+ id: CORE_CATALOG_ID,
578
+ components: {
579
+ // ---- layout ----
580
+ Stack: def("Stack", {
581
+ direction: import_zod2.z.enum(["vertical", "horizontal"]).optional(),
582
+ gap: import_zod2.z.enum(["sm", "md", "lg"]).optional()
583
+ }),
584
+ Grid: def("Grid", {
585
+ columns: import_zod2.z.number().int().min(1).max(6).optional(),
586
+ gap: import_zod2.z.enum(["sm", "md", "lg"]).optional()
587
+ }),
588
+ Row: def("Row", { gap: import_zod2.z.enum(["sm", "md", "lg"]).optional(), wrap: import_zod2.z.boolean().optional() }),
589
+ Column: def("Column", { gap: import_zod2.z.enum(["sm", "md", "lg"]).optional() }),
590
+ Divider: def("Divider", { label: import_zod2.z.string().optional() }),
591
+ Spacer: def("Spacer", { size: import_zod2.z.enum(["sm", "md", "lg"]).optional() }),
592
+ Card: def("Card", { title: import_zod2.z.string().optional(), description: import_zod2.z.string().optional() }, {
593
+ variant: ["elevated", "outline", "ghost"]
594
+ }),
595
+ Accordion: def("Accordion", {
596
+ items: import_zod2.z.array(import_zod2.z.object({ title: import_zod2.z.string(), value: import_zod2.z.string() })).optional()
597
+ }),
598
+ Tabs: def("Tabs", { tabs: import_zod2.z.array(import_zod2.z.object({ label: import_zod2.z.string(), value: import_zod2.z.string() })).min(1) }),
599
+ // ---- content ----
600
+ Text: def("Text", { text: import_zod2.z.string() }, { variant: ["body", "muted", "caption", "lead"] }),
601
+ Heading: def("Heading", { text: import_zod2.z.string(), level: import_zod2.z.number().int().min(1).max(4).optional() }),
602
+ Markdown: def("Markdown", { text: import_zod2.z.string() }),
603
+ Image: def("Image", { src: import_zod2.z.string(), alt: import_zod2.z.string().optional() }),
604
+ Icon: def("Icon", { name: import_zod2.z.string() }),
605
+ Badge: def("Badge", { text: import_zod2.z.string() }, { variant: ["solid", "outline", "subtle"] }),
606
+ Avatar: def("Avatar", { src: import_zod2.z.string().optional(), fallback: import_zod2.z.string().optional() }),
607
+ Alert: def("Alert", { text: import_zod2.z.string(), title: import_zod2.z.string().optional() }, {
608
+ variant: ["info", "success", "warning", "danger"]
609
+ }),
610
+ Stat: def("Stat", { label: import_zod2.z.string(), value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]), delta: import_zod2.z.string().optional() }),
611
+ KeyValue: def("KeyValue", {}),
612
+ List: def("List", { ordered: import_zod2.z.boolean().optional() }),
613
+ Quote: def("Quote", { text: import_zod2.z.string(), cite: import_zod2.z.string().optional() }),
614
+ // ---- inputs ----
615
+ TextField: def("TextField", { label: import_zod2.z.string().optional(), placeholder: import_zod2.z.string().optional() }),
616
+ TextArea: def("TextArea", {
617
+ label: import_zod2.z.string().optional(),
618
+ placeholder: import_zod2.z.string().optional(),
619
+ rows: import_zod2.z.number().int().optional()
620
+ }),
621
+ NumberField: def("NumberField", {
622
+ label: import_zod2.z.string().optional(),
623
+ min: import_zod2.z.number().optional(),
624
+ max: import_zod2.z.number().optional(),
625
+ step: import_zod2.z.number().optional()
626
+ }),
627
+ Select: def("Select", {
628
+ label: import_zod2.z.string().optional(),
629
+ placeholder: import_zod2.z.string().optional(),
630
+ options: import_zod2.z.array(option).min(1)
631
+ }),
632
+ MultiSelect: def("MultiSelect", { label: import_zod2.z.string().optional(), options: import_zod2.z.array(option).min(1) }),
633
+ Combobox: def("Combobox", {
634
+ label: import_zod2.z.string().optional(),
635
+ placeholder: import_zod2.z.string().optional(),
636
+ options: import_zod2.z.array(option).min(1)
637
+ }),
638
+ Checkbox: def("Checkbox", { label: import_zod2.z.string().optional() }),
639
+ CheckboxGroup: def("CheckboxGroup", { label: import_zod2.z.string().optional(), options: import_zod2.z.array(option).min(1) }),
640
+ Radio: def("Radio", { label: import_zod2.z.string().optional(), options: import_zod2.z.array(option).min(1) }),
641
+ Switch: def("Switch", { label: import_zod2.z.string().optional() }),
642
+ Slider: def("Slider", {
643
+ label: import_zod2.z.string().optional(),
644
+ min: import_zod2.z.number().optional(),
645
+ max: import_zod2.z.number().optional(),
646
+ step: import_zod2.z.number().optional()
647
+ }),
648
+ DatePicker: def("DatePicker", { label: import_zod2.z.string().optional() }),
649
+ DateRange: def("DateRange", { label: import_zod2.z.string().optional() }),
650
+ FileUpload: def("FileUpload", {
651
+ label: import_zod2.z.string().optional(),
652
+ accept: import_zod2.z.string().optional(),
653
+ multiple: import_zod2.z.boolean().optional()
654
+ }),
655
+ Rating: def("Rating", { label: import_zod2.z.string().optional(), max: import_zod2.z.number().int().optional() }),
656
+ // ---- data ----
657
+ Table: def("Table", {
658
+ columns: import_zod2.z.array(import_zod2.z.object({ key: import_zod2.z.string(), header: import_zod2.z.string().optional() })).min(1),
659
+ sortable: import_zod2.z.boolean().optional()
660
+ }, { allowedActions: ["sort", "filter"] }),
661
+ Chart: def("Chart", {
662
+ kind: import_zod2.z.enum(["bar", "line", "area", "pie"]),
663
+ x: import_zod2.z.string().optional(),
664
+ series: import_zod2.z.array(import_zod2.z.object({ key: import_zod2.z.string(), label: import_zod2.z.string().optional() })).min(1),
665
+ title: import_zod2.z.string().optional()
666
+ }),
667
+ Progress: def("Progress", { value: import_zod2.z.number(), max: import_zod2.z.number().optional(), label: import_zod2.z.string().optional() }),
668
+ Timeline: def("Timeline", {
669
+ items: import_zod2.z.array(import_zod2.z.object({ title: import_zod2.z.string(), description: import_zod2.z.string().optional(), timestamp: import_zod2.z.string().optional() })).min(1)
670
+ }),
671
+ // ---- actions ----
672
+ Button: def("Button", { text: import_zod2.z.string() }, {
673
+ variant: ["solid", "outline", "ghost", "link"],
674
+ allowedActions: ["submit", "cancel", "reset", "select", "confirm"]
675
+ }),
676
+ ButtonGroup: def("ButtonGroup", {}),
677
+ Link: def("Link", { text: import_zod2.z.string(), href: import_zod2.z.string().optional() })
678
+ }
679
+ };
680
+
681
+ // src/genui/actions.ts
682
+ var URL_LIKE = /^\s*([a-z][a-z0-9+.-]*:|\/\/)/i;
683
+ function assertSafeActionName(name) {
684
+ if (typeof name !== "string" || name.length === 0) {
685
+ throw new Error("action.name must be a non-empty string");
686
+ }
687
+ if (URL_LIKE.test(name)) {
688
+ throw new Error(`action.name must be a semantic name, not a URL: ${name}`);
689
+ }
690
+ }
691
+
692
+ // src/genui/validate.ts
693
+ var import_zod3 = require("zod");
694
+ var surfaceShape = import_zod3.z.object({
695
+ surfaceId: import_zod3.z.string().min(1),
696
+ // Tier 2 canonical id (versioned URI); `catalog` is the legacy alias. Both optional on the
697
+ // wire — the SDK stamps `catalogId` if the agent omits it.
698
+ catalogId: import_zod3.z.string().min(1).optional(),
699
+ catalog: import_zod3.z.string().min(1).optional(),
700
+ // Legacy explicit root. A2UI uses the component with id "root"; kept optional for back-compat.
701
+ root: import_zod3.z.string().min(1).optional(),
702
+ components: import_zod3.z.array(
703
+ import_zod3.z.object({
704
+ id: import_zod3.z.string().min(1),
705
+ component: import_zod3.z.string().min(1),
706
+ children: import_zod3.z.array(import_zod3.z.string()).optional(),
707
+ child: import_zod3.z.string().optional(),
708
+ binding: import_zod3.z.string().optional(),
709
+ action: import_zod3.z.object({ name: import_zod3.z.string(), wantResponse: import_zod3.z.boolean().optional() }).optional()
710
+ }).passthrough()
711
+ ),
712
+ dataModel: import_zod3.z.record(import_zod3.z.unknown()).optional()
713
+ });
714
+ var UAX31_ID = /^[\p{XID_Start}_][\p{XID_Continue}]*$/u;
715
+ function validateProps(node, def2) {
716
+ const { id, component, children, child, binding, action, ...props } = node;
717
+ const parsed = def2.schema.safeParse(props);
718
+ if (parsed.success) return { ok: true, value: parsed.data, errors: [] };
719
+ return {
720
+ ok: false,
721
+ errors: parsed.error.issues.map((i) => ({ path: `${id}.${i.path.join(".")}`, message: i.message }))
722
+ };
723
+ }
724
+ function validateSurface(input, manifest) {
725
+ const shape = surfaceShape.safeParse(input);
726
+ if (!shape.success) {
727
+ return { ok: false, errors: shape.error.issues.map((i) => ({ path: i.path.join("."), message: i.message })) };
728
+ }
729
+ const surface = shape.data;
730
+ const errors = [];
731
+ const warnings = [];
732
+ const ids = new Set(surface.components.map((c) => c.id));
733
+ const rootId = ids.has("root") ? "root" : surface.root;
734
+ if (!rootId || !ids.has(rootId)) {
735
+ errors.push({ path: "root", message: `no root component (expected a component with id "root")` });
736
+ }
737
+ if (!surface.catalogId && !surface.catalog) {
738
+ warnings.push({ path: "catalogId", message: "surface has no catalogId (SDK will stamp the active catalog)" });
739
+ }
740
+ for (const node of surface.components) {
741
+ if (!UAX31_ID.test(node.id)) {
742
+ warnings.push({ path: node.id, message: `id "${node.id}" is not a valid UAX#31 identifier` });
743
+ }
744
+ const def2 = resolveComponent(manifest, node.component);
745
+ if (!def2) {
746
+ warnings.push({ path: node.id, message: `unknown component "${node.component}" (renders as text)` });
747
+ continue;
748
+ }
749
+ warnings.push(...validateProps(node, def2).errors);
750
+ for (const childId of node.children ?? []) {
751
+ if (!ids.has(childId)) {
752
+ warnings.push({ path: node.id, message: `child "${childId}" does not exist (skipped)` });
753
+ }
754
+ }
755
+ if (node.child && !ids.has(node.child)) {
756
+ warnings.push({ path: node.id, message: `child "${node.child}" does not exist (skipped)` });
757
+ }
758
+ if (node.action) {
759
+ try {
760
+ assertSafeActionName(node.action.name);
761
+ } catch (e) {
762
+ errors.push({ path: `${node.id}.action`, message: e.message });
763
+ }
764
+ }
765
+ }
766
+ return errors.length === 0 ? { ok: true, value: surface, errors: [], warnings } : { ok: false, errors, warnings };
767
+ }
768
+
769
+ // src/genui/tool-schema.ts
770
+ var DESCRIPTION = 'Render a UI using A2UI. The arguments are a UI surface, NOT free-form. Shape: { surfaceId, root, components, dataModel? }. `components` is a FLAT array; every item is { id, component, children?, ...props } where `component` is one of the allowed names (use `component`, never `type`) and `children` is an array of string ids referencing other items (never nested objects). Inputs bind to the data model via `binding` (a JSON Pointer like "/name"). To collect input, include a Button with action { name, wantResponse: true } \u2014 its result (the data model) is returned to you; a surface with no such button is display-only. Example: { "surfaceId": "signup", "root": "root", "components": [ { "id": "root", "component": "Stack", "children": ["name", "desc", "submit"] }, { "id": "name", "component": "TextField", "label": "Username", "binding": "/name" }, { "id": "desc", "component": "TextField", "label": "Description", "binding": "/description" }, { "id": "submit", "component": "Button", "text": "Save", "action": { "name": "save", "wantResponse": true } } ], "dataModel": { "name": "", "description": "" } }. IMPORTANT: when you call render_ui, the rendered surface IS your reply \u2014 do NOT also reproduce its content as text, markdown, or ASCII. Any accompanying message must be a brief one-line intro at most, never a duplicate of the UI.';
771
+ function buildRenderUiSchema(manifest, allowlist) {
772
+ const allowed = allowlist.filter((name) => name in manifest.components);
773
+ return {
774
+ title: "render_ui",
775
+ name: "render_ui",
776
+ description: DESCRIPTION,
777
+ type: "object",
778
+ properties: {
779
+ surfaceId: { type: "string" },
780
+ // A2UI catalog id — a versioned URI. `const` nudges the model to echo it; the SDK
781
+ // also stamps it if omitted, so the client always knows which catalog version to render.
782
+ catalogId: { type: "string", const: manifest.id },
783
+ catalog: { type: "string" },
784
+ // legacy alias (accepted, ignored)
785
+ root: { type: "string" },
786
+ // legacy explicit root (accepted; A2UI uses the id:"root" component)
787
+ components: {
788
+ type: "array",
789
+ items: {
790
+ type: "object",
791
+ properties: {
792
+ id: { type: "string" },
793
+ component: { type: "string", enum: allowed },
794
+ children: { type: "array", items: { type: "string" } },
795
+ child: { type: "string" },
796
+ binding: { type: "string" },
797
+ action: {
798
+ type: "object",
799
+ properties: {
800
+ name: { type: "string" },
801
+ wantResponse: { type: "boolean" }
802
+ },
803
+ required: ["name"]
804
+ },
805
+ tone: { type: "string", enum: [...TONES] },
806
+ size: { type: "string", enum: [...SIZES] },
807
+ emphasis: { type: "string", enum: [...EMPHASES] },
808
+ align: { type: "string", enum: [...ALIGNS] },
809
+ density: { type: "string", enum: [...DENSITIES] },
810
+ variant: { type: "string" },
811
+ text: { type: "string" },
812
+ label: { type: "string" },
813
+ placeholder: { type: "string" },
814
+ title: { type: "string" },
815
+ direction: { type: "string" },
816
+ gap: { type: "string" },
817
+ // Data props for data components. Several are legitimately polymorphic across
818
+ // components — type them as their real unions so the runtime doesn't reject
819
+ // valid output (adapters normalize each shape):
820
+ // data: array (Chart/Table rows) | object (KeyValue)
821
+ // rows: array (Table) | number (TextArea row count)
822
+ // columns: array (Table col defs) | number (Grid column count)
823
+ data: { type: ["array", "object"] },
824
+ rows: { type: ["array", "number"] },
825
+ items: { type: "array" },
826
+ // Accept string OR object items — models routinely emit the terse string
827
+ // form (e.g. series:["revenue"], options:["A","B"]); adapters normalize.
828
+ columns: { type: ["array", "number"], items: { type: ["object", "string"] } },
829
+ series: { type: "array", items: { type: ["object", "string"] } },
830
+ options: { type: "array", items: { type: ["object", "string"] } },
831
+ kind: { type: "string" },
832
+ x: { type: "string" },
833
+ value: {}
834
+ },
835
+ required: ["id", "component"]
836
+ }
837
+ },
838
+ dataModel: { type: "object" }
839
+ },
840
+ required: ["surfaceId", "root", "components"],
841
+ additionalProperties: false
842
+ };
843
+ }
844
+
845
+ // src/genui/clause.ts
846
+ function buildGenuiClause(manifest, allowlist) {
847
+ const names = allowlist.filter((n) => n in manifest.components);
848
+ return [
849
+ "You can render UI by calling render_ui with an A2UI surface (a flat components array; each item is { id, component, children?, ...props }; component is one of the allowed names; children are string ids referencing other items).",
850
+ `Components: ${names.join(", ")}.`,
851
+ `Style each component via the semantic vocabulary (never CSS, colors, or HTML): tone (${TONES.join("/")}), size (${SIZES.join("/")}), emphasis (low/medium/high), align (start/center/end), density (compact/comfortable), plus a component-specific variant where offered.`,
852
+ "Data components MUST carry their data: Chart takes `data` (an array of row objects) + `series` (keys to plot) + `x` (the category key); Table takes `rows` (array of row objects) + `columns` ([{ key, header }] matching the row keys); List takes `items` (array); Stat takes `label`+`value`; KeyValue takes `data` (an object). Put the data inline on the component (or in dataModel referenced via `binding`).",
853
+ "To collect input, add a Button with an action { name, wantResponse: true }; its result (the data model) is returned to you. A surface with no such button is display-only.",
854
+ "IMPORTANT: the rendered surface IS your reply \u2014 do NOT reproduce its content as text, markdown, or ASCII; a one-line intro at most.",
855
+ "Prefer plain text for simple replies."
856
+ ].join(" ");
857
+ }
858
+
859
+ // src/genui/emit.ts
860
+ var import_zod_to_json_schema = require("zod-to-json-schema");
340
861
 
341
862
  // src/hooks/alquimia.hook.tsx
342
863
  function useAlquimia(config) {
343
- const sdk = (0, import_react.useMemo)(() => {
864
+ const sdk = (0, import_react2.useMemo)(() => {
344
865
  const instance = new alquimia_sdk_default(config.assistantId, config.adapter, {
345
866
  enforceCharacterization: config.options?.enforceCharacterization
346
867
  });
@@ -351,23 +872,53 @@ function useAlquimia(config) {
351
872
  if (config.providers?.logger) instance.withLoggerProvider(config.providers.logger);
352
873
  if (config.options?.userId) instance.withUserId(config.options.userId);
353
874
  if (config.options?.extraInstructions) instance.withExtraInstructions(config.options.extraInstructions);
875
+ if (config.genui && (config.genui.source ?? "client") === "client") {
876
+ const cat = config.genui.catalog ?? coreCatalog;
877
+ const allow = resolveGenuiAllow(cat, config.genui.allow);
878
+ instance.withTools([buildRenderUiSchema(cat, allow)]);
879
+ instance.withExtraInstructions({
880
+ ...config.options?.extraInstructions ?? {},
881
+ genui: buildGenuiClause(cat, allow)
882
+ });
883
+ }
354
884
  return instance;
355
885
  }, [config.assistantId, config.adapter]);
356
- const [input, setInput] = (0, import_react.useState)("");
357
- const [isMessageLoading, setIsMessageLoading] = (0, import_react.useState)(false);
358
- const [isStreamingLoading, setIsStreamingLoading] = (0, import_react.useState)(false);
359
- const [isMessageStreaming, setIsMessageStreaming] = (0, import_react.useState)(false);
360
- const [streamingMessageId, setStreamingMessageId] = (0, import_react.useState)(null);
361
- const [isAudioRecording, setIsAudioRecording] = (0, import_react.useState)(false);
362
- const [messages, setMessages] = (0, import_react.useState)([]);
363
- const eventSourceRef = (0, import_react.useRef)(null);
364
- const streamUserCancelledRef = (0, import_react.useRef)(false);
365
- const [sessionId, setSessionId] = (0, import_react.useState)(null);
366
- const [activeTool, setActiveTool] = (0, import_react.useState)(null);
367
- const [lastRequest, setLastRequest] = (0, import_react.useState)(null);
368
- const [hasThinkings, setHasThinkings] = (0, import_react.useState)(false);
369
- const [attachments, setAttachments] = (0, import_react.useState)([]);
370
- const [isUploadingAttachments, setIsUploadingAttachments] = (0, import_react.useState)(false);
886
+ const [input, setInput] = (0, import_react2.useState)("");
887
+ const [isMessageLoading, setIsMessageLoading] = (0, import_react2.useState)(false);
888
+ const [isStreamingLoading, setIsStreamingLoading] = (0, import_react2.useState)(false);
889
+ const [isMessageStreaming, setIsMessageStreaming] = (0, import_react2.useState)(false);
890
+ const [streamingMessageId, setStreamingMessageId] = (0, import_react2.useState)(null);
891
+ const [isAudioRecording, setIsAudioRecording] = (0, import_react2.useState)(false);
892
+ const [messages, setMessages] = (0, import_react2.useState)([]);
893
+ const eventSourceRef = (0, import_react2.useRef)(null);
894
+ const streamUserCancelledRef = (0, import_react2.useRef)(false);
895
+ const [sessionId, setSessionId] = (0, import_react2.useState)(null);
896
+ const [activeTool, setActiveTool] = (0, import_react2.useState)(null);
897
+ const [lastRequest, setLastRequest] = (0, import_react2.useState)(null);
898
+ const [hasThinkings, setHasThinkings] = (0, import_react2.useState)(false);
899
+ const worklogEnabled = config.options?.worklog ?? false;
900
+ const [worklog, setWorklog] = (0, import_react2.useState)(
901
+ worklogEnabled ? initialWorklogState() : void 0
902
+ );
903
+ const [attachments, setAttachments] = (0, import_react2.useState)([]);
904
+ const [isUploadingAttachments, setIsUploadingAttachments] = (0, import_react2.useState)(false);
905
+ const [pendingClientTool, setPendingClientTool] = (0, import_react2.useState)(null);
906
+ const completedToolsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
907
+ const genuiCatalog = config.genui?.catalog ?? coreCatalog;
908
+ const [genuiSurfaces, setGenuiSurfaces] = (0, import_react2.useState)([]);
909
+ const messagesLenRef = (0, import_react2.useRef)(0);
910
+ messagesLenRef.current = messages.length;
911
+ const genuiAnchorRef = (0, import_react2.useRef)(0);
912
+ {
913
+ let anchor = messages.length;
914
+ for (let i = messages.length - 1; i >= 0; i--) {
915
+ if (messages[i].role === "user") {
916
+ anchor = i + 1;
917
+ break;
918
+ }
919
+ }
920
+ genuiAnchorRef.current = anchor;
921
+ }
371
922
  const isLoading = isMessageLoading || isStreamingLoading || isUploadingAttachments;
372
923
  function cleanMessages() {
373
924
  setMessages([]);
@@ -571,6 +1122,7 @@ function useAlquimia(config) {
571
1122
  lastMessageAt: null,
572
1123
  closedByClient: false
573
1124
  };
1125
+ if (worklogEnabled) setWorklog(initialWorklogState());
574
1126
  const eventSource = new EventSource(streamUrl);
575
1127
  eventSourceRef.current = eventSource;
576
1128
  eventSource.onopen = () => {
@@ -631,6 +1183,14 @@ function useAlquimia(config) {
631
1183
  }
632
1184
  function handleMessageEvent(event, callBack, eventSource, streamState) {
633
1185
  const data = JSON.parse(event.data);
1186
+ if (worklogEnabled) {
1187
+ setWorklog((prev) => reduceWorklog(prev ?? initialWorklogState(), frameToRecord(data)));
1188
+ }
1189
+ const pending = extractPendingClientTool(data);
1190
+ if (pending) {
1191
+ setPendingClientTool(pending);
1192
+ return;
1193
+ }
634
1194
  const eventTaskId = data.taskid || "";
635
1195
  if (data.errors?.length) {
636
1196
  for (const error of data.errors) {
@@ -660,9 +1220,77 @@ function useAlquimia(config) {
660
1220
  cleanup(false, eventSource);
661
1221
  }
662
1222
  }
1223
+ const completeToolExecution = (0, import_react2.useCallback)(
1224
+ async (controlId, result) => {
1225
+ if (completedToolsRef.current.has(controlId)) return;
1226
+ completedToolsRef.current.add(controlId);
1227
+ try {
1228
+ await sdk.submitToolResult({ ...result, control_id: controlId });
1229
+ setPendingClientTool((cur) => cur?.controlId === controlId ? null : cur);
1230
+ } catch (err) {
1231
+ completedToolsRef.current.delete(controlId);
1232
+ throw err;
1233
+ }
1234
+ },
1235
+ [sdk]
1236
+ );
1237
+ (0, import_react2.useEffect)(() => {
1238
+ if (!config.genui) return;
1239
+ if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
1240
+ const { controlId } = pendingClientTool;
1241
+ const parameters = pendingClientTool.parameters && typeof pendingClientTool.parameters === "object" ? { catalogId: genuiCatalog.id, ...pendingClientTool.parameters } : pendingClientTool.parameters;
1242
+ const res = validateSurface(parameters, genuiCatalog);
1243
+ if (!res.ok || !res.value) {
1244
+ void completeToolExecution(controlId, {
1245
+ control_id: controlId,
1246
+ tool_name: "render_ui",
1247
+ status: "error",
1248
+ message: "Surface failed client validation: " + res.errors.map((e) => `${e.path} ${e.message}`).join("; "),
1249
+ result: { errors: res.errors }
1250
+ });
1251
+ return;
1252
+ }
1253
+ const surface = res.value;
1254
+ setGenuiSurfaces(
1255
+ (prev) => prev.some((s) => s.controlId === controlId) ? prev : [...prev, { controlId, surface, afterCount: genuiAnchorRef.current }]
1256
+ );
1257
+ const interactive = surface.components.some((c) => c.action?.wantResponse === true);
1258
+ if (!interactive) {
1259
+ void completeToolExecution(controlId, {
1260
+ control_id: controlId,
1261
+ tool_name: "render_ui",
1262
+ status: "success",
1263
+ result: {
1264
+ shown: true,
1265
+ instruction: "The UI is rendered and visible to the user. Do NOT restate it as text/markdown/ASCII; reply with at most one short sentence."
1266
+ }
1267
+ });
1268
+ }
1269
+ }, [pendingClientTool, completeToolExecution, config.genui]);
1270
+ const genuiOnAgentAction = (0, import_react2.useCallback)(
1271
+ (controlId) => (action) => {
1272
+ setGenuiSurfaces(
1273
+ (prev) => prev.map((s) => s.controlId === controlId ? { ...s, submitted: true } : s)
1274
+ );
1275
+ void completeToolExecution(controlId, {
1276
+ control_id: controlId,
1277
+ tool_name: "render_ui",
1278
+ status: "success",
1279
+ result: { action: action.action, surfaceId: action.surfaceId, data: action.payload }
1280
+ });
1281
+ },
1282
+ [completeToolExecution]
1283
+ );
1284
+ const genuiAwaitingUser = !!(pendingClientTool && pendingClientTool.toolName === "render_ui" && genuiSurfaces.some(
1285
+ (s) => s.controlId === pendingClientTool.controlId && s.surface.components.some((c) => c.action?.wantResponse === true)
1286
+ ));
1287
+ const genui = config.genui ? { surfaces: genuiSurfaces, onAgentAction: genuiOnAgentAction, awaitingUser: genuiAwaitingUser } : void 0;
663
1288
  return {
664
1289
  sdk,
665
1290
  activeTool,
1291
+ pendingClientTool,
1292
+ completeToolExecution,
1293
+ genui,
666
1294
  cleanMessages,
667
1295
  createMessageId,
668
1296
  handleInputChange,
@@ -688,6 +1316,7 @@ function useAlquimia(config) {
688
1316
  setLastRequest,
689
1317
  setIsAudioRecording,
690
1318
  hasThinkings,
1319
+ worklog,
691
1320
  attachments,
692
1321
  addAttachment,
693
1322
  addAttachments,
@@ -698,7 +1327,7 @@ function useAlquimia(config) {
698
1327
  }
699
1328
 
700
1329
  // src/hooks/useRatings.ts
701
- var import_react2 = require("react");
1330
+ var import_react3 = require("react");
702
1331
  var initialState = {
703
1332
  ratingStars: 0,
704
1333
  ratingThumbs: "",
@@ -717,9 +1346,9 @@ function ratingReducer(state, action) {
717
1346
  }
718
1347
  }
719
1348
  function useRatings({ assistantId, sendRating, topicId }) {
720
- const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
721
- const [hasReviewed, setHasReviewed] = (0, import_react2.useState)(false);
722
- const [state, dispatch] = (0, import_react2.useReducer)(ratingReducer, initialState);
1349
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
1350
+ const [hasReviewed, setHasReviewed] = (0, import_react3.useState)(false);
1351
+ const [state, dispatch] = (0, import_react3.useReducer)(ratingReducer, initialState);
723
1352
  const setRatingStars = (rating) => {
724
1353
  dispatch({ type: "SET_RATING_STARS", payload: rating });
725
1354
  };