@fluxyte/sdk 0.9.0 → 0.11.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.
Files changed (49) hide show
  1. package/README.md +727 -666
  2. package/dist/core/client.d.ts +72 -0
  3. package/dist/core/client.d.ts.map +1 -1
  4. package/dist/core/client.js +116 -0
  5. package/dist/core/client.js.map +1 -1
  6. package/dist/core/commerce.d.ts +57 -0
  7. package/dist/core/commerce.d.ts.map +1 -0
  8. package/dist/core/commerce.js +2 -0
  9. package/dist/core/commerce.js.map +1 -0
  10. package/dist/core/errors.d.ts +4 -0
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js +7 -0
  13. package/dist/core/errors.js.map +1 -1
  14. package/dist/core/sse.d.ts +3 -0
  15. package/dist/core/sse.d.ts.map +1 -1
  16. package/dist/core/sse.js.map +1 -1
  17. package/dist/core/taskAssist.d.ts +22 -0
  18. package/dist/core/taskAssist.d.ts.map +1 -0
  19. package/dist/core/taskAssist.js +33 -0
  20. package/dist/core/taskAssist.js.map +1 -0
  21. package/dist/core/utils.d.ts.map +1 -1
  22. package/dist/core/utils.js +7 -3
  23. package/dist/core/utils.js.map +1 -1
  24. package/dist/generated/openapi.d.ts +249 -1
  25. package/dist/generated/openapi.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/react/index.d.ts +1 -0
  31. package/dist/react/index.d.ts.map +1 -1
  32. package/dist/react/index.js +1 -0
  33. package/dist/react/index.js.map +1 -1
  34. package/dist/react/useChat.d.ts +2 -0
  35. package/dist/react/useChat.d.ts.map +1 -1
  36. package/dist/react/useStreamingChat.d.ts +57 -0
  37. package/dist/react/useStreamingChat.d.ts.map +1 -1
  38. package/dist/react/useStreamingChat.js +4 -1
  39. package/dist/react/useStreamingChat.js.map +1 -1
  40. package/dist/react/useTaskAssist.d.ts +277 -0
  41. package/dist/react/useTaskAssist.d.ts.map +1 -0
  42. package/dist/react/useTaskAssist.js +115 -0
  43. package/dist/react/useTaskAssist.js.map +1 -0
  44. package/dist/react/useVoiceChat.d.ts.map +1 -1
  45. package/dist/vanilla/index.d.ts +3 -0
  46. package/dist/vanilla/index.d.ts.map +1 -1
  47. package/dist/vanilla/index.js +1 -0
  48. package/dist/vanilla/index.js.map +1 -1
  49. package/package.json +58 -58
package/README.md CHANGED
@@ -1,666 +1,727 @@
1
- # Fluxyte SDK (TypeScript)
2
-
3
- The official TypeScript SDK for the Fluxyte Onboarding AI API: a context-aware,
4
- documentation-grounded assistant designed for onboarding and setup flows (not generic chat).
5
-
6
- ## Unreleased
7
-
8
- - Chat and voice now use top-level `sessionId` or `userId`; their context is optional.
9
- - `PublicChatContext` contains only validated catalogue/step hints, diagnostics, runtime metadata, locale, and timezone.
10
- - Structured `error` and `attemptedAction` objects provide richer troubleshooting context.
11
- - Named public context, request, response, diagnostic, and event types are exported from all entrypoints.
12
-
13
- ## What's New In 0.7.0
14
-
15
- - Product Catalogue offering cards now expose optional `imageAlt` and `videoUrl` fields.
16
- - Use `imageAlt` when rendering catalogue images and open `videoUrl` as a validated hosted-video destination.
17
- - Existing `imageUrl`, pricing, availability, and Commerce action fields remain backward compatible.
18
-
19
- ```ts
20
- const response = await client.chat({
21
- message: "Show me your plans",
22
- sessionId: "session_123",
23
- });
24
-
25
- for (const offering of response.commerce?.offerings ?? []) {
26
- console.log(offering.imageUrl, offering.imageAlt, offering.videoUrl);
27
- }
28
- ```
29
-
30
- ## What's New In 0.6.0
31
-
32
- - Product Catalogue responses now include typed `commerce` presentation data.
33
- - Commerce presentations contain products/services offerings, prices, availability, features, and safe action IDs.
34
- - `openCommerceAction()` resolves an external checkout only after the API validates the organization, plan, catalogue publication, offering availability, and action.
35
- - `useStreamingChat()` now exposes both `commerce` and `resources` from the completed response.
36
-
37
- ## What's New In 0.5.0
38
-
39
- - Structured resources are available on chat, streaming, and voice responses.
40
- - Supported resource kinds: `VIDEO`, `AUDIO`, `STORE`, `REDIRECT`, `REFERRAL`, and `CHECKOUT`.
41
- - Video and audio resources can include a safe `embedUrl` for inline rendering.
42
- - `useStreamingChat()` now exposes the completed response's `resources`.
43
-
44
- ## Install
45
-
46
- ```bash
47
- npm install @fluxyte/sdk
48
- ```
49
-
50
- ## Requirements
51
-
52
- - Browser or Node.js 18+ (global `fetch` available)
53
- - A Fluxyte API key (sent as `X-API-Key`)
54
- - `pk_*` for browser/public SDK usage
55
- - `sk_*` for backend/server usage only
56
-
57
- ## Authentication (API Key)
58
-
59
- Treat your API key like a password. Do not commit it to source control.
60
-
61
- ### Create an API key
62
-
63
- 1. Sign up at `https://fluxyte.com/signup`
64
- 2. Verify your email
65
- 3. Open the `Account` menu
66
- 4. Select `API Keys`
67
- 5. Click `Create API Key`
68
- 6. Choose key type:
69
- - `pk_*` (PUBLIC) for frontend/browser SDK usage
70
- - `sk_*` (SECRET) for backend/server-only usage
71
- 7. Copy your API key (shown once)
72
-
73
- ### Use an API key
74
-
75
- All integrations authenticate with an API key sent in the `X-API-Key` header.
76
-
77
- - Use `pk_*` keys in browser/public SDK integrations
78
- - Use `sk_*` keys only in backend/server environments
79
- - For `pk_*` keys, configure allowed origins to restrict frontend domains
80
- - The key controls organization access, docs scope, and analytics ownership
81
-
82
- ### Environment examples
83
-
84
- - Vite: `VITE_FLUXYTE_API_KEY=pk_...`
85
- - Next.js: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...` (public key only)
86
- - Server: `FLUXYTE_API_KEY=sk_...`
87
-
88
- The SDK rejects `sk_*` keys in browser environments to prevent accidental exposure.
89
-
90
- ## Key Management
91
-
92
- ### Key Purpose
93
-
94
- - `pk_*` (PUBLIC): for browser/frontend SDK usage.
95
- - `sk_*` (SECRET): for backend/server usage only.
96
-
97
- ### How to get keys
98
-
99
- 1. Sign in to Fluxyte dashboard.
100
- 2. Go to `Account` -> `API Keys`.
101
- 3. Create a key and choose type:
102
- - `PUBLIC (pk_*)` for frontend SDK traffic.
103
- - `SECRET (sk_*)` for backend automation or server integrations.
104
- 4. Copy the key immediately (shown once).
105
-
106
- ### Allowed origins (PUBLIC keys)
107
-
108
- For `pk_*` keys, configure allowed origins in dashboard (for example `https://app.example.com`).
109
- This limits where browser-based SDK requests are accepted from.
110
-
111
- ### Rotation and revocation
112
-
113
- - Rotate keys on a regular schedule (recommended every 60-90 days).
114
- - Keep a short overlap window where old and new keys are both valid during rollout.
115
- - Revoke keys immediately if exposed.
116
- - Update environment variables and redeploy clients after rotation.
117
-
118
- ### Environment examples
119
-
120
- - Frontend: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...`
121
- - Backend: `FLUXYTE_API_KEY=sk_...`
122
-
123
- Never expose `sk_*` in browser bundles, client logs, or public repos.
124
-
125
- ## Choose An Entrypoint
126
-
127
- The SDK ships multiple entrypoints, all with the same API surface:
128
-
129
- - `@fluxyte/sdk/react` (React hooks + provider)
130
- - `@fluxyte/sdk/node` (server-side; validates `fetch` availability)
131
- - `@fluxyte/sdk/vanilla` (browser usage without React)
132
- - `@fluxyte/sdk` (base exports)
133
-
134
- ## Create A Client (Node / Vanilla)
135
-
136
- ```ts
137
- import { OnboardingAIClient } from "@fluxyte/sdk/node";
138
-
139
- const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!);
140
- ```
141
-
142
- ### Client Options (Session Handling)
143
-
144
- The SDK enforces stable identity. Chat and voice accept top-level `sessionId` or
145
- `userId`; onboarding events retain identity inside their event context.
146
-
147
- - By default, it auto-generates and reuses `sessionId` when missing.
148
- - You can disable this with `autoSessionId: false`.
149
-
150
- ```ts
151
- const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!, {
152
- autoSessionId: true, // default
153
- sessionStorageKey: "fluxyte_onboarding_session_id", // browser storage key
154
- });
155
- ```
156
-
157
- ## Chat Completions (Non-Streaming)
158
-
159
- ```ts
160
- const res = await client.chat({
161
- message: "How do I connect my database?",
162
- sessionId: "sess_123",
163
- context: {
164
- stepSlug: "connect_database",
165
- pageUrl: "https://app.example.com/setup/database",
166
- },
167
- });
168
-
169
- console.log(res.answerId);
170
- console.log(res.reply);
171
- console.log(res.confidence);
172
- console.log(res.sources);
173
- console.log(res.resources);
174
- console.log(res.commerce);
175
- ```
176
-
177
- ### Product Catalogue and Commerce
178
-
179
- When an organization has published catalogue items and its plan includes Commerce,
180
- chat responses may include a structured presentation alongside the natural-language
181
- reply:
182
-
183
- ```ts
184
- for (const offering of res.commerce?.offerings ?? []) {
185
- console.log(offering.name);
186
- console.log(offering.priceAmountMinor, offering.currency);
187
- console.log(offering.availability);
188
- console.log(offering.features);
189
- console.log(offering.actions);
190
- }
191
-
192
- if (res.commerce?.hasMore) {
193
- // Send a normal semantic continuation, such as "Show me more".
194
- }
195
- ```
196
-
197
- The API returns at most five offerings for catalogue discovery at a time. Do not
198
- implement client-side slicing or infer the next page; send the user's semantic
199
- continuation so the server can advance durable Commerce state.
200
-
201
- Catalogue actions are typed as:
202
-
203
- - `EXTERNAL_CHECKOUT` — resolve through `openCommerceAction()`.
204
- - `REQUEST_QUOTE` — send the action label/intention through chat.
205
- - `CONTACT_SALES` — send the action label/intention through chat.
206
-
207
- Only external checkout is a redirect. Never construct or cache checkout URLs from
208
- catalogue data. Resolve the selected action immediately before navigation:
209
-
210
- ```ts
211
- const action = offering.actions.find(
212
- (candidate) => candidate.kind === "EXTERNAL_CHECKOUT",
213
- );
214
-
215
- if (action) {
216
- const redirect = await client.openCommerceAction(action.id, "sess_123");
217
- if (redirect.kind === "REDIRECT") {
218
- window.location.assign(redirect.url);
219
- }
220
- }
221
- ```
222
-
223
- ### Rich Content Resources
224
-
225
- When approved knowledge or a flow step includes a related resource, the response
226
- can contain:
227
-
228
- ```ts
229
- type ContentResource = {
230
- id: string;
231
- kind: "VIDEO" | "AUDIO" | "STORE" | "REDIRECT" | "REFERRAL" | "CHECKOUT";
232
- url: string;
233
- label: string;
234
- embedUrl?: string | null;
235
- };
236
- ```
237
-
238
- Use `url` for links and actions. Use `embedUrl` only when it is present; it is
239
- the server-approved URL intended for an embedded video or audio player.
240
-
241
- ```ts
242
- for (const resource of res.resources ?? []) {
243
- if (resource.embedUrl) {
244
- renderEmbeddedMedia(resource.embedUrl, resource.label);
245
- } else {
246
- renderResourceLink(resource.url, resource.label);
247
- }
248
- }
249
- ```
250
-
251
- ## Chat Completions (Streaming via SSE)
252
-
253
- Streaming is useful when you want:
254
-
255
- - live typing UX
256
- - progressive rendering
257
- - early access to `answerId` (for feedback)
258
-
259
- Streaming lifecycle:
260
-
261
- 1. `meta` (contains `answerId`)
262
- 2. `delta` (partial text; one or more)
263
- 3. `done` (confidence, sources, resources, commerce)
264
-
265
- ```ts
266
- const stop = client.streamChat(
267
- {
268
- message: "What's the next step?",
269
- sessionId: "sess_123",
270
- context: { stepSlug: "connect_database" },
271
- },
272
- (event) => {
273
- if (event.type === "meta") console.log("Answer ID:", event.answerId);
274
- if (event.type === "delta") process.stdout.write(event.replyDelta);
275
- if (event.type === "done") {
276
- console.log("\nConfidence:", event.confidence);
277
- console.log("Sources:", event.sources);
278
- console.log("Resources:", event.resources);
279
- console.log("Commerce:", event.commerce);
280
- }
281
- },
282
- (err) => {
283
- // Optional: handle auth/network/server errors.
284
- console.error("Streaming error:", err);
285
- },
286
- );
287
-
288
- // stop() cancels streaming
289
- ```
290
-
291
- ## Voice Chat (Speech In + Optional Speech Out)
292
-
293
- ```ts
294
- const voice = await client.voiceChat({
295
- audioBase64: "BASE64_AUDIO",
296
- mimeType: "audio/webm",
297
- userId: "user_123",
298
- context: { stepSlug: "connect_database", locale: "en-NG" },
299
- synthesize: true,
300
- voice: "alloy",
301
- audioFormat: "mp3",
302
- });
303
-
304
- console.log(voice.transcript); // text recognized from audio
305
- console.log(voice.reply); // AI text reply
306
- console.log(voice.replyAudioBase64); // optional synthesized voice
307
- console.log(voice.resources); // approved related links or embedded media
308
- ```
309
-
310
- ## Context
311
-
312
- Context is optional for chat and voice requests. Identity is separate and required;
313
- the SDK auto-generates a reusable `sessionId` unless `autoSessionId` is disabled.
314
-
315
- ```ts
316
- type PublicChatContext = {
317
- catalogueSubjectId?: string;
318
- stepSlug?: string;
319
- endpoint?: string;
320
- pageUrl?: string;
321
- fieldHelp?: {
322
- fieldKey: string;
323
- label?: string;
324
- task?: string;
325
- currentValue?: string;
326
- };
327
- error?: {
328
- code?: string;
329
- description?: string;
330
- field?: string;
331
- source?: "client" | "server" | "network" | "third_party";
332
- httpStatus?: number;
333
- retryable?: boolean;
334
- };
335
- attemptedAction?: {
336
- name: string;
337
- description?: string;
338
- target?: string;
339
- status?: "started" | "blocked" | "failed" | "completed";
340
- };
341
- sdk?: "react" | "node" | "vanilla" | "rest";
342
- sdkVersion?: string;
343
- appVersion?: string;
344
- environment?: "development" | "staging" | "production";
345
- locale?: string;
346
- timezone?: string;
347
- };
348
- ```
349
-
350
- Best practices:
351
-
352
- - Pass `catalogueSubjectId` when the customer is viewing a published product or service.
353
- - Pass `stepSlug` when the customer is inside a published onboarding step.
354
- - Pass `fieldHelp` for headless field-level guidance. Your application owns the button, layout, and any field update.
355
- - Keep `sessionId` and `userId` at the request's top level, outside context.
356
- - Treat context like application state (update it as the user moves)
357
- - For task assistance or troubleshooting, pass non-secret diagnostic context when known
358
-
359
- Identity guidance:
360
-
361
- - Anonymous visitors: let SDK auto-manage `sessionId` or pass your own persisted ID.
362
- - Logged-in users: pass a stable top-level `userId`.
363
-
364
- ### Localization & Timezone
365
-
366
- Pass optional localization and timezone fields so the API can tailor user-facing
367
- formatting and time-aware utility responses:
368
-
369
- ```ts
370
- context: {
371
- locale: "en-US", // browser/client locale for user-facing formatting
372
- timezone: "Africa/Lagos", // IANA timezone for the current user
373
- }
374
- ```
375
-
376
- - `locale` — browser/client locale (for example `en-US`, `en-NG`) used for user-facing formatting.
377
- - `timezone` — IANA timezone for the current user (for example `America/New_York`, `Africa/Lagos`, `Europe/London`).
378
-
379
- ### Task and troubleshooting context
380
-
381
- The public assistant can use structured context to understand the user's current task
382
- and troubleshoot with fewer clarification turns. Pass only non-secret values:
383
-
384
- ```ts
385
- await client.chat({
386
- message: "I get a 401 when I send my first message",
387
- userId: "user_123",
388
- context: {
389
- sdk: "react",
390
- sdkVersion: "0.8.0",
391
- appVersion: "2026.08.30",
392
- environment: "production",
393
- endpoint: "/v1/chat/completions",
394
- pageUrl: "https://app.example.com/setup/chat",
395
- error: {
396
- code: "AUTHENTICATION_FAILED",
397
- description: "The first chat request returned 401 after the user saved their public API key.",
398
- source: "server",
399
- httpStatus: 401,
400
- retryable: false,
401
- },
402
- attemptedAction: {
403
- name: "send_first_chat_message",
404
- description: "Send the first message from the React setup screen.",
405
- target: "Public AI chat",
406
- status: "failed",
407
- },
408
- },
409
- });
410
- ```
411
-
412
- - `sdk` identifies the client or entrypoint in use.
413
- - `sdkVersion` identifies the Fluxyte SDK version and `appVersion` the integrating app.
414
- - `environment` is `development`, `staging`, or `production`.
415
- - `endpoint` identifies the affected API route or operation.
416
- - `error` carries structured, customer-safe diagnostics, including a comprehensive description.
417
- - `attemptedAction` describes the action, target, and outcome without duplicating the user's message.
418
-
419
- Do not put API keys, access tokens, passwords, request bodies containing personal
420
- data, or other secrets in context.
421
-
422
- ### Headless field help
423
-
424
- The SDK does not render field-help buttons or mutate form values. Build the UI that
425
- fits your application and send the current field as context:
426
-
427
- ```ts
428
- await client.chat({
429
- message: "What should I enter here?",
430
- context: {
431
- stepSlug: "business-details",
432
- pageUrl: window.location.href,
433
- fieldHelp: {
434
- fieldKey: "annualRevenue",
435
- label: "Annual revenue",
436
- task: "Completing a business loan application",
437
- currentValue: "50000",
438
- },
439
- },
440
- });
441
- ```
442
-
443
- `fieldHelp` improves routing but is not evidence. When `stepSlug` resolves to an
444
- authored input field, its server-owned definition remains authoritative. Never send
445
- passwords, tokens, payment credentials, identity secrets, or other sensitive values.
446
-
447
- #### Field-help properties
448
-
449
- | Property | Required | Purpose |
450
- | --- | --- | --- |
451
- | `fieldKey` | Yes | Stable form or property key used to identify the field. |
452
- | `label` | No | Customer-visible label that helps interpret unfamiliar field keys. |
453
- | `task` | No | Short description of the form or task the customer is completing. |
454
- | `currentValue` | No | Non-sensitive value useful for explaining formatting or validation. |
455
-
456
- Use `stepSlug` whenever the field belongs to a published Fluxyte flow step. Public AI
457
- validates that step against the organization's catalogue and uses its authored input
458
- definition when the `fieldKey` matches. Without `stepSlug`, field help can still use
459
- approved Knowledge content, but the client-provided label, task, and value never become
460
- authoritative evidence.
461
-
462
- Field-help requests continue through the standard Public AI architecture:
463
-
464
- - Flow or Task Assist handles guidance tied to an authored flow step.
465
- - Knowledge handles documented field meaning and requirements.
466
- - Troubleshooting handles rejected values and field-related errors.
467
- - Utility is not selected merely because a field contains a number, date, or currency.
468
- - The normal evidence, policy, multilingual, and response-composition stages remain active.
469
-
470
- The same context works with `chat`, `streamChat`, and `voiceChat`:
471
-
472
- ```ts
473
- const context: PublicChatContext = {
474
- stepSlug: "business-details",
475
- fieldHelp: {
476
- fieldKey: "annualRevenue",
477
- label: "Annual revenue",
478
- task: "Completing a business loan application",
479
- },
480
- };
481
-
482
- const answer = await client.chat({
483
- message: "What does this field mean?",
484
- context,
485
- });
486
-
487
- const stop = client.streamChat(
488
- { message: "What format should I use?", context },
489
- (event) => console.log(event),
490
- );
491
-
492
- const voice = await client.voiceChat({
493
- audioBase64,
494
- mimeType: "audio/webm",
495
- context,
496
- });
497
- ```
498
-
499
- Your application decides whether field help appears as an icon, tooltip, side panel,
500
- chat prompt, or another interface. Responses are guidance only: do not update a field
501
- unless your application explicitly validates the value and the customer accepts it.
502
-
503
- Security rules:
504
-
505
- - Omit `currentValue` unless it materially improves the answer.
506
- - Never send passwords, passcodes, tokens, API keys, PINs, OTPs, card details,
507
- government identifiers, private documents, or hidden form values.
508
- - Prefer a customer-safe error description through `context.error` when troubleshooting.
509
- - Keep stable user/session identity at the request top level, not inside `fieldHelp`.
510
-
511
- ## Onboarding Events
512
-
513
- Events power onboarding analytics, drop-off detection, and AI insights.
514
-
515
- Supported event types:
516
-
517
- 1. `FLOW_STARTED`
518
-
519
- - User begins a tracked onboarding flow.
520
- - Send once when the flow is entered.
521
-
522
- 2. `STEP_VIEWED`
523
-
524
- - User lands on or opens a specific step.
525
- - Send whenever the current step changes.
526
-
527
- 3. `STEP_COMPLETED`
528
-
529
- - User successfully completes a step.
530
- - Send only after verified completion signal.
531
-
532
- 4. `FLOW_COMPLETED`
533
-
534
- - User completes all required steps in the flow.
535
- - Send once at successful finish.
536
-
537
- 5. `ABANDONED`
538
-
539
- - User exits/stalls before completion.
540
- - Send when inactivity timeout or explicit exit indicates drop-off.
541
-
542
- Recommended minimum context for event quality:
543
-
544
- - `flowId`
545
- - `stepId` (for step-level events)
546
- - stable identity (`context.sessionId` or `context.userId`)
547
-
548
- Typical event sequence:
549
-
550
- 1. `FLOW_STARTED`
551
- 2. `STEP_VIEWED` (step 1)
552
- 3. `STEP_COMPLETED` (step 1)
553
- 4. `STEP_VIEWED` (step 2)
554
- 5. `STEP_COMPLETED` (step 2)
555
- 6. `FLOW_COMPLETED`
556
-
557
- Drop-off sequence example:
558
-
559
- 1. `FLOW_STARTED`
560
- 2. `STEP_VIEWED` (step 1)
561
- 3. `STEP_VIEWED` (step 2)
562
- 4. `ABANDONED`
563
-
564
- ```ts
565
- await client.sendEvent({
566
- type: "STEP_COMPLETED",
567
- context: {
568
- flowId: "premium-support-setup",
569
- stepId: "connect-database",
570
- userId: "customer_123",
571
- },
572
- });
573
- ```
574
-
575
- ## Feedback
576
-
577
- ```ts
578
- await client.submitFeedback({
579
- answerId: "ans_7xk9p2m",
580
- rating: "GOOD",
581
- comment: "Clear explanation and actionable advice",
582
- });
583
- ```
584
-
585
- ## React Usage (Recommended)
586
-
587
- ```tsx
588
- import { OnboardingAIClient } from "@fluxyte/sdk";
589
- import { OnboardingAIProvider } from "@fluxyte/sdk/react";
590
-
591
- const client = new OnboardingAIClient(import.meta.env.VITE_FLUXYTE_API_KEY);
592
-
593
- export function App() {
594
- return (
595
- <OnboardingAIProvider client={client}>
596
- {/* your app */}
597
- </OnboardingAIProvider>
598
- );
599
- }
600
- ```
601
-
602
- ### React Hooks
603
-
604
- ```tsx
605
- import { useChat } from "@fluxyte/sdk/react";
606
-
607
- const { send, data, loading, error } = useChat();
608
-
609
- await send({
610
- message: "How do I connect my database?",
611
- sessionId: "sess_123",
612
- context: { stepSlug: "connect_database" },
613
- });
614
- ```
615
-
616
- ```tsx
617
- import { useStreamingChat } from "@fluxyte/sdk/react";
618
-
619
- const { text, answerId, confidence, resources, commerce, streaming, start, stop } =
620
- useStreamingChat();
621
-
622
- start({
623
- message: "What's the next step?",
624
- context: { stepSlug: "connect_database" },
625
- });
626
- ```
627
-
628
- ```tsx
629
- import { useVoiceChat } from "@fluxyte/sdk/react";
630
-
631
- const { sendVoice, loading, data, error } = useVoiceChat();
632
-
633
- const res = await sendVoice({
634
- audioBase64: "BASE64_AUDIO",
635
- mimeType: "audio/webm",
636
- context: { stepSlug: "connect_database" },
637
- synthesize: true,
638
- voice: "alloy",
639
- audioFormat: "mp3",
640
- });
641
-
642
- console.log(res.transcript);
643
- console.log(res.reply);
644
- ```
645
-
646
- ## Multi-Target
647
-
648
- Use `context.product` or `context.service` to isolate onboarding targets under one account (apps, APIs, and services).
649
-
650
- ## Error Handling
651
-
652
- ```ts
653
- import { APIError } from "@fluxyte/sdk";
654
-
655
- try {
656
- await client.chat(/* ... */);
657
- } catch (err) {
658
- if (err instanceof APIError) {
659
- console.error(err.status, err.message);
660
- }
661
- }
662
- ```
663
-
664
- ## License
665
-
666
- Commercial. See `LICENSE.md`.
1
+ # Fluxyte SDK (TypeScript)
2
+
3
+ The official TypeScript SDK for the Fluxyte Onboarding AI API: a context-aware,
4
+ documentation-grounded assistant designed for onboarding and setup flows (not generic chat).
5
+
6
+ ## Unreleased
7
+
8
+ - Commerce cart SDK methods and typed server-authoritative cart contracts are available on this feature branch (not yet published).
9
+
10
+ ## Task Assist operations (unreleased)
11
+
12
+ Customers can complete organization-approved tasks (book, reschedule, update…). Nothing executes until the customer confirms the exact details they reviewed; protected tasks also require the organization's own authorization. Show success only when `status === "COMPLETED"`.
13
+
14
+ ```tsx
15
+ import { useTaskAssist } from "@fluxyte/sdk/react";
16
+
17
+ function BookAppointment() {
18
+ const { operation, start, submitInputs, confirm, cancel, requestAuthorization, loading, error } = useTaskAssist();
19
+
20
+ if (!operation) return <button onClick={() => start("book-appointment", { date: "2026-12-01" })}>Book</button>;
21
+ if (operation.status === "NEEDS_INPUT") return <MissingFields fields={operation.missing} onSubmit={submitInputs} />;
22
+ if (operation.status === "NEEDS_CONFIRMATION") return <Review operation={operation} onConfirm={confirm} onCancel={cancel} />;
23
+ if (operation.status === "WAITING_AUTHORIZATION") {
24
+ return <button onClick={async () => { const link = await requestAuthorization(window.location.href); if (link) window.open(link.url, "_blank", "noopener"); }}>Continue securely</button>;
25
+ }
26
+ if (operation.status === "COMPLETED") return <p>Done.</p>;
27
+ return <p aria-live="polite">{error?.message ?? "Working on it…"}</p>;
28
+ }
29
+ ```
30
+
31
+ - `confirm()` sends the digest of the review currently displayed; if the details changed, the API returns `OPERATION_CONFLICT` and you show the new review.
32
+ - The hook polls with backoff only while the system is working, and stops at final states or when the customer must act.
33
+ - Errors expose `error.code` (stable, e.g. `CONFIRMATION_EXPIRED`, `EXECUTION_LIMIT_REACHED`) and `error.requestId`.
34
+ - Node/vanilla: `client.createTaskAssistOperation()` returns `{ operation, handle }`; keep `handle.token` server-side or in memory only.
35
+ - Chat: streaming `done` events now include `presentation` (including `TASK_OPERATION` cards); follow them with `client.getTaskAssistSessionOperation(operationId)`.
36
+
37
+ ## Commerce cart SDK (unreleased)
38
+
39
+ The Commerce cart is **server-authoritative** and separate from the ordinary chat session. Use a public `pk_*` API key, and keep the server-minted cart credential private. Do not log, publish, or place it in URLs. The SDK does not automatically persist this bearer credential.
40
+
41
+ ```ts
42
+ import { OnboardingAIClient } from "@fluxyte/sdk";
43
+
44
+ const client = new OnboardingAIClient("pk_YOUR_PUBLIC_KEY");
45
+ const session = await client.createCommerceCartSession();
46
+ let credential = session.credential;
47
+ let cart = session.cart;
48
+
49
+ const result = await client.mutateCommerceCart({
50
+ credential,
51
+ expectedRevision: cart.revision,
52
+ requestKey: crypto.randomUUID(), // reuse this key when retrying the SAME mutation
53
+ mutation: { kind: "ADD", offeringId: "PUBLISHED_OFFERING_ID", quantity: 2 },
54
+ });
55
+ cart = result.cart;
56
+
57
+ const preflight = await client.preflightCommerceCheckout({
58
+ credential,
59
+ expectedRevision: cart.revision,
60
+ });
61
+ // Preflight is a server-side quote/eligibility check, NOT an order or payment confirmation.
62
+ ```
63
+
64
+ Available helpers: `createCommerceCartSession()`, `renewCommerceCartSession(credential)`, `readCommerceCart(credential)`, `mutateCommerceCart({ credential, expectedRevision, requestKey, mutation })`, and `preflightCommerceCheckout({ credential, expectedRevision })`. Mutations support `ADD`, `SET_QUANTITY`, and `REMOVE`. The backend validates offering availability, prices, currency, revision, and idempotency. On a stale revision, read the latest cart before deciding whether to issue a **new** mutation; do not blindly replay a different action under the same request key. Renewal requires an unexpired credential and does not revoke the prior credential.
65
+
66
+ These methods require a deployed backend with the Commerce cart routes and configured session signing/rate-limit infrastructure. They do not initiate payment or guarantee checkout success. Existing chat, streaming, voice and legacy Commerce action APIs remain available. No package release has been published for these unreleased changes.
67
+
68
+
69
+ - Chat and voice now use top-level `sessionId` or `userId`; their context is optional.
70
+ - `PublicChatContext` contains only validated catalogue/step hints, diagnostics, runtime metadata, locale, and timezone.
71
+ - Structured `error` and `attemptedAction` objects provide richer troubleshooting context.
72
+ - Named public context, request, response, diagnostic, and event types are exported from all entrypoints.
73
+
74
+ ## What's New In 0.7.0
75
+
76
+ - Product Catalogue offering cards now expose optional `imageAlt` and `videoUrl` fields.
77
+ - Use `imageAlt` when rendering catalogue images and open `videoUrl` as a validated hosted-video destination.
78
+ - Existing `imageUrl`, pricing, availability, and Commerce action fields remain backward compatible.
79
+
80
+ ```ts
81
+ const response = await client.chat({
82
+ message: "Show me your plans",
83
+ sessionId: "session_123",
84
+ });
85
+
86
+ for (const offering of response.commerce?.offerings ?? []) {
87
+ console.log(offering.imageUrl, offering.imageAlt, offering.videoUrl);
88
+ }
89
+ ```
90
+
91
+ ## What's New In 0.6.0
92
+
93
+ - Product Catalogue responses now include typed `commerce` presentation data.
94
+ - Commerce presentations contain products/services offerings, prices, availability, features, and safe action IDs.
95
+ - `openCommerceAction()` resolves an external checkout only after the API validates the organization, plan, catalogue publication, offering availability, and action.
96
+ - `useStreamingChat()` now exposes both `commerce` and `resources` from the completed response.
97
+
98
+ ## What's New In 0.5.0
99
+
100
+ - Structured resources are available on chat, streaming, and voice responses.
101
+ - Supported resource kinds: `VIDEO`, `AUDIO`, `STORE`, `REDIRECT`, `REFERRAL`, and `CHECKOUT`.
102
+ - Video and audio resources can include a safe `embedUrl` for inline rendering.
103
+ - `useStreamingChat()` now exposes the completed response's `resources`.
104
+
105
+ ## Install
106
+
107
+ ```bash
108
+ npm install @fluxyte/sdk
109
+ ```
110
+
111
+ ## Requirements
112
+
113
+ - Browser or Node.js 18+ (global `fetch` available)
114
+ - A Fluxyte API key (sent as `X-API-Key`)
115
+ - `pk_*` for browser/public SDK usage
116
+ - `sk_*` for backend/server usage only
117
+
118
+ ## Authentication (API Key)
119
+
120
+ Treat your API key like a password. Do not commit it to source control.
121
+
122
+ ### Create an API key
123
+
124
+ 1. Sign up at `https://fluxyte.com/signup`
125
+ 2. Verify your email
126
+ 3. Open the `Account` menu
127
+ 4. Select `API Keys`
128
+ 5. Click `Create API Key`
129
+ 6. Choose key type:
130
+ - `pk_*` (PUBLIC) for frontend/browser SDK usage
131
+ - `sk_*` (SECRET) for backend/server-only usage
132
+ 7. Copy your API key (shown once)
133
+
134
+ ### Use an API key
135
+
136
+ All integrations authenticate with an API key sent in the `X-API-Key` header.
137
+
138
+ - Use `pk_*` keys in browser/public SDK integrations
139
+ - Use `sk_*` keys only in backend/server environments
140
+ - For `pk_*` keys, configure allowed origins to restrict frontend domains
141
+ - The key controls organization access, docs scope, and analytics ownership
142
+
143
+ ### Environment examples
144
+
145
+ - Vite: `VITE_FLUXYTE_API_KEY=pk_...`
146
+ - Next.js: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...` (public key only)
147
+ - Server: `FLUXYTE_API_KEY=sk_...`
148
+
149
+ The SDK rejects `sk_*` keys in browser environments to prevent accidental exposure.
150
+
151
+ ## Key Management
152
+
153
+ ### Key Purpose
154
+
155
+ - `pk_*` (PUBLIC): for browser/frontend SDK usage.
156
+ - `sk_*` (SECRET): for backend/server usage only.
157
+
158
+ ### How to get keys
159
+
160
+ 1. Sign in to Fluxyte dashboard.
161
+ 2. Go to `Account` -> `API Keys`.
162
+ 3. Create a key and choose type:
163
+ - `PUBLIC (pk_*)` for frontend SDK traffic.
164
+ - `SECRET (sk_*)` for backend automation or server integrations.
165
+ 4. Copy the key immediately (shown once).
166
+
167
+ ### Allowed origins (PUBLIC keys)
168
+
169
+ For `pk_*` keys, configure allowed origins in dashboard (for example `https://app.example.com`).
170
+ This limits where browser-based SDK requests are accepted from.
171
+
172
+ ### Rotation and revocation
173
+
174
+ - Rotate keys on a regular schedule (recommended every 60-90 days).
175
+ - Keep a short overlap window where old and new keys are both valid during rollout.
176
+ - Revoke keys immediately if exposed.
177
+ - Update environment variables and redeploy clients after rotation.
178
+
179
+ ### Environment examples
180
+
181
+ - Frontend: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...`
182
+ - Backend: `FLUXYTE_API_KEY=sk_...`
183
+
184
+ Never expose `sk_*` in browser bundles, client logs, or public repos.
185
+
186
+ ## Choose An Entrypoint
187
+
188
+ The SDK ships multiple entrypoints, all with the same API surface:
189
+
190
+ - `@fluxyte/sdk/react` (React hooks + provider)
191
+ - `@fluxyte/sdk/node` (server-side; validates `fetch` availability)
192
+ - `@fluxyte/sdk/vanilla` (browser usage without React)
193
+ - `@fluxyte/sdk` (base exports)
194
+
195
+ ## Create A Client (Node / Vanilla)
196
+
197
+ ```ts
198
+ import { OnboardingAIClient } from "@fluxyte/sdk/node";
199
+
200
+ const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!);
201
+ ```
202
+
203
+ ### Client Options (Session Handling)
204
+
205
+ The SDK enforces stable identity. Chat and voice accept top-level `sessionId` or
206
+ `userId`; onboarding events retain identity inside their event context.
207
+
208
+ - By default, it auto-generates and reuses `sessionId` when missing.
209
+ - You can disable this with `autoSessionId: false`.
210
+
211
+ ```ts
212
+ const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!, {
213
+ autoSessionId: true, // default
214
+ sessionStorageKey: "fluxyte_onboarding_session_id", // browser storage key
215
+ });
216
+ ```
217
+
218
+ ## Chat Completions (Non-Streaming)
219
+
220
+ ```ts
221
+ const res = await client.chat({
222
+ message: "How do I connect my database?",
223
+ sessionId: "sess_123",
224
+ context: {
225
+ stepSlug: "connect_database",
226
+ pageUrl: "https://app.example.com/setup/database",
227
+ },
228
+ });
229
+
230
+ console.log(res.answerId);
231
+ console.log(res.reply);
232
+ console.log(res.confidence);
233
+ console.log(res.sources);
234
+ console.log(res.resources);
235
+ console.log(res.commerce);
236
+ ```
237
+
238
+ ### Product Catalogue and Commerce
239
+
240
+ When an organization has published catalogue items and its plan includes Commerce,
241
+ chat responses may include a structured presentation alongside the natural-language
242
+ reply:
243
+
244
+ ```ts
245
+ for (const offering of res.commerce?.offerings ?? []) {
246
+ console.log(offering.name);
247
+ console.log(offering.priceAmountMinor, offering.currency);
248
+ console.log(offering.availability);
249
+ console.log(offering.features);
250
+ console.log(offering.actions);
251
+ }
252
+
253
+ if (res.commerce?.hasMore) {
254
+ // Send a normal semantic continuation, such as "Show me more".
255
+ }
256
+ ```
257
+
258
+ The API returns at most five offerings for catalogue discovery at a time. Do not
259
+ implement client-side slicing or infer the next page; send the user's semantic
260
+ continuation so the server can advance durable Commerce state.
261
+
262
+ Catalogue actions are typed as:
263
+
264
+ - `EXTERNAL_CHECKOUT` — resolve through `openCommerceAction()`.
265
+ - `REQUEST_QUOTE` — send the action label/intention through chat.
266
+ - `CONTACT_SALES` — send the action label/intention through chat.
267
+
268
+ Only external checkout is a redirect. Never construct or cache checkout URLs from
269
+ catalogue data. Resolve the selected action immediately before navigation:
270
+
271
+ ```ts
272
+ const action = offering.actions.find(
273
+ (candidate) => candidate.kind === "EXTERNAL_CHECKOUT",
274
+ );
275
+
276
+ if (action) {
277
+ const redirect = await client.openCommerceAction(action.id, "sess_123");
278
+ if (redirect.kind === "REDIRECT") {
279
+ window.location.assign(redirect.url);
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Rich Content Resources
285
+
286
+ When approved knowledge or a flow step includes a related resource, the response
287
+ can contain:
288
+
289
+ ```ts
290
+ type ContentResource = {
291
+ id: string;
292
+ kind: "VIDEO" | "AUDIO" | "STORE" | "REDIRECT" | "REFERRAL" | "CHECKOUT";
293
+ url: string;
294
+ label: string;
295
+ embedUrl?: string | null;
296
+ };
297
+ ```
298
+
299
+ Use `url` for links and actions. Use `embedUrl` only when it is present; it is
300
+ the server-approved URL intended for an embedded video or audio player.
301
+
302
+ ```ts
303
+ for (const resource of res.resources ?? []) {
304
+ if (resource.embedUrl) {
305
+ renderEmbeddedMedia(resource.embedUrl, resource.label);
306
+ } else {
307
+ renderResourceLink(resource.url, resource.label);
308
+ }
309
+ }
310
+ ```
311
+
312
+ ## Chat Completions (Streaming via SSE)
313
+
314
+ Streaming is useful when you want:
315
+
316
+ - live typing UX
317
+ - progressive rendering
318
+ - early access to `answerId` (for feedback)
319
+
320
+ Streaming lifecycle:
321
+
322
+ 1. `meta` (contains `answerId`)
323
+ 2. `delta` (partial text; one or more)
324
+ 3. `done` (confidence, sources, resources, commerce)
325
+
326
+ ```ts
327
+ const stop = client.streamChat(
328
+ {
329
+ message: "What's the next step?",
330
+ sessionId: "sess_123",
331
+ context: { stepSlug: "connect_database" },
332
+ },
333
+ (event) => {
334
+ if (event.type === "meta") console.log("Answer ID:", event.answerId);
335
+ if (event.type === "delta") process.stdout.write(event.replyDelta);
336
+ if (event.type === "done") {
337
+ console.log("\nConfidence:", event.confidence);
338
+ console.log("Sources:", event.sources);
339
+ console.log("Resources:", event.resources);
340
+ console.log("Commerce:", event.commerce);
341
+ }
342
+ },
343
+ (err) => {
344
+ // Optional: handle auth/network/server errors.
345
+ console.error("Streaming error:", err);
346
+ },
347
+ );
348
+
349
+ // stop() cancels streaming
350
+ ```
351
+
352
+ ## Voice Chat (Speech In + Optional Speech Out)
353
+
354
+ ```ts
355
+ const voice = await client.voiceChat({
356
+ audioBase64: "BASE64_AUDIO",
357
+ mimeType: "audio/webm",
358
+ userId: "user_123",
359
+ context: { stepSlug: "connect_database", locale: "en-NG" },
360
+ synthesize: true,
361
+ voice: "alloy",
362
+ audioFormat: "mp3",
363
+ });
364
+
365
+ console.log(voice.transcript); // text recognized from audio
366
+ console.log(voice.reply); // AI text reply
367
+ console.log(voice.replyAudioBase64); // optional synthesized voice
368
+ console.log(voice.resources); // approved related links or embedded media
369
+ ```
370
+
371
+ ## Context
372
+
373
+ Context is optional for chat and voice requests. Identity is separate and required;
374
+ the SDK auto-generates a reusable `sessionId` unless `autoSessionId` is disabled.
375
+
376
+ ```ts
377
+ type PublicChatContext = {
378
+ catalogueSubjectId?: string;
379
+ stepSlug?: string;
380
+ endpoint?: string;
381
+ pageUrl?: string;
382
+ fieldHelp?: {
383
+ fieldKey: string;
384
+ label?: string;
385
+ task?: string;
386
+ currentValue?: string;
387
+ };
388
+ error?: {
389
+ code?: string;
390
+ description?: string;
391
+ field?: string;
392
+ source?: "client" | "server" | "network" | "third_party";
393
+ httpStatus?: number;
394
+ retryable?: boolean;
395
+ };
396
+ attemptedAction?: {
397
+ name: string;
398
+ description?: string;
399
+ target?: string;
400
+ status?: "started" | "blocked" | "failed" | "completed";
401
+ };
402
+ sdk?: "react" | "node" | "vanilla" | "rest";
403
+ sdkVersion?: string;
404
+ appVersion?: string;
405
+ environment?: "development" | "staging" | "production";
406
+ locale?: string;
407
+ timezone?: string;
408
+ };
409
+ ```
410
+
411
+ Best practices:
412
+
413
+ - Pass `catalogueSubjectId` when the customer is viewing a published product or service.
414
+ - Pass `stepSlug` when the customer is inside a published onboarding step.
415
+ - Pass `fieldHelp` for headless field-level guidance. Your application owns the button, layout, and any field update.
416
+ - Keep `sessionId` and `userId` at the request's top level, outside context.
417
+ - Treat context like application state (update it as the user moves)
418
+ - For task assistance or troubleshooting, pass non-secret diagnostic context when known
419
+
420
+ Identity guidance:
421
+
422
+ - Anonymous visitors: let SDK auto-manage `sessionId` or pass your own persisted ID.
423
+ - Logged-in users: pass a stable top-level `userId`.
424
+
425
+ ### Localization & Timezone
426
+
427
+ Pass optional localization and timezone fields so the API can tailor user-facing
428
+ formatting and time-aware utility responses:
429
+
430
+ ```ts
431
+ context: {
432
+ locale: "en-US", // browser/client locale for user-facing formatting
433
+ timezone: "Africa/Lagos", // IANA timezone for the current user
434
+ }
435
+ ```
436
+
437
+ - `locale` — browser/client locale (for example `en-US`, `en-NG`) used for user-facing formatting.
438
+ - `timezone` — IANA timezone for the current user (for example `America/New_York`, `Africa/Lagos`, `Europe/London`).
439
+
440
+ ### Task and troubleshooting context
441
+
442
+ The public assistant can use structured context to understand the user's current task
443
+ and troubleshoot with fewer clarification turns. Pass only non-secret values:
444
+
445
+ ```ts
446
+ await client.chat({
447
+ message: "I get a 401 when I send my first message",
448
+ userId: "user_123",
449
+ context: {
450
+ sdk: "react",
451
+ sdkVersion: "0.8.0",
452
+ appVersion: "2026.08.30",
453
+ environment: "production",
454
+ endpoint: "/v1/chat/completions",
455
+ pageUrl: "https://app.example.com/setup/chat",
456
+ error: {
457
+ code: "AUTHENTICATION_FAILED",
458
+ description: "The first chat request returned 401 after the user saved their public API key.",
459
+ source: "server",
460
+ httpStatus: 401,
461
+ retryable: false,
462
+ },
463
+ attemptedAction: {
464
+ name: "send_first_chat_message",
465
+ description: "Send the first message from the React setup screen.",
466
+ target: "Public AI chat",
467
+ status: "failed",
468
+ },
469
+ },
470
+ });
471
+ ```
472
+
473
+ - `sdk` identifies the client or entrypoint in use.
474
+ - `sdkVersion` identifies the Fluxyte SDK version and `appVersion` the integrating app.
475
+ - `environment` is `development`, `staging`, or `production`.
476
+ - `endpoint` identifies the affected API route or operation.
477
+ - `error` carries structured, customer-safe diagnostics, including a comprehensive description.
478
+ - `attemptedAction` describes the action, target, and outcome without duplicating the user's message.
479
+
480
+ Do not put API keys, access tokens, passwords, request bodies containing personal
481
+ data, or other secrets in context.
482
+
483
+ ### Headless field help
484
+
485
+ The SDK does not render field-help buttons or mutate form values. Build the UI that
486
+ fits your application and send the current field as context:
487
+
488
+ ```ts
489
+ await client.chat({
490
+ message: "What should I enter here?",
491
+ context: {
492
+ stepSlug: "business-details",
493
+ pageUrl: window.location.href,
494
+ fieldHelp: {
495
+ fieldKey: "annualRevenue",
496
+ label: "Annual revenue",
497
+ task: "Completing a business loan application",
498
+ currentValue: "50000",
499
+ },
500
+ },
501
+ });
502
+ ```
503
+
504
+ `fieldHelp` improves routing but is not evidence. When `stepSlug` resolves to an
505
+ authored input field, its server-owned definition remains authoritative. Never send
506
+ passwords, tokens, payment credentials, identity secrets, or other sensitive values.
507
+
508
+ #### Field-help properties
509
+
510
+ | Property | Required | Purpose |
511
+ | --- | --- | --- |
512
+ | `fieldKey` | Yes | Stable form or property key used to identify the field. |
513
+ | `label` | No | Customer-visible label that helps interpret unfamiliar field keys. |
514
+ | `task` | No | Short description of the form or task the customer is completing. |
515
+ | `currentValue` | No | Non-sensitive value useful for explaining formatting or validation. |
516
+
517
+ Use `stepSlug` whenever the field belongs to a published Fluxyte flow step. Public AI
518
+ validates that step against the organization's catalogue and uses its authored input
519
+ definition when the `fieldKey` matches. Without `stepSlug`, field help can still use
520
+ approved Knowledge content, but the client-provided label, task, and value never become
521
+ authoritative evidence.
522
+
523
+ Field-help requests continue through the standard Public AI architecture:
524
+
525
+ - Flow or Task Assist handles guidance tied to an authored flow step.
526
+ - Knowledge handles documented field meaning and requirements.
527
+ - Troubleshooting handles rejected values and field-related errors.
528
+ - Utility is not selected merely because a field contains a number, date, or currency.
529
+ - The normal evidence, policy, multilingual, and response-composition stages remain active.
530
+
531
+ The same context works with `chat`, `streamChat`, and `voiceChat`:
532
+
533
+ ```ts
534
+ const context: PublicChatContext = {
535
+ stepSlug: "business-details",
536
+ fieldHelp: {
537
+ fieldKey: "annualRevenue",
538
+ label: "Annual revenue",
539
+ task: "Completing a business loan application",
540
+ },
541
+ };
542
+
543
+ const answer = await client.chat({
544
+ message: "What does this field mean?",
545
+ context,
546
+ });
547
+
548
+ const stop = client.streamChat(
549
+ { message: "What format should I use?", context },
550
+ (event) => console.log(event),
551
+ );
552
+
553
+ const voice = await client.voiceChat({
554
+ audioBase64,
555
+ mimeType: "audio/webm",
556
+ context,
557
+ });
558
+ ```
559
+
560
+ Your application decides whether field help appears as an icon, tooltip, side panel,
561
+ chat prompt, or another interface. Responses are guidance only: do not update a field
562
+ unless your application explicitly validates the value and the customer accepts it.
563
+
564
+ Security rules:
565
+
566
+ - Omit `currentValue` unless it materially improves the answer.
567
+ - Never send passwords, passcodes, tokens, API keys, PINs, OTPs, card details,
568
+ government identifiers, private documents, or hidden form values.
569
+ - Prefer a customer-safe error description through `context.error` when troubleshooting.
570
+ - Keep stable user/session identity at the request top level, not inside `fieldHelp`.
571
+
572
+ ## Onboarding Events
573
+
574
+ Events power onboarding analytics, drop-off detection, and AI insights.
575
+
576
+ Supported event types:
577
+
578
+ 1. `FLOW_STARTED`
579
+
580
+ - User begins a tracked onboarding flow.
581
+ - Send once when the flow is entered.
582
+
583
+ 2. `STEP_VIEWED`
584
+
585
+ - User lands on or opens a specific step.
586
+ - Send whenever the current step changes.
587
+
588
+ 3. `STEP_COMPLETED`
589
+
590
+ - User successfully completes a step.
591
+ - Send only after verified completion signal.
592
+
593
+ 4. `FLOW_COMPLETED`
594
+
595
+ - User completes all required steps in the flow.
596
+ - Send once at successful finish.
597
+
598
+ 5. `ABANDONED`
599
+
600
+ - User exits/stalls before completion.
601
+ - Send when inactivity timeout or explicit exit indicates drop-off.
602
+
603
+ Recommended minimum context for event quality:
604
+
605
+ - `flowId`
606
+ - `stepId` (for step-level events)
607
+ - stable identity (`context.sessionId` or `context.userId`)
608
+
609
+ Typical event sequence:
610
+
611
+ 1. `FLOW_STARTED`
612
+ 2. `STEP_VIEWED` (step 1)
613
+ 3. `STEP_COMPLETED` (step 1)
614
+ 4. `STEP_VIEWED` (step 2)
615
+ 5. `STEP_COMPLETED` (step 2)
616
+ 6. `FLOW_COMPLETED`
617
+
618
+ Drop-off sequence example:
619
+
620
+ 1. `FLOW_STARTED`
621
+ 2. `STEP_VIEWED` (step 1)
622
+ 3. `STEP_VIEWED` (step 2)
623
+ 4. `ABANDONED`
624
+
625
+ ```ts
626
+ await client.sendEvent({
627
+ type: "STEP_COMPLETED",
628
+ context: {
629
+ flowId: "premium-support-setup",
630
+ stepId: "connect-database",
631
+ userId: "customer_123",
632
+ },
633
+ });
634
+ ```
635
+
636
+ ## Feedback
637
+
638
+ ```ts
639
+ await client.submitFeedback({
640
+ answerId: "ans_7xk9p2m",
641
+ rating: "GOOD",
642
+ comment: "Clear explanation and actionable advice",
643
+ });
644
+ ```
645
+
646
+ ## React Usage (Recommended)
647
+
648
+ ```tsx
649
+ import { OnboardingAIClient } from "@fluxyte/sdk";
650
+ import { OnboardingAIProvider } from "@fluxyte/sdk/react";
651
+
652
+ const client = new OnboardingAIClient(import.meta.env.VITE_FLUXYTE_API_KEY);
653
+
654
+ export function App() {
655
+ return (
656
+ <OnboardingAIProvider client={client}>
657
+ {/* your app */}
658
+ </OnboardingAIProvider>
659
+ );
660
+ }
661
+ ```
662
+
663
+ ### React Hooks
664
+
665
+ ```tsx
666
+ import { useChat } from "@fluxyte/sdk/react";
667
+
668
+ const { send, data, loading, error } = useChat();
669
+
670
+ await send({
671
+ message: "How do I connect my database?",
672
+ sessionId: "sess_123",
673
+ context: { stepSlug: "connect_database" },
674
+ });
675
+ ```
676
+
677
+ ```tsx
678
+ import { useStreamingChat } from "@fluxyte/sdk/react";
679
+
680
+ const { text, answerId, confidence, resources, commerce, streaming, start, stop } =
681
+ useStreamingChat();
682
+
683
+ start({
684
+ message: "What's the next step?",
685
+ context: { stepSlug: "connect_database" },
686
+ });
687
+ ```
688
+
689
+ ```tsx
690
+ import { useVoiceChat } from "@fluxyte/sdk/react";
691
+
692
+ const { sendVoice, loading, data, error } = useVoiceChat();
693
+
694
+ const res = await sendVoice({
695
+ audioBase64: "BASE64_AUDIO",
696
+ mimeType: "audio/webm",
697
+ context: { stepSlug: "connect_database" },
698
+ synthesize: true,
699
+ voice: "alloy",
700
+ audioFormat: "mp3",
701
+ });
702
+
703
+ console.log(res.transcript);
704
+ console.log(res.reply);
705
+ ```
706
+
707
+ ## Multi-Target
708
+
709
+ Use `context.product` or `context.service` to isolate onboarding targets under one account (apps, APIs, and services).
710
+
711
+ ## Error Handling
712
+
713
+ ```ts
714
+ import { APIError } from "@fluxyte/sdk";
715
+
716
+ try {
717
+ await client.chat(/* ... */);
718
+ } catch (err) {
719
+ if (err instanceof APIError) {
720
+ console.error(err.status, err.message);
721
+ }
722
+ }
723
+ ```
724
+
725
+ ## License
726
+
727
+ Commercial. See `LICENSE.md`.