frontend-agent 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,489 @@
1
+ import { Wllama } from '@wllama/wllama';
2
+
3
+ // src/engine/gbnf.ts
4
+ function lit(s) {
5
+ return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
6
+ }
7
+ function sq(s) {
8
+ return lit("'" + s + "'");
9
+ }
10
+ function rn(name) {
11
+ return name.replace(/[^A-Za-z0-9]/g, "-");
12
+ }
13
+ function valueRule(tname, key, spec, validIds, idKeys) {
14
+ if (idKeys.includes(key) && validIds && validIds.length) return `${rn(tname)}-${rn(key)}-id`;
15
+ const en = spec.enum;
16
+ if (Array.isArray(en)) return "( " + en.map((v) => sq(String(v))).join(" | ") + " )";
17
+ const t = spec.type;
18
+ if (t === "integer" || t === "number") return "numval";
19
+ if (t === "boolean") return "boolval";
20
+ return "strval";
21
+ }
22
+ function buildToolGrammar(tools, validIds, idKeys = ["id"], allowText = true) {
23
+ const rules = ['sep ::= "," " "?'];
24
+ const callAlts = [];
25
+ const idAltsNeeded = [];
26
+ for (const t of tools) {
27
+ const name = t.name;
28
+ const props = t.parameters?.properties ?? {};
29
+ const required = t.parameters?.required ?? [];
30
+ const r = rn(name);
31
+ const reqFrags = [];
32
+ const optFrags = [];
33
+ for (const [key, spec] of Object.entries(props)) {
34
+ const val = valueRule(name, key, spec, validIds, idKeys);
35
+ if (idKeys.includes(key) && validIds && validIds.length) idAltsNeeded.push([name, key]);
36
+ const frag = `${r}-${rn(key)}`;
37
+ rules.push(`${frag} ::= ${lit(key + "=")} ${val}`);
38
+ (required.includes(key) ? reqFrags : optFrags).push(frag);
39
+ }
40
+ let seq = reqFrags.join(" sep ");
41
+ for (const o of optFrags) seq = (seq ? seq + " " : "") + `( sep ${o} )?`;
42
+ rules.push(`${r}-call ::= ${lit(name + "(")} ${seq} ${lit(")")}`.replace(/ {2}/g, " "));
43
+ callAlts.push(`${r}-call`);
44
+ }
45
+ rules.push("call ::= ( " + callAlts.join(" | ") + " )");
46
+ rules.push("toolcall ::= " + lit("[") + " call ( sep call )* " + lit("]"));
47
+ rules.push(
48
+ "wrapped ::= ( " + lit("<|tool_call_start|>") + " )? toolcall ( " + lit("<|tool_call_end|>") + " )?"
49
+ );
50
+ for (const [name, key] of idAltsNeeded) {
51
+ rules.push(`${rn(name)}-${rn(key)}-id ::= ` + validIds.map(sq).join(" | "));
52
+ }
53
+ if (allowText) {
54
+ rules.push("reply ::= [^\\[<] [^\\x00]*");
55
+ rules.push("root ::= wrapped | reply");
56
+ } else {
57
+ rules.push("root ::= wrapped");
58
+ }
59
+ rules.push(`strval ::= "'" [^']* "'"`);
60
+ rules.push('numval ::= "-"? [0-9]+ ("." [0-9]+)?');
61
+ rules.push('boolval ::= "True" | "False"');
62
+ return rules.join("\n");
63
+ }
64
+ function collectIds(value, into) {
65
+ if (Array.isArray(value)) {
66
+ for (const v of value) collectIds(v, into);
67
+ } else if (value && typeof value === "object") {
68
+ for (const [k, v] of Object.entries(value)) {
69
+ if (k === "id" && typeof v === "string" && v) into.add(v);
70
+ else collectIds(v, into);
71
+ }
72
+ }
73
+ }
74
+
75
+ // src/limits.ts
76
+ var N_CTX = 8192;
77
+ var MAX_OUTPUT_TOKENS = 320;
78
+ var SYSTEM_CTX_WARN_FRACTION = 0.15;
79
+ var HISTORY_TOKEN_BUDGET = N_CTX - Math.ceil(N_CTX * SYSTEM_CTX_WARN_FRACTION) - MAX_OUTPUT_TOKENS - 256;
80
+ var estimateTokens = (text) => Math.ceil((text?.length ?? 0) / 4);
81
+
82
+ // src/loop/agent.ts
83
+ var GenerationTimeoutError = class extends Error {
84
+ constructor(ms) {
85
+ super(`Generation timed out after ${ms}ms - the engine may have crashed or hung.`);
86
+ this.name = "GenerationTimeoutError";
87
+ }
88
+ };
89
+ function withTimeout(promise, ms) {
90
+ if (!ms) return promise;
91
+ let timer;
92
+ const timeout = new Promise((_, reject) => {
93
+ timer = setTimeout(() => reject(new GenerationTimeoutError(ms)), ms);
94
+ });
95
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
96
+ }
97
+ var GIVE_UP = "I'm having trouble completing this - let me know if you'd like to try a simpler request.";
98
+ var AbortError = class extends Error {
99
+ constructor() {
100
+ super("aborted");
101
+ this.name = "AbortError";
102
+ }
103
+ };
104
+ function createAgent(config) {
105
+ const maxIterations = config.maxIterations ?? 8;
106
+ const idKeys = config.idKeys ?? ["id"];
107
+ const budget = config.historyBudgetTokens ?? HISTORY_TOKEN_BUDGET;
108
+ const generationTimeoutMs = config.generationTimeoutMs ?? 9e4;
109
+ const history = [];
110
+ const listeners = /* @__PURE__ */ new Set();
111
+ let controller = null;
112
+ const emit = (e) => {
113
+ for (const l of listeners) l(e);
114
+ };
115
+ async function runToolCall(call) {
116
+ const handler = config.tools.handlers[call.name];
117
+ if (!handler) return { error: "unknown_tool", name: call.name };
118
+ try {
119
+ return await handler(call.args);
120
+ } catch (err) {
121
+ return { error: "tool_execution_failed", message: err.message };
122
+ }
123
+ }
124
+ async function runLoop(system, signal) {
125
+ const seenIds = /* @__PURE__ */ new Set();
126
+ for (let i = 0; i < maxIterations; i++) {
127
+ if (signal.aborted) throw new AbortError();
128
+ trimHistory(history, budget);
129
+ const messages = [{ role: "system", content: system }, ...history];
130
+ const grammar = buildToolGrammar(
131
+ config.tools.schemas,
132
+ seenIds.size ? [...seenIds] : void 0,
133
+ idKeys
134
+ );
135
+ const result = await withTimeout(config.engine.generate(messages, grammar), generationTimeoutMs);
136
+ if (signal.aborted) throw new AbortError();
137
+ if (result.toolCalls.length === 0) {
138
+ history.push({ role: "assistant", content: result.raw });
139
+ emit({ type: "assistant", text: result.text });
140
+ emit({ type: "status", status: "ready" });
141
+ return;
142
+ }
143
+ history.push({ role: "assistant", content: result.raw });
144
+ if (result.text) emit({ type: "assistant", text: result.text });
145
+ for (const call of result.toolCalls) {
146
+ emit({ type: "tool_call", call });
147
+ const toolResult = await runToolCall(call);
148
+ collectIds(toolResult, seenIds);
149
+ emit({ type: "tool_result", name: call.name, result: toolResult });
150
+ history.push({ role: "tool", content: JSON.stringify(toolResult) });
151
+ }
152
+ }
153
+ history.push({ role: "assistant", content: GIVE_UP });
154
+ emit({ type: "assistant", text: GIVE_UP });
155
+ emit({ type: "status", status: "ready" });
156
+ }
157
+ return {
158
+ history,
159
+ on(listener) {
160
+ listeners.add(listener);
161
+ return () => listeners.delete(listener);
162
+ },
163
+ abort() {
164
+ controller?.abort();
165
+ },
166
+ reset() {
167
+ history.length = 0;
168
+ },
169
+ async submit(text, opts) {
170
+ controller = new AbortController();
171
+ const signal = opts?.signal ? AbortSignal.any([controller.signal, opts.signal]) : controller.signal;
172
+ history.push({ role: "user", content: text });
173
+ emit({ type: "status", status: "thinking" });
174
+ try {
175
+ const system = typeof config.systemPrompt === "function" ? await config.systemPrompt() : config.systemPrompt;
176
+ await runLoop(system, signal);
177
+ emit({ type: "done" });
178
+ } catch (err) {
179
+ if (err instanceof AbortError || signal.aborted) {
180
+ emit({ type: "status", status: "aborted" });
181
+ emit({ type: "done" });
182
+ } else {
183
+ emit({ type: "error", error: err });
184
+ emit({ type: "status", status: "error" });
185
+ }
186
+ } finally {
187
+ controller = null;
188
+ }
189
+ }
190
+ };
191
+ }
192
+ function trimHistory(history, budget) {
193
+ const tokens = () => history.reduce((n, m) => n + estimateTokens(m.content), 0);
194
+ while (history.length > 2 && tokens() > budget) {
195
+ history.shift();
196
+ while (history.length > 1 && history[0].role === "tool") history.shift();
197
+ }
198
+ }
199
+
200
+ // src/parsing/toolCallParser.ts
201
+ var CALL_BLOCK_RE = /<\|tool_call_start\|>([\s\S]*?)<\|tool_call_end\|>/g;
202
+ function parseToolCalls(raw) {
203
+ const toolCalls = [];
204
+ const text = raw.replace(CALL_BLOCK_RE, (_match, inner) => {
205
+ toolCalls.push(...parseCallList(inner.trim()));
206
+ return "";
207
+ });
208
+ return { text: text.trim(), toolCalls };
209
+ }
210
+ function parseCallList(block) {
211
+ const inner = block.startsWith("[") && block.endsWith("]") ? block.slice(1, -1) : block;
212
+ const calls = [];
213
+ for (const piece of splitTopLevel(inner, ",")) {
214
+ const trimmed = piece.trim();
215
+ if (!trimmed) continue;
216
+ const call = parseSingleCall(trimmed);
217
+ if (call) calls.push(call);
218
+ }
219
+ return calls;
220
+ }
221
+ function parseSingleCall(expr) {
222
+ const match = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/s);
223
+ if (!match) return null;
224
+ const [, name, argsSrc] = match;
225
+ const args = {};
226
+ for (const piece of splitTopLevel(argsSrc, ",")) {
227
+ const trimmed = piece.trim();
228
+ if (!trimmed) continue;
229
+ const eq = trimmed.indexOf("=");
230
+ if (eq === -1) continue;
231
+ const key = trimmed.slice(0, eq).trim();
232
+ const valueSrc = trimmed.slice(eq + 1).trim();
233
+ args[key] = parseValue(valueSrc);
234
+ }
235
+ return { name, args };
236
+ }
237
+ function parseValue(src) {
238
+ if (src === "True") return true;
239
+ if (src === "False") return false;
240
+ if (src === "None") return null;
241
+ if (/^-?\d+$/.test(src)) return parseInt(src, 10);
242
+ if (/^-?\d+\.\d+$/.test(src)) return parseFloat(src);
243
+ if (src.startsWith('"') && src.endsWith('"') || src.startsWith("'") && src.endsWith("'")) {
244
+ return src.slice(1, -1);
245
+ }
246
+ return src;
247
+ }
248
+ function splitTopLevel(src, delimiter) {
249
+ const parts = [];
250
+ let depth = 0;
251
+ let quote = null;
252
+ let current = "";
253
+ for (const ch of src) {
254
+ if (quote) {
255
+ current += ch;
256
+ if (ch === quote) quote = null;
257
+ continue;
258
+ }
259
+ if (ch === '"' || ch === "'") {
260
+ quote = ch;
261
+ current += ch;
262
+ continue;
263
+ }
264
+ if (ch === "(" || ch === "[") depth++;
265
+ if (ch === ")" || ch === "]") depth--;
266
+ if (ch === delimiter && depth === 0) {
267
+ parts.push(current);
268
+ current = "";
269
+ continue;
270
+ }
271
+ current += ch;
272
+ }
273
+ if (current.trim()) parts.push(current);
274
+ return parts;
275
+ }
276
+
277
+ // src/parsing/renderToolCalls.ts
278
+ function formatArg(value) {
279
+ if (typeof value === "string") return `'${value}'`;
280
+ if (value === null) return "None";
281
+ if (typeof value === "boolean") return value ? "True" : "False";
282
+ if (typeof value === "object") return JSON.stringify(value);
283
+ return String(value);
284
+ }
285
+ function renderToolCalls(calls) {
286
+ const rendered = calls.map((c) => {
287
+ const args = Object.entries(c.args).map(([k, v]) => `${k}=${formatArg(v)}`).join(", ");
288
+ return `${c.name}(${args})`;
289
+ });
290
+ return `<|tool_call_start|>[${rendered.join(", ")}]<|tool_call_end|>`;
291
+ }
292
+
293
+ // src/engine/wllamaEngine.ts
294
+ var DEFAULT_MODEL = {
295
+ repo: "lazos/lfm2.5-230m-frontend-agent",
296
+ version: "v1.0.0",
297
+ // Q6_K is the quality sweet spot for this 230M model: it matches Q8 on held-out eval (96.3%) while
298
+ // Q4_K_M drops ~2 pts (worse query formulation / grounding). ~191 MB.
299
+ quant: "Q6_K"
300
+ };
301
+ function resolveModelUrl(ref = {}) {
302
+ const { repo, version, quant } = { ...DEFAULT_MODEL, ...ref };
303
+ const basename = repo.split("/").pop() ?? repo;
304
+ return `https://huggingface.co/${repo}/resolve/main/${basename}-${version}-${quant}.gguf`;
305
+ }
306
+ async function detectWebGPU() {
307
+ try {
308
+ const gpu = navigator.gpu;
309
+ if (!gpu?.requestAdapter) return false;
310
+ return await gpu.requestAdapter() != null;
311
+ } catch {
312
+ return false;
313
+ }
314
+ }
315
+ var WllamaEngine = class {
316
+ constructor(config) {
317
+ this.config = config;
318
+ this.url = config.modelUrl ?? resolveModelUrl(config.model);
319
+ this.nCtx = config.nCtx ?? N_CTX;
320
+ this.maxTokens = config.maxTokens ?? MAX_OUTPUT_TOKENS;
321
+ }
322
+ config;
323
+ wllamaPromise = null;
324
+ warnedSystemSize = false;
325
+ url;
326
+ nCtx;
327
+ maxTokens;
328
+ async load() {
329
+ await this.getWllama();
330
+ }
331
+ /** Tear down the loaded model so it can be reloaded (e.g. on a backend switch). OPFS-cached. */
332
+ async reset() {
333
+ const p = this.wllamaPromise;
334
+ this.wllamaPromise = null;
335
+ try {
336
+ const inst = p ? await p : null;
337
+ await inst?.exit?.();
338
+ } catch {
339
+ }
340
+ }
341
+ getWllama() {
342
+ if (this.wllamaPromise) return this.wllamaPromise;
343
+ this.wllamaPromise = (async () => {
344
+ const { onStatus, onProgress, onBackend, backend = "auto", wllamaAssets } = this.config;
345
+ const hasWebGPU = await detectWebGPU();
346
+ const useGPU = hasWebGPU && backend !== "cpu";
347
+ onBackend?.({ webgpu: hasWebGPU, using: useGPU ? "webgpu" : "cpu" });
348
+ const load = async (nThreads) => {
349
+ const instance2 = new Wllama(wllamaAssets);
350
+ onStatus?.("downloading");
351
+ let announcedLoading = false;
352
+ await instance2.loadModelFromUrl(this.url, {
353
+ n_ctx: this.nCtx,
354
+ n_gpu_layers: useGPU ? 99999 : 0,
355
+ ...nThreads ? { n_threads: nThreads } : {},
356
+ progressCallback: ({ loaded, total }) => {
357
+ onProgress?.({ loaded, total });
358
+ if (!announcedLoading && total > 0 && loaded >= total) {
359
+ announcedLoading = true;
360
+ onStatus?.("loading");
361
+ }
362
+ }
363
+ });
364
+ return instance2;
365
+ };
366
+ let instance;
367
+ try {
368
+ instance = await load();
369
+ } catch (err) {
370
+ console.warn("[frontend-agent] multi-thread load failed; retrying single-thread:", err);
371
+ instance = await load(1);
372
+ }
373
+ onStatus?.("ready");
374
+ return instance;
375
+ })();
376
+ return this.wllamaPromise;
377
+ }
378
+ async generate(messages, grammar) {
379
+ const wllama = await this.getWllama();
380
+ if (!this.warnedSystemSize) {
381
+ const sys = messages.find((m) => m.role === "system")?.content ?? "";
382
+ const tokenize = wllama.tokenize;
383
+ let sysTokens;
384
+ try {
385
+ sysTokens = tokenize ? (await tokenize.call(wllama, sys)).length : estimateTokens(sys);
386
+ } catch {
387
+ sysTokens = estimateTokens(sys);
388
+ }
389
+ if (sysTokens > this.nCtx * SYSTEM_CTX_WARN_FRACTION) {
390
+ this.warnedSystemSize = true;
391
+ console.warn(
392
+ `[frontend-agent] system prompt is ${sysTokens} tokens - over ${Math.round(SYSTEM_CTX_WARN_FRACTION * 100)}% of the ${this.nCtx}-token context. A larger catalog/KB is crowding out the conversation; trim the injected catalog or raise nCtx.`
393
+ );
394
+ }
395
+ }
396
+ const response = await wllama.createChatCompletion({
397
+ messages,
398
+ max_tokens: this.maxTokens,
399
+ temperature: 0,
400
+ ...grammar ? { grammar } : {}
401
+ });
402
+ const message = response.choices[0].message;
403
+ if (message.tool_calls && message.tool_calls.length > 0) {
404
+ const toolCalls = message.tool_calls.map((c) => ({
405
+ name: c.function.name,
406
+ args: safeJson(c.function.arguments)
407
+ }));
408
+ return { text: message.content ?? "", toolCalls, raw: renderToolCalls(toolCalls) };
409
+ }
410
+ const rawContent = message.content ?? "";
411
+ const parsed = parseToolCalls(rawContent);
412
+ return { text: parsed.text, toolCalls: parsed.toolCalls, raw: rawContent };
413
+ }
414
+ };
415
+ function safeJson(s) {
416
+ try {
417
+ return JSON.parse(s || "{}");
418
+ } catch {
419
+ return {};
420
+ }
421
+ }
422
+
423
+ // src/engine/stubEngine.ts
424
+ var StubEngine = class {
425
+ async generate(messages, _grammar) {
426
+ const lastUserIdx = messages.map((m) => m.role).lastIndexOf("user");
427
+ const trailingToolResults = messages.slice(lastUserIdx + 1).filter((m) => m.role === "tool");
428
+ const userText = (messages[lastUserIdx]?.content ?? "").toLowerCase();
429
+ if (trailingToolResults.length > 0) {
430
+ const last = trailingToolResults[trailingToolResults.length - 1];
431
+ const text2 = `Here's what I found: ${last?.content ?? ""}`;
432
+ return { text: text2, toolCalls: [], raw: text2 };
433
+ }
434
+ const call = (name, args) => {
435
+ const toolCalls = [{ name, args }];
436
+ return { text: "", toolCalls, raw: renderToolCalls(toolCalls) };
437
+ };
438
+ const quotedId = userText.match(/['"]([a-z0-9-]+)['"]/);
439
+ if (userText.includes("cart") && quotedId) {
440
+ const quantity = Number(userText.match(/(\d+)\s*(unit|x\b)/)?.[1] ?? "1");
441
+ return call("add_to_cart", { id: quotedId[1], quantity });
442
+ }
443
+ if (/(compat|pair|guide|how|descal|clean)/.test(userText)) {
444
+ return call("search_knowledge", { query: userText.slice(0, 60) });
445
+ }
446
+ if (/(find|search|show|any|looking|need|grinder|machine|accessor)/.test(userText)) {
447
+ return call("search_catalog", { query: userText.slice(0, 60) });
448
+ }
449
+ const text = "I can search the catalog, look things up in the knowledge base, or manage your cart. What would you like to do?";
450
+ return { text, toolCalls: [], raw: text };
451
+ }
452
+ };
453
+
454
+ // src/prompt/pyJson.ts
455
+ function pyJson(v) {
456
+ if (v === null) return "null";
457
+ if (typeof v === "string") return JSON.stringify(v);
458
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
459
+ if (Array.isArray(v)) return "[" + v.map(pyJson).join(", ") + "]";
460
+ if (typeof v === "object") {
461
+ const entries = Object.entries(v);
462
+ return "{" + entries.map(([k, val]) => `${JSON.stringify(k)}: ${pyJson(val)}`).join(", ") + "}";
463
+ }
464
+ return JSON.stringify(v);
465
+ }
466
+
467
+ // src/prompt/systemPrompt.ts
468
+ async function buildSystemPrompt(cfg) {
469
+ const label = cfg.hintLabel ?? "Example catalog items";
470
+ const hint = typeof cfg.catalogHint === "function" ? await cfg.catalogHint() : cfg.catalogHint;
471
+ const head = hint ? `${cfg.persona}
472
+
473
+ ${label}: ${hint}` : cfg.persona;
474
+ const tools = cfg.toolSchemas.map((s) => pyJson({ type: "function", function: s }));
475
+ return `${head}
476
+ List of tools: [${tools.join(", ")}]`;
477
+ }
478
+
479
+ // src/tools/registry.ts
480
+ function buildRegistry(definitions) {
481
+ return {
482
+ schemas: definitions.map((d) => d.schema),
483
+ handlers: Object.fromEntries(definitions.map((d) => [d.schema.name, d.handler]))
484
+ };
485
+ }
486
+
487
+ export { HISTORY_TOKEN_BUDGET, MAX_OUTPUT_TOKENS, N_CTX, SYSTEM_CTX_WARN_FRACTION, StubEngine, WllamaEngine, buildRegistry, buildSystemPrompt, buildToolGrammar, collectIds, createAgent, estimateTokens, parseToolCalls, pyJson, renderToolCalls, resolveModelUrl };
488
+ //# sourceMappingURL=index.js.map
489
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/gbnf.ts","../src/limits.ts","../src/loop/agent.ts","../src/parsing/toolCallParser.ts","../src/parsing/renderToolCalls.ts","../src/engine/wllamaEngine.ts","../src/engine/stubEngine.ts","../src/prompt/pyJson.ts","../src/prompt/systemPrompt.ts","../src/tools/registry.ts"],"names":["instance","text"],"mappings":"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","file":"index.js","sourcesContent":["import type { ToolSchema } from '../tools/types'\n\n/**\n * Generate a GBNF grammar that constrains LFM2.5 tool-call decoding to the injected tool schema.\n * TS port of training/kbft/gbnf.py (kept in lockstep - validated against llama.cpp).\n *\n * The model supplies the POLICY (which tool, when, which id semantically); the grammar GUARANTEES the\n * STRUCTURE at decode time: only valid tool names, that tool's arg keys/types, enum values, and - when\n * `validIds` (the ids present in results so far) is given - the id argument is forced to one of those,\n * so id-grounding becomes unfaultable (a truncated/hallucinated id is impossible to emit). Applied via\n * wllama's `grammar` sampling param. `allowText` keeps a free-text reply branch so normal answers work.\n */\n\nfunction lit(s: string): string {\n return '\"' + s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"') + '\"'\n}\nfunction sq(s: string): string {\n return lit(\"'\" + s + \"'\")\n}\nfunction rn(name: string): string {\n // GBNF rule names take hyphens, not underscores\n return name.replace(/[^A-Za-z0-9]/g, '-')\n}\n\nfunction valueRule(\n tname: string,\n key: string,\n spec: Record<string, unknown>,\n validIds: string[] | undefined,\n idKeys: string[],\n): string {\n if (idKeys.includes(key) && validIds && validIds.length) return `${rn(tname)}-${rn(key)}-id`\n const en = (spec as { enum?: unknown[] }).enum\n if (Array.isArray(en)) return '( ' + en.map((v) => sq(String(v))).join(' | ') + ' )'\n const t = (spec as { type?: string }).type\n if (t === 'integer' || t === 'number') return 'numval'\n if (t === 'boolean') return 'boolval'\n return 'strval'\n}\n\nexport function buildToolGrammar(\n tools: ToolSchema[],\n validIds?: string[],\n idKeys: string[] = ['id'],\n allowText = true,\n): string {\n const rules: string[] = ['sep ::= \",\" \" \"?']\n const callAlts: string[] = []\n const idAltsNeeded: [string, string][] = []\n\n for (const t of tools) {\n const name = t.name\n const props = (t.parameters?.properties ?? {}) as Record<string, Record<string, unknown>>\n const required = t.parameters?.required ?? []\n const r = rn(name)\n const reqFrags: string[] = []\n const optFrags: string[] = []\n for (const [key, spec] of Object.entries(props)) {\n const val = valueRule(name, key, spec, validIds, idKeys)\n if (idKeys.includes(key) && validIds && validIds.length) idAltsNeeded.push([name, key])\n const frag = `${r}-${rn(key)}`\n rules.push(`${frag} ::= ${lit(key + '=')} ${val}`)\n ;(required.includes(key) ? reqFrags : optFrags).push(frag)\n }\n // required args in order, comma-joined; optionals each appended optionally (matches the model)\n let seq = reqFrags.join(' sep ')\n for (const o of optFrags) seq = (seq ? seq + ' ' : '') + `( sep ${o} )?`\n rules.push(`${r}-call ::= ${lit(name + '(')} ${seq} ${lit(')')}`.replace(/ {2}/g, ' '))\n callAlts.push(`${r}-call`)\n }\n\n // One or more calls, comma-joined (`[fn1(...), fn2(...)]`); a single call is N=1. Mirrors kbft/gbnf.py.\n rules.push('call ::= ( ' + callAlts.join(' | ') + ' )')\n rules.push('toolcall ::= ' + lit('[') + ' call ( sep call )* ' + lit(']'))\n rules.push(\n 'wrapped ::= ( ' + lit('<|tool_call_start|>') + ' )? toolcall ( ' + lit('<|tool_call_end|>') + ' )?',\n )\n for (const [name, key] of idAltsNeeded) {\n rules.push(`${rn(name)}-${rn(key)}-id ::= ` + (validIds as string[]).map(sq).join(' | '))\n }\n if (allowText) {\n // a free-text reply: first char isn't a tool-call opener, then any chars (incl. newlines)\n rules.push('reply ::= [^\\\\[<] [^\\\\x00]*')\n rules.push('root ::= wrapped | reply')\n } else {\n rules.push('root ::= wrapped')\n }\n rules.push(`strval ::= \"'\" [^']* \"'\"`)\n rules.push('numval ::= \"-\"? [0-9]+ (\".\" [0-9]+)?')\n rules.push('boolval ::= \"True\" | \"False\"')\n return rules.join('\\n')\n}\n\n/** Collect string values under `id` keys from an arbitrary tool result (search results carry them). */\nexport function collectIds(value: unknown, into: Set<string>): void {\n if (Array.isArray(value)) {\n for (const v of value) collectIds(v, into)\n } else if (value && typeof value === 'object') {\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (k === 'id' && typeof v === 'string' && v) into.add(v)\n else collectIds(v, into)\n }\n }\n}\n","// Shared runtime limits for the on-device agent. Centralized so context caps and budgeting\n// stay consistent across the engine and the agent loop.\n\nexport const N_CTX = 8192 // wllama context window\nexport const MAX_OUTPUT_TOKENS = 320 // max tokens generated per assistant turn\n\n// Warn if the injected system prompt (persona + catalog + tool list) exceeds this fraction of\n// the context window - a signal that a growing catalog/KB is crowding out room for the\n// conversation. Configurable.\nexport const SYSTEM_CTX_WARN_FRACTION = 0.15\n\n// Token budget for accumulated conversation history (everything after the system prompt). Leaves\n// room for the system prompt (up to the warn fraction), one full answer, and a safety margin, so\n// the assembled prompt never overflows N_CTX - on overflow llama.cpp drops tokens from the FRONT,\n// which is the catalog the model grounds slugs on.\nexport const HISTORY_TOKEN_BUDGET =\n N_CTX - Math.ceil(N_CTX * SYSTEM_CTX_WARN_FRACTION) - MAX_OUTPUT_TOKENS - 256\n\n// Rough token estimate (~chars/4) - good enough for history budgeting without a tokenizer.\nexport const estimateTokens = (text: string | undefined): number => Math.ceil((text?.length ?? 0) / 4)\n","import { buildToolGrammar, collectIds } from '../engine/gbnf'\nimport { HISTORY_TOKEN_BUDGET, estimateTokens } from '../limits'\nimport type { AgentEngine, ChatMessage } from '../engine/types'\nimport type { ToolRegistry } from '../tools/registry'\n\nexport interface ToolCall {\n name: string\n args: Record<string, unknown>\n}\n\n/** Events emitted while a turn runs. Subscribe with `session.on(...)`. */\nexport type AgentEvent =\n | { type: 'status'; status: 'thinking' | 'ready' | 'aborted' | 'error' }\n | { type: 'assistant'; text: string }\n | { type: 'tool_call'; call: ToolCall }\n | { type: 'tool_result'; name: string; result: unknown }\n | { type: 'done' }\n | { type: 'error'; error: unknown }\n\nexport interface AgentConfig {\n engine: AgentEngine\n /** The tool set (schemas + handlers). Build with `buildRegistry(referenceTools(...))` or `buildRegistry(yourTools)`. */\n tools: ToolRegistry\n /** The system prompt, or a provider called once per `submit`. */\n systemPrompt: string | (() => string | Promise<string>)\n /** Max tool-loop cycles per turn. Default 8 (the longest trained multi-step flow). */\n maxIterations?: number\n /** Arg keys constrained to seen result ids by the grammar. Default `['id']`. */\n idKeys?: string[]\n /** History token budget (system prompt excluded). Default derived from N_CTX. */\n historyBudgetTokens?: number\n /**\n * Per-generation watchdog (ms). Some WASM engines can hang or crash a worker thread without\n * ever rejecting the in-flight promise (e.g. a trap on a secondary compute thread that leaves\n * the main thread waiting on a futex forever) - without this, that turn would sit in\n * 'thinking' indefinitely with no error surfaced. Default 90s; set 0 to disable.\n */\n generationTimeoutMs?: number\n}\n\nclass GenerationTimeoutError extends Error {\n constructor(ms: number) {\n super(`Generation timed out after ${ms}ms - the engine may have crashed or hung.`)\n this.name = 'GenerationTimeoutError'\n }\n}\n\n/** Race a promise against a timeout, without leaking the timer once either settles. */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n if (!ms) return promise\n let timer: ReturnType<typeof setTimeout>\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new GenerationTimeoutError(ms)), ms)\n })\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer))\n}\n\nexport interface SubmitOptions {\n signal?: AbortSignal\n}\n\nexport interface Session {\n /**\n * The single programmatic entry point: feed user text from anywhere - a text box, an external\n * API, or STT output - and run the tool loop to completion. Emits events as it goes.\n */\n submit(text: string, opts?: SubmitOptions): Promise<void>\n /** Abort the in-flight turn (best-effort: stops between generations/tool calls). */\n abort(): void\n /** Subscribe to events. Returns an unsubscribe function. */\n on(listener: (event: AgentEvent) => void): () => void\n /** The model-format conversation so far (assistant turns include raw tool-call markers). */\n readonly history: ChatMessage[]\n /** Clear the conversation. */\n reset(): void\n}\n\nconst GIVE_UP =\n \"I'm having trouble completing this - let me know if you'd like to try a simpler request.\"\n\nclass AbortError extends Error {\n constructor() {\n super('aborted')\n this.name = 'AbortError'\n }\n}\n\nexport function createAgent(config: AgentConfig): Session {\n const maxIterations = config.maxIterations ?? 8\n const idKeys = config.idKeys ?? ['id']\n const budget = config.historyBudgetTokens ?? HISTORY_TOKEN_BUDGET\n const generationTimeoutMs = config.generationTimeoutMs ?? 90_000\n const history: ChatMessage[] = []\n const listeners = new Set<(e: AgentEvent) => void>()\n let controller: AbortController | null = null\n\n const emit = (e: AgentEvent) => {\n for (const l of listeners) l(e)\n }\n\n async function runToolCall(call: ToolCall): Promise<unknown> {\n const handler = config.tools.handlers[call.name]\n if (!handler) return { error: 'unknown_tool', name: call.name }\n try {\n return await handler(call.args)\n } catch (err) {\n return { error: 'tool_execution_failed', message: (err as Error).message }\n }\n }\n\n async function runLoop(system: string, signal: AbortSignal): Promise<void> {\n const seenIds = new Set<string>()\n for (let i = 0; i < maxIterations; i++) {\n if (signal.aborted) throw new AbortError()\n trimHistory(history, budget)\n const messages: ChatMessage[] = [{ role: 'system', content: system }, ...history]\n const grammar = buildToolGrammar(\n config.tools.schemas,\n seenIds.size ? [...seenIds] : undefined,\n idKeys,\n )\n const result = await withTimeout(config.engine.generate(messages, grammar), generationTimeoutMs)\n if (signal.aborted) throw new AbortError()\n\n if (result.toolCalls.length === 0) {\n history.push({ role: 'assistant', content: result.raw })\n emit({ type: 'assistant', text: result.text })\n emit({ type: 'status', status: 'ready' })\n return\n }\n\n history.push({ role: 'assistant', content: result.raw })\n if (result.text) emit({ type: 'assistant', text: result.text })\n\n for (const call of result.toolCalls) {\n emit({ type: 'tool_call', call })\n const toolResult = await runToolCall(call)\n collectIds(toolResult, seenIds)\n emit({ type: 'tool_result', name: call.name, result: toolResult })\n history.push({ role: 'tool', content: JSON.stringify(toolResult) })\n }\n }\n history.push({ role: 'assistant', content: GIVE_UP })\n emit({ type: 'assistant', text: GIVE_UP })\n emit({ type: 'status', status: 'ready' })\n }\n\n return {\n history,\n on(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n abort() {\n controller?.abort()\n },\n reset() {\n history.length = 0\n },\n async submit(text, opts) {\n controller = new AbortController()\n const signal = opts?.signal\n ? AbortSignal.any([controller.signal, opts.signal])\n : controller.signal\n history.push({ role: 'user', content: text })\n emit({ type: 'status', status: 'thinking' })\n try {\n const system =\n typeof config.systemPrompt === 'function' ? await config.systemPrompt() : config.systemPrompt\n await runLoop(system, signal)\n emit({ type: 'done' })\n } catch (err) {\n if (err instanceof AbortError || signal.aborted) {\n emit({ type: 'status', status: 'aborted' })\n emit({ type: 'done' })\n } else {\n emit({ type: 'error', error: err })\n emit({ type: 'status', status: 'error' })\n }\n } finally {\n controller = null\n }\n },\n }\n}\n\n/**\n * Bound accumulated history so system prompt + conversation never overflows the context window\n * (overflow drops tokens from the FRONT - the catalog the model grounds on). Drops whole oldest\n * turns from the front, never leaving a dangling `tool`/assistant continuation at the head, and\n * always keeps the most recent messages.\n */\nexport function trimHistory(history: ChatMessage[], budget: number): void {\n const tokens = () => history.reduce((n, m) => n + estimateTokens(m.content), 0)\n while (history.length > 2 && tokens() > budget) {\n history.shift()\n // don't start the remaining history on an orphaned tool result / non-user continuation\n while (history.length > 1 && history[0]!.role === 'tool') history.shift()\n }\n}\n","export interface ParsedToolCall {\n name: string\n args: Record<string, unknown>\n}\n\nexport interface ParseResult {\n text: string\n toolCalls: ParsedToolCall[]\n}\n\nconst CALL_BLOCK_RE = /<\\|tool_call_start\\|>([\\s\\S]*?)<\\|tool_call_end\\|>/g\n\n/**\n * Extracts pythonic tool calls (e.g. `[search_products(query=\"x\"), add_to_cart(slug=\"y\", quantity=2)]`)\n * from LFM2.5's tool_call_start/end markers, and returns the remaining plain text.\n * Never uses eval - parses the restricted call grammar by hand.\n */\nexport function parseToolCalls(raw: string): ParseResult {\n const toolCalls: ParsedToolCall[] = []\n const text = raw.replace(CALL_BLOCK_RE, (_match, inner: string) => {\n toolCalls.push(...parseCallList(inner.trim()))\n return ''\n })\n return { text: text.trim(), toolCalls }\n}\n\nfunction parseCallList(block: string): ParsedToolCall[] {\n const inner = block.startsWith('[') && block.endsWith(']') ? block.slice(1, -1) : block\n const calls: ParsedToolCall[] = []\n for (const piece of splitTopLevel(inner, ',')) {\n const trimmed = piece.trim()\n if (!trimmed) continue\n const call = parseSingleCall(trimmed)\n if (call) calls.push(call)\n }\n return calls\n}\n\nfunction parseSingleCall(expr: string): ParsedToolCall | null {\n const match = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\\((.*)\\)$/s)\n if (!match) return null\n const [, name, argsSrc] = match\n const args: Record<string, unknown> = {}\n for (const piece of splitTopLevel(argsSrc, ',')) {\n const trimmed = piece.trim()\n if (!trimmed) continue\n const eq = trimmed.indexOf('=')\n if (eq === -1) continue\n const key = trimmed.slice(0, eq).trim()\n const valueSrc = trimmed.slice(eq + 1).trim()\n args[key] = parseValue(valueSrc)\n }\n return { name, args }\n}\n\nfunction parseValue(src: string): unknown {\n if (src === 'True') return true\n if (src === 'False') return false\n if (src === 'None') return null\n if (/^-?\\d+$/.test(src)) return parseInt(src, 10)\n if (/^-?\\d+\\.\\d+$/.test(src)) return parseFloat(src)\n if (\n (src.startsWith('\"') && src.endsWith('\"')) ||\n (src.startsWith(\"'\") && src.endsWith(\"'\"))\n ) {\n return src.slice(1, -1)\n }\n return src\n}\n\n/** Splits on a delimiter, ignoring delimiters inside quotes/parens/brackets. */\nfunction splitTopLevel(src: string, delimiter: string): string[] {\n const parts: string[] = []\n let depth = 0\n let quote: string | null = null\n let current = ''\n for (const ch of src) {\n if (quote) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n if (ch === '(' || ch === '[') depth++\n if (ch === ')' || ch === ']') depth--\n if (ch === delimiter && depth === 0) {\n parts.push(current)\n current = ''\n continue\n }\n current += ch\n }\n if (current.trim()) parts.push(current)\n return parts\n}\n","import type { ParsedToolCall } from './toolCallParser'\n\n// Inverse of toolCallParser: render parsed calls back into the exact LFM2.5 wire format\n// (mirrors the chat template's render_tool_calls macro) so a tool-call turn can be replayed\n// verbatim into the next generation's context.\n\nfunction formatArg(value: unknown): string {\n if (typeof value === 'string') return `'${value}'`\n if (value === null) return 'None'\n if (typeof value === 'boolean') return value ? 'True' : 'False'\n if (typeof value === 'object') return JSON.stringify(value)\n return String(value)\n}\n\nexport function renderToolCalls(calls: ParsedToolCall[]): string {\n const rendered = calls.map((c) => {\n const args = Object.entries(c.args)\n .map(([k, v]) => `${k}=${formatArg(v)}`)\n .join(', ')\n return `${c.name}(${args})`\n })\n return `<|tool_call_start|>[${rendered.join(', ')}]<|tool_call_end|>`\n}\n","import { Wllama, type AssetsPathConfig } from '@wllama/wllama'\nimport { parseToolCalls } from '../parsing/toolCallParser'\nimport { renderToolCalls } from '../parsing/renderToolCalls'\nimport { N_CTX, MAX_OUTPUT_TOKENS, SYSTEM_CTX_WARN_FRACTION, estimateTokens } from '../limits'\nimport type { AgentEngine, ChatMessage, EngineGenerateResult } from './types'\n\n/** Reference to a GGUF published on the Hugging Face Hub. */\nexport interface HFModelRef {\n /** `owner/name`. Default `lazos/lfm2.5-230m-frontend-agent`. */\n repo?: string\n /** Release/version tag embedded in the filename. Default `v1.0.0`. */\n version?: string\n /** Quantization suffix. Default `Q4_K_M`. */\n quant?: string\n}\n\nexport type EngineStatus = 'idle' | 'downloading' | 'loading' | 'ready'\n\nexport interface WllamaEngineConfig {\n /** Load the GGUF from Hugging Face (default). Ignored if `modelUrl` is set. */\n model?: HFModelRef\n /** Full GGUF URL - self-host escape hatch; overrides `model`. */\n modelUrl?: string\n /** wllama WASM asset config, e.g. `{ 'single-thread/wllama.wasm': url, 'multi-thread/wllama.wasm': url }`. */\n wllamaAssets: AssetsPathConfig\n /** Context window. Default 8192. */\n nCtx?: number\n /** Max tokens per assistant turn. Default 320. */\n maxTokens?: number\n /** Backend preference. `'auto'` uses WebGPU when a real adapter is present. Default `'auto'`. */\n backend?: 'auto' | 'webgpu' | 'cpu'\n onStatus?: (status: EngineStatus) => void\n onProgress?: (progress: { loaded: number; total: number }) => void\n /** Reports whether a usable WebGPU adapter was detected (after load). */\n onBackend?: (info: { webgpu: boolean; using: 'webgpu' | 'cpu' }) => void\n}\n\nconst DEFAULT_MODEL: Required<HFModelRef> = {\n repo: 'lazos/lfm2.5-230m-frontend-agent',\n version: 'v1.0.0',\n // Q6_K is the quality sweet spot for this 230M model: it matches Q8 on held-out eval (96.3%) while\n // Q4_K_M drops ~2 pts (worse query formulation / grounding). ~191 MB.\n quant: 'Q6_K',\n}\n\n/** Resolve a HF model ref to its `resolve/main` GGUF URL: `{basename}-{version}-{quant}.gguf`. */\nexport function resolveModelUrl(ref: HFModelRef = {}): string {\n const { repo, version, quant } = { ...DEFAULT_MODEL, ...ref }\n const basename = repo.split('/').pop() ?? repo\n return `https://huggingface.co/${repo}/resolve/main/${basename}-${version}-${quant}.gguf`\n}\n\nasync function detectWebGPU(): Promise<boolean> {\n try {\n const gpu = (navigator as unknown as { gpu?: { requestAdapter?: () => Promise<unknown> } }).gpu\n if (!gpu?.requestAdapter) return false\n return (await gpu.requestAdapter()) != null\n } catch {\n return false\n }\n}\n\n/**\n * Real engine: runs the GGUF in-browser via wllama (llama.cpp WASM/WebGPU). Loads the model from\n * Hugging Face by default (configurable), caches it in OPFS, and applies the GBNF grammar at decode.\n * Framework-agnostic - reports lifecycle via callbacks, no store coupling.\n */\nexport class WllamaEngine implements AgentEngine {\n private wllamaPromise: Promise<Wllama> | null = null\n private warnedSystemSize = false\n private readonly url: string\n private readonly nCtx: number\n private readonly maxTokens: number\n\n constructor(private readonly config: WllamaEngineConfig) {\n this.url = config.modelUrl ?? resolveModelUrl(config.model)\n this.nCtx = config.nCtx ?? N_CTX\n this.maxTokens = config.maxTokens ?? MAX_OUTPUT_TOKENS\n }\n\n async load(): Promise<void> {\n await this.getWllama()\n }\n\n /** Tear down the loaded model so it can be reloaded (e.g. on a backend switch). OPFS-cached. */\n async reset(): Promise<void> {\n const p = this.wllamaPromise\n this.wllamaPromise = null\n try {\n const inst = p ? await p : null\n await (inst as unknown as { exit?: () => Promise<void> })?.exit?.()\n } catch {\n /* ignore teardown errors */\n }\n }\n\n private getWllama(): Promise<Wllama> {\n if (this.wllamaPromise) return this.wllamaPromise\n this.wllamaPromise = (async () => {\n const { onStatus, onProgress, onBackend, backend = 'auto', wllamaAssets } = this.config\n const hasWebGPU = await detectWebGPU()\n const useGPU = hasWebGPU && backend !== 'cpu'\n onBackend?.({ webgpu: hasWebGPU, using: useGPU ? 'webgpu' : 'cpu' })\n\n const load = async (nThreads?: number): Promise<Wllama> => {\n const instance = new Wllama(wllamaAssets)\n onStatus?.('downloading')\n // wllama has no separate hook for \"download done, now initializing\" - the progress\n // callback stalling at 100% while loadModelFromUrl hasn't resolved yet IS that gap, so\n // infer the transition from it (once) rather than leaving the caller stuck on a stale\n // \"downloading 100%\" label through however long GGUF parsing/context init takes.\n let announcedLoading = false\n await instance.loadModelFromUrl(this.url, {\n n_ctx: this.nCtx,\n n_gpu_layers: useGPU ? 99999 : 0,\n ...(nThreads ? { n_threads: nThreads } : {}),\n progressCallback: ({ loaded, total }) => {\n onProgress?.({ loaded, total })\n if (!announcedLoading && total > 0 && loaded >= total) {\n announcedLoading = true\n onStatus?.('loading')\n }\n },\n })\n return instance\n }\n\n // Try the fast path (auto = WebGPU / multi-thread WASM); on a threads-build trap, fall back\n // once to single-thread. Reacts to the actual failure, not a UA string. OPFS-cached, so the\n // retry never re-downloads.\n let instance: Wllama\n try {\n instance = await load()\n } catch (err) {\n console.warn('[frontend-agent] multi-thread load failed; retrying single-thread:', err)\n instance = await load(1)\n }\n onStatus?.('ready')\n return instance\n })()\n return this.wllamaPromise\n }\n\n async generate(messages: ChatMessage[], grammar?: string): Promise<EngineGenerateResult> {\n const wllama = await this.getWllama()\n\n if (!this.warnedSystemSize) {\n const sys = messages.find((m) => m.role === 'system')?.content ?? ''\n const tokenize = (wllama as { tokenize?: (s: string) => Promise<number[]> }).tokenize\n let sysTokens: number\n try {\n sysTokens = tokenize ? (await tokenize.call(wllama, sys)).length : estimateTokens(sys)\n } catch {\n sysTokens = estimateTokens(sys)\n }\n if (sysTokens > this.nCtx * SYSTEM_CTX_WARN_FRACTION) {\n this.warnedSystemSize = true\n console.warn(\n `[frontend-agent] system prompt is ${sysTokens} tokens - over ` +\n `${Math.round(SYSTEM_CTX_WARN_FRACTION * 100)}% of the ${this.nCtx}-token context. ` +\n 'A larger catalog/KB is crowding out the conversation; trim the injected catalog or raise nCtx.',\n )\n }\n }\n\n // Full system text (persona + catalog + \"List of tools: [...]\") is baked into the system message,\n // so we pass NO `tools` - wllama's chat template renders turns/markers. The GBNF grammar (when\n // supplied) guarantees a structurally-valid call + id-grounding at decode time.\n const response = await wllama.createChatCompletion({\n messages: messages as never,\n max_tokens: this.maxTokens,\n temperature: 0,\n ...(grammar ? { grammar } : {}),\n } as never)\n const message = (response as { choices: { message: WllamaMessage }[] }).choices[0]!.message\n\n if (message.tool_calls && message.tool_calls.length > 0) {\n const toolCalls = message.tool_calls.map((c) => ({\n name: c.function.name,\n args: safeJson(c.function.arguments),\n }))\n return { text: message.content ?? '', toolCalls, raw: renderToolCalls(toolCalls) }\n }\n\n const rawContent = message.content ?? ''\n const parsed = parseToolCalls(rawContent)\n return { text: parsed.text, toolCalls: parsed.toolCalls, raw: rawContent }\n }\n}\n\ninterface WllamaMessage {\n content?: string\n tool_calls?: { function: { name: string; arguments: string } }[]\n}\n\nfunction safeJson(s: string): Record<string, unknown> {\n try {\n return JSON.parse(s || '{}')\n } catch {\n return {}\n }\n}\n","import { renderToolCalls } from '../parsing/renderToolCalls'\nimport type { AgentEngine, ChatMessage, EngineGenerateResult } from './types'\n\n/**\n * Fixture engine with no model weights - for building/testing the loop, tools, and UI without\n * loading the real model. Emits calls on the current v1.0.0 contract (`search_catalog`,\n * `search_knowledge`, `add_to_cart(id)`). Ignores the grammar.\n */\nexport class StubEngine implements AgentEngine {\n async generate(messages: ChatMessage[], _grammar?: string): Promise<EngineGenerateResult> {\n void _grammar\n const lastUserIdx = messages.map((m) => m.role).lastIndexOf('user')\n const trailingToolResults = messages.slice(lastUserIdx + 1).filter((m) => m.role === 'tool')\n const userText = (messages[lastUserIdx]?.content ?? '').toLowerCase()\n\n if (trailingToolResults.length > 0) {\n const last = trailingToolResults[trailingToolResults.length - 1]\n const text = `Here's what I found: ${last?.content ?? ''}`\n return { text, toolCalls: [], raw: text }\n }\n\n const call = (name: string, args: Record<string, unknown>): EngineGenerateResult => {\n const toolCalls = [{ name, args }]\n return { text: '', toolCalls, raw: renderToolCalls(toolCalls) }\n }\n\n const quotedId = userText.match(/['\"]([a-z0-9-]+)['\"]/)\n if (userText.includes('cart') && quotedId) {\n const quantity = Number(userText.match(/(\\d+)\\s*(unit|x\\b)/)?.[1] ?? '1')\n return call('add_to_cart', { id: quotedId[1], quantity })\n }\n if (/(compat|pair|guide|how|descal|clean)/.test(userText)) {\n return call('search_knowledge', { query: userText.slice(0, 60) })\n }\n if (/(find|search|show|any|looking|need|grinder|machine|accessor)/.test(userText)) {\n return call('search_catalog', { query: userText.slice(0, 60) })\n }\n\n const text =\n 'I can search the catalog, look things up in the knowledge base, or manage your cart. What would you like to do?'\n return { text, toolCalls: [], raw: text }\n }\n}\n","/**\n * Serialize a value the way Python's json.dumps (and Jinja2's `tojson`) does by default:\n * \", \" between items and \": \" after keys. The model's chat template renders the tool list\n * with these separators, so the browser must match them byte-for-byte for train/inference\n * parity. (Our tool schemas are ASCII with no <,>,& so htmlsafe escaping never triggers.)\n */\nexport function pyJson(v: unknown): string {\n if (v === null) return 'null'\n if (typeof v === 'string') return JSON.stringify(v)\n if (typeof v === 'number' || typeof v === 'boolean') return String(v)\n if (Array.isArray(v)) return '[' + v.map(pyJson).join(', ') + ']'\n if (typeof v === 'object') {\n const entries = Object.entries(v as Record<string, unknown>)\n return '{' + entries.map(([k, val]) => `${JSON.stringify(k)}: ${pyJson(val)}`).join(', ') + '}'\n }\n return JSON.stringify(v)\n}\n","import { pyJson } from './pyJson'\nimport type { ToolSchema } from '../tools/types'\n\nexport interface SystemPromptConfig {\n /** 1-2 line persona/rules for this deployment (who the assistant is, grounding + refusal rules). */\n persona: string\n /** The tool schemas to advertise (same array the loop dispatches). */\n toolSchemas: ToolSchema[]\n /** A short \"headline entities\" hint (`Title [id]; ...`) or a provider for it. Optional. */\n catalogHint?: string | (() => string | Promise<string>)\n /** Label before the hint. Defaults to the trained `\"Example catalog items\"`. */\n hintLabel?: string\n}\n\n/**\n * Reproduce the exact v1.0.0 system message the model was trained on:\n * `{persona}\\n\\n{hintLabel}: {hint}\\nList of tools: [{fn-json}, ...]`\n * The tool list uses Python `json.dumps` separators (`pyJson`) for byte-for-byte train/inference\n * parity. The full system text is baked here (pass NO `tools` array to the engine's chat template).\n */\nexport async function buildSystemPrompt(cfg: SystemPromptConfig): Promise<string> {\n const label = cfg.hintLabel ?? 'Example catalog items'\n const hint =\n typeof cfg.catalogHint === 'function' ? await cfg.catalogHint() : cfg.catalogHint\n const head = hint ? `${cfg.persona}\\n\\n${label}: ${hint}` : cfg.persona\n const tools = cfg.toolSchemas.map((s) => pyJson({ type: 'function', function: s }))\n return `${head}\\nList of tools: [${tools.join(', ')}]`\n}\n","import type { ToolDefinition, ToolHandler, ToolSchema } from './types'\n\nexport interface ToolRegistry {\n /** Tool schemas, in order - injected into the system prompt (order is contract-significant). */\n schemas: ToolSchema[]\n /** name → handler dispatch map. */\n handlers: Record<string, ToolHandler>\n}\n\n/** Build a registry from a consumer-supplied tool set. Bring your own tools, or use `referenceTools`. */\nexport function buildRegistry(definitions: ToolDefinition[]): ToolRegistry {\n return {\n schemas: definitions.map((d) => d.schema),\n handlers: Object.fromEntries(definitions.map((d) => [d.schema.name, d.handler])),\n }\n}\n"]}
package/dist/rag.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { R as RagBackend, a as RagIndex, C as CatalogItemLite, b as CatalogResult, K as KnowledgeResult } from './types-BqZ6-bpz.js';
2
+ export { c as CatalogItem, d as KnowledgeItem } from './types-BqZ6-bpz.js';
3
+
4
+ /**
5
+ * Zero-infra in-browser retrieval: MiniSearch (BM25 + fuzzy + prefix) with a Porter stemmer over an
6
+ * index you supply. The algorithm need NOT match the training retriever - the model grounds in
7
+ * whatever RESULTS come back; only the result shape is the contract. Construct with an index object,
8
+ * or `LocalMiniSearchRAG.fromUrl(url)` to fetch one.
9
+ */
10
+ declare class LocalMiniSearchRAG implements RagBackend {
11
+ private readonly cat;
12
+ private readonly know;
13
+ private readonly catById;
14
+ private readonly knowById;
15
+ constructor(index: RagIndex);
16
+ static fromUrl(url: string): Promise<LocalMiniSearchRAG>;
17
+ hint(n?: number): string;
18
+ getItem(id: string): CatalogItemLite | null;
19
+ searchCatalog(query: string, opts?: {
20
+ maxPrice?: number;
21
+ k?: number;
22
+ }): Promise<CatalogResult[]>;
23
+ searchKnowledge(query: string, k?: number): Promise<KnowledgeResult[]>;
24
+ }
25
+
26
+ /**
27
+ * Bring-your-own retrieval backend. Speaks a single contract to your search service, then normalizes
28
+ * to the frozen model-facing shape - so the model can't tell which backend answered:
29
+ *
30
+ * POST {endpoint} { index: "catalog"|"knowledge", query, filters, top_k }
31
+ * -> { results: [{ id, title, text, score, meta }] }
32
+ *
33
+ * Point `endpoint` at your Qdrant/pgvector/Elastic/Typesense adapter.
34
+ */
35
+ declare function createRagClient(endpoint: string, init?: RequestInit): RagBackend;
36
+
37
+ export { CatalogItemLite, CatalogResult, KnowledgeResult, LocalMiniSearchRAG, RagBackend, RagIndex, createRagClient };