@alquimia-ai/tools 2.3.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.
@@ -642,6 +642,25 @@ var coreCatalog = {
642
642
  };
643
643
 
644
644
  // src/genui/actions.ts
645
+ var DISMISS_ACTION = "userDismissedSurface";
646
+ function buildDismissResult(surfaceId, userMessage) {
647
+ const said = typeof userMessage === "string" ? userMessage.trim() : "";
648
+ return {
649
+ action: DISMISS_ACTION,
650
+ surfaceId,
651
+ componentId: "",
652
+ data: {},
653
+ ...said ? { userMessage: said } : {}
654
+ };
655
+ }
656
+ function buildSurfaceResult(action) {
657
+ return {
658
+ action: action.action,
659
+ surfaceId: action.surfaceId,
660
+ componentId: action.componentId,
661
+ data: action.payload ?? {}
662
+ };
663
+ }
645
664
  var URL_LIKE = /^\s*([a-z][a-z0-9+.-]*:|\/\/)/i;
646
665
  function assertSafeActionName(name) {
647
666
  if (typeof name !== "string" || name.length === 0) {
@@ -813,7 +832,9 @@ function buildGenuiClause(manifest, allowlist) {
813
832
  `Components: ${names.join(", ")}.`,
814
833
  `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.`,
815
834
  "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`).",
816
- "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.",
835
+ "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.",
836
+ "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.",
837
+ '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.',
817
838
  "IMPORTANT: the rendered surface IS your reply \u2014 do NOT reproduce its content as text, markdown, or ASCII; a one-line intro at most.",
818
839
  "Prefer plain text for simple replies."
819
840
  ].join(" ");
@@ -1239,15 +1260,44 @@ function useAlquimia(config) {
1239
1260
  control_id: controlId,
1240
1261
  tool_name: "render_ui",
1241
1262
  status: "success",
1242
- result: { action: action.action, surfaceId: action.surfaceId, data: action.payload }
1263
+ // Includes componentId, so the agent can tell which element was acted on when
1264
+ // several share one action name (a grid of option cards, say).
1265
+ result: buildSurfaceResult(action)
1243
1266
  });
1244
1267
  },
1245
1268
  [completeToolExecution]
1246
1269
  );
1270
+ const genuiDismiss = useCallback2(
1271
+ (userMessage) => {
1272
+ if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
1273
+ const { controlId } = pendingClientTool;
1274
+ const said = typeof userMessage === "string" ? userMessage.trim() : "";
1275
+ const item = genuiSurfaces.find((s) => s.controlId === controlId);
1276
+ setGenuiSurfaces(
1277
+ (prev) => prev.map(
1278
+ (s) => s.controlId === controlId ? { ...s, submitted: true, dismissedWith: said || void 0 } : s
1279
+ )
1280
+ );
1281
+ if (said) setInput("");
1282
+ void completeToolExecution(controlId, {
1283
+ control_id: controlId,
1284
+ tool_name: "render_ui",
1285
+ status: "success",
1286
+ result: buildDismissResult(item?.surface.surfaceId ?? "", said)
1287
+ });
1288
+ },
1289
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1290
+ [pendingClientTool, genuiSurfaces, completeToolExecution]
1291
+ );
1247
1292
  const genuiAwaitingUser = !!(pendingClientTool && pendingClientTool.toolName === "render_ui" && genuiSurfaces.some(
1248
1293
  (s) => s.controlId === pendingClientTool.controlId && s.surface.components.some((c) => c.action?.wantResponse === true)
1249
1294
  ));
1250
- const genui = config.genui ? { surfaces: genuiSurfaces, onAgentAction: genuiOnAgentAction, awaitingUser: genuiAwaitingUser } : void 0;
1295
+ const genui = config.genui ? {
1296
+ surfaces: genuiSurfaces,
1297
+ onAgentAction: genuiOnAgentAction,
1298
+ awaitingUser: genuiAwaitingUser,
1299
+ dismiss: genuiDismiss
1300
+ } : void 0;
1251
1301
  return {
1252
1302
  sdk,
1253
1303
  activeTool,