@useago/sdk 1.0.0 → 1.0.2

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.
@@ -0,0 +1,257 @@
1
+ import { c as _ } from "./AgoClient-bQozE6-A.js";
2
+ function E(n, e) {
3
+ const i = e ? e.storage ?? (typeof localStorage < "u" ? localStorage : void 0) : void 0;
4
+ let t = n;
5
+ if (e && i) try {
6
+ const c = i.getItem(e.key);
7
+ c != null && (t = JSON.parse(c));
8
+ } catch {
9
+ }
10
+ const a = /* @__PURE__ */ new Set(), f = () => a.forEach((c) => c(t));
11
+ function d() {
12
+ if (e && i) try {
13
+ i.setItem(e.key, JSON.stringify(t));
14
+ } catch {
15
+ }
16
+ }
17
+ return {
18
+ get: () => t,
19
+ set: (c) => {
20
+ t = c, d(), f();
21
+ },
22
+ subscribe: (c) => (a.add(c), () => {
23
+ a.delete(c);
24
+ })
25
+ };
26
+ }
27
+ var F = /^[a-zA-Z0-9_-]{1,40}$/, O = (n) => n == null || n === "";
28
+ function q(n, e) {
29
+ const i = e[n.property];
30
+ if (O(i)) return !1;
31
+ if (Array.isArray(n.value)) return n.value.some((f) => String(i) === String(f));
32
+ if (!n.op) return String(i) === String(n.value);
33
+ const t = Number(i), a = Number(n.value);
34
+ if (Number.isNaN(t) || Number.isNaN(a)) return !1;
35
+ switch (n.op) {
36
+ case ">=":
37
+ return t >= a;
38
+ case "<=":
39
+ return t <= a;
40
+ case ">":
41
+ return t > a;
42
+ case "<":
43
+ return t < a;
44
+ default:
45
+ return _.warn(`Unknown requiredWhen op "${n.op}"; treating as not required.`), !1;
46
+ }
47
+ }
48
+ function g(n, e) {
49
+ const i = n.properties ?? {}, t = /* @__PURE__ */ new Set();
50
+ for (const f of n.required ?? []) {
51
+ const d = i[f]?.requiredWhen;
52
+ (!d || q(d, e)) && t.add(f);
53
+ }
54
+ for (const [f, d] of Object.entries(i)) d.requiredWhen && q(d.requiredWhen, e) && t.add(f);
55
+ const a = [...t].filter((f) => O(e[f]));
56
+ return {
57
+ missing: a,
58
+ complete: a.length === 0
59
+ };
60
+ }
61
+ function P(n) {
62
+ const e = {};
63
+ for (const [i, t] of Object.entries(n.properties ?? {})) e[i] = {
64
+ type: t.type,
65
+ ...t.description != null && { description: t.description },
66
+ ...t.enum && { enum: t.enum },
67
+ ...t.default !== void 0 && { default: t.default }
68
+ };
69
+ return {
70
+ type: "object",
71
+ properties: e,
72
+ required: []
73
+ };
74
+ }
75
+ function W(n) {
76
+ const { name: e, description: i, schema: t } = n;
77
+ if (!F.test(e)) throw new Error(`Invalid form collector name "${e}": must match ${F} (it becomes the update_/submit_ function names).`);
78
+ const a = typeof n.submit == "string" ? n.submit ? {
79
+ via: "client",
80
+ url: n.submit
81
+ } : !1 : n.submit ?? !1, f = t.properties ?? {}, d = (r) => {
82
+ const s = {};
83
+ for (const [u, o] of Object.entries(r)) {
84
+ const l = f[u];
85
+ if (l)
86
+ if (o == null) s[u] = o;
87
+ else if (l.type === "number" || l.type === "integer") {
88
+ const h = typeof o == "number" ? o : Number(o);
89
+ s[u] = Number.isNaN(h) ? o : h;
90
+ } else l.type === "boolean" ? s[u] = typeof o == "boolean" ? o : o === "true" ? !0 : o === "false" ? !1 : !!o : s[u] = o;
91
+ }
92
+ return s;
93
+ }, c = n.initialValues ?? {}, m = E({
94
+ values: c,
95
+ submitted: !1
96
+ }), v = (r) => {
97
+ const s = m.get(), u = {
98
+ ...s,
99
+ values: {
100
+ ...s.values,
101
+ ...r
102
+ }
103
+ };
104
+ return m.set(u), u;
105
+ };
106
+ let b = null;
107
+ const N = async () => {
108
+ const { values: r } = m.get(), { complete: s, missing: u } = g(t, r);
109
+ if (!s) return {
110
+ ok: !1,
111
+ missing: u
112
+ };
113
+ if (!a) return {
114
+ ok: !1,
115
+ error: "No submit target configured for this form collector."
116
+ };
117
+ const o = r;
118
+ try {
119
+ let l;
120
+ if (a.via === "client") if ("handler" in a) l = await a.handler(o);
121
+ else {
122
+ const h = await fetch(a.url, {
123
+ method: "POST",
124
+ headers: { "Content-Type": "application/json" },
125
+ credentials: "include",
126
+ body: JSON.stringify(o)
127
+ });
128
+ if (!h.ok) return {
129
+ ok: !1,
130
+ error: `Submit failed: HTTP ${h.status}`
131
+ };
132
+ l = await h.json().catch(() => {
133
+ });
134
+ }
135
+ else {
136
+ if (!b) return {
137
+ ok: !1,
138
+ error: "Backend submit requires install(client) (or useFormCollector) first."
139
+ };
140
+ l = await b.submitFormCollector(a.destination, o);
141
+ }
142
+ return m.set({
143
+ ...m.get(),
144
+ submitted: !0
145
+ }), {
146
+ ok: !0,
147
+ result: l
148
+ };
149
+ } catch (l) {
150
+ const h = l instanceof Error ? l.message : "Submit failed";
151
+ return _.error(`FormCollector "${e}" submit failed:`, l), {
152
+ ok: !1,
153
+ error: h
154
+ };
155
+ }
156
+ }, $ = {
157
+ name: `update_${e}`,
158
+ description: `Fill or update fields of the "${e}" form. ${i} Pass any subset of fields; call it repeatedly to fill the form as the user provides information. Returns the current values and which required fields are still missing.`,
159
+ parameters: P(t),
160
+ handler: (r) => {
161
+ const s = v(d(r)), { missing: u } = g(t, s.values);
162
+ return {
163
+ ok: !0,
164
+ values: s.values,
165
+ missing: u
166
+ };
167
+ }
168
+ }, p = [$];
169
+ a && p.push({
170
+ name: `submit_${e}`,
171
+ description: `Submit the completed "${e}" form. Only call this once every required field is collected and the user has confirmed. If fields are missing it returns them instead of submitting.`,
172
+ parameters: {
173
+ type: "object",
174
+ properties: {}
175
+ },
176
+ handler: () => N()
177
+ });
178
+ const T = "client__", S = a ? `submit_${e}` : null, x = (r) => {
179
+ const s = r.functionName ?? r.toolName;
180
+ if (s)
181
+ return s.startsWith(T) ? s.slice(8) : s;
182
+ }, w = (r) => {
183
+ let s = { ...c }, u = !1;
184
+ for (const o of r) {
185
+ const l = x(o);
186
+ l === $.name ? o.arguments && (s = {
187
+ ...s,
188
+ ...d(o.arguments)
189
+ }) : S && l === S && o.status?.toLowerCase() === "done" && (u = !0);
190
+ }
191
+ m.set({
192
+ values: s,
193
+ submitted: u
194
+ });
195
+ }, y = `form:${e}`, C = () => {
196
+ const { values: r, submitted: s } = m.get(), { missing: u, complete: o } = g(t, r), l = a ? ` When nothing is missing and the user confirms, call submit_${e}.` : "", h = s ? `The "${e}" form has been submitted.` : `You are currently collecting information from the user to fill the "${e}" form.`;
197
+ return {
198
+ name: `Form: ${e}`,
199
+ description: `${h} ${i} There are conditionnals questions/values to collected, you can find this information with the requiredWhen field in the form. The form schema is the following: ${JSON.stringify(t)}. Data collected so far: ${JSON.stringify(r)}. Call update_${e} with any fields the user provides; ask only for what is still missing.${l}`,
200
+ data: {
201
+ values: r,
202
+ missing: u,
203
+ complete: o,
204
+ submitted: s
205
+ }
206
+ };
207
+ }, k = (r) => {
208
+ w((r.messages ?? []).flatMap((s) => s.toolCalls ?? []));
209
+ };
210
+ return {
211
+ name: e,
212
+ store: m,
213
+ functions: p,
214
+ contextKey: y,
215
+ contextProvider: C,
216
+ install: (r) => {
217
+ b = r;
218
+ for (const u of p) r.register(u);
219
+ r.addDynamicContext(y, C);
220
+ const s = m.subscribe(() => r.notifyContextChanged());
221
+ return r.on("conversation:loaded", k), () => {
222
+ s();
223
+ for (const u of p) r.unregisterFunction(u.name);
224
+ r.removeDynamicContext(y), r.off("conversation:loaded", k), b === r && (b = null);
225
+ };
226
+ },
227
+ hydrate: w,
228
+ submit: N,
229
+ getValues: () => m.get().values,
230
+ setValues: (r) => {
231
+ v(d(r));
232
+ },
233
+ reset: () => {
234
+ m.set({
235
+ values: c,
236
+ submitted: !1
237
+ });
238
+ },
239
+ getStatus: () => g(t, m.get().values)
240
+ };
241
+ }
242
+ async function V(n, e) {
243
+ const i = await n.getFormCollector(e.name);
244
+ return W({
245
+ name: i.name,
246
+ description: e.description ?? i.description,
247
+ schema: e.schema ?? i.schema,
248
+ submit: e.submit ?? i.submit,
249
+ initialValues: e.initialValues
250
+ });
251
+ }
252
+ export {
253
+ E as i,
254
+ g as n,
255
+ V as r,
256
+ W as t
257
+ };
@@ -16,12 +16,26 @@ declare class AgoClient {
16
16
  private contextRegistry;
17
17
  private eventEmitter;
18
18
  private config;
19
+ /** Conversations already warned about an empty reply (one warning each). */
20
+ private warnedEmptyConversations;
19
21
  constructor(config: AgoConfig);
20
22
  /**
21
23
  * Send a message and receive a streaming response
22
24
  */
23
25
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
24
26
  private processSSEResponse;
27
+ /**
28
+ * Detect a reply that completed as DONE with no content and no activity —
29
+ * today's backend answers an unknown `agent` slug with an empty 200, which
30
+ * otherwise fails silently. Emits `message:empty` (always) and warns on the
31
+ * console once per conversation (unless `warnOnEmptyReply: false`).
32
+ *
33
+ * Deferred with setTimeout(0) so both land AFTER `sendMessage` resolves:
34
+ * `await client.sendMessage(...)` followed by `client.waitFor("message:empty")`
35
+ * must not race. TEMPORARY heuristic: remove once the backend returns an
36
+ * explicit error for unknown agents (see TODOS.md, backend issue).
37
+ */
38
+ private maybeFlagEmptyReply;
25
39
  private handleClientFunctionInvocation;
26
40
  /**
27
41
  * Get list of conversations
@@ -53,6 +67,13 @@ declare class AgoClient {
53
67
  * Reject a tool call
54
68
  */
55
69
  rejectToolCall(toolCallId: string): Promise<void>;
70
+ /**
71
+ * Fetch a backend-stored form collector definition by name (the single source
72
+ * of truth for the form's description, schema, and submit target). Used by
73
+ * `loadFormCollector` / the name-only form of `useFormCollector` so the schema
74
+ * lives in the backend instead of being hardcoded in client code.
75
+ */
76
+ getFormCollector(name: string): Promise<FormCollectorDefinition>;
56
77
  /**
57
78
  * Relay a completed form collector's values to a server-configured destination.
58
79
  *
@@ -187,6 +208,11 @@ declare class AgoClient {
187
208
  }): Promise<AgoClientEvents[K]>;
188
209
  /**
189
210
  * Update client configuration
211
+ *
212
+ * Validates the MERGED config before committing: omitting a key (or passing
213
+ * it as `undefined`, common with spread-built partials) keeps the current
214
+ * value; an explicit empty/non-string `baseUrl` throws `config_missing_base_url`
215
+ * and leaves the client unchanged (no half-updated state).
190
216
  */
191
217
  updateConfig(config: Partial<AgoConfig>): void;
192
218
  /**
@@ -222,8 +248,23 @@ declare interface AgoClientEvents {
222
248
  */
223
249
  "message:answer-complete": AgoMessage;
224
250
  "message:complete": AgoMessage;
251
+ /**
252
+ * A reply finished as `DONE` with empty content and no tool calls, client
253
+ * functions, or follow-up replies — usually an unknown `agent` slug (the
254
+ * backend currently answers those with an empty 200). Fires after
255
+ * `message:complete` AND after `sendMessage` resolves, so subscribing right
256
+ * after `await sendMessage(...)` still catches it. `messageId` and
257
+ * `conversationId` are empty strings when the stream completed without any
258
+ * message data at all (e.g. a proxy stripped the SSE stream).
259
+ */
260
+ "message:empty": {
261
+ conversationId: string;
262
+ messageId: string;
263
+ };
225
264
  "message:error": {
226
265
  error: string;
266
+ /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
267
+ code?: string;
227
268
  conversationId?: string;
228
269
  messageId?: string;
229
270
  };
@@ -252,7 +293,7 @@ declare interface AgoClientEvents {
252
293
  declare interface AgoConfig {
253
294
  /** API base URL (e.g., https://YOUR-DOMAIN.useago.com) */
254
295
  baseUrl: string;
255
- /** Widget ID (X-Widget-Id header). Auto-generated if not provided. */
296
+ /** Per-visitor anonymous ID (X-User-Anon-Id header). Auto-generated if not provided. */
256
297
  widgetId?: string;
257
298
  /** Default agent (id or slug) for new conversations. Shorthand for `defaultAgentId`. */
258
299
  agent?: string;
@@ -266,6 +307,13 @@ declare interface AgoConfig {
266
307
  userJwt?: string;
267
308
  /** Enable debug logging */
268
309
  debug?: boolean;
310
+ /**
311
+ * Warn on the console when a reply completes with empty content and no tool
312
+ * calls (the usual cause is an unknown `agent` slug, which the backend
313
+ * currently answers with an empty 200). Default `true`. The `message:empty`
314
+ * event fires regardless of this setting.
315
+ */
316
+ warnOnEmptyReply?: boolean;
269
317
  }
270
318
 
271
319
  declare type AgoEventHandler<K extends AgoEventName> = (data: AgoClientEvents[K]) => void;
@@ -399,6 +447,30 @@ export declare interface DevPanelOptions {
399
447
  */
400
448
  declare type DynamicContextProvider = () => ContextEntry | null | undefined;
401
449
 
450
+ /**
451
+ * The backend-stored shape returned by `GET /api/sdk/v1/forms/{name}` (see
452
+ * {@link AgoClient.getFormCollector}). Mirrors the persistable subset of
453
+ * {@link CreateFormCollectorOptions}: the `submit` here is never the client-only
454
+ * `handler` variant (a function can't be stored server-side).
455
+ */
456
+ declare interface FormCollectorDefinition<V = Record<string, unknown>> {
457
+ name: string;
458
+ description: string;
459
+ schema: FormCollectorSchema;
460
+ /** Server-stored submit target, if any. Absent means collect-only. */
461
+ submit?: SubmitConfig<V>;
462
+ }
463
+
464
+ /**
465
+ * Shape of a form's fields. Mirrors a client function's `parameters` (so the same
466
+ * object feeds the store, the update function, and the dynamic context). Additionally allows the SDK-only per-field `requiredWhen`, which {@link toWireParameters} removes before the schema reaches the LLM.
467
+ */
468
+ declare interface FormCollectorSchema {
469
+ type: "object";
470
+ properties: Record<string, FormFieldSchema>;
471
+ required?: string[];
472
+ }
473
+
402
474
  declare interface FormField {
403
475
  type: "string" | "number" | "boolean" | "array";
404
476
  title?: string;
@@ -407,6 +479,43 @@ declare interface FormField {
407
479
  default?: unknown;
408
480
  }
409
481
 
482
+ /**
483
+ * A field's conditional-requiredness rule. The field becomes required exactly
484
+ * when the controlling `property` matches `value` against the answers collected
485
+ * so far:
486
+ * - a single `value`, no `op` — equality (compared as strings);
487
+ * - a single `value` with `op` — numeric comparison (both sides coerced to numbers);
488
+ * - an array `value`, no `op` — "one of": matches when the property equals any
489
+ * listed value.
490
+ *
491
+ * SDK-only — it is stripped from the schema before it reaches the LLM tool
492
+ */
493
+ declare interface FormFieldCondition {
494
+ /** The controlling field whose value the rule tests. */
495
+ property: string;
496
+ /** Comparison operator; omitted means equality. Ignored when `value` is an array. */
497
+ op?: ">=" | "<=" | ">" | "<";
498
+ /**
499
+ * The value(s) to test the controlling field against. A single value is an
500
+ * equality (or, with `op`, a numeric comparison); an array is an "is one of"
501
+ * membership test, e.g. `value: ["1", "2", "3"]`.
502
+ */
503
+ value: string | number | Array<string | number>;
504
+ }
505
+
506
+ /** A single field's schema: the wire-legal keys plus the SDK-only `requiredWhen`. */
507
+ declare interface FormFieldSchema {
508
+ type: string;
509
+ description?: string;
510
+ enum?: string[];
511
+ default?: unknown;
512
+ /**
513
+ * Makes the field required only while this condition holds. Drives the dynamic
514
+ * `missing` list; stripped before the LLM tool — see {@link toWireParameters}.
515
+ */
516
+ requiredWhen?: FormFieldCondition;
517
+ }
518
+
410
519
  /**
411
520
  * Form schema for tool calls requiring user input
412
521
  */
@@ -432,6 +541,27 @@ declare interface SendMessageOptions {
432
541
  files?: File[];
433
542
  }
434
543
 
544
+ /**
545
+ * Where a completed form is sent.
546
+ * - `"https://..."` (a bare string) — shorthand for `{ via: "client", url }`: the browser
547
+ * POSTs the values to that URL.
548
+ * - `{ via: "client", url }` — same, written out.
549
+ * - `{ via: "client", handler }` — the browser runs your own submit logic.
550
+ * - `{ via: "backend", destination }` — the browser relays the values to the backend,
551
+ * which forwards them to the server-configured destination (URL + secret stay server-side).
552
+ * - `false` — collect only, no submit function.
553
+ */
554
+ declare type SubmitConfig<V = Record<string, unknown>> = string | {
555
+ via: "client";
556
+ url: string;
557
+ } | {
558
+ via: "client";
559
+ handler: (values: V) => Promise<unknown> | unknown;
560
+ } | {
561
+ via: "backend";
562
+ destination: string;
563
+ } | false;
564
+
435
565
  /**
436
566
  * Tool call data from SSE stream
437
567
  */
@@ -16,12 +16,26 @@ declare class AgoClient {
16
16
  private contextRegistry;
17
17
  private eventEmitter;
18
18
  private config;
19
+ /** Conversations already warned about an empty reply (one warning each). */
20
+ private warnedEmptyConversations;
19
21
  constructor(config: AgoConfig);
20
22
  /**
21
23
  * Send a message and receive a streaming response
22
24
  */
23
25
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
24
26
  private processSSEResponse;
27
+ /**
28
+ * Detect a reply that completed as DONE with no content and no activity —
29
+ * today's backend answers an unknown `agent` slug with an empty 200, which
30
+ * otherwise fails silently. Emits `message:empty` (always) and warns on the
31
+ * console once per conversation (unless `warnOnEmptyReply: false`).
32
+ *
33
+ * Deferred with setTimeout(0) so both land AFTER `sendMessage` resolves:
34
+ * `await client.sendMessage(...)` followed by `client.waitFor("message:empty")`
35
+ * must not race. TEMPORARY heuristic: remove once the backend returns an
36
+ * explicit error for unknown agents (see TODOS.md, backend issue).
37
+ */
38
+ private maybeFlagEmptyReply;
25
39
  private handleClientFunctionInvocation;
26
40
  /**
27
41
  * Get list of conversations
@@ -53,6 +67,13 @@ declare class AgoClient {
53
67
  * Reject a tool call
54
68
  */
55
69
  rejectToolCall(toolCallId: string): Promise<void>;
70
+ /**
71
+ * Fetch a backend-stored form collector definition by name (the single source
72
+ * of truth for the form's description, schema, and submit target). Used by
73
+ * `loadFormCollector` / the name-only form of `useFormCollector` so the schema
74
+ * lives in the backend instead of being hardcoded in client code.
75
+ */
76
+ getFormCollector(name: string): Promise<FormCollectorDefinition>;
56
77
  /**
57
78
  * Relay a completed form collector's values to a server-configured destination.
58
79
  *
@@ -187,6 +208,11 @@ declare class AgoClient {
187
208
  }): Promise<AgoClientEvents[K]>;
188
209
  /**
189
210
  * Update client configuration
211
+ *
212
+ * Validates the MERGED config before committing: omitting a key (or passing
213
+ * it as `undefined`, common with spread-built partials) keeps the current
214
+ * value; an explicit empty/non-string `baseUrl` throws `config_missing_base_url`
215
+ * and leaves the client unchanged (no half-updated state).
190
216
  */
191
217
  updateConfig(config: Partial<AgoConfig>): void;
192
218
  /**
@@ -222,8 +248,23 @@ declare interface AgoClientEvents {
222
248
  */
223
249
  "message:answer-complete": AgoMessage;
224
250
  "message:complete": AgoMessage;
251
+ /**
252
+ * A reply finished as `DONE` with empty content and no tool calls, client
253
+ * functions, or follow-up replies — usually an unknown `agent` slug (the
254
+ * backend currently answers those with an empty 200). Fires after
255
+ * `message:complete` AND after `sendMessage` resolves, so subscribing right
256
+ * after `await sendMessage(...)` still catches it. `messageId` and
257
+ * `conversationId` are empty strings when the stream completed without any
258
+ * message data at all (e.g. a proxy stripped the SSE stream).
259
+ */
260
+ "message:empty": {
261
+ conversationId: string;
262
+ messageId: string;
263
+ };
225
264
  "message:error": {
226
265
  error: string;
266
+ /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
267
+ code?: string;
227
268
  conversationId?: string;
228
269
  messageId?: string;
229
270
  };
@@ -252,7 +293,7 @@ declare interface AgoClientEvents {
252
293
  declare interface AgoConfig {
253
294
  /** API base URL (e.g., https://YOUR-DOMAIN.useago.com) */
254
295
  baseUrl: string;
255
- /** Widget ID (X-Widget-Id header). Auto-generated if not provided. */
296
+ /** Per-visitor anonymous ID (X-User-Anon-Id header). Auto-generated if not provided. */
256
297
  widgetId?: string;
257
298
  /** Default agent (id or slug) for new conversations. Shorthand for `defaultAgentId`. */
258
299
  agent?: string;
@@ -266,6 +307,13 @@ declare interface AgoConfig {
266
307
  userJwt?: string;
267
308
  /** Enable debug logging */
268
309
  debug?: boolean;
310
+ /**
311
+ * Warn on the console when a reply completes with empty content and no tool
312
+ * calls (the usual cause is an unknown `agent` slug, which the backend
313
+ * currently answers with an empty 200). Default `true`. The `message:empty`
314
+ * event fires regardless of this setting.
315
+ */
316
+ warnOnEmptyReply?: boolean;
269
317
  }
270
318
 
271
319
  declare type AgoEventHandler<K extends AgoEventName> = (data: AgoClientEvents[K]) => void;
@@ -399,6 +447,30 @@ export declare interface DevPanelOptions {
399
447
  */
400
448
  declare type DynamicContextProvider = () => ContextEntry | null | undefined;
401
449
 
450
+ /**
451
+ * The backend-stored shape returned by `GET /api/sdk/v1/forms/{name}` (see
452
+ * {@link AgoClient.getFormCollector}). Mirrors the persistable subset of
453
+ * {@link CreateFormCollectorOptions}: the `submit` here is never the client-only
454
+ * `handler` variant (a function can't be stored server-side).
455
+ */
456
+ declare interface FormCollectorDefinition<V = Record<string, unknown>> {
457
+ name: string;
458
+ description: string;
459
+ schema: FormCollectorSchema;
460
+ /** Server-stored submit target, if any. Absent means collect-only. */
461
+ submit?: SubmitConfig<V>;
462
+ }
463
+
464
+ /**
465
+ * Shape of a form's fields. Mirrors a client function's `parameters` (so the same
466
+ * object feeds the store, the update function, and the dynamic context). Additionally allows the SDK-only per-field `requiredWhen`, which {@link toWireParameters} removes before the schema reaches the LLM.
467
+ */
468
+ declare interface FormCollectorSchema {
469
+ type: "object";
470
+ properties: Record<string, FormFieldSchema>;
471
+ required?: string[];
472
+ }
473
+
402
474
  declare interface FormField {
403
475
  type: "string" | "number" | "boolean" | "array";
404
476
  title?: string;
@@ -407,6 +479,43 @@ declare interface FormField {
407
479
  default?: unknown;
408
480
  }
409
481
 
482
+ /**
483
+ * A field's conditional-requiredness rule. The field becomes required exactly
484
+ * when the controlling `property` matches `value` against the answers collected
485
+ * so far:
486
+ * - a single `value`, no `op` — equality (compared as strings);
487
+ * - a single `value` with `op` — numeric comparison (both sides coerced to numbers);
488
+ * - an array `value`, no `op` — "one of": matches when the property equals any
489
+ * listed value.
490
+ *
491
+ * SDK-only — it is stripped from the schema before it reaches the LLM tool
492
+ */
493
+ declare interface FormFieldCondition {
494
+ /** The controlling field whose value the rule tests. */
495
+ property: string;
496
+ /** Comparison operator; omitted means equality. Ignored when `value` is an array. */
497
+ op?: ">=" | "<=" | ">" | "<";
498
+ /**
499
+ * The value(s) to test the controlling field against. A single value is an
500
+ * equality (or, with `op`, a numeric comparison); an array is an "is one of"
501
+ * membership test, e.g. `value: ["1", "2", "3"]`.
502
+ */
503
+ value: string | number | Array<string | number>;
504
+ }
505
+
506
+ /** A single field's schema: the wire-legal keys plus the SDK-only `requiredWhen`. */
507
+ declare interface FormFieldSchema {
508
+ type: string;
509
+ description?: string;
510
+ enum?: string[];
511
+ default?: unknown;
512
+ /**
513
+ * Makes the field required only while this condition holds. Drives the dynamic
514
+ * `missing` list; stripped before the LLM tool — see {@link toWireParameters}.
515
+ */
516
+ requiredWhen?: FormFieldCondition;
517
+ }
518
+
410
519
  /**
411
520
  * Form schema for tool calls requiring user input
412
521
  */
@@ -432,6 +541,27 @@ declare interface SendMessageOptions {
432
541
  files?: File[];
433
542
  }
434
543
 
544
+ /**
545
+ * Where a completed form is sent.
546
+ * - `"https://..."` (a bare string) — shorthand for `{ via: "client", url }`: the browser
547
+ * POSTs the values to that URL.
548
+ * - `{ via: "client", url }` — same, written out.
549
+ * - `{ via: "client", handler }` — the browser runs your own submit logic.
550
+ * - `{ via: "backend", destination }` — the browser relays the values to the backend,
551
+ * which forwards them to the server-configured destination (URL + secret stay server-side).
552
+ * - `false` — collect only, no submit function.
553
+ */
554
+ declare type SubmitConfig<V = Record<string, unknown>> = string | {
555
+ via: "client";
556
+ url: string;
557
+ } | {
558
+ via: "client";
559
+ handler: (values: V) => Promise<unknown> | unknown;
560
+ } | {
561
+ via: "backend";
562
+ destination: string;
563
+ } | false;
564
+
435
565
  /**
436
566
  * Tool call data from SSE stream
437
567
  */
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./AgoClient-CPqPT5s6.cjs"),m=require("./createFormCollector-DaWZsBsI.cjs"),A=require("./createConversationSession-CGRO0bs2.cjs"),C=require("./createMockClient-zHQ0cjUH.cjs");require("./testing.cjs");const g=require("./functions-DsmzgHjC.cjs");function h(e){return e}function E(e,o){return e.on("message:complete",o),()=>e.off("message:complete",o)}function S(e,o){return e.on("message:chunk",o),()=>e.off("message:chunk",o)}function y(e,o){return e.on("message:start",o),()=>e.off("message:start",o)}function b(e,o){return e.on("message:error",o),()=>e.off("message:error",o)}function v(e,o){return e.on("toolCall:received",o),()=>e.off("toolCall:received",o)}function M(e,o){return e.on("function:invoke",o),()=>e.off("function:invoke",o)}async function*k(e,o,l){const n=[];let r=null,a=!1;const c=s=>{n.push(s),r?.()},f=s=>c({type:"start",data:s}),t=s=>c({type:"chunk",data:s}),u=s=>{c({type:"complete",data:s}),a=!0,r?.()},d=s=>{c({type:"error",data:s}),a=!0,r?.()},p=s=>c({type:"toolCall",data:s});e.on("message:start",f),e.on("message:chunk",t),e.on("message:complete",u),e.on("message:error",d),e.on("toolCall:received",p),e.sendMessage(o,l).catch(()=>{a=!0,r?.()});try{for(;!a||n.length>0;)n.length>0?yield n.shift():(await new Promise(s=>{r=s}),r=null)}finally{e.off("message:start",f),e.off("message:chunk",t),e.off("message:complete",u),e.off("message:error",d),e.off("toolCall:received",p)}}function w(e){let o,l,n,r,a,c;if(typeof window<"u"&&window.AGO){const t=window.AGO;o=t.basepath,l=t.widgetId,n=t.agent||t.defaultAgent,r=t.permission,a=t.email,c=t.jwt}if(typeof document<"u"){const t=u=>document.querySelector(`meta[name="${u}"]`)?.getAttribute("content")||void 0;o=o||t("ago-base-url"),l=l||t("ago-widget-id"),n=n||t("ago-agent")||t("ago-agent-id"),r=r||t("ago-permission"),a=a||t("ago-user-email")}if(typeof document<"u"){const t=[document.body,...Array.from(document.querySelectorAll("script[data-ago-base-url]"))].filter(Boolean);for(const u of t)u&&(o=o||u.getAttribute("data-ago-base-url")||void 0,l=l||u.getAttribute("data-ago-widget-id")||void 0,n=n||u.getAttribute("data-ago-agent")||u.getAttribute("data-ago-agent-id")||void 0,r=r||u.getAttribute("data-ago-permission")||void 0,a=a||u.getAttribute("data-ago-user-email")||void 0)}o=e?.baseUrl||o,l=e?.widgetId||l,n=e?.agent||e?.defaultAgentId||n,r=e?.permission||r,a=e?.userEmail||a,c=e?.userJwt||c;const f=e?.debug;return o?{baseUrl:o,widgetId:l,agent:n,permission:r,userEmail:a,userJwt:c,debug:f}:null}function F(e){const o=w(e);if(!o)throw new Error('createAgo(): could not detect AGO configuration. Set window.AGO, add <meta name="ago-base-url">, or pass { baseUrl } explicitly.');return new i.AgoClient(o)}function q(e,o){return{...e,handler:o}}exports.AgoApiError=i.AgoApiError;exports.AgoClient=i.AgoClient;exports.AgoError=i.AgoError;exports.AgoFunctionError=i.AgoFunctionError;exports.AgoNetworkError=i.AgoNetworkError;exports.AgoStreamError=i.AgoStreamError;exports.ClientContextRegistry=i.ClientContextRegistry;exports.EventEmitter=i.EventEmitter;exports.FunctionRegistry=i.FunctionRegistry;exports.SSEHandler=i.SSEHandler;exports.autoDetectConfig=w;exports.copyToClipboard=g.copyToClipboard;exports.createAgo=F;exports.createConversationSession=A.createConversationSession;exports.createFormCollector=m.createFormCollector;exports.createMessageStream=k;exports.createMockClient=C.createMockClient;exports.createStore=m.createStore;exports.defineFunction=h;exports.deriveFormStatus=m.deriveFormStatus;exports.getLocalStorage=g.getLocalStorage;exports.getUserLocation=g.getUserLocation;exports.highlightElement=g.highlightElement;exports.isStreamNetworkError=i.isStreamNetworkError;exports.logger=i.logger;exports.onFunctionInvoke=M;exports.onMessage=E;exports.onMessageChunk=S;exports.onMessageError=b;exports.onMessageStart=y;exports.onToolCall=v;exports.openUrl=g.openUrl;exports.scrollToElement=g.scrollToElement;exports.setLocalStorage=g.setLocalStorage;exports.setTheme=g.setTheme;exports.showConfirmDialog=g.showConfirmDialog;exports.showNotification=g.showNotification;exports.showToast=g.showToast;exports.submitForm=g.submitForm;exports.trackEvent=g.trackEvent;exports.withHandler=q;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./AgoClient-_fig8FoN.cjs"),m=require("./createFormCollector-CdQjGAl_.cjs"),p=require("./createConversationSession-DJUFgOXy.cjs"),w=require("./createMockClient-zHQ0cjUH.cjs");require("./testing.cjs");const g=require("./functions-DsmzgHjC.cjs");function h(e){return e}function E(e,o){return e.on("message:complete",o),()=>e.off("message:complete",o)}function S(e,o){return e.on("message:chunk",o),()=>e.off("message:chunk",o)}function y(e,o){return e.on("message:start",o),()=>e.off("message:start",o)}function v(e,o){return e.on("message:error",o),()=>e.off("message:error",o)}function b(e,o){return e.on("toolCall:received",o),()=>e.off("toolCall:received",o)}function F(e,o){return e.on("function:invoke",o),()=>e.off("function:invoke",o)}async function*M(e,o,l){const a=[];let r=null,s=!1;const c=i=>{a.push(i),r?.()},f=i=>c({type:"start",data:i}),t=i=>c({type:"chunk",data:i}),u=i=>{c({type:"complete",data:i}),s=!0,r?.()},d=i=>{c({type:"error",data:i}),s=!0,r?.()},C=i=>c({type:"toolCall",data:i});e.on("message:start",f),e.on("message:chunk",t),e.on("message:complete",u),e.on("message:error",d),e.on("toolCall:received",C),e.sendMessage(o,l).catch(()=>{s=!0,r?.()});try{for(;!s||a.length>0;)a.length>0?yield a.shift():(await new Promise(i=>{r=i}),r=null)}finally{e.off("message:start",f),e.off("message:chunk",t),e.off("message:complete",u),e.off("message:error",d),e.off("toolCall:received",C)}}function A(e){let o,l,a,r,s,c;if(typeof window<"u"&&window.AGO){const t=window.AGO;o=t.basepath,l=t.widgetId,a=t.agent||t.defaultAgent,r=t.permission,s=t.email,c=t.jwt}if(typeof document<"u"){const t=u=>document.querySelector(`meta[name="${u}"]`)?.getAttribute("content")||void 0;o=o||t("ago-base-url"),l=l||t("ago-widget-id"),a=a||t("ago-agent")||t("ago-agent-id"),r=r||t("ago-permission"),s=s||t("ago-user-email")}if(typeof document<"u"){const t=[document.body,...Array.from(document.querySelectorAll("script[data-ago-base-url]"))].filter(Boolean);for(const u of t)u&&(o=o||u.getAttribute("data-ago-base-url")||void 0,l=l||u.getAttribute("data-ago-widget-id")||void 0,a=a||u.getAttribute("data-ago-agent")||u.getAttribute("data-ago-agent-id")||void 0,r=r||u.getAttribute("data-ago-permission")||void 0,s=s||u.getAttribute("data-ago-user-email")||void 0)}o=e?.baseUrl||o,l=e?.widgetId||l,a=e?.agent||e?.defaultAgentId||a,r=e?.permission||r,s=e?.userEmail||s,c=e?.userJwt||c;const f=e?.debug;return o?{baseUrl:o,widgetId:l,agent:a,permission:r,userEmail:s,userJwt:c,debug:f}:null}function k(e){const o=A(e);if(!o)throw new n.AgoError('createAgo(): could not detect AGO configuration. Set window.AGO, add <meta name="ago-base-url">, or pass { baseUrl } explicitly.',"config_missing_base_url");return new n.AgoClient(o)}function q(e,o){return{...e,handler:o}}exports.AgoApiError=n.AgoApiError;exports.AgoClient=n.AgoClient;exports.AgoError=n.AgoError;exports.AgoFunctionError=n.AgoFunctionError;exports.AgoNetworkError=n.AgoNetworkError;exports.AgoStreamError=n.AgoStreamError;exports.ClientContextRegistry=n.ClientContextRegistry;exports.EventEmitter=n.EventEmitter;exports.FunctionRegistry=n.FunctionRegistry;exports.SSEHandler=n.SSEHandler;exports.autoDetectConfig=A;exports.copyToClipboard=g.copyToClipboard;exports.createAgo=k;exports.createConversationSession=p.createConversationSession;exports.createFormCollector=m.createFormCollector;exports.createMessageStream=M;exports.createMockClient=w.createMockClient;exports.createStore=m.createStore;exports.defineFunction=h;exports.deriveFormStatus=m.deriveFormStatus;exports.getLocalStorage=g.getLocalStorage;exports.getUserLocation=g.getUserLocation;exports.highlightElement=g.highlightElement;exports.isStreamNetworkError=n.isStreamNetworkError;exports.loadFormCollector=m.loadFormCollector;exports.logger=n.logger;exports.onFunctionInvoke=F;exports.onMessage=E;exports.onMessageChunk=S;exports.onMessageError=v;exports.onMessageStart=y;exports.onToolCall=b;exports.openUrl=g.openUrl;exports.scrollToElement=g.scrollToElement;exports.setLocalStorage=g.setLocalStorage;exports.setTheme=g.setTheme;exports.showConfirmDialog=g.showConfirmDialog;exports.showNotification=g.showNotification;exports.showToast=g.showToast;exports.submitForm=g.submitForm;exports.trackEvent=g.trackEvent;exports.validateConfig=n.validateConfig;exports.withHandler=q;