@absolutejs/ai 0.6.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.6.1 — 2026-10-20
10
+
11
+ ### Added
12
+
13
+ - **Add typed forms and shared task next actions for top-up continuation** (`FormField`, `formCard`, `parseFormSpec`, `prepareFormSubmission`, `FormSubmission`, `BackgroundWorkCard`, `backgroundWorkNextAction`)
14
+
9
15
  ## 0.6.0 — 2026-10-20
10
16
 
11
17
  ### Added
package/README.md CHANGED
@@ -575,3 +575,18 @@ can prevent `done`, and no adapter can reconstruct usage the provider never sent
575
575
  `onUsage` still reports only complete usage, once per instrumented stream. Keep
576
576
  credit reservations separate from charges and reconcile unknown outcomes in the
577
577
  host application. The package does not invent token counts or decide billing policy.
578
+
579
+ ### Typed forms and background work
580
+
581
+ Form fields support `valueType` (`string`, `number`, `integer`, `boolean`). For a
582
+ numeric dropdown use `type: "select", options: ["3", "10"], valueType: "integer"`.
583
+ All hosts should call `prepareFormSubmission(form, values)` before invoking the
584
+ bound tool: it returns typed input or field errors without discarding answers.
585
+ The host must still validate the complete tool schema and apply authorization.
586
+ Legacy dropdowns retain string values; migrate numeric fields explicitly.
587
+
588
+ `BackgroundWorkCard` and `backgroundWorkNextAction` provide framework-neutral
589
+ progress/CTA semantics. A funding pause offers Add credits; only confirmed
590
+ funding changes it to Continue research. Completed work offers View results;
591
+ uncertain work offers Check status, never replay. The host persists the work ID,
592
+ polls or subscribes to actual progress, and binds actions to authenticated routes.
package/changelog.json CHANGED
@@ -2,6 +2,25 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/ai",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "kind": "added",
9
+ "summary": "Add typed forms and shared task next actions for top-up continuation",
10
+ "symbols": [
11
+ "FormField",
12
+ "formCard",
13
+ "parseFormSpec",
14
+ "prepareFormSubmission",
15
+ "FormSubmission",
16
+ "BackgroundWorkCard",
17
+ "backgroundWorkNextAction"
18
+ ]
19
+ }
20
+ ],
21
+ "date": "2026-10-20",
22
+ "version": "0.6.1"
23
+ },
5
24
  {
6
25
  "changes": [
7
26
  {
package/dist/ai/index.js CHANGED
@@ -2751,6 +2751,11 @@ var parseFormField = (raw) => {
2751
2751
  if (!name || !label || !type)
2752
2752
  return null;
2753
2753
  const field = { label, name, type };
2754
+ if (raw.valueType !== undefined) {
2755
+ if (!["string", "number", "integer", "boolean"].includes(String(raw.valueType)))
2756
+ return null;
2757
+ field.valueType = raw.valueType;
2758
+ }
2754
2759
  const placeholder = cleanString(raw.placeholder, LABEL_MAX_CHARS);
2755
2760
  if (placeholder)
2756
2761
  field.placeholder = placeholder;
@@ -3122,6 +3127,11 @@ var formCard = {
3122
3127
  items: { type: "string" },
3123
3128
  type: "array"
3124
3129
  },
3130
+ valueType: {
3131
+ enum: ["string", "number", "integer", "boolean"],
3132
+ type: "string",
3133
+ description: "Submitted scalar type, especially integer for numeric select options"
3134
+ },
3125
3135
  placeholder: { type: "string" },
3126
3136
  required: { type: "boolean" },
3127
3137
  type: { enum: [...FORM_FIELD_TYPES], type: "string" },
@@ -3628,6 +3638,68 @@ var renderChartSvg = (spec, options = {}) => {
3628
3638
  const body = spec.type === "donut" ? donutSvg(spec, frame) : cartesianSvg(spec, frame);
3629
3639
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}" font-family="${FONT}"><rect width="${width}" height="${height}" fill="${theme.surface}" rx="12"/>${spec.type === "donut" ? headerSvg({ ...spec, series: [] }, frame) : headerSvg(spec, frame)}${body}</svg>`;
3630
3640
  };
3641
+ // src/ai/ui/formSubmission.ts
3642
+ var prepareFormSubmission = (form, values) => {
3643
+ const input = { ...form.submit.input };
3644
+ const errors = {};
3645
+ for (const field of form.fields) {
3646
+ if (["__proto__", "constructor", "prototype"].includes(field.name)) {
3647
+ errors.form = "Invalid field name";
3648
+ continue;
3649
+ }
3650
+ const raw = values[field.name];
3651
+ if (raw === undefined || raw === null || typeof raw === "string" && !raw.trim()) {
3652
+ if (field.required)
3653
+ errors[field.name] = "This field is required";
3654
+ continue;
3655
+ }
3656
+ if (field.type === "select" && !field.options?.includes(String(raw))) {
3657
+ errors[field.name] = "Choose one of the available options";
3658
+ continue;
3659
+ }
3660
+ const type = field.valueType ?? (field.type === "number" ? "number" : field.type === "checkbox" ? "boolean" : "string");
3661
+ let value = raw;
3662
+ if (type === "number" || type === "integer") {
3663
+ value = typeof raw === "number" ? raw : typeof raw === "string" && /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/iu.test(raw.trim()) ? Number(raw) : NaN;
3664
+ if (typeof value !== "number" || !Number.isFinite(value) || type === "integer" && !Number.isSafeInteger(value)) {
3665
+ errors[field.name] = type === "integer" ? "Enter a whole number" : "Enter a number";
3666
+ continue;
3667
+ }
3668
+ } else if (type === "boolean") {
3669
+ if (![true, false, "true", "false"].includes(raw)) {
3670
+ errors[field.name] = "Choose yes or no";
3671
+ continue;
3672
+ }
3673
+ value = raw === true || raw === "true";
3674
+ if (field.type === "checkbox" && field.required && !value) {
3675
+ errors[field.name] = "This field is required";
3676
+ continue;
3677
+ }
3678
+ } else {
3679
+ if (typeof raw !== "string") {
3680
+ errors[field.name] = "Enter text";
3681
+ continue;
3682
+ }
3683
+ value = field.type === "password" ? raw : raw.trim();
3684
+ }
3685
+ input[field.name] = value;
3686
+ }
3687
+ return Object.keys(errors).length ? { ok: false, errors } : { ok: true, input };
3688
+ };
3689
+ // src/ai/ui/backgroundWork.ts
3690
+ var backgroundWorkNextAction = (work) => {
3691
+ if (!work.workId || !Number.isSafeInteger(work.totalSteps) || work.totalSteps < 1 || !Number.isSafeInteger(work.completedSteps) || work.completedSteps < 0 || work.completedSteps > work.totalSteps)
3692
+ throw new Error("Invalid background work progress");
3693
+ if (work.status === "completed")
3694
+ return { kind: "view_results", label: "View results" };
3695
+ if (work.status === "paused" && work.pauseReason === "funding")
3696
+ return work.fundingConfirmed ? { kind: "continue", label: "Continue research" } : { kind: "top_up", label: "Add credits" };
3697
+ if (work.status === "unknown")
3698
+ return { kind: "check_status", label: "Check status" };
3699
+ if (work.hasResults)
3700
+ return { kind: "view_results", label: "View saved results" };
3701
+ return { kind: "check_status", label: "Check status" };
3702
+ };
3631
3703
  // src/ai/conversationManager.ts
3632
3704
  var NOT_FOUND2 = -1;
3633
3705
  var TITLE_MAX_LENGTH2 = 80;
@@ -9160,6 +9232,7 @@ export {
9160
9232
  aiChat,
9161
9233
  alibaba,
9162
9234
  anthropic,
9235
+ backgroundWorkNextAction,
9163
9236
  chartCard,
9164
9237
  choiceCard,
9165
9238
  configureProviderResilience,
@@ -9223,6 +9296,7 @@ export {
9223
9296
  planCard,
9224
9297
  prepareAITextInput,
9225
9298
  prepareAITextInputStep,
9299
+ prepareFormSubmission,
9226
9300
  providerStatusPage,
9227
9301
  remoteProvider,
9228
9302
  renderChartSvg,
@@ -9243,5 +9317,5 @@ export {
9243
9317
  xai
9244
9318
  };
9245
9319
 
9246
- //# debugId=9A889A121344253764756E2164756E21
9320
+ //# debugId=75EDBEC482A1645464756E2164756E21
9247
9321
  //# sourceMappingURL=index.js.map