frontend-agent 1.0.0 → 1.0.2

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 CHANGED
@@ -1,95 +1,63 @@
1
1
  # frontend-agent
2
2
 
3
- Drive the **LFM2.5-230M frontend-agent** model in the browser - a tiny, on-device web agent that
4
- calls real tools and answers grounded in retrieved context. Fully typed, framework-agnostic, loads
5
- the GGUF from Hugging Face via [wllama](https://github.com/ngxson/wllama) (no server-side inference).
3
+ [Demo - Github Pages](https://lajosbencz.github.io/frontend-agent/)
6
4
 
7
- The model is trained for **patterns, not facts**: you supply the tools and the grounding context at
8
- runtime, and the library reproduces the exact v1.0.0 model contract (system prompt, tool-call format,
9
- tool-result shapes, and a GBNF grammar that pins tool ids to real search results at decode time).
5
+ A generic English **web/front-end agent** built on [LiquidAI LFM2.5-230M](https://huggingface.co/LiquidAI/LFM2.5-230M), small enough to run
6
+ **entirely in the browser** (edge / local inference via [wllama](https://github.com/ngxson/wllama), no server-side model). It doesn't
7
+ just chat - it calls real tools to act on a page (search, navigate, cart operations) and answers
8
+ questions grounded in retrieved context.
10
9
 
11
- ## Install
10
+ **Trained for patterns, not lexical knowledge.** The weights hold *behaviors* - tool selection,
11
+ argument binding, reference tracking, RAG-grounded answering, clean refusal - not any specific
12
+ catalog or domain facts. Meaning and grounding are supplied **at runtime**: the host injects a
13
+ compact catalog hint and serves retrieval as a `search_*` tool, and the model grounds strictly in
14
+ what it is given. Swap the site, swap the injected context - the same model works, no retraining.
12
15
 
13
- ```bash
14
- npm i frontend-agent @wllama/wllama
15
- npm i minisearch stemmer # optional: the in-browser RAG adapter (./rag)
16
- ```
16
+ ## Repo map
17
17
 
18
- ## Quickstart
19
-
20
- ```ts
21
- import { createAgent, buildRegistry, WllamaEngine, buildSystemPrompt } from 'frontend-agent'
22
- import { referenceTools } from 'frontend-agent/reference'
23
- import { LocalMiniSearchRAG } from 'frontend-agent/rag'
24
-
25
- const rag = new LocalMiniSearchRAG({ catalog, knowledge }) // your indexed data
26
- const tools = referenceTools({
27
- rag,
28
- cart: myCartHandlers, // add/remove/view/clear over your own state
29
- navigate: (target, id) => router.push(pathFor(target, id)),
30
- })
31
- const registry = buildRegistry(tools) // schemas + handlers, what createAgent expects
32
-
33
- const engine = new WllamaEngine({
34
- // loads https://huggingface.co/lazos/lfm2.5-230m-frontend-agent/resolve/main/...-v1.0.0-Q6_K.gguf
35
- model: { repo: 'lazos/lfm2.5-230m-frontend-agent', version: 'v1.0.0', quant: 'Q6_K' },
36
- wllamaAssets, // wllama single/multi-thread WASM URLs
37
- onProgress: (p) => {},
38
- })
39
-
40
- const session = createAgent({
41
- engine,
42
- tools: registry,
43
- systemPrompt: () => buildSystemPrompt({ persona, catalogHint: () => rag.hint(6), toolSchemas: registry.schemas }),
44
- })
45
-
46
- session.on((e) => {
47
- if (e.type === 'assistant') console.log(e.text)
48
- })
49
-
50
- // one entry point - feed it from a text box, an API, or a speech-to-text transcript:
51
- await session.submit('do you have any espresso grinders?')
52
- session.abort() // cancel an in-flight turn
18
+ ```
19
+ frontend-agent/ npm workspaces root
20
+ ├─ packages/frontend-agent/ frontend-agent - the TS client library that drives the model
21
+ └─ demo/ Nuxt demo: three storefronts (BrewCraft/Emporium/Vendor) consuming it
53
22
  ```
54
23
 
55
- `submit(text)` is the whole input channel - the library is agnostic to where the text comes from.
56
- Wire your own source (typed box, an external API, or a speech-to-text engine like Whisper) and call
57
- `submit` with the resulting text. The demo shows a Whisper `submit` voice path.
58
-
59
- ## Model source
24
+ **Client library** - [`frontend-agent`](packages/frontend-agent) is a fully-typed,
25
+ framework-agnostic TypeScript package for driving the model in any web app: `createAgent(...)` returns
26
+ a session with a single `submit(text)` feed point, tool-calling + GBNF id-grounding, a configurable
27
+ Hugging Face model source, and optional RAG adapters.
60
28
 
61
- `WllamaEngine`'s `model` resolves a Hugging Face GGUF by default:
62
- `https://huggingface.co/{repo}/resolve/main/{repo-basename}-{version}-{quant}.gguf`. Override any of
63
- `{ repo, version, quant }`, or pass `modelUrl` to self-host. Cross-origin loading under COOP/COEP
64
- requires the page to be cross-origin-isolated; the HF `resolve` CDN sends permissive CORS.
29
+ **Demo** - [`demo/`](demo) is the reference consumer: three storefronts sharing one on-device
30
+ assistant. See [`demo/README.md`](demo/README.md).
65
31
 
66
- ## Cross-origin isolation (multi-threading)
32
+ ## How it works
67
33
 
68
- wllama runs **multi-threaded** when the page is cross-origin isolated (`SharedArrayBuffer`), and
69
- **falls back to single-thread** otherwise - slower, but works with no action required. To enable
70
- threads, make the page cross-origin isolated by either:
34
+ - **RAG as a tool** - retrieval is a `search_*` tool the model calls; it grounds answers in the
35
+ results and refuses when they don't contain the answer. Backend-agnostic (BM25, vector, hybrid).
36
+ - **Tool-list randomization at training time** - tool names and argument keys were permuted per
37
+ training example, so the model reads the injected schema rather than memorizing a fixed toolset.
38
+ - **Held-out-domain eval** - the model was evaluated on verticals never seen in training; the demo's
39
+ BrewCraft domain is itself held out, proving the generalization thesis end to end.
71
40
 
72
- - serving it with `Cross-Origin-Opener-Policy: same-origin` + `Cross-Origin-Embedder-Policy:
73
- require-corp` (or `credentialless`) headers, or
74
- - on a static host that can't set headers (e.g. GitHub Pages), registering a COI service worker such
75
- as [`coi-serviceworker`](https://github.com/gzuidhof/coi-serviceworker) - the demo does this.
41
+ ## Quickstart
76
42
 
77
- The library deliberately **does not register a service worker** - an SW hijacks your whole origin's
78
- fetches and is a deployment concern you own. Note: under COEP `require-corp`, the cross-origin HF
79
- model load needs the response to send CORP or be fetched CORS/credentialless; `coi-serviceworker`'s
80
- **credentialless** mode handles this (so threads + the HF default compose on Pages).
43
+ ```bash
44
+ npm install
45
+ npm run build -w frontend-agent
46
+ npm run dev -w demo # http://localhost:3000
47
+ ```
81
48
 
82
- ## Exports
49
+ The assistant downloads its GGUF once from Hugging Face (cached in the browser via OPFS) on first
50
+ activation - no server-side inference.
83
51
 
84
- - `.` - core: `createAgent`, `buildRegistry`, `WllamaEngine`, `StubEngine`, `buildSystemPrompt`,
85
- `parseToolCalls`, `renderToolCalls`, `buildToolGrammar`, and all types.
86
- - `./reference` - `referenceTools({ rag, cart, navigate })`: the 7 canonical tools
87
- (search_catalog, search_knowledge, add/remove/view/clear cart, navigate) with trained result shapes.
88
- - `./rag` - `LocalMiniSearchRAG` (in-browser BM25) + `createRagClient` (bring-your-own `POST /search`).
52
+ ## Licensing
89
53
 
90
- Input is source-agnostic: `session.submit(text)` is the only feed point - connect a text box, an API,
91
- or a speech-to-text transcript. (The library ships no STT/TTS; the demo shows a Whisper voice path.)
54
+ - **Code & docs** - Apache-2.0 ([`LICENSE`](LICENSE)).
55
+ - **Model weights** - a derivative of `LiquidAI/LFM2.5-230M`, so they inherit the **LFM Open License
56
+ v1.0** (permissive, with a commercial-revenue cap; attribution to LiquidAI required). Cannot be
57
+ relicensed.
92
58
 
93
- ## License
59
+ ## Authoring
94
60
 
95
- Apache-2.0. The model weights it drives are under the LFM Open License v1.0 (see the model repo).
61
+ This repository was substantially authored with the following models, under human direction and review:
62
+ - Qwen3 Coder 30B A3B Q4_K_M (local Ollama)
63
+ - Claude Opus 4.8 (Anthropic)
package/dist/index.d.ts CHANGED
@@ -111,11 +111,8 @@ declare function createAgent(config: AgentConfig): Session;
111
111
 
112
112
  /** Reference to a GGUF published on the Hugging Face Hub. */
113
113
  interface HFModelRef {
114
- /** `owner/name`. Default `lazos/lfm2.5-230m-frontend-agent`. */
115
114
  repo?: string;
116
- /** Release/version tag embedded in the filename. Default `v1.0.0`. */
117
115
  version?: string;
118
- /** Quantization suffix. Default `Q4_K_M`. */
119
116
  quant?: string;
120
117
  }
121
118
  type EngineStatus = 'idle' | 'downloading' | 'loading' | 'ready';
package/dist/index.js CHANGED
@@ -293,15 +293,13 @@ function renderToolCalls(calls) {
293
293
  // src/engine/wllamaEngine.ts
294
294
  var DEFAULT_MODEL = {
295
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.
296
+ version: "main",
299
297
  quant: "Q6_K"
300
298
  };
301
299
  function resolveModelUrl(ref = {}) {
302
300
  const { repo, version, quant } = { ...DEFAULT_MODEL, ...ref };
303
301
  const basename = repo.split("/").pop() ?? repo;
304
- return `https://huggingface.co/${repo}/resolve/main/${basename}-${version}-${quant}.gguf`;
302
+ return `https://huggingface.co/${repo}/resolve/${version}/${basename}-${quant}.gguf`;
305
303
  }
306
304
  async function detectWebGPU() {
307
305
  try {
package/dist/index.js.map CHANGED
@@ -1 +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":";;;AAaA,SAAS,IAAI,CAAA,EAAmB;AAC9B,EAAA,OAAO,GAAA,GAAM,EAAE,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA,GAAI,GAAA;AAC/D;AACA,SAAS,GAAG,CAAA,EAAmB;AAC7B,EAAA,OAAO,GAAA,CAAI,GAAA,GAAM,CAAA,GAAI,GAAG,CAAA;AAC1B;AACA,SAAS,GAAG,IAAA,EAAsB;AAEhC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,eAAA,EAAiB,GAAG,CAAA;AAC1C;AAEA,SAAS,SAAA,CACP,KAAA,EACA,GAAA,EACA,IAAA,EACA,UACA,MAAA,EACQ;AACR,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,IAAK,YAAY,QAAA,CAAS,MAAA,EAAQ,OAAO,CAAA,EAAG,GAAG,KAAK,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,GAAG,CAAC,CAAA,GAAA,CAAA;AACvF,EAAA,MAAM,KAAM,IAAA,CAA8B,IAAA;AAC1C,EAAA,IAAI,MAAM,OAAA,CAAQ,EAAE,GAAG,OAAO,IAAA,GAAO,GAAG,GAAA,CAAI,CAAC,CAAA,KAAM,EAAA,CAAG,OAAO,CAAC,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA,GAAI,IAAA;AAChF,EAAA,MAAM,IAAK,IAAA,CAA2B,IAAA;AACtC,EAAA,IAAI,CAAA,KAAM,SAAA,IAAa,CAAA,KAAM,QAAA,EAAU,OAAO,QAAA;AAC9C,EAAA,IAAI,CAAA,KAAM,WAAW,OAAO,SAAA;AAC5B,EAAA,OAAO,QAAA;AACT;AAEO,SAAS,gBAAA,CACd,OACA,QAAA,EACA,MAAA,GAAmB,CAAC,IAAI,CAAA,EACxB,YAAY,IAAA,EACJ;AACR,EAAA,MAAM,KAAA,GAAkB,CAAC,kBAAkB,CAAA;AAC3C,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,MAAM,eAAmC,EAAC;AAE1C,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,MAAM,OAAO,CAAA,CAAE,IAAA;AACf,IAAA,MAAM,KAAA,GAAS,CAAA,CAAE,UAAA,EAAY,UAAA,IAAc,EAAC;AAC5C,IAAA,MAAM,QAAA,GAAW,CAAA,CAAE,UAAA,EAAY,QAAA,IAAY,EAAC;AAC5C,IAAA,MAAM,CAAA,GAAI,GAAG,IAAI,CAAA;AACjB,IAAA,MAAM,WAAqB,EAAC;AAC5B,IAAA,MAAM,WAAqB,EAAC;AAC5B,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/C,MAAA,MAAM,MAAM,SAAA,CAAU,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,UAAU,MAAM,CAAA;AACvD,MAAA,IAAI,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,IAAK,QAAA,IAAY,QAAA,CAAS,MAAA,EAAQ,YAAA,CAAa,IAAA,CAAK,CAAC,IAAA,EAAM,GAAG,CAAC,CAAA;AACtF,MAAA,MAAM,OAAO,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,EAAA,CAAG,GAAG,CAAC,CAAA,CAAA;AAC5B,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,IAAI,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,GAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAE,CAAA;AAChD,MAAA,CAAC,SAAS,QAAA,CAAS,GAAG,IAAI,QAAA,GAAW,QAAA,EAAU,KAAK,IAAI,CAAA;AAAA,IAC3D;AAEA,IAAA,IAAI,GAAA,GAAM,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA;AAC/B,IAAA,KAAA,MAAW,CAAA,IAAK,UAAU,GAAA,GAAA,CAAO,GAAA,GAAM,MAAM,GAAA,GAAM,EAAA,IAAM,SAAS,CAAC,CAAA,GAAA,CAAA;AACnE,IAAA,KAAA,CAAM,KAAK,CAAA,EAAG,CAAC,aAAa,GAAA,CAAI,IAAA,GAAO,GAAG,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,EAAI,IAAI,GAAG,CAAC,GAAG,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAC,CAAA;AACtF,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,EAAG,CAAC,CAAA,KAAA,CAAO,CAAA;AAAA,EAC3B;AAGA,EAAA,KAAA,CAAM,KAAK,aAAA,GAAgB,QAAA,CAAS,IAAA,CAAK,KAAK,IAAI,IAAI,CAAA;AACtD,EAAA,KAAA,CAAM,IAAA,CAAK,kBAAkB,GAAA,CAAI,GAAG,IAAI,sBAAA,GAAyB,GAAA,CAAI,GAAG,CAAC,CAAA;AACzE,EAAA,KAAA,CAAM,IAAA;AAAA,IACJ,mBAAmB,GAAA,CAAI,qBAAqB,IAAI,iBAAA,GAAoB,GAAA,CAAI,mBAAmB,CAAA,GAAI;AAAA,GACjG;AACA,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,GAAG,CAAA,IAAK,YAAA,EAAc;AACtC,IAAA,KAAA,CAAM,KAAK,CAAA,EAAG,EAAA,CAAG,IAAI,CAAC,IAAI,EAAA,CAAG,GAAG,CAAC,CAAA,QAAA,CAAA,GAAc,SAAsB,GAAA,CAAI,EAAE,CAAA,CAAE,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA,EAC1F;AACA,EAAA,IAAI,SAAA,EAAW;AAEb,IAAA,KAAA,CAAM,KAAK,6BAA6B,CAAA;AACxC,IAAA,KAAA,CAAM,KAAK,0BAA0B,CAAA;AAAA,EACvC,CAAA,MAAO;AACL,IAAA,KAAA,CAAM,KAAK,kBAAkB,CAAA;AAAA,EAC/B;AACA,EAAA,KAAA,CAAM,KAAK,CAAA,wBAAA,CAA0B,CAAA;AACrC,EAAA,KAAA,CAAM,KAAK,sCAAsC,CAAA;AACjD,EAAA,KAAA,CAAM,KAAK,8BAA8B,CAAA;AACzC,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;AAGO,SAAS,UAAA,CAAW,OAAgB,IAAA,EAAyB;AAClE,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,UAAA,CAAW,CAAA,EAAG,IAAI,CAAA;AAAA,EAC3C,CAAA,MAAA,IAAW,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AAC7C,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAgC,CAAA,EAAG;AACrE,MAAA,IAAI,CAAA,KAAM,QAAQ,OAAO,CAAA,KAAM,YAAY,CAAA,EAAG,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,WACnD,UAAA,CAAW,GAAG,IAAI,CAAA;AAAA,IACzB;AAAA,EACF;AACF;;;ACpGO,IAAM,KAAA,GAAQ;AACd,IAAM,iBAAA,GAAoB;AAK1B,IAAM,wBAAA,GAA2B;AAMjC,IAAM,uBACX,KAAA,GAAQ,IAAA,CAAK,KAAK,KAAA,GAAQ,wBAAwB,IAAI,iBAAA,GAAoB;AAGrE,IAAM,cAAA,GAAiB,CAAC,IAAA,KAAqC,IAAA,CAAK,MAAM,IAAA,EAAM,MAAA,IAAU,KAAK,CAAC;;;ACqBrG,IAAM,sBAAA,GAAN,cAAqC,KAAA,CAAM;AAAA,EACzC,YAAY,EAAA,EAAY;AACtB,IAAA,KAAA,CAAM,CAAA,2BAAA,EAA8B,EAAE,CAAA,yCAAA,CAA2C,CAAA;AACjF,IAAA,IAAA,CAAK,IAAA,GAAO,wBAAA;AAAA,EACd;AACF,CAAA;AAGA,SAAS,WAAA,CAAe,SAAqB,EAAA,EAAwB;AACnE,EAAA,IAAI,CAAC,IAAI,OAAO,OAAA;AAChB,EAAA,IAAI,KAAA;AACJ,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAe,CAAC,GAAG,MAAA,KAAW;AAChD,IAAA,KAAA,GAAQ,UAAA,CAAW,MAAM,MAAA,CAAO,IAAI,uBAAuB,EAAE,CAAC,GAAG,EAAE,CAAA;AAAA,EACrE,CAAC,CAAA;AACD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,CAAC,OAAA,EAAS,OAAO,CAAC,CAAA,CAAE,OAAA,CAAQ,MAAM,YAAA,CAAa,KAAK,CAAC,CAAA;AAC3E;AAsBA,IAAM,OAAA,GACJ,0FAAA;AAEF,IAAM,UAAA,GAAN,cAAyB,KAAA,CAAM;AAAA,EAC7B,WAAA,GAAc;AACZ,IAAA,KAAA,CAAM,SAAS,CAAA;AACf,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AAAA,EACd;AACF,CAAA;AAEO,SAAS,YAAY,MAAA,EAA8B;AACxD,EAAA,MAAM,aAAA,GAAgB,OAAO,aAAA,IAAiB,CAAA;AAC9C,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,MAAA,IAAU,CAAC,IAAI,CAAA;AACrC,EAAA,MAAM,MAAA,GAAS,OAAO,mBAAA,IAAuB,oBAAA;AAC7C,EAAA,MAAM,mBAAA,GAAsB,OAAO,mBAAA,IAAuB,GAAA;AAC1D,EAAA,MAAM,UAAyB,EAAC;AAChC,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAA6B;AACnD,EAAA,IAAI,UAAA,GAAqC,IAAA;AAEzC,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAkB;AAC9B,IAAA,KAAA,MAAW,CAAA,IAAK,SAAA,EAAW,CAAA,CAAE,CAAC,CAAA;AAAA,EAChC,CAAA;AAEA,EAAA,eAAe,YAAY,IAAA,EAAkC;AAC3D,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,QAAA,CAAS,KAAK,IAAI,CAAA;AAC/C,IAAA,IAAI,CAAC,SAAS,OAAO,EAAE,OAAO,cAAA,EAAgB,IAAA,EAAM,KAAK,IAAA,EAAK;AAC9D,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,IAChC,SAAS,GAAA,EAAK;AACZ,MAAA,OAAO,EAAE,KAAA,EAAO,uBAAA,EAAyB,OAAA,EAAU,IAAc,OAAA,EAAQ;AAAA,IAC3E;AAAA,EACF;AAEA,EAAA,eAAe,OAAA,CAAQ,QAAgB,MAAA,EAAoC;AACzE,IAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,aAAA,EAAe,CAAA,EAAA,EAAK;AACtC,MAAA,IAAI,MAAA,CAAO,OAAA,EAAS,MAAM,IAAI,UAAA,EAAW;AACzC,MAAA,WAAA,CAAY,SAAS,MAAM,CAAA;AAC3B,MAAA,MAAM,QAAA,GAA0B,CAAC,EAAE,IAAA,EAAM,UAAU,OAAA,EAAS,MAAA,EAAO,EAAG,GAAG,OAAO,CAAA;AAChF,MAAA,MAAM,OAAA,GAAU,gBAAA;AAAA,QACd,OAAO,KAAA,CAAM,OAAA;AAAA,QACb,OAAA,CAAQ,IAAA,GAAO,CAAC,GAAG,OAAO,CAAA,GAAI,MAAA;AAAA,QAC9B;AAAA,OACF;AACA,MAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,MAAA,CAAO,OAAO,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA,EAAG,mBAAmB,CAAA;AAC/F,MAAA,IAAI,MAAA,CAAO,OAAA,EAAS,MAAM,IAAI,UAAA,EAAW;AAEzC,MAAA,IAAI,MAAA,CAAO,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG;AACjC,QAAA,OAAA,CAAQ,KAAK,EAAE,IAAA,EAAM,aAAa,OAAA,EAAS,MAAA,CAAO,KAAK,CAAA;AACvD,QAAA,IAAA,CAAK,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,MAAA,CAAO,MAAM,CAAA;AAC7C,QAAA,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,SAAS,CAAA;AACxC,QAAA;AAAA,MACF;AAEA,MAAA,OAAA,CAAQ,KAAK,EAAE,IAAA,EAAM,aAAa,OAAA,EAAS,MAAA,CAAO,KAAK,CAAA;AACvD,MAAA,IAAI,MAAA,CAAO,MAAM,IAAA,CAAK,EAAE,MAAM,WAAA,EAAa,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,CAAA;AAE9D,MAAA,KAAA,MAAW,IAAA,IAAQ,OAAO,SAAA,EAAW;AACnC,QAAA,IAAA,CAAK,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,CAAA;AAChC,QAAA,MAAM,UAAA,GAAa,MAAM,WAAA,CAAY,IAAI,CAAA;AACzC,QAAA,UAAA,CAAW,YAAY,OAAO,CAAA;AAC9B,QAAA,IAAA,CAAK,EAAE,MAAM,aAAA,EAAe,IAAA,EAAM,KAAK,IAAA,EAAM,MAAA,EAAQ,YAAY,CAAA;AACjE,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,IAAA,CAAK,SAAA,CAAU,UAAU,CAAA,EAAG,CAAA;AAAA,MACpE;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,KAAK,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,SAAS,CAAA;AACpD,IAAA,IAAA,CAAK,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAM,SAAS,CAAA;AACzC,IAAA,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,SAAS,CAAA;AAAA,EAC1C;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,GAAG,QAAA,EAAU;AACX,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACxC,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,UAAA,EAAY,KAAA,EAAM;AAAA,IACpB,CAAA;AAAA,IACA,KAAA,GAAQ;AACN,MAAA,OAAA,CAAQ,MAAA,GAAS,CAAA;AAAA,IACnB,CAAA;AAAA,IACA,MAAM,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM;AACvB,MAAA,UAAA,GAAa,IAAI,eAAA,EAAgB;AACjC,MAAA,MAAM,MAAA,GAAS,IAAA,EAAM,MAAA,GACjB,WAAA,CAAY,GAAA,CAAI,CAAC,UAAA,CAAW,MAAA,EAAQ,IAAA,CAAK,MAAM,CAAC,CAAA,GAChD,UAAA,CAAW,MAAA;AACf,MAAA,OAAA,CAAQ,KAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,MAAM,CAAA;AAC5C,MAAA,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,YAAY,CAAA;AAC3C,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GACJ,OAAO,MAAA,CAAO,YAAA,KAAiB,aAAa,MAAM,MAAA,CAAO,YAAA,EAAa,GAAI,MAAA,CAAO,YAAA;AACnF,QAAA,MAAM,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAC5B,QAAA,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AAAA,MACvB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,GAAA,YAAe,UAAA,IAAc,MAAA,CAAO,OAAA,EAAS;AAC/C,UAAA,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,WAAW,CAAA;AAC1C,UAAA,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AAAA,QACvB,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,KAAK,CAAA;AAClC,UAAA,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,SAAS,CAAA;AAAA,QAC1C;AAAA,MACF,CAAA,SAAE;AACA,QAAA,UAAA,GAAa,IAAA;AAAA,MACf;AAAA,IACF;AAAA,GACF;AACF;AAQO,SAAS,WAAA,CAAY,SAAwB,MAAA,EAAsB;AACxE,EAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,cAAA,CAAe,CAAA,CAAE,OAAO,CAAA,EAAG,CAAC,CAAA;AAC9E,EAAA,OAAO,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,MAAA,KAAW,MAAA,EAAQ;AAC9C,IAAA,OAAA,CAAQ,KAAA,EAAM;AAEd,IAAA,OAAO,OAAA,CAAQ,SAAS,CAAA,IAAK,OAAA,CAAQ,CAAC,CAAA,CAAG,IAAA,KAAS,MAAA,EAAQ,OAAA,CAAQ,KAAA,EAAM;AAAA,EAC1E;AACF;;;AC7LA,IAAM,aAAA,GAAgB,qDAAA;AAOf,SAAS,eAAe,GAAA,EAA0B;AACvD,EAAA,MAAM,YAA8B,EAAC;AACrC,EAAA,MAAM,OAAO,GAAA,CAAI,OAAA,CAAQ,aAAA,EAAe,CAAC,QAAQ,KAAA,KAAkB;AACjE,IAAA,SAAA,CAAU,KAAK,GAAG,aAAA,CAAc,KAAA,CAAM,IAAA,EAAM,CAAC,CAAA;AAC7C,IAAA,OAAO,EAAA;AAAA,EACT,CAAC,CAAA;AACD,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,IAAQ,SAAA,EAAU;AACxC;AAEA,SAAS,cAAc,KAAA,EAAiC;AACtD,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,IAAK,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,KAAA;AAClF,EAAA,MAAM,QAA0B,EAAC;AACjC,EAAA,KAAA,MAAW,KAAA,IAAS,aAAA,CAAc,KAAA,EAAO,GAAG,CAAA,EAAG;AAC7C,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,IAAA,GAAO,gBAAgB,OAAO,CAAA;AACpC,IAAA,IAAI,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAAA,EAC3B;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,gBAAgB,IAAA,EAAqC;AAC5D,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,qCAAqC,CAAA;AAC9D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,GAAG,IAAA,EAAM,OAAO,CAAA,GAAI,KAAA;AAC1B,EAAA,MAAM,OAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,KAAA,IAAS,aAAA,CAAc,OAAA,EAAS,GAAG,CAAA,EAAG;AAC/C,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AAC9B,IAAA,IAAI,OAAO,EAAA,EAAI;AACf,IAAA,MAAM,MAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,EAAE,IAAA,EAAK;AACtC,IAAA,MAAM,WAAW,OAAA,CAAQ,KAAA,CAAM,EAAA,GAAK,CAAC,EAAE,IAAA,EAAK;AAC5C,IAAA,IAAA,CAAK,GAAG,CAAA,GAAI,UAAA,CAAW,QAAQ,CAAA;AAAA,EACjC;AACA,EAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AACtB;AAEA,SAAS,WAAW,GAAA,EAAsB;AACxC,EAAA,IAAI,GAAA,KAAQ,QAAQ,OAAO,IAAA;AAC3B,EAAA,IAAI,GAAA,KAAQ,SAAS,OAAO,KAAA;AAC5B,EAAA,IAAI,GAAA,KAAQ,QAAQ,OAAO,IAAA;AAC3B,EAAA,IAAI,UAAU,IAAA,CAAK,GAAG,GAAG,OAAO,QAAA,CAAS,KAAK,EAAE,CAAA;AAChD,EAAA,IAAI,eAAe,IAAA,CAAK,GAAG,CAAA,EAAG,OAAO,WAAW,GAAG,CAAA;AACnD,EAAA,IACG,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,IAAK,IAAI,QAAA,CAAS,GAAG,CAAA,IACvC,GAAA,CAAI,WAAW,GAAG,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,EACxC;AACA,IAAA,OAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,GAAA;AACT;AAGA,SAAS,aAAA,CAAc,KAAa,SAAA,EAA6B;AAC/D,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,KAAA,GAAuB,IAAA;AAC3B,EAAA,IAAI,OAAA,GAAU,EAAA;AACd,EAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AACpB,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAA,IAAW,EAAA;AACX,MAAA,IAAI,EAAA,KAAO,OAAO,KAAA,GAAQ,IAAA;AAC1B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK;AAC5B,MAAA,KAAA,GAAQ,EAAA;AACR,MAAA,OAAA,IAAW,EAAA;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK,KAAA,EAAA;AAC9B,IAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK,KAAA,EAAA;AAC9B,IAAA,IAAI,EAAA,KAAO,SAAA,IAAa,KAAA,KAAU,CAAA,EAAG;AACnC,MAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAClB,MAAA,OAAA,GAAU,EAAA;AACV,MAAA;AAAA,IACF;AACA,IAAA,OAAA,IAAW,EAAA;AAAA,EACb;AACA,EAAA,IAAI,OAAA,CAAQ,IAAA,EAAK,EAAG,KAAA,CAAM,KAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA;AACT;;;AC5FA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,IAAI,KAAK,CAAA,CAAA,CAAA;AAC/C,EAAA,IAAI,KAAA,KAAU,MAAM,OAAO,MAAA;AAC3B,EAAA,IAAI,OAAO,KAAA,KAAU,SAAA,EAAW,OAAO,QAAQ,MAAA,GAAS,OAAA;AACxD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,IAAA,CAAK,UAAU,KAAK,CAAA;AAC1D,EAAA,OAAO,OAAO,KAAK,CAAA;AACrB;AAEO,SAAS,gBAAgB,KAAA,EAAiC;AAC/D,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM;AAChC,IAAA,MAAM,IAAA,GAAO,OAAO,OAAA,CAAQ,CAAA,CAAE,IAAI,CAAA,CAC/B,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,SAAA,CAAU,CAAC,CAAC,CAAA,CAAE,CAAA,CACtC,IAAA,CAAK,IAAI,CAAA;AACZ,IAAA,OAAO,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,CAAA;AAAA,EAC1B,CAAC,CAAA;AACD,EAAA,OAAO,CAAA,oBAAA,EAAuB,QAAA,CAAS,IAAA,CAAK,IAAI,CAAC,CAAA,kBAAA,CAAA;AACnD;;;ACeA,IAAM,aAAA,GAAsC;AAAA,EAC1C,IAAA,EAAM,kCAAA;AAAA,EACN,OAAA,EAAS,QAAA;AAAA;AAAA;AAAA,EAGT,KAAA,EAAO;AACT,CAAA;AAGO,SAAS,eAAA,CAAgB,GAAA,GAAkB,EAAC,EAAW;AAC5D,EAAA,MAAM,EAAE,MAAM,OAAA,EAAS,KAAA,KAAU,EAAE,GAAG,aAAA,EAAe,GAAG,GAAA,EAAI;AAC5D,EAAA,MAAM,WAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,KAAI,IAAK,IAAA;AAC1C,EAAA,OAAO,0BAA0B,IAAI,CAAA,cAAA,EAAiB,QAAQ,CAAA,CAAA,EAAI,OAAO,IAAI,KAAK,CAAA,KAAA,CAAA;AACpF;AAEA,eAAe,YAAA,GAAiC;AAC9C,EAAA,IAAI;AACF,IAAA,MAAM,MAAO,SAAA,CAA+E,GAAA;AAC5F,IAAA,IAAI,CAAC,GAAA,EAAK,cAAA,EAAgB,OAAO,KAAA;AACjC,IAAA,OAAQ,MAAM,GAAA,CAAI,cAAA,EAAe,IAAM,IAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAOO,IAAM,eAAN,MAA0C;AAAA,EAO/C,YAA6B,MAAA,EAA4B;AAA5B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAC3B,IAAA,IAAA,CAAK,GAAA,GAAM,MAAA,CAAO,QAAA,IAAY,eAAA,CAAgB,OAAO,KAAK,CAAA;AAC1D,IAAA,IAAA,CAAK,IAAA,GAAO,OAAO,IAAA,IAAQ,KAAA;AAC3B,IAAA,IAAA,CAAK,SAAA,GAAY,OAAO,SAAA,IAAa,iBAAA;AAAA,EACvC;AAAA,EAJ6B,MAAA;AAAA,EANrB,aAAA,GAAwC,IAAA;AAAA,EACxC,gBAAA,GAAmB,KAAA;AAAA,EACV,GAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EAQjB,MAAM,IAAA,GAAsB;AAC1B,IAAA,MAAM,KAAK,SAAA,EAAU;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,IAAI,IAAA,CAAK,aAAA;AACf,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,CAAA,GAAI,MAAM,CAAA,GAAI,IAAA;AAC3B,MAAA,MAAO,MAAoD,IAAA,IAAO;AAAA,IACpE,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,SAAA,GAA6B;AACnC,IAAA,IAAI,IAAA,CAAK,aAAA,EAAe,OAAO,IAAA,CAAK,aAAA;AACpC,IAAA,IAAA,CAAK,iBAAiB,YAAY;AAChC,MAAA,MAAM,EAAE,UAAU,UAAA,EAAY,SAAA,EAAW,UAAU,MAAA,EAAQ,YAAA,KAAiB,IAAA,CAAK,MAAA;AACjF,MAAA,MAAM,SAAA,GAAY,MAAM,YAAA,EAAa;AACrC,MAAA,MAAM,MAAA,GAAS,aAAa,OAAA,KAAY,KAAA;AACxC,MAAA,SAAA,GAAY,EAAE,MAAA,EAAQ,SAAA,EAAW,OAAO,MAAA,GAAS,QAAA,GAAW,OAAO,CAAA;AAEnE,MAAA,MAAM,IAAA,GAAO,OAAO,QAAA,KAAuC;AACzD,QAAA,MAAMA,SAAAA,GAAW,IAAI,MAAA,CAAO,YAAY,CAAA;AACxC,QAAA,QAAA,GAAW,aAAa,CAAA;AAKxB,QAAA,IAAI,gBAAA,GAAmB,KAAA;AACvB,QAAA,MAAMA,SAAAA,CAAS,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK;AAAA,UACxC,OAAO,IAAA,CAAK,IAAA;AAAA,UACZ,YAAA,EAAc,SAAS,KAAA,GAAQ,CAAA;AAAA,UAC/B,GAAI,QAAA,GAAW,EAAE,SAAA,EAAW,QAAA,KAAa,EAAC;AAAA,UAC1C,gBAAA,EAAkB,CAAC,EAAE,MAAA,EAAQ,OAAM,KAAM;AACvC,YAAA,UAAA,GAAa,EAAE,MAAA,EAAQ,KAAA,EAAO,CAAA;AAC9B,YAAA,IAAI,CAAC,gBAAA,IAAoB,KAAA,GAAQ,CAAA,IAAK,UAAU,KAAA,EAAO;AACrD,cAAA,gBAAA,GAAmB,IAAA;AACnB,cAAA,QAAA,GAAW,SAAS,CAAA;AAAA,YACtB;AAAA,UACF;AAAA,SACD,CAAA;AACD,QAAA,OAAOA,SAAAA;AAAA,MACT,CAAA;AAKA,MAAA,IAAI,QAAA;AACJ,MAAA,IAAI;AACF,QAAA,QAAA,GAAW,MAAM,IAAA,EAAK;AAAA,MACxB,SAAS,GAAA,EAAK;AACZ,QAAA,OAAA,CAAQ,IAAA,CAAK,sEAAsE,GAAG,CAAA;AACtF,QAAA,QAAA,GAAW,MAAM,KAAK,CAAC,CAAA;AAAA,MACzB;AACA,MAAA,QAAA,GAAW,OAAO,CAAA;AAClB,MAAA,OAAO,QAAA;AAAA,IACT,CAAA,GAAG;AACH,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd;AAAA,EAEA,MAAM,QAAA,CAAS,QAAA,EAAyB,OAAA,EAAiD;AACvF,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,SAAA,EAAU;AAEpC,IAAA,IAAI,CAAC,KAAK,gBAAA,EAAkB;AAC1B,MAAA,MAAM,GAAA,GAAM,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,IAAA,KAAS,QAAQ,CAAA,EAAG,OAAA,IAAW,EAAA;AAClE,MAAA,MAAM,WAAY,MAAA,CAA2D,QAAA;AAC7E,MAAA,IAAI,SAAA;AACJ,MAAA,IAAI;AACF,QAAA,SAAA,GAAY,QAAA,GAAA,CAAY,MAAM,QAAA,CAAS,IAAA,CAAK,QAAQ,GAAG,CAAA,EAAG,MAAA,GAAS,cAAA,CAAe,GAAG,CAAA;AAAA,MACvF,CAAA,CAAA,MAAQ;AACN,QAAA,SAAA,GAAY,eAAe,GAAG,CAAA;AAAA,MAChC;AACA,MAAA,IAAI,SAAA,GAAY,IAAA,CAAK,IAAA,GAAO,wBAAA,EAA0B;AACpD,QAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AACxB,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,CAAA,kCAAA,EAAqC,SAAS,CAAA,eAAA,EACzC,IAAA,CAAK,KAAA,CAAM,2BAA2B,GAAG,CAAC,CAAA,SAAA,EAAY,IAAA,CAAK,IAAI,CAAA,8GAAA;AAAA,SAEtE;AAAA,MACF;AAAA,IACF;AAKA,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,oBAAA,CAAqB;AAAA,MACjD,QAAA;AAAA,MACA,YAAY,IAAA,CAAK,SAAA;AAAA,MACjB,WAAA,EAAa,CAAA;AAAA,MACb,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY;AAAC,KACrB,CAAA;AACV,IAAA,MAAM,OAAA,GAAW,QAAA,CAAuD,OAAA,CAAQ,CAAC,CAAA,CAAG,OAAA;AAEpF,IAAA,IAAI,OAAA,CAAQ,UAAA,IAAc,OAAA,CAAQ,UAAA,CAAW,SAAS,CAAA,EAAG;AACvD,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QAC/C,IAAA,EAAM,EAAE,QAAA,CAAS,IAAA;AAAA,QACjB,IAAA,EAAM,QAAA,CAAS,CAAA,CAAE,QAAA,CAAS,SAAS;AAAA,OACrC,CAAE,CAAA;AACF,MAAA,OAAO,EAAE,MAAM,OAAA,CAAQ,OAAA,IAAW,IAAI,SAAA,EAAW,GAAA,EAAK,eAAA,CAAgB,SAAS,CAAA,EAAE;AAAA,IACnF;AAEA,IAAA,MAAM,UAAA,GAAa,QAAQ,OAAA,IAAW,EAAA;AACtC,IAAA,MAAM,MAAA,GAAS,eAAe,UAAU,CAAA;AACxC,IAAA,OAAO,EAAE,MAAM,MAAA,CAAO,IAAA,EAAM,WAAW,MAAA,CAAO,SAAA,EAAW,KAAK,UAAA,EAAW;AAAA,EAC3E;AACF;AAOA,SAAS,SAAS,CAAA,EAAoC;AACpD,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,IAAK,IAAI,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;;;ACjMO,IAAM,aAAN,MAAwC;AAAA,EAC7C,MAAM,QAAA,CAAS,QAAA,EAAyB,QAAA,EAAkD;AAExF,IAAA,MAAM,WAAA,GAAc,SAAS,GAAA,CAAI,CAAC,MAAM,CAAA,CAAE,IAAI,CAAA,CAAE,WAAA,CAAY,MAAM,CAAA;AAClE,IAAA,MAAM,mBAAA,GAAsB,QAAA,CAAS,KAAA,CAAM,WAAA,GAAc,CAAC,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA;AAC3F,IAAA,MAAM,YAAY,QAAA,CAAS,WAAW,CAAA,EAAG,OAAA,IAAW,IAAI,WAAA,EAAY;AAEpE,IAAA,IAAI,mBAAA,CAAoB,SAAS,CAAA,EAAG;AAClC,MAAA,MAAM,IAAA,GAAO,mBAAA,CAAoB,mBAAA,CAAoB,MAAA,GAAS,CAAC,CAAA;AAC/D,MAAA,MAAMC,KAAAA,GAAO,CAAA,qBAAA,EAAwB,IAAA,EAAM,OAAA,IAAW,EAAE,CAAA,CAAA;AACxD,MAAA,OAAO,EAAE,IAAA,EAAAA,KAAAA,EAAM,WAAW,EAAC,EAAG,KAAKA,KAAAA,EAAK;AAAA,IAC1C;AAEA,IAAA,MAAM,IAAA,GAAO,CAAC,IAAA,EAAc,IAAA,KAAwD;AAClF,MAAA,MAAM,SAAA,GAAY,CAAC,EAAE,IAAA,EAAM,MAAM,CAAA;AACjC,MAAA,OAAO,EAAE,IAAA,EAAM,EAAA,EAAI,WAAW,GAAA,EAAK,eAAA,CAAgB,SAAS,CAAA,EAAE;AAAA,IAChE,CAAA;AAEA,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,sBAAsB,CAAA;AACtD,IAAA,IAAI,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,IAAK,QAAA,EAAU;AACzC,MAAA,MAAM,QAAA,GAAW,OAAO,QAAA,CAAS,KAAA,CAAM,oBAAoB,CAAA,GAAI,CAAC,KAAK,GAAG,CAAA;AACxE,MAAA,OAAO,IAAA,CAAK,eAAe,EAAE,EAAA,EAAI,SAAS,CAAC,CAAA,EAAG,UAAU,CAAA;AAAA,IAC1D;AACA,IAAA,IAAI,sCAAA,CAAuC,IAAA,CAAK,QAAQ,CAAA,EAAG;AACzD,MAAA,OAAO,IAAA,CAAK,oBAAoB,EAAE,KAAA,EAAO,SAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA;AAAA,IAClE;AACA,IAAA,IAAI,8DAAA,CAA+D,IAAA,CAAK,QAAQ,CAAA,EAAG;AACjF,MAAA,OAAO,IAAA,CAAK,kBAAkB,EAAE,KAAA,EAAO,SAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA;AAAA,IAChE;AAEA,IAAA,MAAM,IAAA,GACJ,iHAAA;AACF,IAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,EAAC,EAAG,KAAK,IAAA,EAAK;AAAA,EAC1C;AACF;;;ACpCO,SAAS,OAAO,CAAA,EAAoB;AACzC,EAAA,IAAI,CAAA,KAAM,MAAM,OAAO,MAAA;AACvB,EAAA,IAAI,OAAO,CAAA,KAAM,QAAA,EAAU,OAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AAClD,EAAA,IAAI,OAAO,MAAM,QAAA,IAAY,OAAO,MAAM,SAAA,EAAW,OAAO,OAAO,CAAC,CAAA;AACpE,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,GAAA,GAAM,CAAA,CAAE,GAAA,CAAI,MAAM,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,GAAI,GAAA;AAC9D,EAAA,IAAI,OAAO,MAAM,QAAA,EAAU;AACzB,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,CAA4B,CAAA;AAC3D,IAAA,OAAO,GAAA,GAAM,QAAQ,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,GAAG,MAAM,CAAA,EAAG,IAAA,CAAK,UAAU,CAAC,CAAC,KAAK,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,GAAI,GAAA;AAAA,EAC9F;AACA,EAAA,OAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AACzB;;;ACIA,eAAsB,kBAAkB,GAAA,EAA0C;AAChF,EAAA,MAAM,KAAA,GAAQ,IAAI,SAAA,IAAa,uBAAA;AAC/B,EAAA,MAAM,IAAA,GACJ,OAAO,GAAA,CAAI,WAAA,KAAgB,aAAa,MAAM,GAAA,CAAI,WAAA,EAAY,GAAI,GAAA,CAAI,WAAA;AACxE,EAAA,MAAM,IAAA,GAAO,IAAA,GAAO,CAAA,EAAG,GAAA,CAAI,OAAO;;AAAA,EAAO,KAAK,CAAA,EAAA,EAAK,IAAI,CAAA,CAAA,GAAK,GAAA,CAAI,OAAA;AAChE,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,MAAA,CAAO,EAAE,IAAA,EAAM,UAAA,EAAY,QAAA,EAAU,CAAA,EAAG,CAAC,CAAA;AAClF,EAAA,OAAO,GAAG,IAAI;AAAA,gBAAA,EAAqB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAA;AACrD;;;ACjBO,SAAS,cAAc,WAAA,EAA6C;AACzE,EAAA,OAAO;AAAA,IACL,SAAS,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACxC,QAAA,EAAU,MAAA,CAAO,WAAA,CAAY,WAAA,CAAY,IAAI,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,MAAA,CAAO,IAAA,EAAM,CAAA,CAAE,OAAO,CAAC,CAAC;AAAA,GACjF;AACF","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"]}
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 repo?: string\n version?: string\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: 'main',\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/${version}/${basename}-${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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontend-agent",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Drive the LFM2.5-230M frontend-agent model in the browser: tool-calling + RAG-grounded answering, fully typed. Loads the GGUF from Hugging Face via wllama.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -46,7 +46,8 @@
46
46
  "scripts": {
47
47
  "build": "tsup",
48
48
  "typecheck": "tsc --noEmit",
49
- "test": "vitest run"
49
+ "test": "vitest run",
50
+ "prepack": "cp ../../README.md ./README.md"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "@wllama/wllama": ">=3.5.0",