@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.
package/dist/react.d.cts CHANGED
@@ -18,12 +18,26 @@ declare class AgoClient {
18
18
  private contextRegistry;
19
19
  private eventEmitter;
20
20
  private config;
21
+ /** Conversations already warned about an empty reply (one warning each). */
22
+ private warnedEmptyConversations;
21
23
  constructor(config: AgoConfig);
22
24
  /**
23
25
  * Send a message and receive a streaming response
24
26
  */
25
27
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
26
28
  private processSSEResponse;
29
+ /**
30
+ * Detect a reply that completed as DONE with no content and no activity —
31
+ * today's backend answers an unknown `agent` slug with an empty 200, which
32
+ * otherwise fails silently. Emits `message:empty` (always) and warns on the
33
+ * console once per conversation (unless `warnOnEmptyReply: false`).
34
+ *
35
+ * Deferred with setTimeout(0) so both land AFTER `sendMessage` resolves:
36
+ * `await client.sendMessage(...)` followed by `client.waitFor("message:empty")`
37
+ * must not race. TEMPORARY heuristic: remove once the backend returns an
38
+ * explicit error for unknown agents (see TODOS.md, backend issue).
39
+ */
40
+ private maybeFlagEmptyReply;
27
41
  private handleClientFunctionInvocation;
28
42
  /**
29
43
  * Get list of conversations
@@ -55,6 +69,13 @@ declare class AgoClient {
55
69
  * Reject a tool call
56
70
  */
57
71
  rejectToolCall(toolCallId: string): Promise<void>;
72
+ /**
73
+ * Fetch a backend-stored form collector definition by name (the single source
74
+ * of truth for the form's description, schema, and submit target). Used by
75
+ * `loadFormCollector` / the name-only form of `useFormCollector` so the schema
76
+ * lives in the backend instead of being hardcoded in client code.
77
+ */
78
+ getFormCollector(name: string): Promise<FormCollectorDefinition>;
58
79
  /**
59
80
  * Relay a completed form collector's values to a server-configured destination.
60
81
  *
@@ -189,6 +210,11 @@ declare class AgoClient {
189
210
  }): Promise<AgoClientEvents[K]>;
190
211
  /**
191
212
  * Update client configuration
213
+ *
214
+ * Validates the MERGED config before committing: omitting a key (or passing
215
+ * it as `undefined`, common with spread-built partials) keeps the current
216
+ * value; an explicit empty/non-string `baseUrl` throws `config_missing_base_url`
217
+ * and leaves the client unchanged (no half-updated state).
192
218
  */
193
219
  updateConfig(config: Partial<AgoConfig>): void;
194
220
  /**
@@ -224,8 +250,23 @@ declare interface AgoClientEvents {
224
250
  */
225
251
  "message:answer-complete": AgoMessage;
226
252
  "message:complete": AgoMessage;
253
+ /**
254
+ * A reply finished as `DONE` with empty content and no tool calls, client
255
+ * functions, or follow-up replies — usually an unknown `agent` slug (the
256
+ * backend currently answers those with an empty 200). Fires after
257
+ * `message:complete` AND after `sendMessage` resolves, so subscribing right
258
+ * after `await sendMessage(...)` still catches it. `messageId` and
259
+ * `conversationId` are empty strings when the stream completed without any
260
+ * message data at all (e.g. a proxy stripped the SSE stream).
261
+ */
262
+ "message:empty": {
263
+ conversationId: string;
264
+ messageId: string;
265
+ };
227
266
  "message:error": {
228
267
  error: string;
268
+ /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
269
+ code?: string;
229
270
  conversationId?: string;
230
271
  messageId?: string;
231
272
  };
@@ -254,7 +295,7 @@ declare interface AgoClientEvents {
254
295
  export declare interface AgoConfig {
255
296
  /** API base URL (e.g., https://YOUR-DOMAIN.useago.com) */
256
297
  baseUrl: string;
257
- /** Widget ID (X-Widget-Id header). Auto-generated if not provided. */
298
+ /** Per-visitor anonymous ID (X-User-Anon-Id header). Auto-generated if not provided. */
258
299
  widgetId?: string;
259
300
  /** Default agent (id or slug) for new conversations. Shorthand for `defaultAgentId`. */
260
301
  agent?: string;
@@ -268,6 +309,13 @@ export declare interface AgoConfig {
268
309
  userJwt?: string;
269
310
  /** Enable debug logging */
270
311
  debug?: boolean;
312
+ /**
313
+ * Warn on the console when a reply completes with empty content and no tool
314
+ * calls (the usual cause is an unknown `agent` slug, which the backend
315
+ * currently answers with an empty 200). Default `true`. The `message:empty`
316
+ * event fires regardless of this setting.
317
+ */
318
+ warnOnEmptyReply?: boolean;
271
319
  }
272
320
 
273
321
  /**
@@ -395,10 +443,13 @@ export declare interface ChatWidgetProps {
395
443
  showAgentName?: boolean;
396
444
  /**
397
445
  * Conversational forms the agent can fill and submit during the chat. Each
398
- * config is installed as a {@link createFormCollector} (its `update_<name>` /
446
+ * entry is installed as a {@link createFormCollector} (its `update_<name>` /
399
447
  * `submit_<name>` functions + dynamic context) for the lifetime of the widget.
448
+ *
449
+ * Pass a full config (with `schema`) to define it inline, or just `{ name }` to
450
+ * fetch the definition from the backend ({@link loadFormCollector}).
400
451
  */
401
- forms?: CreateFormCollectorOptions[];
452
+ forms?: Array<CreateFormCollectorOptions | LoadFormCollectorOptions>;
402
453
  /**
403
454
  * How clicking a suggested follow-up reply behaves. Defaults to sending the
404
455
  * reply as a new user message. Pass a handler to override, or `false` to
@@ -592,6 +643,22 @@ export declare interface FormCollector<V = Record<string, unknown>> {
592
643
  getValues: () => Partial<V>;
593
644
  setValues: (patch: Partial<V>) => void;
594
645
  reset: () => void;
646
+ /** Completeness status (`missing`/`complete`) derived from the current values + schema. */
647
+ getStatus: () => FormCollectorStatus;
648
+ }
649
+
650
+ /**
651
+ * The backend-stored shape returned by `GET /api/sdk/v1/forms/{name}` (see
652
+ * {@link AgoClient.getFormCollector}). Mirrors the persistable subset of
653
+ * {@link CreateFormCollectorOptions}: the `submit` here is never the client-only
654
+ * `handler` variant (a function can't be stored server-side).
655
+ */
656
+ export declare interface FormCollectorDefinition<V = Record<string, unknown>> {
657
+ name: string;
658
+ description: string;
659
+ schema: FormCollectorSchema;
660
+ /** Server-stored submit target, if any. Absent means collect-only. */
661
+ submit?: SubmitConfig<V>;
595
662
  }
596
663
 
597
664
  /**
@@ -682,6 +749,30 @@ export declare interface FormSubmitResult {
682
749
  error?: string;
683
750
  }
684
751
 
752
+ /**
753
+ * Async sibling of {@link createFormCollector} that pulls the form's definition
754
+ * (description + schema + submit target) from the backend by name — the single
755
+ * source of truth — instead of hardcoding it in client code. Any field passed in
756
+ * `options` overrides the fetched one (e.g. a client-only `submit` handler or
757
+ * `initialValues`).
758
+ *
759
+ * ```ts
760
+ * const order = await loadFormCollector(client, { name: "order" });
761
+ * order.install(client);
762
+ * ```
763
+ */
764
+ export declare function loadFormCollector<V = Record<string, unknown>>(client: Pick<AgoClient, "getFormCollector">, options: LoadFormCollectorOptions<V>): Promise<FormCollector<V>>;
765
+
766
+ /**
767
+ * Options for {@link loadFormCollector} / the name-only form of `useFormCollector`:
768
+ * the `name` is required and the definition is fetched from the backend, while any
769
+ * field here overrides the fetched one — notably a client-only `submit` handler,
770
+ * which can't be stored server-side.
771
+ */
772
+ export declare interface LoadFormCollectorOptions<V = Record<string, unknown>> extends Partial<Omit<CreateFormCollectorOptions<V>, "name">> {
773
+ name: string;
774
+ }
775
+
685
776
  /**
686
777
  * Renders markdown content with GitHub-flavored markdown support
687
778
  * (tables, task lists, strikethrough, autolinks) and inline-styled
@@ -707,12 +798,6 @@ export declare interface MessageProps {
707
798
  * render on the last message. Defaults to `false`.
708
799
  */
709
800
  isLast?: boolean;
710
- /**
711
- * The main answer is done but its follow-up replies are still being generated.
712
- * On the last message this renders the "..." indicator in the follow-up slot
713
- * until the suggestions arrive. Defaults to `false`.
714
- */
715
- isAwaitingFollowUps?: boolean;
716
801
  /**
717
802
  * Called when the user clicks a suggested follow-up reply. When omitted the
718
803
  * follow-up buttons render but are not interactive (backwards-compatible).
@@ -826,9 +911,18 @@ declare type ToolCallType = "form" | "confirmation_input" | "status_message" | "
826
911
 
827
912
  /**
828
913
  * Hook to create and manage an AgoClient instance
914
+ *
915
+ * Pass `null` to skip client creation entirely (used by `AgoProvider` when an
916
+ * external client is supplied — constructing a throwaway client would waste
917
+ * resources and fail `baseUrl` validation).
829
918
  */
830
919
  export declare function useAgo(config: UseAgoOptions): UseAgoResult;
831
920
 
921
+ export declare function useAgo(config: UseAgoOptions | null): {
922
+ client: AgoClient | null;
923
+ isReady: boolean;
924
+ };
925
+
832
926
  /**
833
927
  * Returns the AgoClient from the nearest AgoProvider.
834
928
  * Throws if used outside of an AgoProvider.
@@ -1039,32 +1133,26 @@ export declare interface UseConversationResult {
1039
1133
  * functions + dynamic context on mount, removes them on unmount, and returns the
1040
1134
  * live store state.
1041
1135
  *
1042
- * The collector is recreated only when `name`, the schema, or the submit target
1043
- * changes. Keep the schema and submit object stable (declare them outside render
1044
- * or memoize them) so an inline `handler` isn't captured stale.
1136
+ * Two modes, by whether you pass a `schema`:
1137
+ *
1138
+ * - **Inline** — pass `{ name, description, schema, submit? }` and the collector is
1139
+ * built synchronously (recreated only when `name`, the schema, or the submit
1140
+ * target changes; keep them stable). `loading` is always `false`.
1141
+ * - **Backend** — pass `{ name }` (no `schema`) and the definition is fetched from
1142
+ * the backend ({@link loadFormCollector}) so the schema lives server-side, not in
1143
+ * client code. `loading` is `true` until it resolves; any field you do pass
1144
+ * overrides the fetched one (e.g. a client-only `submit` handler).
1045
1145
  *
1046
1146
  * ```tsx
1047
- * const order = useFormCollector({
1048
- * name: "order",
1049
- * description: "The order the user wants to place",
1050
- * schema: {
1051
- * type: "object",
1052
- * properties: { product: { type: "string" }, quantity: { type: "number" } },
1053
- * required: ["product", "quantity"],
1054
- * },
1055
- * submit: { via: "backend", destination: "order_webhook" },
1056
- * });
1147
+ * // Backend-defined form — schema lives in the backend:
1148
+ * const order = useFormCollector({ name: "order" });
1057
1149
  *
1058
- * return (
1059
- * <div>
1060
- * {order.state.missing.length > 0
1061
- * ? `Still need: ${order.state.missing.join(", ")}`
1062
- * : "Ready to submit"}
1063
- * </div>
1064
- * );
1150
+ * return order.loading
1151
+ * ? <p>Loading…</p>
1152
+ * : <p>{order.state.missing.length ? `Still need: ${order.state.missing.join(", ")}` : "Ready"}</p>;
1065
1153
  * ```
1066
1154
  */
1067
- export declare function useFormCollector<V = Record<string, unknown>>(options: CreateFormCollectorOptions<V>): UseFormCollectorResult<V>;
1155
+ export declare function useFormCollector<V = Record<string, unknown>>(options: CreateFormCollectorOptions<V> | LoadFormCollectorOptions<V>): UseFormCollectorResult<V>;
1068
1156
 
1069
1157
  export declare interface UseFormCollectorResult<V = Record<string, unknown>> extends FormCollector<V> {
1070
1158
  /**
@@ -1072,6 +1160,13 @@ export declare interface UseFormCollectorResult<V = Record<string, unknown>> ext
1072
1160
  * status (`missing`, `complete`) derived on read — re-renders on every change.
1073
1161
  */
1074
1162
  state: FormCollectorState<V> & FormCollectorStatus;
1163
+ /**
1164
+ * True while the backend definition is being fetched (name-only usage). Always
1165
+ * `false` when an inline `schema` is provided.
1166
+ */
1167
+ loading: boolean;
1168
+ /** Set if fetching the backend definition failed. */
1169
+ error: Error | null;
1075
1170
  }
1076
1171
 
1077
1172
  /**
package/dist/react.d.ts CHANGED
@@ -18,12 +18,26 @@ declare class AgoClient {
18
18
  private contextRegistry;
19
19
  private eventEmitter;
20
20
  private config;
21
+ /** Conversations already warned about an empty reply (one warning each). */
22
+ private warnedEmptyConversations;
21
23
  constructor(config: AgoConfig);
22
24
  /**
23
25
  * Send a message and receive a streaming response
24
26
  */
25
27
  sendMessage(content: string, options?: SendMessageOptions): Promise<AgoMessage>;
26
28
  private processSSEResponse;
29
+ /**
30
+ * Detect a reply that completed as DONE with no content and no activity —
31
+ * today's backend answers an unknown `agent` slug with an empty 200, which
32
+ * otherwise fails silently. Emits `message:empty` (always) and warns on the
33
+ * console once per conversation (unless `warnOnEmptyReply: false`).
34
+ *
35
+ * Deferred with setTimeout(0) so both land AFTER `sendMessage` resolves:
36
+ * `await client.sendMessage(...)` followed by `client.waitFor("message:empty")`
37
+ * must not race. TEMPORARY heuristic: remove once the backend returns an
38
+ * explicit error for unknown agents (see TODOS.md, backend issue).
39
+ */
40
+ private maybeFlagEmptyReply;
27
41
  private handleClientFunctionInvocation;
28
42
  /**
29
43
  * Get list of conversations
@@ -55,6 +69,13 @@ declare class AgoClient {
55
69
  * Reject a tool call
56
70
  */
57
71
  rejectToolCall(toolCallId: string): Promise<void>;
72
+ /**
73
+ * Fetch a backend-stored form collector definition by name (the single source
74
+ * of truth for the form's description, schema, and submit target). Used by
75
+ * `loadFormCollector` / the name-only form of `useFormCollector` so the schema
76
+ * lives in the backend instead of being hardcoded in client code.
77
+ */
78
+ getFormCollector(name: string): Promise<FormCollectorDefinition>;
58
79
  /**
59
80
  * Relay a completed form collector's values to a server-configured destination.
60
81
  *
@@ -189,6 +210,11 @@ declare class AgoClient {
189
210
  }): Promise<AgoClientEvents[K]>;
190
211
  /**
191
212
  * Update client configuration
213
+ *
214
+ * Validates the MERGED config before committing: omitting a key (or passing
215
+ * it as `undefined`, common with spread-built partials) keeps the current
216
+ * value; an explicit empty/non-string `baseUrl` throws `config_missing_base_url`
217
+ * and leaves the client unchanged (no half-updated state).
192
218
  */
193
219
  updateConfig(config: Partial<AgoConfig>): void;
194
220
  /**
@@ -224,8 +250,23 @@ declare interface AgoClientEvents {
224
250
  */
225
251
  "message:answer-complete": AgoMessage;
226
252
  "message:complete": AgoMessage;
253
+ /**
254
+ * A reply finished as `DONE` with empty content and no tool calls, client
255
+ * functions, or follow-up replies — usually an unknown `agent` slug (the
256
+ * backend currently answers those with an empty 200). Fires after
257
+ * `message:complete` AND after `sendMessage` resolves, so subscribing right
258
+ * after `await sendMessage(...)` still catches it. `messageId` and
259
+ * `conversationId` are empty strings when the stream completed without any
260
+ * message data at all (e.g. a proxy stripped the SSE stream).
261
+ */
262
+ "message:empty": {
263
+ conversationId: string;
264
+ messageId: string;
265
+ };
227
266
  "message:error": {
228
267
  error: string;
268
+ /** Stable error code (see configuration.md#error-codes) when the failure was an AgoError. */
269
+ code?: string;
229
270
  conversationId?: string;
230
271
  messageId?: string;
231
272
  };
@@ -254,7 +295,7 @@ declare interface AgoClientEvents {
254
295
  export declare interface AgoConfig {
255
296
  /** API base URL (e.g., https://YOUR-DOMAIN.useago.com) */
256
297
  baseUrl: string;
257
- /** Widget ID (X-Widget-Id header). Auto-generated if not provided. */
298
+ /** Per-visitor anonymous ID (X-User-Anon-Id header). Auto-generated if not provided. */
258
299
  widgetId?: string;
259
300
  /** Default agent (id or slug) for new conversations. Shorthand for `defaultAgentId`. */
260
301
  agent?: string;
@@ -268,6 +309,13 @@ export declare interface AgoConfig {
268
309
  userJwt?: string;
269
310
  /** Enable debug logging */
270
311
  debug?: boolean;
312
+ /**
313
+ * Warn on the console when a reply completes with empty content and no tool
314
+ * calls (the usual cause is an unknown `agent` slug, which the backend
315
+ * currently answers with an empty 200). Default `true`. The `message:empty`
316
+ * event fires regardless of this setting.
317
+ */
318
+ warnOnEmptyReply?: boolean;
271
319
  }
272
320
 
273
321
  /**
@@ -395,10 +443,13 @@ export declare interface ChatWidgetProps {
395
443
  showAgentName?: boolean;
396
444
  /**
397
445
  * Conversational forms the agent can fill and submit during the chat. Each
398
- * config is installed as a {@link createFormCollector} (its `update_<name>` /
446
+ * entry is installed as a {@link createFormCollector} (its `update_<name>` /
399
447
  * `submit_<name>` functions + dynamic context) for the lifetime of the widget.
448
+ *
449
+ * Pass a full config (with `schema`) to define it inline, or just `{ name }` to
450
+ * fetch the definition from the backend ({@link loadFormCollector}).
400
451
  */
401
- forms?: CreateFormCollectorOptions[];
452
+ forms?: Array<CreateFormCollectorOptions | LoadFormCollectorOptions>;
402
453
  /**
403
454
  * How clicking a suggested follow-up reply behaves. Defaults to sending the
404
455
  * reply as a new user message. Pass a handler to override, or `false` to
@@ -592,6 +643,22 @@ export declare interface FormCollector<V = Record<string, unknown>> {
592
643
  getValues: () => Partial<V>;
593
644
  setValues: (patch: Partial<V>) => void;
594
645
  reset: () => void;
646
+ /** Completeness status (`missing`/`complete`) derived from the current values + schema. */
647
+ getStatus: () => FormCollectorStatus;
648
+ }
649
+
650
+ /**
651
+ * The backend-stored shape returned by `GET /api/sdk/v1/forms/{name}` (see
652
+ * {@link AgoClient.getFormCollector}). Mirrors the persistable subset of
653
+ * {@link CreateFormCollectorOptions}: the `submit` here is never the client-only
654
+ * `handler` variant (a function can't be stored server-side).
655
+ */
656
+ export declare interface FormCollectorDefinition<V = Record<string, unknown>> {
657
+ name: string;
658
+ description: string;
659
+ schema: FormCollectorSchema;
660
+ /** Server-stored submit target, if any. Absent means collect-only. */
661
+ submit?: SubmitConfig<V>;
595
662
  }
596
663
 
597
664
  /**
@@ -682,6 +749,30 @@ export declare interface FormSubmitResult {
682
749
  error?: string;
683
750
  }
684
751
 
752
+ /**
753
+ * Async sibling of {@link createFormCollector} that pulls the form's definition
754
+ * (description + schema + submit target) from the backend by name — the single
755
+ * source of truth — instead of hardcoding it in client code. Any field passed in
756
+ * `options` overrides the fetched one (e.g. a client-only `submit` handler or
757
+ * `initialValues`).
758
+ *
759
+ * ```ts
760
+ * const order = await loadFormCollector(client, { name: "order" });
761
+ * order.install(client);
762
+ * ```
763
+ */
764
+ export declare function loadFormCollector<V = Record<string, unknown>>(client: Pick<AgoClient, "getFormCollector">, options: LoadFormCollectorOptions<V>): Promise<FormCollector<V>>;
765
+
766
+ /**
767
+ * Options for {@link loadFormCollector} / the name-only form of `useFormCollector`:
768
+ * the `name` is required and the definition is fetched from the backend, while any
769
+ * field here overrides the fetched one — notably a client-only `submit` handler,
770
+ * which can't be stored server-side.
771
+ */
772
+ export declare interface LoadFormCollectorOptions<V = Record<string, unknown>> extends Partial<Omit<CreateFormCollectorOptions<V>, "name">> {
773
+ name: string;
774
+ }
775
+
685
776
  /**
686
777
  * Renders markdown content with GitHub-flavored markdown support
687
778
  * (tables, task lists, strikethrough, autolinks) and inline-styled
@@ -707,12 +798,6 @@ export declare interface MessageProps {
707
798
  * render on the last message. Defaults to `false`.
708
799
  */
709
800
  isLast?: boolean;
710
- /**
711
- * The main answer is done but its follow-up replies are still being generated.
712
- * On the last message this renders the "..." indicator in the follow-up slot
713
- * until the suggestions arrive. Defaults to `false`.
714
- */
715
- isAwaitingFollowUps?: boolean;
716
801
  /**
717
802
  * Called when the user clicks a suggested follow-up reply. When omitted the
718
803
  * follow-up buttons render but are not interactive (backwards-compatible).
@@ -826,9 +911,18 @@ declare type ToolCallType = "form" | "confirmation_input" | "status_message" | "
826
911
 
827
912
  /**
828
913
  * Hook to create and manage an AgoClient instance
914
+ *
915
+ * Pass `null` to skip client creation entirely (used by `AgoProvider` when an
916
+ * external client is supplied — constructing a throwaway client would waste
917
+ * resources and fail `baseUrl` validation).
829
918
  */
830
919
  export declare function useAgo(config: UseAgoOptions): UseAgoResult;
831
920
 
921
+ export declare function useAgo(config: UseAgoOptions | null): {
922
+ client: AgoClient | null;
923
+ isReady: boolean;
924
+ };
925
+
832
926
  /**
833
927
  * Returns the AgoClient from the nearest AgoProvider.
834
928
  * Throws if used outside of an AgoProvider.
@@ -1039,32 +1133,26 @@ export declare interface UseConversationResult {
1039
1133
  * functions + dynamic context on mount, removes them on unmount, and returns the
1040
1134
  * live store state.
1041
1135
  *
1042
- * The collector is recreated only when `name`, the schema, or the submit target
1043
- * changes. Keep the schema and submit object stable (declare them outside render
1044
- * or memoize them) so an inline `handler` isn't captured stale.
1136
+ * Two modes, by whether you pass a `schema`:
1137
+ *
1138
+ * - **Inline** — pass `{ name, description, schema, submit? }` and the collector is
1139
+ * built synchronously (recreated only when `name`, the schema, or the submit
1140
+ * target changes; keep them stable). `loading` is always `false`.
1141
+ * - **Backend** — pass `{ name }` (no `schema`) and the definition is fetched from
1142
+ * the backend ({@link loadFormCollector}) so the schema lives server-side, not in
1143
+ * client code. `loading` is `true` until it resolves; any field you do pass
1144
+ * overrides the fetched one (e.g. a client-only `submit` handler).
1045
1145
  *
1046
1146
  * ```tsx
1047
- * const order = useFormCollector({
1048
- * name: "order",
1049
- * description: "The order the user wants to place",
1050
- * schema: {
1051
- * type: "object",
1052
- * properties: { product: { type: "string" }, quantity: { type: "number" } },
1053
- * required: ["product", "quantity"],
1054
- * },
1055
- * submit: { via: "backend", destination: "order_webhook" },
1056
- * });
1147
+ * // Backend-defined form — schema lives in the backend:
1148
+ * const order = useFormCollector({ name: "order" });
1057
1149
  *
1058
- * return (
1059
- * <div>
1060
- * {order.state.missing.length > 0
1061
- * ? `Still need: ${order.state.missing.join(", ")}`
1062
- * : "Ready to submit"}
1063
- * </div>
1064
- * );
1150
+ * return order.loading
1151
+ * ? <p>Loading…</p>
1152
+ * : <p>{order.state.missing.length ? `Still need: ${order.state.missing.join(", ")}` : "Ready"}</p>;
1065
1153
  * ```
1066
1154
  */
1067
- export declare function useFormCollector<V = Record<string, unknown>>(options: CreateFormCollectorOptions<V>): UseFormCollectorResult<V>;
1155
+ export declare function useFormCollector<V = Record<string, unknown>>(options: CreateFormCollectorOptions<V> | LoadFormCollectorOptions<V>): UseFormCollectorResult<V>;
1068
1156
 
1069
1157
  export declare interface UseFormCollectorResult<V = Record<string, unknown>> extends FormCollector<V> {
1070
1158
  /**
@@ -1072,6 +1160,13 @@ export declare interface UseFormCollectorResult<V = Record<string, unknown>> ext
1072
1160
  * status (`missing`, `complete`) derived on read — re-renders on every change.
1073
1161
  */
1074
1162
  state: FormCollectorState<V> & FormCollectorStatus;
1163
+ /**
1164
+ * True while the backend definition is being fetched (name-only usage). Always
1165
+ * `false` when an inline `schema` is provided.
1166
+ */
1167
+ loading: boolean;
1168
+ /** Set if fetching the backend definition failed. */
1169
+ error: Error | null;
1075
1170
  }
1076
1171
 
1077
1172
  /**