@mordn/chat-widget 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -275,4 +275,4 @@ interface ChatStore {
275
275
  */
276
276
  type ChatStoreFactory = (userId: string) => ChatStore;
277
277
 
278
- export { type ChatStore as C, type ListMessagesOptions as L, type StoredAttachment as S, type ChatStoreFactory as a, type StoredConversation as b, type StoredMessage as c, type SaveTurnInput as d, ConversationOwnershipError as e };
278
+ export { type ChatStore as C, type ListMessagesOptions as L, type StoredAttachment as S, type StoredConversation as a, type StoredMessage as b, type SaveTurnInput as c, type ChatStoreFactory as d, ConversationOwnershipError as e };
@@ -275,4 +275,4 @@ interface ChatStore {
275
275
  */
276
276
  type ChatStoreFactory = (userId: string) => ChatStore;
277
277
 
278
- export { type ChatStore as C, type ListMessagesOptions as L, type StoredAttachment as S, type ChatStoreFactory as a, type StoredConversation as b, type StoredMessage as c, type SaveTurnInput as d, ConversationOwnershipError as e };
278
+ export { type ChatStore as C, type ListMessagesOptions as L, type StoredAttachment as S, type StoredConversation as a, type StoredMessage as b, type SaveTurnInput as c, type ChatStoreFactory as d, ConversationOwnershipError as e };
@@ -0,0 +1,204 @@
1
+ import { LanguageModel, ToolSet, ModelMessage, UIMessage, StopCondition } from 'ai';
2
+ import { d as ChatStoreFactory } from './chat-store-Dq1Zy_qV.js';
3
+ import { a as StorageAdapterFactory } from './storage-adapter-DD8uqiAP.js';
4
+
5
+ /**
6
+ * Public configuration surface for `createChatHandler`.
7
+ *
8
+ * This is the API a host app touches to mount the chat backend. The design
9
+ * follows one rule, derived from auditing a real power-user integration
10
+ * (Jarvis): take away the shared, dangerous-to-get-wrong plumbing; keep every
11
+ * point of genuine per-app variation as an injection point or a hook.
12
+ *
13
+ * Three tiers of override, from "you must" to "you rarely will":
14
+ *
15
+ * REQUIRED injections — no safe default exists:
16
+ * • getUserId (identity; the security boundary)
17
+ *
18
+ * OPTIONAL injections — a default exists, swap to take control:
19
+ * • model (which LLM)
20
+ * • buildTools (your tools, incl. per-request resources)
21
+ * • store (persistence; hosted default)
22
+ * • storage (attachments; hosted default)
23
+ *
24
+ * HOOKS — the loop runs fine without them; override one seam at a time:
25
+ * • buildSystemPrompt, transformMessages, onChatFinish, onError,
26
+ * stopWhen, upload
27
+ *
28
+ * Everything NOT in this file — ownership checks, idempotency, pagination,
29
+ * re-signing, socket teardown, save-on-finish — is owned by the handler and
30
+ * is intentionally not configurable, because getting it wrong is a security
31
+ * or correctness bug, not a preference.
32
+ */
33
+
34
+ /**
35
+ * Everything a per-request hook/injection needs to know about the current
36
+ * request, assembled by the handler AFTER authentication. Passed to
37
+ * `buildTools`, `model` (when a function), `buildSystemPrompt`, and
38
+ * `transformMessages`.
39
+ *
40
+ * Note `userId` is the server-verified identity — the same value the store
41
+ * and storage are bound to. Hooks can trust it.
42
+ */
43
+ interface ChatRequestContext {
44
+ /** Server-verified user id. Never client-supplied. */
45
+ userId: string;
46
+ /** The conversation this request targets. */
47
+ conversationId: string;
48
+ /** The raw request, for hooks that need headers/cookies (e.g. an org id). */
49
+ request: Request;
50
+ }
51
+ /**
52
+ * Per-agent declarative config returned by a hosted control plane. All fields
53
+ * optional — only what the dashboard has set is present; the rest falls through
54
+ * to code/defaults. `model` is a gateway model string (e.g. "anthropic/…").
55
+ */
56
+ interface HostedAgentConfig {
57
+ model?: string | null;
58
+ systemPrompt?: string | null;
59
+ greeting?: string | null;
60
+ appearance?: Record<string, unknown> | null;
61
+ }
62
+ /**
63
+ * What `buildTools` returns. The `cleanup` callback is the critical piece the
64
+ * naive "just return a ToolSet" design misses: tools backed by a per-request
65
+ * resource (an MCP client holding a socket, a DB transaction, a temp scope)
66
+ * MUST be torn down after the stream finishes — exactly once, whether the
67
+ * stream completed, errored, or the client aborted. The handler guarantees
68
+ * that single, correctly-timed call so the host app never leaks sockets.
69
+ */
70
+ interface BuiltTools {
71
+ tools: ToolSet;
72
+ /**
73
+ * Called exactly once when the request is fully done (success, error, or
74
+ * abort), after the response stream has settled. Optional — omit when your
75
+ * tools hold no per-request resource.
76
+ */
77
+ cleanup?: () => void | Promise<void>;
78
+ }
79
+ /**
80
+ * Server-side upload policy. Enforced by the handler's upload route BEFORE
81
+ * any bytes touch storage, so an oversized or disallowed file is rejected
82
+ * with a clean 4xx instead of a framework body-parse failure. The widget's
83
+ * client-side `accept`/`maxBytes` should mirror these, but the SERVER is the
84
+ * source of truth — the client checks are UX, these checks are the boundary.
85
+ */
86
+ interface UploadPolicy {
87
+ /**
88
+ * Allowed MIME types. An exact-match allow-list (not a prefix match) so
89
+ * `image/svg+xml` can be excluded while `image/png` is allowed. Defaults to
90
+ * a conservative image+pdf set that current vision models accept natively.
91
+ */
92
+ allowedMediaTypes?: string[];
93
+ /** Per-file size cap in bytes. Defaults to 5 MB. */
94
+ maxBytes?: number;
95
+ }
96
+ interface CreateChatHandlerOptions {
97
+ /**
98
+ * Derive the authenticated user's id from the SERVER session — a verified
99
+ * cookie/JWT, Clerk `auth()`, NextAuth `getServerSession()`,
100
+ * `supabase.auth.getUser()`, etc. Return `null` for an unauthenticated
101
+ * request; the handler responds 401.
102
+ *
103
+ * SECURITY: never read the id from the request body, query string, or a
104
+ * header the browser controls (e.g. `X-User-Id`). Those are forgeable and
105
+ * doing so reintroduces the IDOR this whole design exists to prevent. The
106
+ * handler passes you the `Request` so you can read *verified* cookies — not
107
+ * so you can read a client-asserted id.
108
+ */
109
+ getUserId: (request: Request) => Promise<string | null> | string | null;
110
+ /**
111
+ * The model to stream from. Either a fixed `LanguageModel` or a function of
112
+ * the request context (for per-user/per-org model selection). Defaults to a
113
+ * sensible current model when omitted.
114
+ */
115
+ model?: LanguageModel | ((ctx: ChatRequestContext) => LanguageModel | Promise<LanguageModel>);
116
+ /**
117
+ * Build the tool set for this request. Async and context-aware so tools can
118
+ * close over the user and open per-request resources (e.g. an MCP client).
119
+ * Return `{ tools, cleanup }`; the handler calls `cleanup` exactly once when
120
+ * the request settles. Omit for a chat with no tools.
121
+ */
122
+ buildTools?: (ctx: ChatRequestContext) => Promise<BuiltTools> | BuiltTools;
123
+ /**
124
+ * Persistence backend. Omit to use the hosted/default store. Provide a
125
+ * factory to bring your own DB. The factory is called per request with the
126
+ * server-verified `userId`; the handler never constructs a store with a
127
+ * client-supplied id.
128
+ */
129
+ store?: ChatStoreFactory;
130
+ /**
131
+ * Attachment storage backend. Omit to use the hosted/default adapter.
132
+ * Provide a factory for BYO storage (S3/R2/own bucket). Same per-request,
133
+ * verified-userId construction as `store`.
134
+ */
135
+ storage?: StorageAdapterFactory;
136
+ /**
137
+ * Fetch per-agent declarative config (model / systemPrompt / greeting /
138
+ * appearance) from a hosted control plane (mordn's GET /v1/config). This is
139
+ * how dashboard-managed config reaches the loop WITHOUT a redeploy.
140
+ *
141
+ * Precedence is always **code > hosted > package default**: any `model` /
142
+ * `buildSystemPrompt` you pass here in code takes priority; the hosted value
143
+ * only fills what code leaves unset. Returning `null` (or throwing) falls
144
+ * through to code/defaults — a control-plane hiccup never breaks a turn.
145
+ *
146
+ * Use `createHostedConfig({ apiKey })` from `@mordn/chat-widget/server/hosted`
147
+ * to get a ready-made fetcher.
148
+ */
149
+ getHostedConfig?: (ctx: ChatRequestContext) => Promise<HostedAgentConfig | null> | HostedAgentConfig | null;
150
+ /**
151
+ * Produce the system prompt for this request. Receives the context so it
152
+ * can personalise (e.g. bake in the user's name). Defaults to a generic
153
+ * assistant prompt.
154
+ */
155
+ buildSystemPrompt?: (ctx: ChatRequestContext) => string | Promise<string>;
156
+ /**
157
+ * Last-chance transform of the model-ready messages before they're sent to
158
+ * the model — after the handler has applied its own sliding-window prune.
159
+ * Use for provider-specific rewrites (e.g. image file-parts → image-parts)
160
+ * or extra capping. Defaults to identity.
161
+ */
162
+ transformMessages?: (messages: ModelMessage[], ctx: ChatRequestContext) => ModelMessage[] | Promise<ModelMessage[]>;
163
+ /**
164
+ * Called after the assistant turn has been persisted. For telemetry/usage
165
+ * logging. NOT where you save messages — the handler already did that. Any
166
+ * error thrown here is logged and swallowed; it never fails the response
167
+ * (the user already has their answer).
168
+ */
169
+ onChatFinish?: (info: {
170
+ ctx: ChatRequestContext;
171
+ messages: UIMessage[];
172
+ usage?: unknown;
173
+ providerMetadata?: unknown;
174
+ }) => void | Promise<void>;
175
+ /**
176
+ * Map a stream error to the user-facing string the widget shows. Lets you
177
+ * downgrade benign post-finish teardown noise and localise messages.
178
+ * Defaults to a generic message + server-side error log.
179
+ */
180
+ onError?: (error: unknown) => string;
181
+ /**
182
+ * When the model may chain tool calls, how long to let it run before it
183
+ * must answer. Defaults to a bounded step count so a misbehaving tool loop
184
+ * can't run forever. Pass any AI SDK `StopCondition`.
185
+ */
186
+ stopWhen?: StopCondition<ToolSet>;
187
+ /** Server-side upload policy (types + size). See `UploadPolicy`. */
188
+ upload?: UploadPolicy;
189
+ /**
190
+ * How many of the most-recent messages to send to the model (sliding
191
+ * window). Defaults to 30. The handler always prunes; this tunes the
192
+ * window. Older messages stay in the store and in the UI — only the model
193
+ * payload is windowed.
194
+ */
195
+ maxHistoryMessages?: number;
196
+ /**
197
+ * Defensive per-message character cap applied during pruning so a single
198
+ * giant pasted blob can't dominate the context window. Defaults to 4000.
199
+ * Set to `0` to disable.
200
+ */
201
+ maxMessageChars?: number;
202
+ }
203
+
204
+ export type { BuiltTools as B, CreateChatHandlerOptions as C, HostedAgentConfig as H, UploadPolicy as U, ChatRequestContext as a };
@@ -0,0 +1,204 @@
1
+ import { LanguageModel, ToolSet, ModelMessage, UIMessage, StopCondition } from 'ai';
2
+ import { d as ChatStoreFactory } from './chat-store-Dq1Zy_qV.mjs';
3
+ import { a as StorageAdapterFactory } from './storage-adapter-DD8uqiAP.mjs';
4
+
5
+ /**
6
+ * Public configuration surface for `createChatHandler`.
7
+ *
8
+ * This is the API a host app touches to mount the chat backend. The design
9
+ * follows one rule, derived from auditing a real power-user integration
10
+ * (Jarvis): take away the shared, dangerous-to-get-wrong plumbing; keep every
11
+ * point of genuine per-app variation as an injection point or a hook.
12
+ *
13
+ * Three tiers of override, from "you must" to "you rarely will":
14
+ *
15
+ * REQUIRED injections — no safe default exists:
16
+ * • getUserId (identity; the security boundary)
17
+ *
18
+ * OPTIONAL injections — a default exists, swap to take control:
19
+ * • model (which LLM)
20
+ * • buildTools (your tools, incl. per-request resources)
21
+ * • store (persistence; hosted default)
22
+ * • storage (attachments; hosted default)
23
+ *
24
+ * HOOKS — the loop runs fine without them; override one seam at a time:
25
+ * • buildSystemPrompt, transformMessages, onChatFinish, onError,
26
+ * stopWhen, upload
27
+ *
28
+ * Everything NOT in this file — ownership checks, idempotency, pagination,
29
+ * re-signing, socket teardown, save-on-finish — is owned by the handler and
30
+ * is intentionally not configurable, because getting it wrong is a security
31
+ * or correctness bug, not a preference.
32
+ */
33
+
34
+ /**
35
+ * Everything a per-request hook/injection needs to know about the current
36
+ * request, assembled by the handler AFTER authentication. Passed to
37
+ * `buildTools`, `model` (when a function), `buildSystemPrompt`, and
38
+ * `transformMessages`.
39
+ *
40
+ * Note `userId` is the server-verified identity — the same value the store
41
+ * and storage are bound to. Hooks can trust it.
42
+ */
43
+ interface ChatRequestContext {
44
+ /** Server-verified user id. Never client-supplied. */
45
+ userId: string;
46
+ /** The conversation this request targets. */
47
+ conversationId: string;
48
+ /** The raw request, for hooks that need headers/cookies (e.g. an org id). */
49
+ request: Request;
50
+ }
51
+ /**
52
+ * Per-agent declarative config returned by a hosted control plane. All fields
53
+ * optional — only what the dashboard has set is present; the rest falls through
54
+ * to code/defaults. `model` is a gateway model string (e.g. "anthropic/…").
55
+ */
56
+ interface HostedAgentConfig {
57
+ model?: string | null;
58
+ systemPrompt?: string | null;
59
+ greeting?: string | null;
60
+ appearance?: Record<string, unknown> | null;
61
+ }
62
+ /**
63
+ * What `buildTools` returns. The `cleanup` callback is the critical piece the
64
+ * naive "just return a ToolSet" design misses: tools backed by a per-request
65
+ * resource (an MCP client holding a socket, a DB transaction, a temp scope)
66
+ * MUST be torn down after the stream finishes — exactly once, whether the
67
+ * stream completed, errored, or the client aborted. The handler guarantees
68
+ * that single, correctly-timed call so the host app never leaks sockets.
69
+ */
70
+ interface BuiltTools {
71
+ tools: ToolSet;
72
+ /**
73
+ * Called exactly once when the request is fully done (success, error, or
74
+ * abort), after the response stream has settled. Optional — omit when your
75
+ * tools hold no per-request resource.
76
+ */
77
+ cleanup?: () => void | Promise<void>;
78
+ }
79
+ /**
80
+ * Server-side upload policy. Enforced by the handler's upload route BEFORE
81
+ * any bytes touch storage, so an oversized or disallowed file is rejected
82
+ * with a clean 4xx instead of a framework body-parse failure. The widget's
83
+ * client-side `accept`/`maxBytes` should mirror these, but the SERVER is the
84
+ * source of truth — the client checks are UX, these checks are the boundary.
85
+ */
86
+ interface UploadPolicy {
87
+ /**
88
+ * Allowed MIME types. An exact-match allow-list (not a prefix match) so
89
+ * `image/svg+xml` can be excluded while `image/png` is allowed. Defaults to
90
+ * a conservative image+pdf set that current vision models accept natively.
91
+ */
92
+ allowedMediaTypes?: string[];
93
+ /** Per-file size cap in bytes. Defaults to 5 MB. */
94
+ maxBytes?: number;
95
+ }
96
+ interface CreateChatHandlerOptions {
97
+ /**
98
+ * Derive the authenticated user's id from the SERVER session — a verified
99
+ * cookie/JWT, Clerk `auth()`, NextAuth `getServerSession()`,
100
+ * `supabase.auth.getUser()`, etc. Return `null` for an unauthenticated
101
+ * request; the handler responds 401.
102
+ *
103
+ * SECURITY: never read the id from the request body, query string, or a
104
+ * header the browser controls (e.g. `X-User-Id`). Those are forgeable and
105
+ * doing so reintroduces the IDOR this whole design exists to prevent. The
106
+ * handler passes you the `Request` so you can read *verified* cookies — not
107
+ * so you can read a client-asserted id.
108
+ */
109
+ getUserId: (request: Request) => Promise<string | null> | string | null;
110
+ /**
111
+ * The model to stream from. Either a fixed `LanguageModel` or a function of
112
+ * the request context (for per-user/per-org model selection). Defaults to a
113
+ * sensible current model when omitted.
114
+ */
115
+ model?: LanguageModel | ((ctx: ChatRequestContext) => LanguageModel | Promise<LanguageModel>);
116
+ /**
117
+ * Build the tool set for this request. Async and context-aware so tools can
118
+ * close over the user and open per-request resources (e.g. an MCP client).
119
+ * Return `{ tools, cleanup }`; the handler calls `cleanup` exactly once when
120
+ * the request settles. Omit for a chat with no tools.
121
+ */
122
+ buildTools?: (ctx: ChatRequestContext) => Promise<BuiltTools> | BuiltTools;
123
+ /**
124
+ * Persistence backend. Omit to use the hosted/default store. Provide a
125
+ * factory to bring your own DB. The factory is called per request with the
126
+ * server-verified `userId`; the handler never constructs a store with a
127
+ * client-supplied id.
128
+ */
129
+ store?: ChatStoreFactory;
130
+ /**
131
+ * Attachment storage backend. Omit to use the hosted/default adapter.
132
+ * Provide a factory for BYO storage (S3/R2/own bucket). Same per-request,
133
+ * verified-userId construction as `store`.
134
+ */
135
+ storage?: StorageAdapterFactory;
136
+ /**
137
+ * Fetch per-agent declarative config (model / systemPrompt / greeting /
138
+ * appearance) from a hosted control plane (mordn's GET /v1/config). This is
139
+ * how dashboard-managed config reaches the loop WITHOUT a redeploy.
140
+ *
141
+ * Precedence is always **code > hosted > package default**: any `model` /
142
+ * `buildSystemPrompt` you pass here in code takes priority; the hosted value
143
+ * only fills what code leaves unset. Returning `null` (or throwing) falls
144
+ * through to code/defaults — a control-plane hiccup never breaks a turn.
145
+ *
146
+ * Use `createHostedConfig({ apiKey })` from `@mordn/chat-widget/server/hosted`
147
+ * to get a ready-made fetcher.
148
+ */
149
+ getHostedConfig?: (ctx: ChatRequestContext) => Promise<HostedAgentConfig | null> | HostedAgentConfig | null;
150
+ /**
151
+ * Produce the system prompt for this request. Receives the context so it
152
+ * can personalise (e.g. bake in the user's name). Defaults to a generic
153
+ * assistant prompt.
154
+ */
155
+ buildSystemPrompt?: (ctx: ChatRequestContext) => string | Promise<string>;
156
+ /**
157
+ * Last-chance transform of the model-ready messages before they're sent to
158
+ * the model — after the handler has applied its own sliding-window prune.
159
+ * Use for provider-specific rewrites (e.g. image file-parts → image-parts)
160
+ * or extra capping. Defaults to identity.
161
+ */
162
+ transformMessages?: (messages: ModelMessage[], ctx: ChatRequestContext) => ModelMessage[] | Promise<ModelMessage[]>;
163
+ /**
164
+ * Called after the assistant turn has been persisted. For telemetry/usage
165
+ * logging. NOT where you save messages — the handler already did that. Any
166
+ * error thrown here is logged and swallowed; it never fails the response
167
+ * (the user already has their answer).
168
+ */
169
+ onChatFinish?: (info: {
170
+ ctx: ChatRequestContext;
171
+ messages: UIMessage[];
172
+ usage?: unknown;
173
+ providerMetadata?: unknown;
174
+ }) => void | Promise<void>;
175
+ /**
176
+ * Map a stream error to the user-facing string the widget shows. Lets you
177
+ * downgrade benign post-finish teardown noise and localise messages.
178
+ * Defaults to a generic message + server-side error log.
179
+ */
180
+ onError?: (error: unknown) => string;
181
+ /**
182
+ * When the model may chain tool calls, how long to let it run before it
183
+ * must answer. Defaults to a bounded step count so a misbehaving tool loop
184
+ * can't run forever. Pass any AI SDK `StopCondition`.
185
+ */
186
+ stopWhen?: StopCondition<ToolSet>;
187
+ /** Server-side upload policy (types + size). See `UploadPolicy`. */
188
+ upload?: UploadPolicy;
189
+ /**
190
+ * How many of the most-recent messages to send to the model (sliding
191
+ * window). Defaults to 30. The handler always prunes; this tunes the
192
+ * window. Older messages stay in the store and in the UI — only the model
193
+ * payload is windowed.
194
+ */
195
+ maxHistoryMessages?: number;
196
+ /**
197
+ * Defensive per-message character cap applied during pruning so a single
198
+ * giant pasted blob can't dominate the context window. Defaults to 4000.
199
+ * Set to `0` to disable.
200
+ */
201
+ maxMessageChars?: number;
202
+ }
203
+
204
+ export type { BuiltTools as B, CreateChatHandlerOptions as C, HostedAgentConfig as H, UploadPolicy as U, ChatRequestContext as a };
package/dist/index.d.mts CHANGED
@@ -344,11 +344,30 @@ interface ChatWidgetProps extends ChatWidgetConfig {
344
344
  */
345
345
  className?: string;
346
346
  /**
347
- * Widget ID (for loading config from hosted service later)
347
+ * Agent ID identifies which agent this widget talks to. Used to scope the
348
+ * browser cache to (agent, user) so the same browser never surfaces another
349
+ * agent's cached tabs/config, and (later) to load hosted per-agent config.
350
+ */
351
+ agentId?: string;
352
+ /**
353
+ * @deprecated Use `agentId`. Kept as an alias for one minor version.
348
354
  */
349
355
  widgetId?: string;
356
+ /**
357
+ * Base path the widget calls for chat / upload / history. Defaults to
358
+ * `/api/chat`. Override to mount the handler elsewhere (e.g. a dashboard
359
+ * preview at `/api/preview-chat/<agentId>`). The widget appends `/upload`
360
+ * and `/history` to this base.
361
+ */
362
+ apiBase?: string;
363
+ /**
364
+ * Extra headers sent on every chat request. Used by the dashboard playground
365
+ * to pass an unsaved draft (model / system prompt) for an owner-authed
366
+ * preview. Not for normal embeds.
367
+ */
368
+ extraHeaders?: Record<string, string>;
350
369
  }
351
- declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, inputPlugins, toolRenderers, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
370
+ declare function ChatWidget({ userId, agentId, apiBase, extraHeaders, widgetId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, inputPlugins, toolRenderers, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
352
371
 
353
372
  interface ChatTheme {
354
373
  lightPrimary: string;
@@ -398,13 +417,33 @@ declare function useChatTheme(): {
398
417
  };
399
418
 
400
419
  interface ChatStorageContextValue {
401
- storageKeyPrefix: string;
420
+ /**
421
+ * Prefix for all browser-stored chat keys, scoped to (agent, user):
422
+ * `${agentId}-${userId}`. `null` means we do NOT have a complete identity
423
+ * (missing agentId or userId) and callers MUST NOT persist anything — there
424
+ * is deliberately no shared/static fallback bucket, which previously let one
425
+ * user's cached chats leak to another on the same browser.
426
+ */
427
+ storageKeyPrefix: string | null;
402
428
  }
403
- declare function ChatStorageProvider({ children, userId, }: {
429
+ declare function ChatStorageProvider({ children, userId, agentId, }: {
404
430
  children: ReactNode;
405
431
  userId?: string;
432
+ agentId?: string;
406
433
  }): react_jsx_runtime.JSX.Element;
407
434
  declare function useChatStorageKey(): ChatStorageContextValue;
435
+ /**
436
+ * Remove chat data this widget persisted in localStorage. Call this on sign-out
437
+ * / user switch so a subsequent user on the same browser can never see the
438
+ * previous user's conversation tabs, titles, prompts, or model.
439
+ *
440
+ * - No args → clears EVERY `chat-*` key (safe blanket sign-out clear).
441
+ * - `{ agentId, userId }` → clears only that scope's keys (`chat-${agentId}-${userId}-*`).
442
+ */
443
+ declare function clearChatStorage(opts?: {
444
+ agentId?: string;
445
+ userId?: string;
446
+ }): void;
408
447
 
409
448
  declare const buttonVariants: (props?: ({
410
449
  variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
@@ -468,4 +507,4 @@ type ToolOutputProps = ComponentProps<"div"> & {
468
507
  };
469
508
  declare const ToolOutput: ({ className, output, errorText, ...props }: ToolOutputProps) => react_jsx_runtime.JSX.Element | null;
470
509
 
471
- export { Button, ChatStorageProvider, type ChatTheme, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type ChatWidgetSize, type ConversationStarter, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, type DisplayConfig, type FeatureConfig, Input, type InputPlugin, type InputPluginItem, StarterMessageItem, StarterMessages, type StarterPrompt, type ThemeConfig, type ThemeMode, Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, type ToolPartLike, type ToolRenderer, ChatWidget as default, fontOptions, useChatStorageKey, useChatTheme };
510
+ export { Button, ChatStorageProvider, type ChatTheme, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type ChatWidgetSize, type ConversationStarter, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, type DisplayConfig, type FeatureConfig, Input, type InputPlugin, type InputPluginItem, StarterMessageItem, StarterMessages, type StarterPrompt, type ThemeConfig, type ThemeMode, Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, type ToolPartLike, type ToolRenderer, clearChatStorage, ChatWidget as default, fontOptions, useChatStorageKey, useChatTheme };
package/dist/index.d.ts CHANGED
@@ -344,11 +344,30 @@ interface ChatWidgetProps extends ChatWidgetConfig {
344
344
  */
345
345
  className?: string;
346
346
  /**
347
- * Widget ID (for loading config from hosted service later)
347
+ * Agent ID identifies which agent this widget talks to. Used to scope the
348
+ * browser cache to (agent, user) so the same browser never surfaces another
349
+ * agent's cached tabs/config, and (later) to load hosted per-agent config.
350
+ */
351
+ agentId?: string;
352
+ /**
353
+ * @deprecated Use `agentId`. Kept as an alias for one minor version.
348
354
  */
349
355
  widgetId?: string;
356
+ /**
357
+ * Base path the widget calls for chat / upload / history. Defaults to
358
+ * `/api/chat`. Override to mount the handler elsewhere (e.g. a dashboard
359
+ * preview at `/api/preview-chat/<agentId>`). The widget appends `/upload`
360
+ * and `/history` to this base.
361
+ */
362
+ apiBase?: string;
363
+ /**
364
+ * Extra headers sent on every chat request. Used by the dashboard playground
365
+ * to pass an unsaved draft (model / system prompt) for an owner-authed
366
+ * preview. Not for normal embeds.
367
+ */
368
+ extraHeaders?: Record<string, string>;
350
369
  }
351
- declare function ChatWidget({ userId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, inputPlugins, toolRenderers, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
370
+ declare function ChatWidget({ userId, agentId, apiBase, extraHeaders, widgetId, conversationId, initialMessages, className, model, systemPrompt, temperature, theme, features, display, starterPrompts, onClose, headerActions, open, onOpenChange, inputPlugins, toolRenderers, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
352
371
 
353
372
  interface ChatTheme {
354
373
  lightPrimary: string;
@@ -398,13 +417,33 @@ declare function useChatTheme(): {
398
417
  };
399
418
 
400
419
  interface ChatStorageContextValue {
401
- storageKeyPrefix: string;
420
+ /**
421
+ * Prefix for all browser-stored chat keys, scoped to (agent, user):
422
+ * `${agentId}-${userId}`. `null` means we do NOT have a complete identity
423
+ * (missing agentId or userId) and callers MUST NOT persist anything — there
424
+ * is deliberately no shared/static fallback bucket, which previously let one
425
+ * user's cached chats leak to another on the same browser.
426
+ */
427
+ storageKeyPrefix: string | null;
402
428
  }
403
- declare function ChatStorageProvider({ children, userId, }: {
429
+ declare function ChatStorageProvider({ children, userId, agentId, }: {
404
430
  children: ReactNode;
405
431
  userId?: string;
432
+ agentId?: string;
406
433
  }): react_jsx_runtime.JSX.Element;
407
434
  declare function useChatStorageKey(): ChatStorageContextValue;
435
+ /**
436
+ * Remove chat data this widget persisted in localStorage. Call this on sign-out
437
+ * / user switch so a subsequent user on the same browser can never see the
438
+ * previous user's conversation tabs, titles, prompts, or model.
439
+ *
440
+ * - No args → clears EVERY `chat-*` key (safe blanket sign-out clear).
441
+ * - `{ agentId, userId }` → clears only that scope's keys (`chat-${agentId}-${userId}-*`).
442
+ */
443
+ declare function clearChatStorage(opts?: {
444
+ agentId?: string;
445
+ userId?: string;
446
+ }): void;
408
447
 
409
448
  declare const buttonVariants: (props?: ({
410
449
  variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
@@ -468,4 +507,4 @@ type ToolOutputProps = ComponentProps<"div"> & {
468
507
  };
469
508
  declare const ToolOutput: ({ className, output, errorText, ...props }: ToolOutputProps) => react_jsx_runtime.JSX.Element | null;
470
509
 
471
- export { Button, ChatStorageProvider, type ChatTheme, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type ChatWidgetSize, type ConversationStarter, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, type DisplayConfig, type FeatureConfig, Input, type InputPlugin, type InputPluginItem, StarterMessageItem, StarterMessages, type StarterPrompt, type ThemeConfig, type ThemeMode, Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, type ToolPartLike, type ToolRenderer, ChatWidget as default, fontOptions, useChatStorageKey, useChatTheme };
510
+ export { Button, ChatStorageProvider, type ChatTheme, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type ChatWidgetSize, type ConversationStarter, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, type DisplayConfig, type FeatureConfig, Input, type InputPlugin, type InputPluginItem, StarterMessageItem, StarterMessages, type StarterPrompt, type ThemeConfig, type ThemeMode, Tool, ToolContent, ToolHeader, ToolInput, ToolOutput, type ToolPartLike, type ToolRenderer, clearChatStorage, ChatWidget as default, fontOptions, useChatStorageKey, useChatTheme };