@boldare/askdare 0.0.0-stage → 0.1.0-rc.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.
@@ -0,0 +1,413 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Every text the widget shows. The keys are public API (ADR-0008): override any of them with
4
+ * `widget.texts`. Plural keys end in a CLDR category (`zero`, `one`, `two`, `few`, `many`, `other`).
5
+ */
6
+ declare const EN: {
7
+ readonly "header.title": "Assistant";
8
+ readonly "header.newConversation": "Start a new conversation";
9
+ readonly "header.close": "Close chat";
10
+ readonly "confirm.newConversation": "Start a new conversation? The current one will be deleted.";
11
+ readonly "confirm.cancel": "Cancel";
12
+ readonly "confirm.accept": "Start new";
13
+ readonly "welcome.label": "Welcome";
14
+ readonly "welcome.greeting": "Hi! How can I help?";
15
+ readonly "welcome.online": "Online";
16
+ readonly "welcome.suggestions": "Suggested questions";
17
+ readonly "composer.placeholder": "Type your message…";
18
+ readonly "composer.send": "Send";
19
+ readonly "composer.attach": "Attach files";
20
+ readonly "composer.dictate": "Dictate a message";
21
+ readonly "status.loading": "Loading the conversation…";
22
+ readonly "status.thinking": "Thinking…";
23
+ readonly "status.searching": "Searching documents…";
24
+ readonly "status.tool": "Using {name}…";
25
+ readonly "status.typing": "The assistant is typing";
26
+ readonly "message.sources": "Sources";
27
+ readonly "message.attachments.one": "{count} attachment";
28
+ readonly "message.attachments.other": "{count} attachments";
29
+ readonly "attachment.list": "Attachments";
30
+ readonly "attachment.uploading": "Uploading…";
31
+ readonly "attachment.remove": "Remove {name}";
32
+ readonly "attachment.unsupported": "This file type is not supported.";
33
+ readonly "attachment.tooLarge": "Too large (max {size}).";
34
+ readonly "attachment.tooMany": "You can attach up to {count} files per message.";
35
+ readonly "dictation.listening": "Listening…";
36
+ readonly "dictation.transcribing": "Transcribing…";
37
+ readonly "dictation.cancel": "Cancel dictation";
38
+ readonly "dictation.done": "Done";
39
+ readonly "dictation.empty": "Nothing was recorded. Please try again.";
40
+ readonly "microphone.blocked": "Microphone access is blocked. Allow it in your browser to dictate.";
41
+ readonly "microphone.missing": "No microphone was found.";
42
+ readonly "microphone.failed": "The microphone could not be started.";
43
+ readonly "disclosure.text": "You’re chatting with an AI assistant. It can make mistakes.";
44
+ readonly "disclosure.privacy": "Privacy policy";
45
+ readonly "launcher.label": "Ask us";
46
+ readonly "launcher.open": "Open chat";
47
+ readonly "launcher.close": "Close chat";
48
+ readonly "launcher.unavailable": "The chat couldn’t load. Please try again later.";
49
+ readonly "error.connection": "Connection problem. Please try again.";
50
+ readonly "error.history": "Could not load the previous conversation.";
51
+ readonly "error.updated": "The assistant was updated. Please reload the page.";
52
+ readonly "error.stalled": "The connection stalled. Please try again.";
53
+ readonly "error.uploadFailed": "The file could not be uploaded.";
54
+ readonly "error.transcriptionFailed": "The recording could not be transcribed.";
55
+ readonly "error.invalid_input": "The request was invalid.";
56
+ readonly "error.unauthorized": "Authentication is required.";
57
+ readonly "error.session_expired": "Your session has expired. Please try again.";
58
+ readonly "error.not_found": "Not found.";
59
+ readonly "error.content_blocked": "This message cannot be processed.";
60
+ readonly "error.rate_limited": "Too many messages. Please wait a moment.";
61
+ readonly "error.budget_exceeded": "The assistant is unavailable for the rest of the day.";
62
+ readonly "error.internal_error": "Something went wrong. Please try again.";
63
+ readonly "error.upstream_error": "The assistant is temporarily unavailable. Please try again.";
64
+ readonly "error.bot_unavailable": "The assistant is currently unavailable.";
65
+ readonly "error.timeout": "The assistant took too long to respond. Please try again.";
66
+ readonly "error.conversationTooLong": "This conversation is too long. Please start a new one.";
67
+ readonly "error.conversationGone": "This conversation is no longer available.";
68
+ readonly "error.messageLength": "Messages can be up to {max} characters.";
69
+ readonly "error.attachmentExpired": "An attachment expired or is invalid. Please attach it again.";
70
+ readonly "error.filesNotAccepted": "This assistant does not accept files.";
71
+ readonly "error.tooManyFiles": "Attach up to {count} files per message.";
72
+ readonly "error.fileType.images": "Only images (PNG, JPEG, WebP, GIF) are supported.";
73
+ readonly "error.fileType.pdf": "Only PDF files are supported.";
74
+ readonly "error.fileType.both": "Only images (PNG, JPEG, WebP, GIF) and PDF files are supported.";
75
+ readonly "error.fileTooLarge": "Files can be up to {size}.";
76
+ readonly "error.pdfPages": "PDFs can have up to {max} pages.";
77
+ readonly "error.voiceNotAccepted": "This assistant does not accept voice input.";
78
+ readonly "error.recordingFormat": "This recording format is not supported.";
79
+ readonly "error.recordingTooLong": "This recording is too long.";
80
+ readonly "error.dictationBusy": "Another dictation is still being transcribed. Please try again in a moment.";
81
+ readonly "error.dictationBudget": "Dictation is unavailable for the rest of the day. You can still type.";
82
+ };
83
+ /** Plural texts: the base plus a CLDR category, every category overridable. */
84
+ declare const PLURAL_BASES: readonly ["message.attachments"];
85
+ declare const PLURAL_FORMS: readonly ["zero", "one", "two", "few", "many", "other"];
86
+ type TextKey = keyof typeof EN | `${(typeof PLURAL_BASES)[number]}.${(typeof PLURAL_FORMS)[number]}`;
87
+ /** A signed-in user, as the site's backend vouched for it (ADR-0009). */
88
+ interface VisitorIdentity {
89
+ /** The site's own user ID: the JWT's `sub`. */
90
+ readonly id: string;
91
+ readonly name?: string | undefined;
92
+ readonly email?: string | undefined;
93
+ /** Other string, number or boolean claims of the token, such as a plan or a role. */
94
+ readonly claims: Readonly<Record<string, string | number | boolean>>;
95
+ }
96
+ declare const PRESET_NAMES: readonly ["halo", "aura", "sage", "care"];
97
+ type PresetName = (typeof PRESET_NAMES)[number];
98
+ /**
99
+ * Semantic design tokens. Each one becomes a `--ask-*` CSS variable, which is public API
100
+ * (ADR-0008); everything else in the stylesheet is derived from them. Colors are hex.
101
+ */
102
+ interface ThemeTokens {
103
+ /** Buttons, links, focus ring. */
104
+ readonly accent: string;
105
+ /** Text and icons on `accent`. */
106
+ readonly onAccent: string;
107
+ /** Panel background. */
108
+ readonly background: string;
109
+ /** Assistant bubbles, cards and chips. */
110
+ readonly surface: string;
111
+ readonly text: string;
112
+ /** Secondary text: subtitles, timestamps, statuses. */
113
+ readonly muted: string;
114
+ /** Hairlines; the only color that may carry alpha (#rrggbbaa). */
115
+ readonly border: string;
116
+ readonly danger: string;
117
+ /** Confirmations, such as the checks in the intro welcome. */
118
+ readonly success: string;
119
+ readonly userBubble: string;
120
+ readonly onUserBubble: string;
121
+ /** CSS background painted behind the panel, such as a gradient; `none` for flat. */
122
+ readonly backdrop: string;
123
+ /** CSS background of the orb avatar and launcher. */
124
+ readonly orb: string;
125
+ readonly fontFamily: string;
126
+ readonly headingFamily: string;
127
+ readonly fontSize: string;
128
+ readonly radius: string;
129
+ }
130
+ /** Component variants a preset picks; each becomes a `data-ask-*` attribute on the document. */
131
+ interface ThemeVariants {
132
+ readonly avatar: "orb" | "icon" | "initials" | "none";
133
+ readonly welcome: "hero" | "persona" | "intro";
134
+ readonly launcher: "orb" | "circle" | "pill";
135
+ readonly bubbles: "filled" | "plain";
136
+ readonly surface: "solid" | "glass";
137
+ readonly density: "comfortable" | "compact";
138
+ readonly composer: "outline" | "gradient";
139
+ }
140
+ type ColorScheme = "auto" | "light" | "dark";
141
+ /** A theme built on a preset. Create it with {@link defineTheme} to share it between bots. */
142
+ interface ThemeDefinition {
143
+ /** Preset to start from. Default `aura`. */
144
+ readonly extends?: PresetName;
145
+ /** Brand color for buttons, links and your own messages; text on it is picked for contrast. */
146
+ readonly accent?: string;
147
+ /** Token overrides for the light scheme (and the dark one, unless `dark` sets them). */
148
+ readonly tokens?: Partial<ThemeTokens>;
149
+ /** Token overrides for the dark scheme. */
150
+ readonly dark?: Partial<ThemeTokens>;
151
+ readonly variants?: Partial<ThemeVariants>;
152
+ }
153
+ interface ResolvedTheme {
154
+ readonly preset: PresetName;
155
+ readonly colorScheme: ColorScheme;
156
+ readonly light: ThemeTokens;
157
+ readonly dark: ThemeTokens;
158
+ readonly variants: ThemeVariants;
159
+ }
160
+ /** Validates a theme once, so several bots can share it. Throws on invalid values. */
161
+ declare function defineTheme(definition: ThemeDefinition): ThemeDefinition;
162
+ /** What a tool may know about the request. Identity here is verified, never chosen by the model. */
163
+ interface ToolContext {
164
+ readonly botId: string;
165
+ /** Server-generated, pseudonymous visitor ID. */
166
+ readonly visitorId: string;
167
+ readonly locale: string;
168
+ /**
169
+ * The signed-in user, verified with IDENTITY_SECRET (ADR-0009); undefined for anonymous
170
+ * visitors. Trust only this for who the visitor is, never the model's arguments.
171
+ */
172
+ readonly identity?: VisitorIdentity | undefined;
173
+ /** Aborted when the visitor leaves, the turn deadline passes or the tool times out. */
174
+ readonly signal: AbortSignal;
175
+ }
176
+ interface ToolDefinition<Input extends z.ZodObject> {
177
+ /** Name the model calls, 1–64 letters, digits, `_` or `-`. */
178
+ readonly name: string;
179
+ /** Tells the model when and how to use the tool. */
180
+ readonly description: string;
181
+ /**
182
+ * Arguments schema. OpenAI strict mode requires every field: use `.nullable()` instead of
183
+ * `.optional()`.
184
+ */
185
+ readonly input: Input;
186
+ /** Runs on the server with validated arguments. The result is sent back to the model as JSON. */
187
+ readonly execute: (input: z.infer<Input>, context: ToolContext) => unknown;
188
+ /** Default 10 s. */
189
+ readonly timeoutMs?: number;
190
+ }
191
+ interface Tool {
192
+ readonly name: string;
193
+ readonly description: string;
194
+ readonly input: z.ZodObject;
195
+ readonly execute: (input: unknown, context: ToolContext) => unknown;
196
+ readonly timeoutMs: number;
197
+ }
198
+ /**
199
+ * Declares a server-side function the model may call. Arguments are validated with the schema
200
+ * before `execute` runs; failures are reported to the model, never to the visitor.
201
+ */
202
+ declare function defineTool<Input extends z.ZodObject>(definition: ToolDefinition<Input>): Tool;
203
+ /** Per-request context available when building instructions. */
204
+ interface BotContext {
205
+ /** Validated BCP 47 tag chosen by the widget. */
206
+ readonly locale: string;
207
+ /** The signed-in user, verified with IDENTITY_SECRET (ADR-0009); undefined when anonymous. */
208
+ readonly identity?: VisitorIdentity | undefined;
209
+ }
210
+ interface BotLimits {
211
+ /** Upper bound for each model response. Default 1024. */
212
+ readonly maxOutputTokens?: number;
213
+ /** Longest accepted user message, in characters. Default 4000. */
214
+ readonly maxInputChars?: number;
215
+ /** Model calls per turn, including tool round trips. Default 5. */
216
+ readonly maxSteps?: number;
217
+ /** Server-side deadline for a whole turn. Keep it below your route's `maxDuration`. Default 120 s. */
218
+ readonly turnTimeoutMs?: number;
219
+ /** Input plus output tokens per UTC day across all visitors; then `budget_exceeded`. Default 2 000 000. */
220
+ readonly dailyTokens?: number;
221
+ /** Input plus output tokens per client IP per UTC day. Default 200 000. */
222
+ readonly dailyTokensPerIp?: number;
223
+ /** New sessions per client IP per hour (renewals by returning visitors count separately). Default 30. */
224
+ readonly sessionsPerIpPerHour?: number;
225
+ /** New conversations per client IP per hour. Default 20. */
226
+ readonly conversationsPerIpPerHour?: number;
227
+ /** Messages per visitor per minute. Default 10. */
228
+ readonly messagesPerVisitorPerMinute?: number;
229
+ /** Messages per client IP per minute, across visitors. Default 30. */
230
+ readonly messagesPerIpPerMinute?: number;
231
+ /** Turns per conversation before the visitor must start a new one. Default 50. */
232
+ readonly maxTurns?: number;
233
+ /** File uploads per visitor per hour, when attachments are on. Default 20. */
234
+ readonly uploadsPerVisitorPerHour?: number;
235
+ /** File uploads per client IP per hour, across visitors. Default 60. */
236
+ readonly uploadsPerIpPerHour?: number;
237
+ /** File uploads per UTC day across all visitors; bounds storage, since unsent files cost no tokens. Default 500. */
238
+ readonly uploadsPerDay?: number;
239
+ /** Dictations per visitor per hour, when voice input is on. Default 30. */
240
+ readonly transcriptionsPerVisitorPerHour?: number;
241
+ /** Seconds of speech transcribed per UTC day across all visitors (billed by duration). Default 3600. */
242
+ readonly voiceSecondsPerDay?: number;
243
+ /** Seconds of speech transcribed per client IP per UTC day. Default 600. */
244
+ readonly voiceSecondsPerIpPerDay?: number;
245
+ }
246
+ interface BotSecurity {
247
+ /**
248
+ * Origins allowed to embed the widget, e.g. `https://www.example.com`. The app's own origin is
249
+ * always allowed. Enforced by the browser through `frame-ancestors` (ADR-0004, ADR-0005).
250
+ */
251
+ readonly allowedOrigins?: readonly string[];
252
+ }
253
+ interface KnowledgeConfig {
254
+ /** Vector store filled by `askdare kb sync`, usually `process.env.KB_VECTOR_STORE_ID`. */
255
+ readonly vectorStoreId: string;
256
+ /** Chunks the model reads per search: more grounds answers better but costs input tokens. Default 5. */
257
+ readonly maxResults?: number;
258
+ }
259
+ interface AttachmentConfig {
260
+ /** PNG, JPEG, WebP and GIF. The widget downsizes photos and strips their metadata. Default true. */
261
+ readonly images?: boolean;
262
+ /** PDF documents; the model reads their text and page images. Default true. */
263
+ readonly pdf?: boolean;
264
+ /** Largest file in bytes. Default 4 MB, which fits Vercel's 4.5 MB request body limit. */
265
+ readonly maxBytes?: number;
266
+ /** Files per message. Default 3. */
267
+ readonly maxPerMessage?: number;
268
+ /**
269
+ * Longest PDF accepted. Each page costs about 2,000 input tokens, reserved before the turn; a
270
+ * PDF whose pages can't be counted is charged as if it had this many. Default 50.
271
+ */
272
+ readonly maxPdfPages?: number;
273
+ }
274
+ interface VoiceConfig {
275
+ /** OpenAI transcription model, for example `gpt-transcribe`. Required: the library never assumes a model. */
276
+ readonly model: string;
277
+ /** Longest dictation; recording stops by itself at this point. Default 60 seconds. */
278
+ readonly maxSeconds?: number;
279
+ }
280
+ /** Public widget settings. Everything here is sent to browsers, so never put secrets in it. */
281
+ interface WidgetConfig {
282
+ /** Shortcut for `texts['header.title']`. */
283
+ readonly title?: string;
284
+ /** Under the title, for example "Usually replies in a minute". */
285
+ readonly subtitle?: string;
286
+ /** Shortcut for `texts['welcome.greeting']`. */
287
+ readonly greeting?: string;
288
+ /** Shortcut for `texts['composer.placeholder']`. */
289
+ readonly placeholder?: string;
290
+ /**
291
+ * BCP 47 tag for the widget's texts and the default answer language, or `auto`: the language
292
+ * of the page embedding the widget, else the visitor's browser. Texts ship in English and
293
+ * Polish; other languages show English unless you set `texts`. Default `en`.
294
+ */
295
+ readonly locale?: string;
296
+ /** Overrides for any text the widget shows, by key (see the dictionary in the docs). */
297
+ readonly texts?: Readonly<Partial<Record<TextKey, string>>>;
298
+ /**
299
+ * The note that visitors are talking to an AI (EU AI Act, art. 50), shown under the message
300
+ * field, with an optional link to your privacy policy (an https URL or a path on your app).
301
+ * `false` hides it; only do that when your page already says so. Default: shown, no link.
302
+ */
303
+ readonly disclosure?: false | {
304
+ readonly privacyUrl?: string;
305
+ };
306
+ /**
307
+ * Your own CSS for the chat, loaded after the theme. It is not in the library's layer, so it
308
+ * wins without !important. Target `[data-ask-part]` parts and the `--ask-*` tokens.
309
+ */
310
+ readonly css?: string;
311
+ /** Suggested questions on the welcome screen; a click sends one. Up to 4. */
312
+ readonly starters?: readonly string[];
313
+ /** The assistant's picture: a path on your app such as `/avatar.png`, or a small data: image. */
314
+ readonly avatar?: string;
315
+ /** Text on the pill launcher; shortcut for `texts['launcher.label']`. */
316
+ readonly launcherLabel?: string;
317
+ /** Built-in look (`halo`, `aura`, `sage`, `care`) or a theme from `defineTheme`. Default `aura`. */
318
+ readonly theme?: PresetName | ThemeDefinition;
319
+ /** Default `auto`: follows the visitor's system setting. */
320
+ readonly colorScheme?: ColorScheme;
321
+ }
322
+ /** Widget settings with defaults applied and the theme resolved. */
323
+ interface BotWidget {
324
+ /** In the configured locale (English for `auto`); the visitor may see another language. */
325
+ readonly title: string;
326
+ readonly subtitle: string;
327
+ readonly greeting: string;
328
+ readonly placeholder: string;
329
+ /** The configured BCP 47 tag, or `auto`. */
330
+ readonly locale: string;
331
+ readonly starters: readonly string[];
332
+ readonly avatar: string | undefined;
333
+ readonly launcherLabel: string;
334
+ /** Text overrides, including the title, greeting, placeholder and launcher label shortcuts. */
335
+ readonly texts: Readonly<Partial<Record<TextKey, string>>>;
336
+ readonly disclosure: false | {
337
+ readonly privacyUrl: string | undefined;
338
+ };
339
+ readonly css: string;
340
+ readonly theme: ResolvedTheme;
341
+ }
342
+ interface BotDefinition {
343
+ /** URL-safe identifier used in the script tag (`widget.js?bot=<id>`) and routes. */
344
+ readonly id: string;
345
+ /** OpenAI model ID. Required on purpose: the library never assumes a model (ADR-0002). */
346
+ readonly model: string;
347
+ readonly instructions: string | ((context: BotContext) => string);
348
+ readonly limits?: BotLimits;
349
+ /** Server-side functions the model may call; see `defineTool`. Keep it under about 20. */
350
+ readonly tools?: readonly Tool[];
351
+ /**
352
+ * Accept a description of the page from `askdare('configure', { context })` and pass it to the
353
+ * model with the visitor's message. Any script on the page, or the visitor, can set it, so it
354
+ * gets no more trust than what visitors type. Off by default.
355
+ */
356
+ readonly pageContext?: boolean;
357
+ /** Documents the model searches with OpenAI file search and cites in its answers. */
358
+ readonly knowledge?: KnowledgeConfig | undefined;
359
+ /**
360
+ * Lets visitors attach images and PDFs. Off when absent. Files go to OpenAI (purpose
361
+ * `user_data`) and expire after 30 days, or when the visitor deletes the conversation.
362
+ */
363
+ readonly attachments?: AttachmentConfig | undefined;
364
+ /**
365
+ * Lets visitors dictate messages: speech is transcribed by OpenAI into the message field, where
366
+ * they can check it before sending. Off when absent.
367
+ */
368
+ readonly voice?: VoiceConfig | undefined;
369
+ readonly security?: BotSecurity;
370
+ readonly widget?: WidgetConfig;
371
+ }
372
+ interface Bot {
373
+ readonly id: string;
374
+ readonly model: string;
375
+ readonly instructions: (context: BotContext) => string;
376
+ readonly limits: Required<BotLimits>;
377
+ readonly tools: readonly Tool[];
378
+ readonly pageContext: boolean;
379
+ readonly knowledge: Required<KnowledgeConfig> | undefined;
380
+ readonly attachments: Required<AttachmentConfig> | undefined;
381
+ readonly voice: Required<VoiceConfig> | undefined;
382
+ readonly security: {
383
+ readonly allowedOrigins: readonly string[];
384
+ };
385
+ readonly widget: BotWidget;
386
+ }
387
+ /**
388
+ * Declares a chatbot. Validates eagerly so misconfiguration fails at startup, not on the first
389
+ * visitor's message.
390
+ */
391
+ declare function defineBot(definition: BotDefinition): Bot;
392
+ /** Result of incrementing a fixed-window counter. */
393
+ interface CounterHit {
394
+ /** Counter value after the increment. */
395
+ readonly count: number;
396
+ /** Milliseconds until the window resets. */
397
+ readonly resetMs: number;
398
+ }
399
+ /**
400
+ * Shared counter storage behind rate limits and budgets. The in-memory default works for a single
401
+ * instance; multi-instance deployments (Vercel, several dynos) need a shared store such as
402
+ * `@boldare/askdare-upstash`. Implementations must be atomic per key.
403
+ */
404
+ interface RateLimitStore {
405
+ /**
406
+ * Adds `amount` to `key`, starting a `windowMs` window if none is open. `0` reads without
407
+ * changing; negative amounts refund.
408
+ */
409
+ hit(key: string, windowMs: number, amount: number): Promise<CounterHit>;
410
+ /** True for stores that are shared across server instances. */
411
+ readonly shared: boolean;
412
+ }
413
+ export { VisitorIdentity as C, ThemeVariants as S, ColorScheme as _, BotContext as a, PresetName as b, BotSecurity as c, WidgetConfig as d, defineBot as f, defineTool as g, ToolDefinition as h, Bot as i, KnowledgeConfig as l, ToolContext as m, RateLimitStore as n, BotDefinition as o, Tool as p, AttachmentConfig as r, BotLimits as s, CounterHit as t, VoiceConfig as u, ThemeDefinition as v, ThemeTokens as x, defineTheme as y };