chatbotlite 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,60 @@ var jsxRuntime = require('react/jsx-runtime');
5
5
 
6
6
  // src/react/ChatWidget.tsx
7
7
 
8
+ // src/core/tools.ts
9
+ var MARKER_RE = /\[SKILL:(\w+)((?:\s+\w+=(?:"[^"]*"|[\w./@*+,:-]+))*)\s*\]/g;
10
+ var ARG_RE = /(\w+)=("([^"]*)"|([\w./@*+,:-]+))/g;
11
+ function coerce(value) {
12
+ if (value === "true") return true;
13
+ if (value === "false") return false;
14
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
15
+ return value;
16
+ }
17
+ function parseToolMarkers(text) {
18
+ const markers = [];
19
+ let m;
20
+ MARKER_RE.lastIndex = 0;
21
+ while ((m = MARKER_RE.exec(text)) !== null) {
22
+ const name = m[1];
23
+ const argsRaw = m[2] ?? "";
24
+ const args = {};
25
+ let a;
26
+ ARG_RE.lastIndex = 0;
27
+ while ((a = ARG_RE.exec(argsRaw)) !== null) {
28
+ const key = a[1];
29
+ const value = a[3] ?? a[4] ?? "";
30
+ args[key] = coerce(value);
31
+ }
32
+ markers.push({ name, args, raw: m[0] });
33
+ }
34
+ return markers;
35
+ }
36
+ function stripToolMarkers(text) {
37
+ return text.replace(MARKER_RE, "").replace(/\s+\n/g, "\n").trim();
38
+ }
39
+ function buildToolsPromptAddendum(enabledTools) {
40
+ if (enabledTools.length === 0) return "";
41
+ const examples = {
42
+ uploadForReview: '[SKILL:uploadForReview purpose="T4 slip" accept="image/*,application/pdf" maxMb=10] \u2014 collect a document for human review (bytes go to webhook, you never see content)',
43
+ scheduleCallback: '[SKILL:scheduleCallback durationMin=15 timezone="America/Vancouver"] \u2014 let the user pick a callback time slot',
44
+ requestPayment: '[SKILL:requestPayment amount=4250 currency="cad" reason="initial deposit"] \u2014 collect payment via inline card'
45
+ };
46
+ const lines = enabledTools.filter((t) => examples[t]).map((t) => `- ${examples[t]}`);
47
+ if (lines.length === 0) return "";
48
+ return [
49
+ "",
50
+ "## Available tools",
51
+ "When you need one of these workflows, emit the marker INLINE in your reply.",
52
+ "Write a short message first, THEN the marker. The marker will be replaced by an interactive card.",
53
+ "Pause the conversation after emitting \u2014 wait for the tool result before continuing.",
54
+ "",
55
+ ...lines
56
+ ].join("\n");
57
+ }
58
+
8
59
  // src/core/prompts.ts
9
- function buildSystemPrompt(knowledge) {
60
+ function buildSystemPrompt(knowledge, enabledTools = []) {
61
+ const toolsAddendum = buildToolsPromptAddendum(enabledTools);
10
62
  return [
11
63
  "You are an AI assistant on a business website. Use ONLY the knowledge below to answer.",
12
64
  "",
@@ -19,8 +71,9 @@ function buildSystemPrompt(knowledge) {
19
71
  "- For anything not covered in the knowledge above, say the owner will follow up \u2014 do NOT guess.",
20
72
  '- 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."',
21
73
  `- If wrong number or asked to stop, say: "Sorry about that. We won't text again."`,
22
- "- Match the caller's language automatically."
23
- ].join("\n");
74
+ "- Match the caller's language automatically.",
75
+ toolsAddendum
76
+ ].filter(Boolean).join("\n");
24
77
  }
25
78
 
26
79
  // src/core/guards.ts
@@ -143,10 +196,12 @@ var ChatBot = class {
143
196
  timeoutMs;
144
197
  cachedSystemPrompt;
145
198
  guards;
199
+ knowledge;
146
200
  constructor(init) {
147
201
  if (!init.knowledge || typeof init.knowledge !== "string" || init.knowledge.trim().length === 0) {
148
202
  throw new Error("chatbotlite: knowledge is required (a non-empty markdown string).");
149
203
  }
204
+ this.knowledge = init.knowledge;
150
205
  this.keys = init.providers.keys ?? {};
151
206
  this.steps = resolveChain(init.providers);
152
207
  this.fetcher = init.options?.fetch ?? globalThis.fetch.bind(globalThis);
@@ -154,6 +209,14 @@ var ChatBot = class {
154
209
  this.cachedSystemPrompt = buildSystemPrompt(init.knowledge);
155
210
  this.guards = init.guards ?? {};
156
211
  }
212
+ /** Build system prompt for given opts — uses cached if no enabledTools, else rebuilds. */
213
+ resolveSystemPrompt(opts) {
214
+ if (opts.systemPrompt) return opts.systemPrompt;
215
+ if (opts.enabledTools && opts.enabledTools.length > 0) {
216
+ return buildSystemPrompt(this.knowledge, opts.enabledTools);
217
+ }
218
+ return this.cachedSystemPrompt;
219
+ }
157
220
  /** Run an LLM judge against content. Fail-open on errors. */
158
221
  async judge(config, content) {
159
222
  const endpoint = PROVIDER_ENDPOINTS[config.provider];
@@ -190,7 +253,7 @@ var ChatBot = class {
190
253
  * event: error data: {"message":"...","attempts":[...]}
191
254
  */
192
255
  async replyStream(message, opts = {}) {
193
- const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
256
+ const systemPrompt = this.resolveSystemPrompt(opts);
194
257
  const messages = [
195
258
  { role: "system", content: systemPrompt },
196
259
  ...opts.history ?? [],
@@ -303,7 +366,7 @@ data: ${data}
303
366
  return this.reply(message, opts);
304
367
  }
305
368
  const dataUrls = await Promise.all(images.map(fileToDataUrl));
306
- const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
369
+ const systemPrompt = this.resolveSystemPrompt(opts);
307
370
  const userContent = [];
308
371
  if (message) userContent.push({ type: "text", text: message });
309
372
  for (const url of dataUrls) userContent.push({ type: "image_url", image_url: { url } });
@@ -383,7 +446,7 @@ data: ${data}
383
446
  };
384
447
  }
385
448
  }
386
- const systemPrompt = opts.systemPrompt ?? this.cachedSystemPrompt;
449
+ const systemPrompt = this.resolveSystemPrompt(opts);
387
450
  const messages = [
388
451
  { role: "system", content: systemPrompt },
389
452
  ...opts.history ?? [],
@@ -496,38 +559,6 @@ function normalizeChainEntry(entry, keys) {
496
559
  }
497
560
  return { provider, model, label: `${provider}/${model}` };
498
561
  }
499
-
500
- // src/core/tools.ts
501
- var MARKER_RE = /\[SKILL:(\w+)((?:\s+\w+=(?:"[^"]*"|[\w./@*+,:-]+))*)\s*\]/g;
502
- var ARG_RE = /(\w+)=("([^"]*)"|([\w./@*+,:-]+))/g;
503
- function coerce(value) {
504
- if (value === "true") return true;
505
- if (value === "false") return false;
506
- if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
507
- return value;
508
- }
509
- function parseToolMarkers(text) {
510
- const markers = [];
511
- let m;
512
- MARKER_RE.lastIndex = 0;
513
- while ((m = MARKER_RE.exec(text)) !== null) {
514
- const name = m[1];
515
- const argsRaw = m[2] ?? "";
516
- const args = {};
517
- let a;
518
- ARG_RE.lastIndex = 0;
519
- while ((a = ARG_RE.exec(argsRaw)) !== null) {
520
- const key = a[1];
521
- const value = a[3] ?? a[4] ?? "";
522
- args[key] = coerce(value);
523
- }
524
- markers.push({ name, args, raw: m[0] });
525
- }
526
- return markers;
527
- }
528
- function stripToolMarkers(text) {
529
- return text.replace(MARKER_RE, "").replace(/\s+\n/g, "\n").trim();
530
- }
531
562
  var CLOUD_ICON = "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12";
532
563
  function UploadForReview(props) {
533
564
  const {
@@ -1158,17 +1189,19 @@ function ChatWidget(props) {
1158
1189
  return void 0;
1159
1190
  }, [open]);
1160
1191
  async function fetchReplyFromEndpoint(text, history, attachedFiles, onToken) {
1192
+ const enabledTools = Object.keys(tools);
1161
1193
  let body;
1162
1194
  const headers = { Accept: "text/event-stream, application/json" };
1163
1195
  if (attachedFiles.length > 0) {
1164
1196
  const form = new FormData();
1165
1197
  form.append("message", text);
1166
1198
  form.append("transcript", JSON.stringify(history));
1199
+ form.append("enabledTools", JSON.stringify(enabledTools));
1167
1200
  for (const f of attachedFiles) form.append("attachments", f, f.name);
1168
1201
  body = form;
1169
1202
  } else {
1170
1203
  headers["Content-Type"] = "application/json";
1171
- body = JSON.stringify({ message: text, transcript: history });
1204
+ body = JSON.stringify({ message: text, transcript: history, enabledTools });
1172
1205
  }
1173
1206
  const res = await fetch(props.endpoint, { method: "POST", headers, body });
1174
1207
  if (!res.ok) throw new Error(`Endpoint ${res.status}: ${await res.text().catch(() => "")}`);