@alquimia-ai/tools 2.2.0 → 2.4.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/actions/index.d.mts +1 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/index.mjs.map +1 -1
- package/dist/actions-B0hfoYRd.d.ts +57 -0
- package/dist/actions-CZU_GRy5.d.mts +57 -0
- package/dist/adapters/fetch.d.mts +1 -0
- package/dist/adapters/fetch.d.ts +1 -0
- package/dist/adapters/fetch.js +4 -0
- package/dist/adapters/fetch.js.map +1 -1
- package/dist/adapters/fetch.mjs +4 -0
- package/dist/adapters/fetch.mjs.map +1 -1
- package/dist/adapters/index.d.mts +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/next.d.mts +1 -0
- package/dist/adapters/next.d.ts +1 -0
- package/dist/adapters/next.js +4 -0
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/next.mjs +4 -0
- package/dist/adapters/next.mjs.map +1 -1
- package/dist/genui/index.d.mts +3027 -0
- package/dist/genui/index.d.ts +3027 -0
- package/dist/genui/index.js +3368 -0
- package/dist/genui/index.js.map +1 -0
- package/dist/genui/index.mjs +3345 -0
- package/dist/genui/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +25 -2
- package/dist/hooks/index.d.ts +25 -2
- package/dist/hooks/index.js +510 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +511 -1
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/next/index.d.mts +7 -0
- package/dist/next/index.d.ts +7 -0
- package/dist/next/index.js +50 -0
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +50 -0
- package/dist/next/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +3 -2
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs.map +1 -1
- package/dist/proxy.d.mts +2 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +41 -0
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +41 -0
- package/dist/proxy.mjs.map +1 -1
- package/dist/registry-Bcu_ZYL1.d.mts +19 -0
- package/dist/registry-Bcu_ZYL1.d.ts +19 -0
- package/dist/sdk/index.d.mts +10 -0
- package/dist/sdk/index.d.ts +10 -0
- package/dist/sdk/index.js +26 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/index.mjs +26 -0
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/services/index.d.mts +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/types/index.d.mts +12 -1
- package/dist/types/index.d.ts +12 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/types-DnfWUE9V.d.mts +56 -0
- package/dist/types-DnfWUE9V.d.ts +56 -0
- package/dist/{types-CIqszY3g.d.mts → types-Dvu9y4WM.d.mts} +1 -1
- package/dist/{types-CIqszY3g.d.ts → types-Dvu9y4WM.d.ts} +1 -1
- package/dist/utils/index.d.mts +6 -2
- package/dist/utils/index.d.ts +6 -2
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +4 -0
- package/dist/utils/index.mjs.map +1 -1
- package/dist/worklog/index.d.mts +2 -2
- package/dist/worklog/index.d.ts +2 -2
- package/package.json +19 -7
package/dist/hooks/index.js
CHANGED
|
@@ -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,6 +363,9 @@ 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
|
+
}
|
|
340
369
|
|
|
341
370
|
// src/worklog/types.ts
|
|
342
371
|
function initialWorklogState(taskId = null) {
|
|
@@ -498,6 +527,359 @@ function frameToRecord(frame) {
|
|
|
498
527
|
};
|
|
499
528
|
}
|
|
500
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 DISMISS_ACTION = "userDismissedSurface";
|
|
683
|
+
function buildDismissResult(surfaceId, userMessage) {
|
|
684
|
+
const said = typeof userMessage === "string" ? userMessage.trim() : "";
|
|
685
|
+
return {
|
|
686
|
+
action: DISMISS_ACTION,
|
|
687
|
+
surfaceId,
|
|
688
|
+
componentId: "",
|
|
689
|
+
data: {},
|
|
690
|
+
...said ? { userMessage: said } : {}
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function buildSurfaceResult(action) {
|
|
694
|
+
return {
|
|
695
|
+
action: action.action,
|
|
696
|
+
surfaceId: action.surfaceId,
|
|
697
|
+
componentId: action.componentId,
|
|
698
|
+
data: action.payload ?? {}
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
var URL_LIKE = /^\s*([a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
702
|
+
function assertSafeActionName(name) {
|
|
703
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
704
|
+
throw new Error("action.name must be a non-empty string");
|
|
705
|
+
}
|
|
706
|
+
if (URL_LIKE.test(name)) {
|
|
707
|
+
throw new Error(`action.name must be a semantic name, not a URL: ${name}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/genui/validate.ts
|
|
712
|
+
var import_zod3 = require("zod");
|
|
713
|
+
var surfaceShape = import_zod3.z.object({
|
|
714
|
+
surfaceId: import_zod3.z.string().min(1),
|
|
715
|
+
// Tier 2 canonical id (versioned URI); `catalog` is the legacy alias. Both optional on the
|
|
716
|
+
// wire — the SDK stamps `catalogId` if the agent omits it.
|
|
717
|
+
catalogId: import_zod3.z.string().min(1).optional(),
|
|
718
|
+
catalog: import_zod3.z.string().min(1).optional(),
|
|
719
|
+
// Legacy explicit root. A2UI uses the component with id "root"; kept optional for back-compat.
|
|
720
|
+
root: import_zod3.z.string().min(1).optional(),
|
|
721
|
+
components: import_zod3.z.array(
|
|
722
|
+
import_zod3.z.object({
|
|
723
|
+
id: import_zod3.z.string().min(1),
|
|
724
|
+
component: import_zod3.z.string().min(1),
|
|
725
|
+
children: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
726
|
+
child: import_zod3.z.string().optional(),
|
|
727
|
+
binding: import_zod3.z.string().optional(),
|
|
728
|
+
action: import_zod3.z.object({ name: import_zod3.z.string(), wantResponse: import_zod3.z.boolean().optional() }).optional()
|
|
729
|
+
}).passthrough()
|
|
730
|
+
),
|
|
731
|
+
dataModel: import_zod3.z.record(import_zod3.z.unknown()).optional()
|
|
732
|
+
});
|
|
733
|
+
var UAX31_ID = /^[\p{XID_Start}_][\p{XID_Continue}]*$/u;
|
|
734
|
+
function validateProps(node, def2) {
|
|
735
|
+
const { id, component, children, child, binding, action, ...props } = node;
|
|
736
|
+
const parsed = def2.schema.safeParse(props);
|
|
737
|
+
if (parsed.success) return { ok: true, value: parsed.data, errors: [] };
|
|
738
|
+
return {
|
|
739
|
+
ok: false,
|
|
740
|
+
errors: parsed.error.issues.map((i) => ({ path: `${id}.${i.path.join(".")}`, message: i.message }))
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function validateSurface(input, manifest) {
|
|
744
|
+
const shape = surfaceShape.safeParse(input);
|
|
745
|
+
if (!shape.success) {
|
|
746
|
+
return { ok: false, errors: shape.error.issues.map((i) => ({ path: i.path.join("."), message: i.message })) };
|
|
747
|
+
}
|
|
748
|
+
const surface = shape.data;
|
|
749
|
+
const errors = [];
|
|
750
|
+
const warnings = [];
|
|
751
|
+
const ids = new Set(surface.components.map((c) => c.id));
|
|
752
|
+
const rootId = ids.has("root") ? "root" : surface.root;
|
|
753
|
+
if (!rootId || !ids.has(rootId)) {
|
|
754
|
+
errors.push({ path: "root", message: `no root component (expected a component with id "root")` });
|
|
755
|
+
}
|
|
756
|
+
if (!surface.catalogId && !surface.catalog) {
|
|
757
|
+
warnings.push({ path: "catalogId", message: "surface has no catalogId (SDK will stamp the active catalog)" });
|
|
758
|
+
}
|
|
759
|
+
for (const node of surface.components) {
|
|
760
|
+
if (!UAX31_ID.test(node.id)) {
|
|
761
|
+
warnings.push({ path: node.id, message: `id "${node.id}" is not a valid UAX#31 identifier` });
|
|
762
|
+
}
|
|
763
|
+
const def2 = resolveComponent(manifest, node.component);
|
|
764
|
+
if (!def2) {
|
|
765
|
+
warnings.push({ path: node.id, message: `unknown component "${node.component}" (renders as text)` });
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
warnings.push(...validateProps(node, def2).errors);
|
|
769
|
+
for (const childId of node.children ?? []) {
|
|
770
|
+
if (!ids.has(childId)) {
|
|
771
|
+
warnings.push({ path: node.id, message: `child "${childId}" does not exist (skipped)` });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (node.child && !ids.has(node.child)) {
|
|
775
|
+
warnings.push({ path: node.id, message: `child "${node.child}" does not exist (skipped)` });
|
|
776
|
+
}
|
|
777
|
+
if (node.action) {
|
|
778
|
+
try {
|
|
779
|
+
assertSafeActionName(node.action.name);
|
|
780
|
+
} catch (e) {
|
|
781
|
+
errors.push({ path: `${node.id}.action`, message: e.message });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return errors.length === 0 ? { ok: true, value: surface, errors: [], warnings } : { ok: false, errors, warnings };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/genui/tool-schema.ts
|
|
789
|
+
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.';
|
|
790
|
+
function buildRenderUiSchema(manifest, allowlist) {
|
|
791
|
+
const allowed = allowlist.filter((name) => name in manifest.components);
|
|
792
|
+
return {
|
|
793
|
+
title: "render_ui",
|
|
794
|
+
name: "render_ui",
|
|
795
|
+
description: DESCRIPTION,
|
|
796
|
+
type: "object",
|
|
797
|
+
properties: {
|
|
798
|
+
surfaceId: { type: "string" },
|
|
799
|
+
// A2UI catalog id — a versioned URI. `const` nudges the model to echo it; the SDK
|
|
800
|
+
// also stamps it if omitted, so the client always knows which catalog version to render.
|
|
801
|
+
catalogId: { type: "string", const: manifest.id },
|
|
802
|
+
catalog: { type: "string" },
|
|
803
|
+
// legacy alias (accepted, ignored)
|
|
804
|
+
root: { type: "string" },
|
|
805
|
+
// legacy explicit root (accepted; A2UI uses the id:"root" component)
|
|
806
|
+
components: {
|
|
807
|
+
type: "array",
|
|
808
|
+
items: {
|
|
809
|
+
type: "object",
|
|
810
|
+
properties: {
|
|
811
|
+
id: { type: "string" },
|
|
812
|
+
component: { type: "string", enum: allowed },
|
|
813
|
+
children: { type: "array", items: { type: "string" } },
|
|
814
|
+
child: { type: "string" },
|
|
815
|
+
binding: { type: "string" },
|
|
816
|
+
action: {
|
|
817
|
+
type: "object",
|
|
818
|
+
properties: {
|
|
819
|
+
name: { type: "string" },
|
|
820
|
+
wantResponse: { type: "boolean" }
|
|
821
|
+
},
|
|
822
|
+
required: ["name"]
|
|
823
|
+
},
|
|
824
|
+
tone: { type: "string", enum: [...TONES] },
|
|
825
|
+
size: { type: "string", enum: [...SIZES] },
|
|
826
|
+
emphasis: { type: "string", enum: [...EMPHASES] },
|
|
827
|
+
align: { type: "string", enum: [...ALIGNS] },
|
|
828
|
+
density: { type: "string", enum: [...DENSITIES] },
|
|
829
|
+
variant: { type: "string" },
|
|
830
|
+
text: { type: "string" },
|
|
831
|
+
label: { type: "string" },
|
|
832
|
+
placeholder: { type: "string" },
|
|
833
|
+
title: { type: "string" },
|
|
834
|
+
direction: { type: "string" },
|
|
835
|
+
gap: { type: "string" },
|
|
836
|
+
// Data props for data components. Several are legitimately polymorphic across
|
|
837
|
+
// components — type them as their real unions so the runtime doesn't reject
|
|
838
|
+
// valid output (adapters normalize each shape):
|
|
839
|
+
// data: array (Chart/Table rows) | object (KeyValue)
|
|
840
|
+
// rows: array (Table) | number (TextArea row count)
|
|
841
|
+
// columns: array (Table col defs) | number (Grid column count)
|
|
842
|
+
data: { type: ["array", "object"] },
|
|
843
|
+
rows: { type: ["array", "number"] },
|
|
844
|
+
items: { type: "array" },
|
|
845
|
+
// Accept string OR object items — models routinely emit the terse string
|
|
846
|
+
// form (e.g. series:["revenue"], options:["A","B"]); adapters normalize.
|
|
847
|
+
columns: { type: ["array", "number"], items: { type: ["object", "string"] } },
|
|
848
|
+
series: { type: "array", items: { type: ["object", "string"] } },
|
|
849
|
+
options: { type: "array", items: { type: ["object", "string"] } },
|
|
850
|
+
kind: { type: "string" },
|
|
851
|
+
x: { type: "string" },
|
|
852
|
+
value: {}
|
|
853
|
+
},
|
|
854
|
+
required: ["id", "component"]
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
dataModel: { type: "object" }
|
|
858
|
+
},
|
|
859
|
+
required: ["surfaceId", "root", "components"],
|
|
860
|
+
additionalProperties: false
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/genui/clause.ts
|
|
865
|
+
function buildGenuiClause(manifest, allowlist) {
|
|
866
|
+
const names = allowlist.filter((n) => n in manifest.components);
|
|
867
|
+
return [
|
|
868
|
+
"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).",
|
|
869
|
+
`Components: ${names.join(", ")}.`,
|
|
870
|
+
`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.`,
|
|
871
|
+
"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`).",
|
|
872
|
+
"To collect input, add a Button with an action { name, wantResponse: true }. You get back { action, surfaceId, componentId, data } \u2014 `data` is the collected data model and `componentId` is the id of the component that was acted on. A surface with no such button is display-only.",
|
|
873
|
+
"When you render several options to choose from (a grid of cards, each with its own button), give every option component a MEANINGFUL id (e.g. flight-NH107, hotel-memmo-alfama): `componentId` tells you which one the user picked, so never ask them to repeat a choice they already made.",
|
|
874
|
+
'The user may ignore an interactive surface and just type instead. You then receive action "userDismissedSurface" with their words in `userMessage` \u2014 treat it as their reply, answer it directly, and do not re-render the same surface or scold them for not using it.',
|
|
875
|
+
"IMPORTANT: the rendered surface IS your reply \u2014 do NOT reproduce its content as text, markdown, or ASCII; a one-line intro at most.",
|
|
876
|
+
"Prefer plain text for simple replies."
|
|
877
|
+
].join(" ");
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/genui/emit.ts
|
|
881
|
+
var import_zod_to_json_schema = require("zod-to-json-schema");
|
|
882
|
+
|
|
501
883
|
// src/hooks/alquimia.hook.tsx
|
|
502
884
|
function useAlquimia(config) {
|
|
503
885
|
const sdk = (0, import_react2.useMemo)(() => {
|
|
@@ -511,6 +893,15 @@ function useAlquimia(config) {
|
|
|
511
893
|
if (config.providers?.logger) instance.withLoggerProvider(config.providers.logger);
|
|
512
894
|
if (config.options?.userId) instance.withUserId(config.options.userId);
|
|
513
895
|
if (config.options?.extraInstructions) instance.withExtraInstructions(config.options.extraInstructions);
|
|
896
|
+
if (config.genui && (config.genui.source ?? "client") === "client") {
|
|
897
|
+
const cat = config.genui.catalog ?? coreCatalog;
|
|
898
|
+
const allow = resolveGenuiAllow(cat, config.genui.allow);
|
|
899
|
+
instance.withTools([buildRenderUiSchema(cat, allow)]);
|
|
900
|
+
instance.withExtraInstructions({
|
|
901
|
+
...config.options?.extraInstructions ?? {},
|
|
902
|
+
genui: buildGenuiClause(cat, allow)
|
|
903
|
+
});
|
|
904
|
+
}
|
|
514
905
|
return instance;
|
|
515
906
|
}, [config.assistantId, config.adapter]);
|
|
516
907
|
const [input, setInput] = (0, import_react2.useState)("");
|
|
@@ -532,6 +923,23 @@ function useAlquimia(config) {
|
|
|
532
923
|
);
|
|
533
924
|
const [attachments, setAttachments] = (0, import_react2.useState)([]);
|
|
534
925
|
const [isUploadingAttachments, setIsUploadingAttachments] = (0, import_react2.useState)(false);
|
|
926
|
+
const [pendingClientTool, setPendingClientTool] = (0, import_react2.useState)(null);
|
|
927
|
+
const completedToolsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
928
|
+
const genuiCatalog = config.genui?.catalog ?? coreCatalog;
|
|
929
|
+
const [genuiSurfaces, setGenuiSurfaces] = (0, import_react2.useState)([]);
|
|
930
|
+
const messagesLenRef = (0, import_react2.useRef)(0);
|
|
931
|
+
messagesLenRef.current = messages.length;
|
|
932
|
+
const genuiAnchorRef = (0, import_react2.useRef)(0);
|
|
933
|
+
{
|
|
934
|
+
let anchor = messages.length;
|
|
935
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
936
|
+
if (messages[i].role === "user") {
|
|
937
|
+
anchor = i + 1;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
genuiAnchorRef.current = anchor;
|
|
942
|
+
}
|
|
535
943
|
const isLoading = isMessageLoading || isStreamingLoading || isUploadingAttachments;
|
|
536
944
|
function cleanMessages() {
|
|
537
945
|
setMessages([]);
|
|
@@ -799,6 +1207,11 @@ function useAlquimia(config) {
|
|
|
799
1207
|
if (worklogEnabled) {
|
|
800
1208
|
setWorklog((prev) => reduceWorklog(prev ?? initialWorklogState(), frameToRecord(data)));
|
|
801
1209
|
}
|
|
1210
|
+
const pending = extractPendingClientTool(data);
|
|
1211
|
+
if (pending) {
|
|
1212
|
+
setPendingClientTool(pending);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
802
1215
|
const eventTaskId = data.taskid || "";
|
|
803
1216
|
if (data.errors?.length) {
|
|
804
1217
|
for (const error of data.errors) {
|
|
@@ -828,9 +1241,106 @@ function useAlquimia(config) {
|
|
|
828
1241
|
cleanup(false, eventSource);
|
|
829
1242
|
}
|
|
830
1243
|
}
|
|
1244
|
+
const completeToolExecution = (0, import_react2.useCallback)(
|
|
1245
|
+
async (controlId, result) => {
|
|
1246
|
+
if (completedToolsRef.current.has(controlId)) return;
|
|
1247
|
+
completedToolsRef.current.add(controlId);
|
|
1248
|
+
try {
|
|
1249
|
+
await sdk.submitToolResult({ ...result, control_id: controlId });
|
|
1250
|
+
setPendingClientTool((cur) => cur?.controlId === controlId ? null : cur);
|
|
1251
|
+
} catch (err) {
|
|
1252
|
+
completedToolsRef.current.delete(controlId);
|
|
1253
|
+
throw err;
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
[sdk]
|
|
1257
|
+
);
|
|
1258
|
+
(0, import_react2.useEffect)(() => {
|
|
1259
|
+
if (!config.genui) return;
|
|
1260
|
+
if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
|
|
1261
|
+
const { controlId } = pendingClientTool;
|
|
1262
|
+
const parameters = pendingClientTool.parameters && typeof pendingClientTool.parameters === "object" ? { catalogId: genuiCatalog.id, ...pendingClientTool.parameters } : pendingClientTool.parameters;
|
|
1263
|
+
const res = validateSurface(parameters, genuiCatalog);
|
|
1264
|
+
if (!res.ok || !res.value) {
|
|
1265
|
+
void completeToolExecution(controlId, {
|
|
1266
|
+
control_id: controlId,
|
|
1267
|
+
tool_name: "render_ui",
|
|
1268
|
+
status: "error",
|
|
1269
|
+
message: "Surface failed client validation: " + res.errors.map((e) => `${e.path} ${e.message}`).join("; "),
|
|
1270
|
+
result: { errors: res.errors }
|
|
1271
|
+
});
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const surface = res.value;
|
|
1275
|
+
setGenuiSurfaces(
|
|
1276
|
+
(prev) => prev.some((s) => s.controlId === controlId) ? prev : [...prev, { controlId, surface, afterCount: genuiAnchorRef.current }]
|
|
1277
|
+
);
|
|
1278
|
+
const interactive = surface.components.some((c) => c.action?.wantResponse === true);
|
|
1279
|
+
if (!interactive) {
|
|
1280
|
+
void completeToolExecution(controlId, {
|
|
1281
|
+
control_id: controlId,
|
|
1282
|
+
tool_name: "render_ui",
|
|
1283
|
+
status: "success",
|
|
1284
|
+
result: {
|
|
1285
|
+
shown: true,
|
|
1286
|
+
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."
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}, [pendingClientTool, completeToolExecution, config.genui]);
|
|
1291
|
+
const genuiOnAgentAction = (0, import_react2.useCallback)(
|
|
1292
|
+
(controlId) => (action) => {
|
|
1293
|
+
setGenuiSurfaces(
|
|
1294
|
+
(prev) => prev.map((s) => s.controlId === controlId ? { ...s, submitted: true } : s)
|
|
1295
|
+
);
|
|
1296
|
+
void completeToolExecution(controlId, {
|
|
1297
|
+
control_id: controlId,
|
|
1298
|
+
tool_name: "render_ui",
|
|
1299
|
+
status: "success",
|
|
1300
|
+
// Includes componentId, so the agent can tell which element was acted on when
|
|
1301
|
+
// several share one action name (a grid of option cards, say).
|
|
1302
|
+
result: buildSurfaceResult(action)
|
|
1303
|
+
});
|
|
1304
|
+
},
|
|
1305
|
+
[completeToolExecution]
|
|
1306
|
+
);
|
|
1307
|
+
const genuiDismiss = (0, import_react2.useCallback)(
|
|
1308
|
+
(userMessage) => {
|
|
1309
|
+
if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
|
|
1310
|
+
const { controlId } = pendingClientTool;
|
|
1311
|
+
const said = typeof userMessage === "string" ? userMessage.trim() : "";
|
|
1312
|
+
const item = genuiSurfaces.find((s) => s.controlId === controlId);
|
|
1313
|
+
setGenuiSurfaces(
|
|
1314
|
+
(prev) => prev.map(
|
|
1315
|
+
(s) => s.controlId === controlId ? { ...s, submitted: true, dismissedWith: said || void 0 } : s
|
|
1316
|
+
)
|
|
1317
|
+
);
|
|
1318
|
+
if (said) setInput("");
|
|
1319
|
+
void completeToolExecution(controlId, {
|
|
1320
|
+
control_id: controlId,
|
|
1321
|
+
tool_name: "render_ui",
|
|
1322
|
+
status: "success",
|
|
1323
|
+
result: buildDismissResult(item?.surface.surfaceId ?? "", said)
|
|
1324
|
+
});
|
|
1325
|
+
},
|
|
1326
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1327
|
+
[pendingClientTool, genuiSurfaces, completeToolExecution]
|
|
1328
|
+
);
|
|
1329
|
+
const genuiAwaitingUser = !!(pendingClientTool && pendingClientTool.toolName === "render_ui" && genuiSurfaces.some(
|
|
1330
|
+
(s) => s.controlId === pendingClientTool.controlId && s.surface.components.some((c) => c.action?.wantResponse === true)
|
|
1331
|
+
));
|
|
1332
|
+
const genui = config.genui ? {
|
|
1333
|
+
surfaces: genuiSurfaces,
|
|
1334
|
+
onAgentAction: genuiOnAgentAction,
|
|
1335
|
+
awaitingUser: genuiAwaitingUser,
|
|
1336
|
+
dismiss: genuiDismiss
|
|
1337
|
+
} : void 0;
|
|
831
1338
|
return {
|
|
832
1339
|
sdk,
|
|
833
1340
|
activeTool,
|
|
1341
|
+
pendingClientTool,
|
|
1342
|
+
completeToolExecution,
|
|
1343
|
+
genui,
|
|
834
1344
|
cleanMessages,
|
|
835
1345
|
createMessageId,
|
|
836
1346
|
handleInputChange,
|