chatbotlite 0.2.0 → 0.3.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.
- package/README.md +93 -53
- package/dist/client/index.cjs +27 -91
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +14 -9
- package/dist/client/index.d.ts +14 -9
- package/dist/client/index.js +28 -91
- package/dist/client/index.js.map +1 -1
- package/dist/core/index.cjs +15 -50
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -7
- package/dist/core/index.d.ts +9 -7
- package/dist/core/index.js +15 -50
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +27 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -91
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +41 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +37 -0
- package/dist/node/index.d.ts +37 -0
- package/dist/node/index.js +38 -0
- package/dist/node/index.js.map +1 -0
- package/dist/react/index.cjs +32 -95
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +7 -7
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +32 -95
- package/dist/react/index.js.map +1 -1
- package/dist/types-4alyzg8O.d.cts +16 -0
- package/dist/types-4alyzg8O.d.ts +16 -0
- package/dist/types-J7BXpiRU.d.cts +63 -0
- package/dist/types-J7BXpiRU.d.ts +63 -0
- package/package.json +12 -3
- package/dist/types-DS7rM6Vl.d.cts +0 -82
- package/dist/types-DS7rM6Vl.d.ts +0 -82
- package/dist/types-DYNx47by.d.cts +0 -39
- package/dist/types-DYNx47by.d.ts +0 -39
package/README.md
CHANGED
|
@@ -33,21 +33,24 @@ export default function Layout({ children }) {
|
|
|
33
33
|
import { ChatBot } from "chatbotlite";
|
|
34
34
|
|
|
35
35
|
const bot = new ChatBot({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
knowledge: `
|
|
37
|
+
# Acme Plumbing
|
|
38
|
+
Plumbing service in Vancouver & Burnaby. Mon-Sat 8am-6pm.
|
|
39
|
+
|
|
40
|
+
## Services
|
|
41
|
+
- Sink leak inspection: $95
|
|
42
|
+
- Toilet unclogging: $85-150
|
|
43
|
+
- Burst pipe emergency: urgent owner review
|
|
44
|
+
`,
|
|
45
45
|
providers: {
|
|
46
46
|
keys: {
|
|
47
47
|
deepseek: process.env.DEEPSEEK_API_KEY!,
|
|
48
48
|
openai: process.env.OPENAI_API_KEY!
|
|
49
49
|
},
|
|
50
|
-
chain: [
|
|
50
|
+
chain: [
|
|
51
|
+
{ provider: "deepseek", model: "deepseek-chat" },
|
|
52
|
+
{ provider: "openai", model: "gpt-4o-mini" }
|
|
53
|
+
]
|
|
51
54
|
}
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -58,6 +61,8 @@ export async function POST(req: Request) {
|
|
|
58
61
|
}
|
|
59
62
|
```
|
|
60
63
|
|
|
64
|
+
That's the **whole** integration. The `knowledge` field is just markdown — works for any business: plumber, restaurant, school, museum, portfolio. No schema to fight.
|
|
65
|
+
|
|
61
66
|
That's the whole integration. You now have a floating chat bubble that:
|
|
62
67
|
|
|
63
68
|
- Knows your business (services, prices, hours, area)
|
|
@@ -139,13 +144,8 @@ export default function ChatMount() {
|
|
|
139
144
|
import { ChatWidget } from "chatbotlite/react";
|
|
140
145
|
|
|
141
146
|
<ChatWidget
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
services: [{ name: "Drop-in class", price: "$22" }]
|
|
145
|
-
}}
|
|
146
|
-
providers={{
|
|
147
|
-
keys: { openai: import.meta.env.VITE_OPENAI_KEY }
|
|
148
|
-
}}
|
|
147
|
+
knowledge="# Sunrise Yoga\n- Drop-in class: $22\n- Open Mon-Sun 6am-9pm"
|
|
148
|
+
providers={{ keys: { openai: import.meta.env.VITE_OPENAI_KEY } }}
|
|
149
149
|
/>
|
|
150
150
|
```
|
|
151
151
|
|
|
@@ -193,16 +193,16 @@ providers: {
|
|
|
193
193
|
openai: "sk-..." // paid fallback, max reliability
|
|
194
194
|
},
|
|
195
195
|
chain: [
|
|
196
|
-
"deepseek
|
|
197
|
-
"groq
|
|
198
|
-
"openai
|
|
196
|
+
{ provider: "deepseek", model: "deepseek-chat" },
|
|
197
|
+
{ provider: "groq", model: "llama-3.3-70b-versatile" },
|
|
198
|
+
{ provider: "openai", model: "gpt-4o-mini" }
|
|
199
199
|
]
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
Top-to-bottom = priority. When a step throws a retryable error (429, 5xx, timeout), it falls through to the next.
|
|
204
204
|
|
|
205
|
-
###
|
|
205
|
+
### Same provider, cheaper fallback
|
|
206
206
|
|
|
207
207
|
```ts
|
|
208
208
|
providers: {
|
|
@@ -218,44 +218,83 @@ providers: {
|
|
|
218
218
|
|
|
219
219
|
`openai`, `deepseek`, `groq`, `gemini`, `anthropic`, `cerebras`, `sambanova`, `fireworks`, `mistral`, `openrouter`, `moonshot`
|
|
220
220
|
|
|
221
|
-
Use any model the provider supports — just
|
|
221
|
+
Use any model the provider supports — just pass the model name string.
|
|
222
222
|
|
|
223
223
|
---
|
|
224
224
|
|
|
225
|
-
##
|
|
225
|
+
## Knowledge — just markdown
|
|
226
226
|
|
|
227
|
-
The `
|
|
227
|
+
The `knowledge` field is the bot's brain. It's plain markdown. Write it like you'd write a one-page memo for a new receptionist. No JSON schema, no required fields. Works for **any vertical** — plumber, restaurant, school, museum, portfolio site.
|
|
228
228
|
|
|
229
229
|
```ts
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
230
|
+
const bot = new ChatBot({
|
|
231
|
+
knowledge: `
|
|
232
|
+
# Acme Plumbing
|
|
233
|
+
Plumbing service in Greater Vancouver since 2018.
|
|
234
|
+
|
|
235
|
+
## Services
|
|
236
|
+
- Sink leak inspection: $95 first-visit fee
|
|
237
|
+
- Toilet unclogging: $85-150
|
|
238
|
+
- Burst pipe emergency: urgent — owner reviews directly
|
|
239
|
+
|
|
240
|
+
## Hours
|
|
241
|
+
Mon-Sat 8am-6pm
|
|
242
|
+
|
|
243
|
+
## Service area
|
|
244
|
+
Vancouver, Burnaby, Richmond
|
|
245
|
+
|
|
246
|
+
## Policies
|
|
247
|
+
- Payment: Interac e-Transfer or major credit cards
|
|
248
|
+
- Cancellation: free up to 24h before appointment
|
|
249
|
+
|
|
250
|
+
## Rules
|
|
251
|
+
- NEVER promise specific arrival times — give windows
|
|
252
|
+
- NEVER give final quotes without inspection
|
|
253
|
+
- Remind customers to send photos of leaks for faster diagnosis
|
|
254
|
+
`,
|
|
255
|
+
providers: { ... }
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The bot uses only what's in your markdown. Ask it about a service not listed → it defers to owner review. Ask for a guaranteed price → it refuses politely.
|
|
260
|
+
|
|
261
|
+
### Loading from a folder
|
|
262
|
+
|
|
263
|
+
For bigger knowledge bases (50+ services / multiple FAQ files), split into files:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
kb/
|
|
267
|
+
about.md
|
|
268
|
+
services.md
|
|
269
|
+
policies.md
|
|
270
|
+
faq.md
|
|
271
|
+
hours.md
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { ChatBot } from "chatbotlite";
|
|
276
|
+
import { knowledgeFromDir } from "chatbotlite/node";
|
|
277
|
+
|
|
278
|
+
const bot = new ChatBot({
|
|
279
|
+
knowledge: knowledgeFromDir("./kb"),
|
|
280
|
+
providers: { ... }
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`knowledgeFromDir` concatenates all `.md` / `.markdown` / `.txt` files alphabetically, each headed by its filename.
|
|
285
|
+
|
|
286
|
+
### Loading from a single file
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { knowledgeFromFile } from "chatbotlite/node";
|
|
290
|
+
|
|
291
|
+
const bot = new ChatBot({
|
|
292
|
+
knowledge: knowledgeFromFile("./business.md"),
|
|
293
|
+
providers: { ... }
|
|
294
|
+
});
|
|
256
295
|
```
|
|
257
296
|
|
|
258
|
-
|
|
297
|
+
> **Why not a typed JSON schema?** Because your business is yours. A bookstore doesn't have "services with prices". A school doesn't have a "service area". A portfolio doesn't have "hours". Markdown lets every vertical describe themselves naturally — and the LLM is plenty smart to read prose.
|
|
259
298
|
|
|
260
299
|
---
|
|
261
300
|
|
|
@@ -321,8 +360,9 @@ Want a different UI? Use `ChatBot` headless and build your own.
|
|
|
321
360
|
|
|
322
361
|
- [x] **v0.1** — MVP: business config, React widget, fallback chain, basic guards
|
|
323
362
|
- [x] **v0.2** — Polished UI, model-based fallback chain (Vercel-style), attempts metadata
|
|
324
|
-
- [
|
|
325
|
-
- [ ] **v0.4** —
|
|
363
|
+
- [x] **v0.3** — Markdown knowledge (any vertical), object-only chain, folder loader
|
|
364
|
+
- [ ] **v0.4** — Streaming, vanilla JS bundle, image upload, voice input
|
|
365
|
+
- [ ] **v0.5** — Auto-RAG when knowledge > 8k tokens, custom guards API
|
|
326
366
|
- [ ] **v0.5** — Owner-review escalation flow, analytics + conversation export
|
|
327
367
|
- [ ] **v1.0** — API stable
|
|
328
368
|
|
package/dist/client/index.cjs
CHANGED
|
@@ -14,24 +14,6 @@ var PROVIDER_NAMES = /* @__PURE__ */ new Set([
|
|
|
14
14
|
"openrouter",
|
|
15
15
|
"moonshot"
|
|
16
16
|
]);
|
|
17
|
-
function parseChainSpec(spec) {
|
|
18
|
-
const slash = spec.indexOf("/");
|
|
19
|
-
if (slash === -1) {
|
|
20
|
-
if (!PROVIDER_NAMES.has(spec)) {
|
|
21
|
-
throw new Error(`chatbotlite: unknown provider "${spec}". Use "provider/model" or a known provider name.`);
|
|
22
|
-
}
|
|
23
|
-
return { provider: spec, model: null };
|
|
24
|
-
}
|
|
25
|
-
const provider = spec.slice(0, slash);
|
|
26
|
-
const model = spec.slice(slash + 1);
|
|
27
|
-
if (!PROVIDER_NAMES.has(provider)) {
|
|
28
|
-
throw new Error(`chatbotlite: unknown provider "${provider}" in chain spec "${spec}".`);
|
|
29
|
-
}
|
|
30
|
-
if (!model) {
|
|
31
|
-
throw new Error(`chatbotlite: empty model name in chain spec "${spec}".`);
|
|
32
|
-
}
|
|
33
|
-
return { provider, model };
|
|
34
|
-
}
|
|
35
17
|
function isKnownProvider(name) {
|
|
36
18
|
return PROVIDER_NAMES.has(name);
|
|
37
19
|
}
|
|
@@ -56,56 +38,21 @@ function isRetryableError(err) {
|
|
|
56
38
|
}
|
|
57
39
|
|
|
58
40
|
// src/core/prompts.ts
|
|
59
|
-
function buildSystemPrompt(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
parts.push(`Hours: ${business.hours}`);
|
|
75
|
-
}
|
|
76
|
-
if (business.serviceArea && business.serviceArea.length > 0) {
|
|
77
|
-
parts.push(`Service area: ${business.serviceArea.join(", ")}.`);
|
|
78
|
-
parts.push("If the caller is outside this area, say so and recommend owner review.");
|
|
79
|
-
}
|
|
80
|
-
if (business.policies && business.policies.length > 0) {
|
|
81
|
-
parts.push("");
|
|
82
|
-
parts.push("Known policies (use these exact answers when asked):");
|
|
83
|
-
for (const p of business.policies) {
|
|
84
|
-
parts.push(`- ${p.topic}: ${p.answer}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
parts.push("");
|
|
88
|
-
parts.push("Rules:");
|
|
89
|
-
parts.push("- Reply in 1-2 short sentences, conversational tone.");
|
|
90
|
-
parts.push("- NEVER invent prices, availability, dispatch times, or appointment confirmations.");
|
|
91
|
-
parts.push("- For anything not covered in the setup above, say it needs owner review.");
|
|
92
|
-
parts.push('- If the caller is clearly a vendor/sales pitch, say: "This does not look like a customer service request, so we will not continue this thread."');
|
|
93
|
-
parts.push(`- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`);
|
|
94
|
-
if (business.doNotPromise && business.doNotPromise.length > 0) {
|
|
95
|
-
parts.push("");
|
|
96
|
-
parts.push("Never promise:");
|
|
97
|
-
for (const p of business.doNotPromise) parts.push(`- ${p}`);
|
|
98
|
-
}
|
|
99
|
-
if (business.customInstructions) {
|
|
100
|
-
parts.push("");
|
|
101
|
-
parts.push("Additional instructions:");
|
|
102
|
-
parts.push(business.customInstructions);
|
|
103
|
-
}
|
|
104
|
-
if (business.language && business.language !== "en") {
|
|
105
|
-
parts.push("");
|
|
106
|
-
parts.push(`Reply in ${business.language}.`);
|
|
107
|
-
}
|
|
108
|
-
return parts.join("\n");
|
|
41
|
+
function buildSystemPrompt(knowledge) {
|
|
42
|
+
return [
|
|
43
|
+
"You are an AI assistant on a business website. Use ONLY the knowledge below to answer.",
|
|
44
|
+
"",
|
|
45
|
+
"## Business knowledge",
|
|
46
|
+
knowledge.trim(),
|
|
47
|
+
"",
|
|
48
|
+
"## Reply rules",
|
|
49
|
+
"- Reply in 1-2 short sentences, conversational tone.",
|
|
50
|
+
"- NEVER invent prices, availability, dispatch times, appointment confirmations, or facts not present in the business knowledge above.",
|
|
51
|
+
"- For anything not covered in the knowledge above, say the owner will follow up \u2014 do NOT guess.",
|
|
52
|
+
'- If the caller is clearly a vendor/sales pitch, say: "This does not look like a customer service request, so we will not continue this thread."',
|
|
53
|
+
`- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`,
|
|
54
|
+
"- Match the caller's language automatically."
|
|
55
|
+
].join("\n");
|
|
109
56
|
}
|
|
110
57
|
|
|
111
58
|
// src/core/guards.ts
|
|
@@ -157,19 +104,20 @@ function stripForbidden(reply) {
|
|
|
157
104
|
|
|
158
105
|
// src/client/chatbot.ts
|
|
159
106
|
var ChatBot = class {
|
|
160
|
-
business;
|
|
161
107
|
steps;
|
|
162
108
|
keys;
|
|
163
109
|
fetcher;
|
|
164
110
|
timeoutMs;
|
|
165
111
|
cachedSystemPrompt;
|
|
166
112
|
constructor(init) {
|
|
167
|
-
|
|
113
|
+
if (!init.knowledge || typeof init.knowledge !== "string" || init.knowledge.trim().length === 0) {
|
|
114
|
+
throw new Error("chatbotlite: knowledge is required (a non-empty markdown string).");
|
|
115
|
+
}
|
|
168
116
|
this.keys = init.providers.keys ?? {};
|
|
169
117
|
this.steps = resolveChain(init.providers);
|
|
170
118
|
this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
171
119
|
this.timeoutMs = init.options?.timeoutMs ?? 3e4;
|
|
172
|
-
this.cachedSystemPrompt = buildSystemPrompt(
|
|
120
|
+
this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);
|
|
173
121
|
}
|
|
174
122
|
async reply(message, opts = {}) {
|
|
175
123
|
const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
|
|
@@ -206,7 +154,7 @@ var ChatBot = class {
|
|
|
206
154
|
latencyMs: Date.now() - t0
|
|
207
155
|
});
|
|
208
156
|
if (!isRetryableError(err)) {
|
|
209
|
-
throw new Error(`chatbotlite: ${step.
|
|
157
|
+
throw new Error(`chatbotlite: ${step.label} failed (non-retryable). ${errMsg}`);
|
|
210
158
|
}
|
|
211
159
|
}
|
|
212
160
|
}
|
|
@@ -263,36 +211,24 @@ function resolveChain(providers) {
|
|
|
263
211
|
return orderedProviders.map((provider) => ({
|
|
264
212
|
provider,
|
|
265
213
|
model: PROVIDER_ENDPOINTS[provider].defaultModel,
|
|
266
|
-
|
|
214
|
+
label: `${provider}/${PROVIDER_ENDPOINTS[provider].defaultModel}`
|
|
267
215
|
}));
|
|
268
216
|
}
|
|
269
217
|
function normalizeChainEntry(entry, keys) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
let spec;
|
|
273
|
-
if (typeof entry === "string") {
|
|
274
|
-
const parsed = parseChainSpec(entry);
|
|
275
|
-
provider = parsed.provider;
|
|
276
|
-
model = parsed.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
277
|
-
spec = entry;
|
|
278
|
-
} else {
|
|
279
|
-
if (!isKnownProvider(entry.provider)) {
|
|
280
|
-
throw new Error(`chatbotlite: unknown provider "${entry.provider}" in chain entry.`);
|
|
281
|
-
}
|
|
282
|
-
provider = entry.provider;
|
|
283
|
-
model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
284
|
-
spec = `${provider}/${model}`;
|
|
218
|
+
if (!isKnownProvider(entry.provider)) {
|
|
219
|
+
throw new Error(`chatbotlite: unknown provider "${entry.provider}" in chain entry.`);
|
|
285
220
|
}
|
|
221
|
+
const provider = entry.provider;
|
|
222
|
+
const model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;
|
|
286
223
|
if (!keys[provider]) {
|
|
287
|
-
throw new Error(`chatbotlite: chain
|
|
224
|
+
throw new Error(`chatbotlite: chain entry for "${provider}" needs a matching key in providers.keys.`);
|
|
288
225
|
}
|
|
289
|
-
return { provider, model,
|
|
226
|
+
return { provider, model, label: `${provider}/${model}` };
|
|
290
227
|
}
|
|
291
228
|
|
|
292
229
|
exports.ChatBot = ChatBot;
|
|
293
230
|
exports.PROVIDER_ENDPOINTS = PROVIDER_ENDPOINTS;
|
|
294
231
|
exports.isKnownProvider = isKnownProvider;
|
|
295
232
|
exports.isRetryableError = isRetryableError;
|
|
296
|
-
exports.parseChainSpec = parseChainSpec;
|
|
297
233
|
//# sourceMappingURL=index.cjs.map
|
|
298
234
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/types.ts","../../src/client/providers.ts","../../src/core/prompts.ts","../../src/core/guards.ts","../../src/client/chatbot.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Client types — provider + chain config\n\nexport type Provider =\n | \"openai\"\n | \"deepseek\"\n | \"groq\"\n | \"gemini\"\n | \"anthropic\"\n | \"cerebras\"\n | \"sambanova\"\n | \"fireworks\"\n | \"mistral\"\n | \"openrouter\"\n | \"moonshot\";\n\n/**\n * One step in the fallback chain. Either a string spec or an object.\n *\n * - **String**: `\"provider/model\"` (e.g. `\"openai/gpt-4o\"`), or bare `\"provider\"` (uses provider's default model).\n * - **Object**: `{ provider, model? }` — type-safe alternative.\n */\nexport type ChainEntry =\n | string\n | { provider: Provider; model?: string };\n\n/**\n * Provider configuration.\n *\n * `keys` are auth credentials (one key per provider — that key covers all that provider's models).\n * `chain` is the ordered fallback list. Each entry is `\"provider/model\"` (string) or `{ provider, model }` (object).\n *\n * @example String form (compact, paste-friendly)\n * ```ts\n * providers: {\n * keys: {\n * deepseek: \"sk-...\",\n * groq: \"gsk-...\",\n * openai: \"sk-...\"\n * },\n * chain: [\n * \"deepseek/deepseek-chat\", // try first\n * \"groq/llama-3.3-70b-versatile\", // fallback 1\n * \"openai/gpt-4o-mini\" // fallback 2\n * ]\n * }\n * ```\n *\n * @example Object form (type-safe, IDE autocomplete on `provider`)\n * ```ts\n * providers: {\n * keys: { deepseek: \"sk-...\", openai: \"sk-...\" },\n * chain: [\n * { provider: \"deepseek\" }, // default model\n * { provider: \"openai\", model: \"gpt-4o\" },\n * { provider: \"openai\", model: \"gpt-4o-mini\" } // same key, cheaper model\n * ]\n * }\n * ```\n *\n * If `chain` is omitted, defaults to `[<each provider in keys> with its default model]`\n * in key insertion order.\n */\nexport interface ProviderConfig {\n /** API keys per provider. One key covers all that provider's models. */\n keys: Partial<Record<Provider, string>>;\n /**\n * Ordered fallback chain. Each entry: string `\"provider/model\"` OR object `{ provider, model? }`.\n * Defaults to `[provider/defaultModel]` for each key in insertion order.\n */\n chain?: ChainEntry[];\n}\n\nexport interface ClientOptions {\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n maxRetries?: number;\n}\n\nexport interface ChainStep {\n provider: Provider;\n model: string;\n /** Original spec string for diagnostics. */\n spec: string;\n}\n\nexport interface AttemptInfo {\n provider: Provider;\n model: string;\n status: \"ok\" | \"error\";\n error?: string;\n latencyMs: number;\n}\n\nconst PROVIDER_NAMES: ReadonlySet<string> = new Set([\n \"openai\", \"deepseek\", \"groq\", \"gemini\", \"anthropic\",\n \"cerebras\", \"sambanova\", \"fireworks\", \"mistral\", \"openrouter\", \"moonshot\"\n]);\n\nexport function parseChainSpec(spec: string): { provider: Provider; model: string | null } {\n const slash = spec.indexOf(\"/\");\n if (slash === -1) {\n if (!PROVIDER_NAMES.has(spec)) {\n throw new Error(`chatbotlite: unknown provider \"${spec}\". Use \"provider/model\" or a known provider name.`);\n }\n return { provider: spec as Provider, model: null };\n }\n const provider = spec.slice(0, slash);\n const model = spec.slice(slash + 1);\n if (!PROVIDER_NAMES.has(provider)) {\n throw new Error(`chatbotlite: unknown provider \"${provider}\" in chain spec \"${spec}\".`);\n }\n if (!model) {\n throw new Error(`chatbotlite: empty model name in chain spec \"${spec}\".`);\n }\n return { provider: provider as Provider, model };\n}\n\nexport function isKnownProvider(name: string): name is Provider {\n return PROVIDER_NAMES.has(name);\n}\n","import type { Provider } from \"./types.js\";\n\nexport interface ProviderEndpoint {\n baseUrl: string;\n defaultModel: string;\n}\n\n/**\n * Built-in OpenAI-compatible providers. All use /v1/chat/completions\n * with response in OpenAI format. Caller supplies API key per provider.\n */\nexport const PROVIDER_ENDPOINTS: Record<Provider, ProviderEndpoint> = {\n openai: { baseUrl: \"https://api.openai.com/v1\", defaultModel: \"gpt-4o-mini\" },\n deepseek: { baseUrl: \"https://api.deepseek.com/v1\", defaultModel: \"deepseek-chat\" },\n groq: { baseUrl: \"https://api.groq.com/openai/v1\", defaultModel: \"llama-3.3-70b-versatile\" },\n gemini: { baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\", defaultModel: \"gemini-2.5-flash\" },\n anthropic: { baseUrl: \"https://api.anthropic.com/v1\", defaultModel: \"claude-haiku-4-5\" },\n cerebras: { baseUrl: \"https://api.cerebras.ai/v1\", defaultModel: \"qwen-3-235b-a22b-instruct-2507\" },\n sambanova: { baseUrl: \"https://api.sambanova.ai/v1\", defaultModel: \"Meta-Llama-3.3-70B-Instruct\" },\n fireworks: { baseUrl: \"https://api.fireworks.ai/inference/v1\", defaultModel: \"accounts/fireworks/models/llama-v3p3-70b-instruct\" },\n mistral: { baseUrl: \"https://api.mistral.ai/v1\", defaultModel: \"mistral-small-latest\" },\n openrouter: { baseUrl: \"https://openrouter.ai/api/v1\", defaultModel: \"deepseek/deepseek-chat\" },\n moonshot: { baseUrl: \"https://api.moonshot.ai/v1\", defaultModel: \"moonshot-v1-32k\" }\n};\n\nexport function isRetryableError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return /\\b(429|rate.?limit|quota|exceed|5\\d\\d|timeout|ECONNRESET|fetch failed)\\b/i.test(msg);\n}\n","import type { BusinessConfig } from \"./types.js\";\n\n/**\n * Build the system prompt that anchors the bot to the business config.\n * Designed to minimize hallucination: only mention what's in the config,\n * defer to owner review for anything outside scope.\n */\nexport function buildSystemPrompt(business: BusinessConfig): string {\n const parts: string[] = [];\n\n parts.push(`You are an AI receptionist for ${business.name}.`);\n if (business.description) parts.push(business.description);\n parts.push(\"\");\n\n if (business.services && business.services.length > 0) {\n parts.push(\"Services offered (only these — do not invent others):\");\n for (const s of business.services) {\n const price = s.price ? ` — ${s.price}` : \"\";\n const notes = s.notes ? ` (${s.notes})` : \"\";\n parts.push(`- ${s.name}${price}${notes}`);\n }\n parts.push(\"\");\n }\n\n if (business.hours) {\n parts.push(`Hours: ${business.hours}`);\n }\n\n if (business.serviceArea && business.serviceArea.length > 0) {\n parts.push(`Service area: ${business.serviceArea.join(\", \")}.`);\n parts.push(\"If the caller is outside this area, say so and recommend owner review.\");\n }\n\n if (business.policies && business.policies.length > 0) {\n parts.push(\"\");\n parts.push(\"Known policies (use these exact answers when asked):\");\n for (const p of business.policies) {\n parts.push(`- ${p.topic}: ${p.answer}`);\n }\n }\n\n parts.push(\"\");\n parts.push(\"Rules:\");\n parts.push(\"- Reply in 1-2 short sentences, conversational tone.\");\n parts.push(\"- NEVER invent prices, availability, dispatch times, or appointment confirmations.\");\n parts.push(\"- For anything not covered in the setup above, say it needs owner review.\");\n parts.push('- If the caller is clearly a vendor/sales pitch, say: \"This does not look like a customer service request, so we will not continue this thread.\"');\n parts.push('- If wrong number or asked to stop, say: \"Sorry about that. We won\\'t text again.\"');\n\n if (business.doNotPromise && business.doNotPromise.length > 0) {\n parts.push(\"\");\n parts.push(\"Never promise:\");\n for (const p of business.doNotPromise) parts.push(`- ${p}`);\n }\n\n if (business.customInstructions) {\n parts.push(\"\");\n parts.push(\"Additional instructions:\");\n parts.push(business.customInstructions);\n }\n\n if (business.language && business.language !== \"en\") {\n parts.push(\"\");\n parts.push(`Reply in ${business.language}.`);\n }\n\n return parts.join(\"\\n\");\n}\n","import type { GuardResult } from \"./types.js\";\n\n/**\n * Phrases that almost always indicate hallucination for SMB customer service:\n * inventing dispatch promises, fake confirmations, or appointment locks.\n */\nexport const FORBIDDEN_PHRASES: readonly string[] = [\n \"help is coming\",\n \"someone is on the way\",\n \"technician is on the way\",\n \"provider is on the way\",\n \"dispatching someone\",\n \"i've booked\",\n \"i have booked\",\n \"reservation confirmed\",\n \"your appointment is confirmed\",\n \"i've scheduled\",\n \"i have scheduled\",\n \"we've dispatched\",\n \"we have dispatched\",\n \"i can confirm\",\n \"i guarantee\",\n \"guaranteed delivery\",\n \"guaranteed arrival\",\n \"will arrive at\",\n \"arriving at\",\n \"i'll send\",\n \"i will send\"\n];\n\n/**\n * Check a reply against the built-in forbidden phrase list.\n * Returns ok=true when clean, ok=false with violations when not.\n */\nexport function checkForbiddenPhrases(reply: string): GuardResult {\n const lower = reply.toLowerCase();\n const violations: string[] = [];\n for (const phrase of FORBIDDEN_PHRASES) {\n if (lower.includes(phrase)) {\n violations.push(`Forbidden phrase: \"${phrase}\"`);\n }\n }\n return { ok: violations.length === 0, violations };\n}\n\n/**\n * Remove forbidden sentences from a reply (best-effort sentence drop).\n * If too much is removed, returns a safe fallback.\n */\nexport function stripForbidden(reply: string): string {\n const sentences = reply.split(/(?<=[.!?])\\s+/);\n const kept = sentences.filter((s) => {\n const lower = s.toLowerCase();\n return !FORBIDDEN_PHRASES.some((p) => lower.includes(p));\n });\n const trimmed = kept.join(\" \").trim();\n if (trimmed.length < 10) {\n return \"Thanks for reaching out — let me check with the owner and get back to you.\";\n }\n return trimmed;\n}\n","import type { BusinessConfig, Message } from \"../core/types.js\";\nimport { buildSystemPrompt } from \"../core/prompts.js\";\nimport { checkForbiddenPhrases, stripForbidden } from \"../core/guards.js\";\nimport type { Provider, ProviderConfig, ClientOptions, ChainStep, ChainEntry, AttemptInfo } from \"./types.js\";\nimport { parseChainSpec, isKnownProvider } from \"./types.js\";\nimport { PROVIDER_ENDPOINTS, isRetryableError } from \"./providers.js\";\n\nexport interface ChatBotInit {\n business: BusinessConfig;\n providers: ProviderConfig;\n options?: ClientOptions;\n}\n\nexport interface ReplyOptions {\n /** Conversation history (excluding the new user message). */\n history?: Message[];\n /** Override system prompt — advanced use only. */\n systemPrompt?: string;\n}\n\nexport interface ReplyResult {\n reply: string;\n /** Provider/model that produced the final reply (after fallback). */\n usedProvider: Provider;\n usedModel: string;\n /** Token usage if reported by the final provider. */\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n /** Guard violations the bot caught and stripped, if any. */\n guardWarnings: string[];\n /** Debug trace of every attempt in the chain. */\n attempts: AttemptInfo[];\n}\n\n/**\n * The main ChatBot entry. Holds business config + provider chain.\n *\n * @example\n * const bot = new ChatBot({\n * business: { name: \"Acme Plumbing\", services: [{ name: \"Sink leak\", price: \"$95\" }] },\n * providers: {\n * keys: { deepseek: \"sk-...\", groq: \"gsk-...\", openai: \"sk-...\" },\n * chain: [\"deepseek/deepseek-chat\", \"groq/llama-3.3-70b-versatile\", \"openai/gpt-4o-mini\"]\n * }\n * });\n * const { reply } = await bot.reply(\"My sink is leaking\");\n */\nexport class ChatBot {\n private readonly business: BusinessConfig;\n private readonly steps: ChainStep[];\n private readonly keys: Partial<Record<Provider, string>>;\n private readonly fetcher: typeof globalThis.fetch;\n private readonly timeoutMs: number;\n private readonly cachedSystemPrompt: string;\n\n constructor(init: ChatBotInit) {\n this.business = init.business;\n this.keys = init.providers.keys ?? {};\n this.steps = resolveChain(init.providers);\n this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);\n this.timeoutMs = init.options?.timeoutMs ?? 30_000;\n this.cachedSystemPrompt = buildSystemPrompt(this.business);\n }\n\n async reply(message: string, opts: ReplyOptions = {}): Promise<ReplyResult> {\n const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;\n const messages: Message[] = [\n { role: \"system\", content: systemPrompt },\n ...(opts.history ?? []),\n { role: \"user\", content: message }\n ];\n const attempts: AttemptInfo[] = [];\n let lastError: unknown;\n for (const step of this.steps) {\n const t0 = Date.now();\n try {\n const result = await this.callProvider(step, messages);\n attempts.push({ provider: step.provider, model: step.model, status: \"ok\", latencyMs: Date.now() - t0 });\n const guard = checkForbiddenPhrases(result.reply);\n const finalReply = guard.ok ? result.reply : stripForbidden(result.reply);\n return {\n reply: finalReply,\n usedProvider: step.provider,\n usedModel: step.model,\n ...(result.usage ? { usage: result.usage } : {}),\n guardWarnings: guard.violations,\n attempts\n };\n } catch (err) {\n lastError = err;\n const errMsg = err instanceof Error ? err.message : String(err);\n attempts.push({\n provider: step.provider,\n model: step.model,\n status: \"error\",\n error: errMsg,\n latencyMs: Date.now() - t0\n });\n if (!isRetryableError(err)) {\n throw new Error(`chatbotlite: ${step.provider}/${step.model} failed (non-retryable). ${errMsg}`);\n }\n }\n }\n const summary = attempts.map((a) => `${a.provider}/${a.model}:${a.error ?? \"ok\"}`).join(\" → \");\n throw new Error(`chatbotlite: all chain steps failed. Trace: ${summary}. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);\n }\n\n private async callProvider(step: ChainStep, messages: Message[]): Promise<{ reply: string; usage?: { prompt_tokens?: number; completion_tokens?: number } }> {\n const endpoint = PROVIDER_ENDPOINTS[step.provider];\n const key = this.keys[step.provider];\n if (!key) throw new Error(`Missing API key for provider: ${step.provider}`);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const res = await this.fetcher(`${endpoint.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${key}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({\n model: step.model,\n messages,\n temperature: 0.3,\n max_tokens: 300\n }),\n signal: controller.signal\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`${res.status}: ${body.slice(0, 200)}`);\n }\n const data = (await res.json()) as {\n choices?: Array<{ message?: { content?: string; reasoning_content?: string } }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n };\n const msg = data.choices?.[0]?.message;\n const reply = (msg?.content?.trim() || msg?.reasoning_content?.trim()) ?? \"\";\n if (!reply) throw new Error(\"empty reply from provider\");\n const result: { reply: string; usage?: { prompt_tokens?: number; completion_tokens?: number } } = { reply };\n if (data.usage) result.usage = data.usage;\n return result;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n/**\n * Resolve `ProviderConfig` into an ordered list of concrete `{ provider, model }` steps.\n *\n * If `chain` is provided, parses each entry as `\"provider/model\"` or `\"provider\"` (uses default model).\n * If `chain` is omitted, builds chain from `keys` insertion order, each using provider's default model.\n */\nfunction resolveChain(providers: ProviderConfig): ChainStep[] {\n const keys = providers.keys ?? {};\n const explicit = providers.chain;\n if (explicit && explicit.length > 0) {\n return explicit.map((entry) => normalizeChainEntry(entry, keys));\n }\n const orderedProviders = Object.keys(keys).filter((k) => isKnownProvider(k) && keys[k as Provider]) as Provider[];\n if (orderedProviders.length === 0) {\n throw new Error(\"chatbotlite: at least one provider key is required.\");\n }\n return orderedProviders.map((provider) => ({\n provider,\n model: PROVIDER_ENDPOINTS[provider].defaultModel,\n spec: `${provider}/${PROVIDER_ENDPOINTS[provider].defaultModel}`\n }));\n}\n\nfunction normalizeChainEntry(entry: ChainEntry, keys: Partial<Record<Provider, string>>): ChainStep {\n let provider: Provider;\n let model: string;\n let spec: string;\n if (typeof entry === \"string\") {\n const parsed = parseChainSpec(entry);\n provider = parsed.provider;\n model = parsed.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;\n spec = entry;\n } else {\n if (!isKnownProvider(entry.provider)) {\n throw new Error(`chatbotlite: unknown provider \"${entry.provider}\" in chain entry.`);\n }\n provider = entry.provider;\n model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;\n spec = `${provider}/${model}`;\n }\n if (!keys[provider]) {\n throw new Error(`chatbotlite: chain step \"${spec}\" needs a key for provider \"${provider}\" but none was provided.`);\n }\n return { provider, model, spec };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/client/types.ts","../../src/client/providers.ts","../../src/core/prompts.ts","../../src/core/guards.ts","../../src/client/chatbot.ts"],"names":[],"mappings":";;;AA+EA,IAAM,cAAA,uBAA0C,GAAA,CAAI;AAAA,EAClD,QAAA;AAAA,EAAU,UAAA;AAAA,EAAY,MAAA;AAAA,EAAQ,QAAA;AAAA,EAAU,WAAA;AAAA,EACxC,UAAA;AAAA,EAAY,WAAA;AAAA,EAAa,WAAA;AAAA,EAAa,SAAA;AAAA,EAAW,YAAA;AAAA,EAAc;AACjE,CAAC,CAAA;AAEM,SAAS,gBAAgB,IAAA,EAAgC;AAC9D,EAAA,OAAO,cAAA,CAAe,IAAI,IAAI,CAAA;AAChC;;;AC3EO,IAAM,kBAAA,GAAyD;AAAA,EACpE,MAAA,EAAY,EAAE,OAAA,EAAS,2BAAA,EAA0D,cAAc,aAAA,EAAc;AAAA,EAC7G,QAAA,EAAY,EAAE,OAAA,EAAS,6BAAA,EAA0D,cAAc,eAAA,EAAgB;AAAA,EAC/G,IAAA,EAAY,EAAE,OAAA,EAAS,gCAAA,EAA0D,cAAc,yBAAA,EAA0B;AAAA,EACzH,MAAA,EAAY,EAAE,OAAA,EAAS,yDAAA,EAA2D,cAAc,kBAAA,EAAmB;AAAA,EACnH,SAAA,EAAY,EAAE,OAAA,EAAS,8BAAA,EAA0D,cAAc,kBAAA,EAAmB;AAAA,EAClH,QAAA,EAAY,EAAE,OAAA,EAAS,4BAAA,EAA0D,cAAc,gCAAA,EAAiC;AAAA,EAChI,SAAA,EAAY,EAAE,OAAA,EAAS,6BAAA,EAA0D,cAAc,6BAAA,EAA8B;AAAA,EAC7H,SAAA,EAAY,EAAE,OAAA,EAAS,uCAAA,EAA0D,cAAc,mDAAA,EAAoD;AAAA,EACnJ,OAAA,EAAY,EAAE,OAAA,EAAS,2BAAA,EAA0D,cAAc,sBAAA,EAAuB;AAAA,EACtH,UAAA,EAAY,EAAE,OAAA,EAAS,8BAAA,EAA0D,cAAc,wBAAA,EAAyB;AAAA,EACxH,QAAA,EAAY,EAAE,OAAA,EAAS,4BAAA,EAA0D,cAAc,iBAAA;AACjG;AAEO,SAAS,iBAAiB,GAAA,EAAuB;AACtD,EAAA,MAAM,MAAM,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC3D,EAAA,OAAO,2EAAA,CAA4E,KAAK,GAAG,CAAA;AAC7F;;;ACnBO,SAAS,kBAAkB,SAAA,EAA8B;AAC9D,EAAA,OAAO;AAAA,IACL,wFAAA;AAAA,IACA,EAAA;AAAA,IACA,uBAAA;AAAA,IACA,UAAU,IAAA,EAAK;AAAA,IACf,EAAA;AAAA,IACA,gBAAA;AAAA,IACA,sDAAA;AAAA,IACA,uIAAA;AAAA,IACA,sGAAA;AAAA,IACA,kJAAA;AAAA,IACA,CAAA,iFAAA,CAAA;AAAA,IACA;AAAA,GACF,CAAE,KAAK,IAAI,CAAA;AACb;;;AClBO,IAAM,iBAAA,GAAuC;AAAA,EAClD,gBAAA;AAAA,EACA,uBAAA;AAAA,EACA,0BAAA;AAAA,EACA,wBAAA;AAAA,EACA,qBAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,uBAAA;AAAA,EACA,+BAAA;AAAA,EACA,gBAAA;AAAA,EACA,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA,qBAAA;AAAA,EACA,oBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA;AAMO,SAAS,sBAAsB,KAAA,EAA4B;AAChE,EAAA,MAAM,KAAA,GAAQ,MAAM,WAAA,EAAY;AAChC,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,UAAU,iBAAA,EAAmB;AACtC,IAAA,IAAI,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,EAAG;AAC1B,MAAA,UAAA,CAAW,IAAA,CAAK,CAAA,mBAAA,EAAsB,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,IACjD;AAAA,EACF;AACA,EAAA,OAAO,EAAE,EAAA,EAAI,UAAA,CAAW,MAAA,KAAW,GAAG,UAAA,EAAW;AACnD;AAMO,SAAS,eAAe,KAAA,EAAuB;AACpD,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,eAAe,CAAA;AAC7C,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,MAAA,CAAO,CAAC,CAAA,KAAM;AACnC,IAAA,MAAM,KAAA,GAAQ,EAAE,WAAA,EAAY;AAC5B,IAAA,OAAO,CAAC,kBAAkB,IAAA,CAAK,CAAC,MAAM,KAAA,CAAM,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,EACzD,CAAC,CAAA;AACD,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,GAAG,EAAE,IAAA,EAAK;AACpC,EAAA,IAAI,OAAA,CAAQ,SAAS,EAAA,EAAI;AACvB,IAAA,OAAO,iFAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAA;AACT;;;ACRO,IAAM,UAAN,MAAc;AAAA,EACF,KAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA;AAAA,EAEjB,YAAY,IAAA,EAAmB;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,IAAa,OAAO,IAAA,CAAK,SAAA,KAAc,QAAA,IAAY,IAAA,CAAK,SAAA,CAAU,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,EAAG;AAC/F,MAAA,MAAM,IAAI,MAAM,mEAAmE,CAAA;AAAA,IACrF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAA,IAAQ,EAAC;AACpC,IAAA,IAAA,CAAK,KAAA,GAAQ,YAAA,CAAa,IAAA,CAAK,SAAS,CAAA;AACxC,IAAA,IAAA,CAAK,UAAU,IAAA,CAAK,OAAA,EAAS,SAAS,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AACtE,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA,CAAK,OAAA,EAAS,SAAA,IAAa,GAAA;AAC5C,IAAA,IAAA,CAAK,kBAAA,GAAqB,iBAAA,CAAkB,IAAA,CAAK,SAAS,CAAA;AAAA,EAC5D;AAAA,EAEA,MAAM,KAAA,CAAM,OAAA,EAAiB,IAAA,GAAqB,EAAC,EAAyB;AAC1E,IAAA,MAAM,YAAA,GAAe,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,kBAAA;AAC/C,IAAA,MAAM,QAAA,GAAsB;AAAA,MAC1B,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,YAAA,EAAa;AAAA,MACxC,GAAI,IAAA,CAAK,OAAA,IAAW,EAAC;AAAA,MACrB,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,OAAA;AAAQ,KACnC;AACA,IAAA,MAAM,WAA0B,EAAC;AACjC,IAAA,IAAI,SAAA;AACJ,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,MAAA,MAAM,EAAA,GAAK,KAAK,GAAA,EAAI;AACpB,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,YAAA,CAAa,MAAM,QAAQ,CAAA;AACrD,QAAA,QAAA,CAAS,IAAA,CAAK,EAAE,QAAA,EAAU,IAAA,CAAK,UAAU,KAAA,EAAO,IAAA,CAAK,KAAA,EAAO,MAAA,EAAQ,MAAM,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAI,CAAA;AACtG,QAAA,MAAM,KAAA,GAAQ,qBAAA,CAAsB,MAAA,CAAO,KAAK,CAAA;AAChD,QAAA,MAAM,aAAa,KAAA,CAAM,EAAA,GAAK,OAAO,KAAA,GAAQ,cAAA,CAAe,OAAO,KAAK,CAAA;AACxE,QAAA,OAAO;AAAA,UACL,KAAA,EAAO,UAAA;AAAA,UACP,cAAc,IAAA,CAAK,QAAA;AAAA,UACnB,WAAW,IAAA,CAAK,KAAA;AAAA,UAChB,GAAI,OAAO,KAAA,GAAQ,EAAE,OAAO,MAAA,CAAO,KAAA,KAAU,EAAC;AAAA,UAC9C,eAAe,KAAA,CAAM,UAAA;AAAA,UACrB;AAAA,SACF;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,SAAA,GAAY,GAAA;AACZ,QAAA,MAAM,SAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC9D,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACZ,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,MAAA,EAAQ,OAAA;AAAA,UACR,KAAA,EAAO,MAAA;AAAA,UACP,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,SACzB,CAAA;AACD,QAAA,IAAI,CAAC,gBAAA,CAAiB,GAAG,CAAA,EAAG;AAC1B,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,aAAA,EAAgB,KAAK,KAAK,CAAA,yBAAA,EAA4B,MAAM,CAAA,CAAE,CAAA;AAAA,QAChF;AAAA,MACF;AAAA,IACF;AACA,IAAA,MAAM,UAAU,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAG,EAAE,QAAQ,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,IAAI,CAAA,CAAE,KAAA,IAAS,IAAI,CAAA,CAAE,CAAA,CAAE,KAAK,UAAK,CAAA;AAC7F,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4CAAA,EAA+C,OAAO,CAAA,cAAA,EAAiB,SAAA,YAAqB,KAAA,GAAQ,SAAA,CAAU,OAAA,GAAU,MAAA,CAAO,SAAS,CAAC,CAAA,CAAE,CAAA;AAAA,EAC7J;AAAA,EAEA,MAAc,YAAA,CAAa,IAAA,EAAiB,QAAA,EAAiH;AAC3J,IAAA,MAAM,QAAA,GAAW,kBAAA,CAAmB,IAAA,CAAK,QAAQ,CAAA;AACjD,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA;AACnC,IAAA,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,CAAA,8BAAA,EAAiC,IAAA,CAAK,QAAQ,CAAA,CAAE,CAAA;AAE1E,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,SAAS,CAAA;AACjE,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,MAAM,IAAA,CAAK,QAAQ,CAAA,EAAG,QAAA,CAAS,OAAO,CAAA,iBAAA,CAAA,EAAqB;AAAA,QACrE,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,eAAA,EAAiB,UAAU,GAAG,CAAA,CAAA;AAAA,UAC9B,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,QAAA;AAAA,UACA,WAAA,EAAa,GAAA;AAAA,UACb,UAAA,EAAY;AAAA,SACb,CAAA;AAAA,QACD,QAAQ,UAAA,CAAW;AAAA,OACpB,CAAA;AACD,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,QAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAA,EAAK,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,MACxD;AACA,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAI7B,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,GAAU,CAAC,CAAA,EAAG,OAAA;AAC/B,MAAA,MAAM,KAAA,GAAA,CAAS,KAAK,OAAA,EAAS,IAAA,MAAU,GAAA,EAAK,iBAAA,EAAmB,MAAK,KAAM,EAAA;AAC1E,MAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,2BAA2B,CAAA;AACvD,MAAA,MAAM,MAAA,GAA4F,EAAE,KAAA,EAAM;AAC1G,MAAA,IAAI,IAAA,CAAK,KAAA,EAAO,MAAA,CAAO,KAAA,GAAQ,IAAA,CAAK,KAAA;AACpC,MAAA,OAAO,MAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF;AACF;AAEA,SAAS,aAAa,SAAA,EAAwC;AAC5D,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,IAAA,IAAQ,EAAC;AAChC,EAAA,MAAM,WAAW,SAAA,CAAU,KAAA;AAC3B,EAAA,IAAI,QAAA,IAAY,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG;AACnC,IAAA,OAAO,SAAS,GAAA,CAAI,CAAC,UAAU,mBAAA,CAAoB,KAAA,EAAO,IAAI,CAAC,CAAA;AAAA,EACjE;AACA,EAAA,MAAM,gBAAA,GAAmB,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,eAAA,CAAgB,CAAC,CAAA,IAAK,IAAA,CAAK,CAAa,CAAC,CAAA;AAClG,EAAA,IAAI,gBAAA,CAAiB,WAAW,CAAA,EAAG;AACjC,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,gBAAA,CAAiB,GAAA,CAAI,CAAC,QAAA,MAAc;AAAA,IACzC,QAAA;AAAA,IACA,KAAA,EAAO,kBAAA,CAAmB,QAAQ,CAAA,CAAE,YAAA;AAAA,IACpC,OAAO,CAAA,EAAG,QAAQ,IAAI,kBAAA,CAAmB,QAAQ,EAAE,YAAY,CAAA;AAAA,GACjE,CAAE,CAAA;AACJ;AAEA,SAAS,mBAAA,CAAoB,OAAmB,IAAA,EAAoD;AAClG,EAAA,IAAI,CAAC,eAAA,CAAgB,KAAA,CAAM,QAAQ,CAAA,EAAG;AACpC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,KAAA,CAAM,QAAQ,CAAA,iBAAA,CAAmB,CAAA;AAAA,EACrF;AACA,EAAA,MAAM,WAAW,KAAA,CAAM,QAAA;AACvB,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,IAAS,kBAAA,CAAmB,QAAQ,CAAA,CAAE,YAAA;AAC1D,EAAA,IAAI,CAAC,IAAA,CAAK,QAAQ,CAAA,EAAG;AACnB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,QAAQ,CAAA,yCAAA,CAA2C,CAAA;AAAA,EACtG;AACA,EAAA,OAAO,EAAE,UAAU,KAAA,EAAO,KAAA,EAAO,GAAG,QAAQ,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAG;AAC1D","file":"index.cjs","sourcesContent":["// Client types — provider + chain config\n\nexport type Provider =\n | \"openai\"\n | \"deepseek\"\n | \"groq\"\n | \"gemini\"\n | \"anthropic\"\n | \"cerebras\"\n | \"sambanova\"\n | \"fireworks\"\n | \"mistral\"\n | \"openrouter\"\n | \"moonshot\";\n\n/**\n * One step in the fallback chain. Provider is required; model defaults to the provider's preset.\n */\nexport interface ChainEntry {\n provider: Provider;\n model?: string;\n}\n\n/**\n * Provider configuration.\n *\n * `keys` are auth credentials (one key per provider — that key covers all that provider's models).\n * `chain` is the ordered fallback list. Each entry is `{ provider, model? }`.\n *\n * @example\n * ```ts\n * providers: {\n * keys: {\n * deepseek: \"sk-...\",\n * groq: \"gsk-...\",\n * openai: \"sk-...\"\n * },\n * chain: [\n * { provider: \"deepseek\", model: \"deepseek-chat\" },\n * { provider: \"groq\", model: \"llama-3.3-70b-versatile\" },\n * { provider: \"openai\", model: \"gpt-4o-mini\" }\n * ]\n * }\n * ```\n *\n * If `chain` is omitted, defaults to one entry per key (in insertion order) using each provider's\n * default model.\n */\nexport interface ProviderConfig {\n /** API keys per provider. One key covers all that provider's models. */\n keys: Partial<Record<Provider, string>>;\n /**\n * Ordered fallback chain. Each entry: `{ provider, model? }`.\n * Omit `model` to use the provider's default model.\n * Omit `chain` entirely to auto-build from keys.\n */\n chain?: ChainEntry[];\n}\n\nexport interface ClientOptions {\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n}\n\nexport interface ChainStep {\n provider: Provider;\n model: string;\n /** Human-readable label used in attempt traces, e.g. `\"openai/gpt-4o-mini\"`. */\n label: string;\n}\n\nexport interface AttemptInfo {\n provider: Provider;\n model: string;\n status: \"ok\" | \"error\";\n error?: string;\n latencyMs: number;\n}\n\nconst PROVIDER_NAMES: ReadonlySet<string> = new Set([\n \"openai\", \"deepseek\", \"groq\", \"gemini\", \"anthropic\",\n \"cerebras\", \"sambanova\", \"fireworks\", \"mistral\", \"openrouter\", \"moonshot\"\n]);\n\nexport function isKnownProvider(name: string): name is Provider {\n return PROVIDER_NAMES.has(name);\n}\n","import type { Provider } from \"./types.js\";\n\nexport interface ProviderEndpoint {\n baseUrl: string;\n defaultModel: string;\n}\n\n/**\n * Built-in OpenAI-compatible providers. All use /v1/chat/completions\n * with response in OpenAI format. Caller supplies API key per provider.\n */\nexport const PROVIDER_ENDPOINTS: Record<Provider, ProviderEndpoint> = {\n openai: { baseUrl: \"https://api.openai.com/v1\", defaultModel: \"gpt-4o-mini\" },\n deepseek: { baseUrl: \"https://api.deepseek.com/v1\", defaultModel: \"deepseek-chat\" },\n groq: { baseUrl: \"https://api.groq.com/openai/v1\", defaultModel: \"llama-3.3-70b-versatile\" },\n gemini: { baseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\", defaultModel: \"gemini-2.5-flash\" },\n anthropic: { baseUrl: \"https://api.anthropic.com/v1\", defaultModel: \"claude-haiku-4-5\" },\n cerebras: { baseUrl: \"https://api.cerebras.ai/v1\", defaultModel: \"qwen-3-235b-a22b-instruct-2507\" },\n sambanova: { baseUrl: \"https://api.sambanova.ai/v1\", defaultModel: \"Meta-Llama-3.3-70B-Instruct\" },\n fireworks: { baseUrl: \"https://api.fireworks.ai/inference/v1\", defaultModel: \"accounts/fireworks/models/llama-v3p3-70b-instruct\" },\n mistral: { baseUrl: \"https://api.mistral.ai/v1\", defaultModel: \"mistral-small-latest\" },\n openrouter: { baseUrl: \"https://openrouter.ai/api/v1\", defaultModel: \"deepseek/deepseek-chat\" },\n moonshot: { baseUrl: \"https://api.moonshot.ai/v1\", defaultModel: \"moonshot-v1-32k\" }\n};\n\nexport function isRetryableError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return /\\b(429|rate.?limit|quota|exceed|5\\d\\d|timeout|ECONNRESET|fetch failed)\\b/i.test(msg);\n}\n","import type { Knowledge } from \"./types.js\";\n\n/**\n * Build the system prompt by wrapping the user's markdown knowledge\n * with anti-hallucination rules and reply-style guidance.\n *\n * The markdown is injected verbatim — headings, lists, tables all preserved.\n * Works for any vertical because we don't enforce a schema.\n */\nexport function buildSystemPrompt(knowledge: Knowledge): string {\n return [\n \"You are an AI assistant on a business website. Use ONLY the knowledge below to answer.\",\n \"\",\n \"## Business knowledge\",\n knowledge.trim(),\n \"\",\n \"## Reply rules\",\n \"- Reply in 1-2 short sentences, conversational tone.\",\n \"- NEVER invent prices, availability, dispatch times, appointment confirmations, or facts not present in the business knowledge above.\",\n \"- For anything not covered in the knowledge above, say the owner will follow up — do NOT guess.\",\n '- If the caller is clearly a vendor/sales pitch, say: \"This does not look like a customer service request, so we will not continue this thread.\"',\n '- If wrong number or asked to stop, say: \"Sorry about that. We won\\'t text again.\"',\n \"- Match the caller's language automatically.\"\n ].join(\"\\n\");\n}\n","import type { GuardResult } from \"./types.js\";\n\n/**\n * Phrases that almost always indicate hallucination for SMB customer service:\n * inventing dispatch promises, fake confirmations, or appointment locks.\n */\nexport const FORBIDDEN_PHRASES: readonly string[] = [\n \"help is coming\",\n \"someone is on the way\",\n \"technician is on the way\",\n \"provider is on the way\",\n \"dispatching someone\",\n \"i've booked\",\n \"i have booked\",\n \"reservation confirmed\",\n \"your appointment is confirmed\",\n \"i've scheduled\",\n \"i have scheduled\",\n \"we've dispatched\",\n \"we have dispatched\",\n \"i can confirm\",\n \"i guarantee\",\n \"guaranteed delivery\",\n \"guaranteed arrival\",\n \"will arrive at\",\n \"arriving at\",\n \"i'll send\",\n \"i will send\"\n];\n\n/**\n * Check a reply against the built-in forbidden phrase list.\n * Returns ok=true when clean, ok=false with violations when not.\n */\nexport function checkForbiddenPhrases(reply: string): GuardResult {\n const lower = reply.toLowerCase();\n const violations: string[] = [];\n for (const phrase of FORBIDDEN_PHRASES) {\n if (lower.includes(phrase)) {\n violations.push(`Forbidden phrase: \"${phrase}\"`);\n }\n }\n return { ok: violations.length === 0, violations };\n}\n\n/**\n * Remove forbidden sentences from a reply (best-effort sentence drop).\n * If too much is removed, returns a safe fallback.\n */\nexport function stripForbidden(reply: string): string {\n const sentences = reply.split(/(?<=[.!?])\\s+/);\n const kept = sentences.filter((s) => {\n const lower = s.toLowerCase();\n return !FORBIDDEN_PHRASES.some((p) => lower.includes(p));\n });\n const trimmed = kept.join(\" \").trim();\n if (trimmed.length < 10) {\n return \"Thanks for reaching out — let me check with the owner and get back to you.\";\n }\n return trimmed;\n}\n","import type { Knowledge, Message } from \"../core/types.js\";\nimport { buildSystemPrompt } from \"../core/prompts.js\";\nimport { checkForbiddenPhrases, stripForbidden } from \"../core/guards.js\";\nimport type { Provider, ProviderConfig, ClientOptions, ChainStep, ChainEntry, AttemptInfo } from \"./types.js\";\nimport { isKnownProvider } from \"./types.js\";\nimport { PROVIDER_ENDPOINTS, isRetryableError } from \"./providers.js\";\n\nexport interface ChatBotInit {\n /** Markdown describing the business — services, hours, policies, anything. */\n knowledge: Knowledge;\n /** Provider keys + fallback chain. */\n providers: ProviderConfig;\n /** Optional runtime overrides. */\n options?: ClientOptions;\n}\n\nexport interface ReplyOptions {\n /** Conversation history (excluding the new user message). */\n history?: Message[];\n /** Override system prompt — advanced use only. */\n systemPrompt?: string;\n}\n\nexport interface ReplyResult {\n reply: string;\n /** Provider/model that produced the final reply (after fallback). */\n usedProvider: Provider;\n usedModel: string;\n /** Token usage if reported by the final provider. */\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n /** Guard violations the bot caught and stripped, if any. */\n guardWarnings: string[];\n /** Debug trace of every attempt in the chain. */\n attempts: AttemptInfo[];\n}\n\n/**\n * The main ChatBot entry. Holds knowledge + provider chain.\n *\n * @example\n * const bot = new ChatBot({\n * knowledge: `# Acme Plumbing\\n## Services\\n- Sink leak: $95`,\n * providers: {\n * keys: { deepseek: \"sk-...\", openai: \"sk-...\" },\n * chain: [\n * { provider: \"deepseek\", model: \"deepseek-chat\" },\n * { provider: \"openai\", model: \"gpt-4o-mini\" }\n * ]\n * }\n * });\n * const { reply } = await bot.reply(\"My sink is leaking\");\n */\nexport class ChatBot {\n private readonly steps: ChainStep[];\n private readonly keys: Partial<Record<Provider, string>>;\n private readonly fetcher: typeof globalThis.fetch;\n private readonly timeoutMs: number;\n private readonly cachedSystemPrompt: string;\n\n constructor(init: ChatBotInit) {\n if (!init.knowledge || typeof init.knowledge !== \"string\" || init.knowledge.trim().length === 0) {\n throw new Error(\"chatbotlite: knowledge is required (a non-empty markdown string).\");\n }\n this.keys = init.providers.keys ?? {};\n this.steps = resolveChain(init.providers);\n this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);\n this.timeoutMs = init.options?.timeoutMs ?? 30_000;\n this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);\n }\n\n async reply(message: string, opts: ReplyOptions = {}): Promise<ReplyResult> {\n const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;\n const messages: Message[] = [\n { role: \"system\", content: systemPrompt },\n ...(opts.history ?? []),\n { role: \"user\", content: message }\n ];\n const attempts: AttemptInfo[] = [];\n let lastError: unknown;\n for (const step of this.steps) {\n const t0 = Date.now();\n try {\n const result = await this.callProvider(step, messages);\n attempts.push({ provider: step.provider, model: step.model, status: \"ok\", latencyMs: Date.now() - t0 });\n const guard = checkForbiddenPhrases(result.reply);\n const finalReply = guard.ok ? result.reply : stripForbidden(result.reply);\n return {\n reply: finalReply,\n usedProvider: step.provider,\n usedModel: step.model,\n ...(result.usage ? { usage: result.usage } : {}),\n guardWarnings: guard.violations,\n attempts\n };\n } catch (err) {\n lastError = err;\n const errMsg = err instanceof Error ? err.message : String(err);\n attempts.push({\n provider: step.provider,\n model: step.model,\n status: \"error\",\n error: errMsg,\n latencyMs: Date.now() - t0\n });\n if (!isRetryableError(err)) {\n throw new Error(`chatbotlite: ${step.label} failed (non-retryable). ${errMsg}`);\n }\n }\n }\n const summary = attempts.map((a) => `${a.provider}/${a.model}:${a.error ?? \"ok\"}`).join(\" → \");\n throw new Error(`chatbotlite: all chain steps failed. Trace: ${summary}. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);\n }\n\n private async callProvider(step: ChainStep, messages: Message[]): Promise<{ reply: string; usage?: { prompt_tokens?: number; completion_tokens?: number } }> {\n const endpoint = PROVIDER_ENDPOINTS[step.provider];\n const key = this.keys[step.provider];\n if (!key) throw new Error(`Missing API key for provider: ${step.provider}`);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n try {\n const res = await this.fetcher(`${endpoint.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${key}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({\n model: step.model,\n messages,\n temperature: 0.3,\n max_tokens: 300\n }),\n signal: controller.signal\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`${res.status}: ${body.slice(0, 200)}`);\n }\n const data = (await res.json()) as {\n choices?: Array<{ message?: { content?: string; reasoning_content?: string } }>;\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n };\n const msg = data.choices?.[0]?.message;\n const reply = (msg?.content?.trim() || msg?.reasoning_content?.trim()) ?? \"\";\n if (!reply) throw new Error(\"empty reply from provider\");\n const result: { reply: string; usage?: { prompt_tokens?: number; completion_tokens?: number } } = { reply };\n if (data.usage) result.usage = data.usage;\n return result;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\nfunction resolveChain(providers: ProviderConfig): ChainStep[] {\n const keys = providers.keys ?? {};\n const explicit = providers.chain;\n if (explicit && explicit.length > 0) {\n return explicit.map((entry) => normalizeChainEntry(entry, keys));\n }\n const orderedProviders = Object.keys(keys).filter((k) => isKnownProvider(k) && keys[k as Provider]) as Provider[];\n if (orderedProviders.length === 0) {\n throw new Error(\"chatbotlite: at least one provider key is required.\");\n }\n return orderedProviders.map((provider) => ({\n provider,\n model: PROVIDER_ENDPOINTS[provider].defaultModel,\n label: `${provider}/${PROVIDER_ENDPOINTS[provider].defaultModel}`\n }));\n}\n\nfunction normalizeChainEntry(entry: ChainEntry, keys: Partial<Record<Provider, string>>): ChainStep {\n if (!isKnownProvider(entry.provider)) {\n throw new Error(`chatbotlite: unknown provider \"${entry.provider}\" in chain entry.`);\n }\n const provider = entry.provider;\n const model = entry.model ?? PROVIDER_ENDPOINTS[provider].defaultModel;\n if (!keys[provider]) {\n throw new Error(`chatbotlite: chain entry for \"${provider}\" needs a matching key in providers.keys.`);\n }\n return { provider, model, label: `${provider}/${model}` };\n}\n"]}
|
package/dist/client/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P as Provider, c as ProviderConfig, b as ClientOptions, A as AttemptInfo } from '../types-
|
|
2
|
-
export { C as ChainEntry, a as ChainStep, i as isKnownProvider
|
|
3
|
-
import {
|
|
1
|
+
import { P as Provider, c as ProviderConfig, b as ClientOptions, A as AttemptInfo } from '../types-J7BXpiRU.cjs';
|
|
2
|
+
export { C as ChainEntry, a as ChainStep, i as isKnownProvider } from '../types-J7BXpiRU.cjs';
|
|
3
|
+
import { K as Knowledge, M as Message } from '../types-4alyzg8O.cjs';
|
|
4
4
|
|
|
5
5
|
interface ProviderEndpoint {
|
|
6
6
|
baseUrl: string;
|
|
@@ -14,8 +14,11 @@ declare const PROVIDER_ENDPOINTS: Record<Provider, ProviderEndpoint>;
|
|
|
14
14
|
declare function isRetryableError(err: unknown): boolean;
|
|
15
15
|
|
|
16
16
|
interface ChatBotInit {
|
|
17
|
-
business
|
|
17
|
+
/** Markdown describing the business — services, hours, policies, anything. */
|
|
18
|
+
knowledge: Knowledge;
|
|
19
|
+
/** Provider keys + fallback chain. */
|
|
18
20
|
providers: ProviderConfig;
|
|
21
|
+
/** Optional runtime overrides. */
|
|
19
22
|
options?: ClientOptions;
|
|
20
23
|
}
|
|
21
24
|
interface ReplyOptions {
|
|
@@ -40,20 +43,22 @@ interface ReplyResult {
|
|
|
40
43
|
attempts: AttemptInfo[];
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
|
-
* The main ChatBot entry. Holds
|
|
46
|
+
* The main ChatBot entry. Holds knowledge + provider chain.
|
|
44
47
|
*
|
|
45
48
|
* @example
|
|
46
49
|
* const bot = new ChatBot({
|
|
47
|
-
*
|
|
50
|
+
* knowledge: `# Acme Plumbing\n## Services\n- Sink leak: $95`,
|
|
48
51
|
* providers: {
|
|
49
|
-
* keys: { deepseek: "sk-...",
|
|
50
|
-
* chain: [
|
|
52
|
+
* keys: { deepseek: "sk-...", openai: "sk-..." },
|
|
53
|
+
* chain: [
|
|
54
|
+
* { provider: "deepseek", model: "deepseek-chat" },
|
|
55
|
+
* { provider: "openai", model: "gpt-4o-mini" }
|
|
56
|
+
* ]
|
|
51
57
|
* }
|
|
52
58
|
* });
|
|
53
59
|
* const { reply } = await bot.reply("My sink is leaking");
|
|
54
60
|
*/
|
|
55
61
|
declare class ChatBot {
|
|
56
|
-
private readonly business;
|
|
57
62
|
private readonly steps;
|
|
58
63
|
private readonly keys;
|
|
59
64
|
private readonly fetcher;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { P as Provider, c as ProviderConfig, b as ClientOptions, A as AttemptInfo } from '../types-
|
|
2
|
-
export { C as ChainEntry, a as ChainStep, i as isKnownProvider
|
|
3
|
-
import {
|
|
1
|
+
import { P as Provider, c as ProviderConfig, b as ClientOptions, A as AttemptInfo } from '../types-J7BXpiRU.js';
|
|
2
|
+
export { C as ChainEntry, a as ChainStep, i as isKnownProvider } from '../types-J7BXpiRU.js';
|
|
3
|
+
import { K as Knowledge, M as Message } from '../types-4alyzg8O.js';
|
|
4
4
|
|
|
5
5
|
interface ProviderEndpoint {
|
|
6
6
|
baseUrl: string;
|
|
@@ -14,8 +14,11 @@ declare const PROVIDER_ENDPOINTS: Record<Provider, ProviderEndpoint>;
|
|
|
14
14
|
declare function isRetryableError(err: unknown): boolean;
|
|
15
15
|
|
|
16
16
|
interface ChatBotInit {
|
|
17
|
-
business
|
|
17
|
+
/** Markdown describing the business — services, hours, policies, anything. */
|
|
18
|
+
knowledge: Knowledge;
|
|
19
|
+
/** Provider keys + fallback chain. */
|
|
18
20
|
providers: ProviderConfig;
|
|
21
|
+
/** Optional runtime overrides. */
|
|
19
22
|
options?: ClientOptions;
|
|
20
23
|
}
|
|
21
24
|
interface ReplyOptions {
|
|
@@ -40,20 +43,22 @@ interface ReplyResult {
|
|
|
40
43
|
attempts: AttemptInfo[];
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
|
-
* The main ChatBot entry. Holds
|
|
46
|
+
* The main ChatBot entry. Holds knowledge + provider chain.
|
|
44
47
|
*
|
|
45
48
|
* @example
|
|
46
49
|
* const bot = new ChatBot({
|
|
47
|
-
*
|
|
50
|
+
* knowledge: `# Acme Plumbing\n## Services\n- Sink leak: $95`,
|
|
48
51
|
* providers: {
|
|
49
|
-
* keys: { deepseek: "sk-...",
|
|
50
|
-
* chain: [
|
|
52
|
+
* keys: { deepseek: "sk-...", openai: "sk-..." },
|
|
53
|
+
* chain: [
|
|
54
|
+
* { provider: "deepseek", model: "deepseek-chat" },
|
|
55
|
+
* { provider: "openai", model: "gpt-4o-mini" }
|
|
56
|
+
* ]
|
|
51
57
|
* }
|
|
52
58
|
* });
|
|
53
59
|
* const { reply } = await bot.reply("My sink is leaking");
|
|
54
60
|
*/
|
|
55
61
|
declare class ChatBot {
|
|
56
|
-
private readonly business;
|
|
57
62
|
private readonly steps;
|
|
58
63
|
private readonly keys;
|
|
59
64
|
private readonly fetcher;
|