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/LICENSE +208 -0
- package/README.md +95 -0
- package/dist/index.d.ts +230 -0
- package/dist/index.js +489 -0
- package/dist/index.js.map +1 -0
- package/dist/rag.d.ts +37 -0
- package/dist/rag.js +132 -0
- package/dist/rag.js.map +1 -0
- package/dist/reference.d.ts +38 -0
- package/dist/reference.js +119 -0
- package/dist/reference.js.map +1 -0
- package/dist/types-BqZ6-bpz.d.ts +59 -0
- package/dist/types-ChI5DrVS.d.ts +16 -0
- package/package.json +69 -0
package/dist/rag.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
import { stemmer } from 'stemmer';
|
|
3
|
+
|
|
4
|
+
// src/rag/localRetriever.ts
|
|
5
|
+
var LocalMiniSearchRAG = class _LocalMiniSearchRAG {
|
|
6
|
+
cat;
|
|
7
|
+
know;
|
|
8
|
+
catById;
|
|
9
|
+
knowById;
|
|
10
|
+
constructor(index) {
|
|
11
|
+
const processTerm = (t) => stemmer(t.toLowerCase());
|
|
12
|
+
const searchOptions = { fuzzy: 0.2, prefix: true, boost: { title: 2 } };
|
|
13
|
+
this.cat = new MiniSearch({
|
|
14
|
+
idField: "id",
|
|
15
|
+
fields: ["title", "group", "summary"],
|
|
16
|
+
processTerm,
|
|
17
|
+
searchOptions
|
|
18
|
+
});
|
|
19
|
+
this.cat.addAll(index.catalog);
|
|
20
|
+
this.know = new MiniSearch({
|
|
21
|
+
idField: "id",
|
|
22
|
+
fields: ["title", "text"],
|
|
23
|
+
processTerm,
|
|
24
|
+
searchOptions
|
|
25
|
+
});
|
|
26
|
+
this.know.addAll(index.knowledge);
|
|
27
|
+
this.catById = new Map(index.catalog.map((c) => [c.id, c]));
|
|
28
|
+
this.knowById = new Map(index.knowledge.map((d) => [d.id, d]));
|
|
29
|
+
}
|
|
30
|
+
static async fromUrl(url) {
|
|
31
|
+
const res = await fetch(url);
|
|
32
|
+
if (!res.ok) throw new Error(`failed to load RAG index: ${res.status} ${url}`);
|
|
33
|
+
return new _LocalMiniSearchRAG(await res.json());
|
|
34
|
+
}
|
|
35
|
+
hint(n = 6) {
|
|
36
|
+
const groups = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const c of this.catById.values()) {
|
|
38
|
+
const g = c.group ?? "";
|
|
39
|
+
const bucket = groups.get(g);
|
|
40
|
+
if (bucket) bucket.push(c);
|
|
41
|
+
else groups.set(g, [c]);
|
|
42
|
+
}
|
|
43
|
+
const buckets = [...groups.values()];
|
|
44
|
+
const picked = [];
|
|
45
|
+
const max = Math.max(0, ...buckets.map((b) => b.length));
|
|
46
|
+
for (let i = 0; i < max && picked.length < n; i++) {
|
|
47
|
+
for (const b of buckets) {
|
|
48
|
+
if (b[i]) picked.push(b[i]);
|
|
49
|
+
if (picked.length >= n) break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return picked.map((c) => `${c.title} [${c.id}]`).join("; ");
|
|
53
|
+
}
|
|
54
|
+
getItem(id) {
|
|
55
|
+
const it = this.catById.get(id);
|
|
56
|
+
return it ? { id: it.id, title: it.title, price: it.price, in_stock: it.in_stock } : null;
|
|
57
|
+
}
|
|
58
|
+
async searchCatalog(query, opts = {}) {
|
|
59
|
+
const k = opts.k ?? 5;
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const hit of this.cat.search(query)) {
|
|
62
|
+
const item = this.catById.get(hit.id);
|
|
63
|
+
if (!item) continue;
|
|
64
|
+
if (opts.maxPrice != null && (item.price == null || item.price > opts.maxPrice)) continue;
|
|
65
|
+
out.push({
|
|
66
|
+
id: item.id,
|
|
67
|
+
title: item.title,
|
|
68
|
+
snippet: snippet(item.summary, 120),
|
|
69
|
+
price: item.price,
|
|
70
|
+
in_stock: item.in_stock,
|
|
71
|
+
attrs: {},
|
|
72
|
+
score: round(hit.score)
|
|
73
|
+
});
|
|
74
|
+
if (out.length >= k) break;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
async searchKnowledge(query, k = 4) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const hit of this.know.search(query)) {
|
|
81
|
+
const item = this.knowById.get(hit.id);
|
|
82
|
+
if (!item) continue;
|
|
83
|
+
out.push({ id: item.id, title: item.title, snippet: snippet(item.text, 480), score: round(hit.score) });
|
|
84
|
+
if (out.length >= k) break;
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var snippet = (t, cap) => t.trim().slice(0, cap).trimEnd();
|
|
90
|
+
var round = (n) => Math.round(n * 1e3) / 1e3;
|
|
91
|
+
|
|
92
|
+
// src/rag/ragClient.ts
|
|
93
|
+
function createRagClient(endpoint, init) {
|
|
94
|
+
async function remote(index, query, filters, topK) {
|
|
95
|
+
const res = await fetch(endpoint, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "content-type": "application/json", ...init?.headers ?? {} },
|
|
98
|
+
body: JSON.stringify({ index, query, filters, top_k: topK }),
|
|
99
|
+
...init
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) throw new Error(`rag endpoint ${res.status}`);
|
|
102
|
+
const json = await res.json();
|
|
103
|
+
return json.results ?? [];
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
async searchCatalog(query, opts = {}) {
|
|
107
|
+
const k = opts.k ?? 5;
|
|
108
|
+
const filters = opts.maxPrice != null ? { max_price: opts.maxPrice } : {};
|
|
109
|
+
return (await remote("catalog", query, filters, k)).map((r) => ({
|
|
110
|
+
id: r.id,
|
|
111
|
+
title: r.title,
|
|
112
|
+
snippet: r.snippet ?? r.text ?? "",
|
|
113
|
+
price: r.price ?? r.meta?.price ?? null,
|
|
114
|
+
in_stock: r.in_stock ?? r.meta?.in_stock ?? true,
|
|
115
|
+
attrs: r.attrs ?? r.meta ?? {},
|
|
116
|
+
score: r.score ?? 0
|
|
117
|
+
}));
|
|
118
|
+
},
|
|
119
|
+
async searchKnowledge(query, k = 4) {
|
|
120
|
+
return (await remote("knowledge", query, {}, k)).map((r) => ({
|
|
121
|
+
id: r.id,
|
|
122
|
+
title: r.title,
|
|
123
|
+
snippet: r.snippet ?? r.text ?? "",
|
|
124
|
+
score: r.score ?? 0
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { LocalMiniSearchRAG, createRagClient };
|
|
131
|
+
//# sourceMappingURL=rag.js.map
|
|
132
|
+
//# sourceMappingURL=rag.js.map
|
package/dist/rag.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rag/localRetriever.ts","../src/rag/ragClient.ts"],"names":[],"mappings":"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","file":"rag.js","sourcesContent":["import MiniSearch from 'minisearch'\nimport { stemmer } from 'stemmer'\nimport type {\n CatalogItem,\n CatalogItemLite,\n CatalogResult,\n KnowledgeItem,\n KnowledgeResult,\n RagBackend,\n RagIndex,\n} from './types'\n\n/**\n * Zero-infra in-browser retrieval: MiniSearch (BM25 + fuzzy + prefix) with a Porter stemmer over an\n * index you supply. The algorithm need NOT match the training retriever - the model grounds in\n * whatever RESULTS come back; only the result shape is the contract. Construct with an index object,\n * or `LocalMiniSearchRAG.fromUrl(url)` to fetch one.\n */\nexport class LocalMiniSearchRAG implements RagBackend {\n private readonly cat: MiniSearch<CatalogItem>\n private readonly know: MiniSearch<KnowledgeItem>\n private readonly catById: Map<string, CatalogItem>\n private readonly knowById: Map<string, KnowledgeItem>\n\n constructor(index: RagIndex) {\n const processTerm = (t: string) => stemmer(t.toLowerCase())\n const searchOptions = { fuzzy: 0.2, prefix: true, boost: { title: 2 } }\n this.cat = new MiniSearch<CatalogItem>({\n idField: 'id',\n fields: ['title', 'group', 'summary'],\n processTerm,\n searchOptions,\n })\n this.cat.addAll(index.catalog)\n this.know = new MiniSearch<KnowledgeItem>({\n idField: 'id',\n fields: ['title', 'text'],\n processTerm,\n searchOptions,\n })\n this.know.addAll(index.knowledge)\n this.catById = new Map(index.catalog.map((c) => [c.id, c]))\n this.knowById = new Map(index.knowledge.map((d) => [d.id, d]))\n }\n\n static async fromUrl(url: string): Promise<LocalMiniSearchRAG> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`failed to load RAG index: ${res.status} ${url}`)\n return new LocalMiniSearchRAG((await res.json()) as RagIndex)\n }\n\n hint(n = 6): string {\n // Representative sample ACROSS groups - not the first n (which may all be one category, leaving\n // the model unaware the store even sells other categories). Round-robin: one per group, repeat.\n const groups = new Map<string, CatalogItem[]>()\n for (const c of this.catById.values()) {\n const g = c.group ?? ''\n const bucket = groups.get(g)\n if (bucket) bucket.push(c)\n else groups.set(g, [c])\n }\n const buckets = [...groups.values()]\n const picked: CatalogItem[] = []\n const max = Math.max(0, ...buckets.map((b) => b.length))\n for (let i = 0; i < max && picked.length < n; i++) {\n for (const b of buckets) {\n if (b[i]) picked.push(b[i])\n if (picked.length >= n) break\n }\n }\n return picked.map((c) => `${c.title} [${c.id}]`).join('; ')\n }\n\n getItem(id: string): CatalogItemLite | null {\n const it = this.catById.get(id)\n return it ? { id: it.id, title: it.title, price: it.price, in_stock: it.in_stock } : null\n }\n\n async searchCatalog(\n query: string,\n opts: { maxPrice?: number; k?: number } = {},\n ): Promise<CatalogResult[]> {\n const k = opts.k ?? 5\n const out: CatalogResult[] = []\n for (const hit of this.cat.search(query)) {\n const item = this.catById.get(hit.id as string)\n if (!item) continue\n if (opts.maxPrice != null && (item.price == null || item.price > opts.maxPrice)) continue\n out.push({\n id: item.id,\n title: item.title,\n snippet: snippet(item.summary, 120),\n price: item.price,\n in_stock: item.in_stock,\n attrs: {},\n score: round(hit.score),\n })\n if (out.length >= k) break\n }\n return out\n }\n\n async searchKnowledge(query: string, k = 4): Promise<KnowledgeResult[]> {\n const out: KnowledgeResult[] = []\n for (const hit of this.know.search(query)) {\n const item = this.knowById.get(hit.id as string)\n if (!item) continue\n // long knowledge snippets (480) so how-to/procedural answers are fully present in the passage\n out.push({ id: item.id, title: item.title, snippet: snippet(item.text, 480), score: round(hit.score) })\n if (out.length >= k) break\n }\n return out\n }\n}\n\nconst snippet = (t: string, cap: number): string => t.trim().slice(0, cap).trimEnd()\nconst round = (n: number): number => Math.round(n * 1000) / 1000\n","import type { CatalogResult, KnowledgeResult, RagBackend } from './types'\n\n/**\n * Bring-your-own retrieval backend. Speaks a single contract to your search service, then normalizes\n * to the frozen model-facing shape - so the model can't tell which backend answered:\n *\n * POST {endpoint} { index: \"catalog\"|\"knowledge\", query, filters, top_k }\n * -> { results: [{ id, title, text, score, meta }] }\n *\n * Point `endpoint` at your Qdrant/pgvector/Elastic/Typesense adapter.\n */\nexport function createRagClient(endpoint: string, init?: RequestInit): RagBackend {\n async function remote(\n index: 'catalog' | 'knowledge',\n query: string,\n filters: Record<string, unknown>,\n topK: number,\n ): Promise<RemoteRow[]> {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...(init?.headers ?? {}) },\n body: JSON.stringify({ index, query, filters, top_k: topK }),\n ...init,\n })\n if (!res.ok) throw new Error(`rag endpoint ${res.status}`)\n const json = (await res.json()) as { results?: RemoteRow[] }\n return json.results ?? []\n }\n\n return {\n async searchCatalog(query, opts = {}): Promise<CatalogResult[]> {\n const k = opts.k ?? 5\n const filters = opts.maxPrice != null ? { max_price: opts.maxPrice } : {}\n return (await remote('catalog', query, filters, k)).map((r) => ({\n id: r.id,\n title: r.title,\n snippet: r.snippet ?? r.text ?? '',\n price: r.price ?? r.meta?.price ?? null,\n in_stock: r.in_stock ?? r.meta?.in_stock ?? true,\n attrs: r.attrs ?? r.meta ?? {},\n score: r.score ?? 0,\n }))\n },\n async searchKnowledge(query, k = 4): Promise<KnowledgeResult[]> {\n return (await remote('knowledge', query, {}, k)).map((r) => ({\n id: r.id,\n title: r.title,\n snippet: r.snippet ?? r.text ?? '',\n score: r.score ?? 0,\n }))\n },\n }\n}\n\ninterface RemoteRow {\n id: string\n title: string\n text?: string\n snippet?: string\n price?: number | null\n in_stock?: boolean\n attrs?: Record<string, unknown>\n score?: number\n meta?: Record<string, unknown> & { price?: number | null; in_stock?: boolean }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { b as ToolDefinition } from './types-ChI5DrVS.js';
|
|
2
|
+
import { R as RagBackend } from './types-BqZ6-bpz.js';
|
|
3
|
+
|
|
4
|
+
/** Cart operations the reference cart tools drive. Implement over your own state (store, signal, ...). */
|
|
5
|
+
interface CartHandlers {
|
|
6
|
+
add(item: {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
price: number;
|
|
10
|
+
}, quantity: number): void | Promise<void>;
|
|
11
|
+
remove(id: string): void | Promise<void>;
|
|
12
|
+
view(): CartView | Promise<CartView>;
|
|
13
|
+
clear(): void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
interface CartView {
|
|
16
|
+
cart: {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
price: number;
|
|
20
|
+
quantity: number;
|
|
21
|
+
}[];
|
|
22
|
+
total: number;
|
|
23
|
+
}
|
|
24
|
+
/** Move the storefront to a page. `target` is one of checkout|cart|home|product; `id` for product. */
|
|
25
|
+
type NavigateHandler = (target: string, id?: string) => void | Promise<void>;
|
|
26
|
+
interface ReferenceToolsConfig {
|
|
27
|
+
rag: RagBackend;
|
|
28
|
+
cart: CartHandlers;
|
|
29
|
+
navigate: NavigateHandler;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The 7 canonical tools the v1.0.0 model was trained on, wired to your handlers. Schemas (names,
|
|
33
|
+
* descriptions, order) are byte-identical to training; results use the trained shapes. Retrieval is
|
|
34
|
+
* RAG-as-a-tool; cart acts by `id`; navigate is read-only by construction.
|
|
35
|
+
*/
|
|
36
|
+
declare function referenceTools(cfg: ReferenceToolsConfig): ToolDefinition[];
|
|
37
|
+
|
|
38
|
+
export { type CartHandlers, type CartView, type NavigateHandler, type ReferenceToolsConfig, referenceTools };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/reference/tools.ts
|
|
2
|
+
function referenceTools(cfg) {
|
|
3
|
+
const { rag, cart, navigate } = cfg;
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
schema: {
|
|
7
|
+
name: "search_catalog",
|
|
8
|
+
description: "Full-text search the product catalog. Returns matching items with their id, title, price, availability (in stock or not), and a short snippet. Use it for products, prices, and stock/availability.",
|
|
9
|
+
parameters: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: { query: { type: "string" }, max_price: { type: "number" } },
|
|
12
|
+
required: ["query"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
async handler(args) {
|
|
16
|
+
const query = String(args.query ?? "");
|
|
17
|
+
const maxPrice = args.max_price != null ? Number(args.max_price) : void 0;
|
|
18
|
+
return { results: await rag.searchCatalog(query, { maxPrice }) };
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
schema: {
|
|
23
|
+
name: "search_knowledge",
|
|
24
|
+
description: "Full-text search the knowledge base: buying guides, how-to, care, and policies. Use it for how-to and policy questions - not for prices or stock (use the catalog for those).",
|
|
25
|
+
parameters: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: { query: { type: "string" } },
|
|
28
|
+
required: ["query"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
async handler(args) {
|
|
32
|
+
return { results: await rag.searchKnowledge(String(args.query ?? "")) };
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
schema: {
|
|
37
|
+
name: "add_to_cart",
|
|
38
|
+
description: "Add a catalog item to the cart by its id.",
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: { id: { type: "string" }, quantity: { type: "integer" } },
|
|
42
|
+
required: ["id"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
async handler(args) {
|
|
46
|
+
const id = String(args.id ?? "");
|
|
47
|
+
const quantity = Number(args.quantity ?? 1);
|
|
48
|
+
const item = rag.getItem ? await rag.getItem(id) : { id, title: id, price: 0, in_stock: true };
|
|
49
|
+
if (!item) return { error: "not_found", id };
|
|
50
|
+
if (!item.in_stock) return { error: "out_of_stock", id };
|
|
51
|
+
await cart.add({ id: item.id, title: item.title, price: item.price ?? 0 }, quantity);
|
|
52
|
+
return { ok: true, added: { id: item.id, title: item.title, quantity } };
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
schema: {
|
|
57
|
+
name: "remove_from_cart",
|
|
58
|
+
description: "Remove an item from the cart by its id.",
|
|
59
|
+
parameters: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: { id: { type: "string" } },
|
|
62
|
+
required: ["id"]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async handler(args) {
|
|
66
|
+
const id = String(args.id ?? "");
|
|
67
|
+
await cart.remove(id);
|
|
68
|
+
return { ok: true, removed: id };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
schema: {
|
|
73
|
+
name: "view_cart",
|
|
74
|
+
description: "Show the current cart contents.",
|
|
75
|
+
parameters: { type: "object", properties: {} }
|
|
76
|
+
},
|
|
77
|
+
async handler() {
|
|
78
|
+
return await cart.view();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
schema: {
|
|
83
|
+
name: "clear_cart",
|
|
84
|
+
description: "Empty the cart.",
|
|
85
|
+
parameters: { type: "object", properties: {} }
|
|
86
|
+
},
|
|
87
|
+
async handler() {
|
|
88
|
+
await cart.clear();
|
|
89
|
+
return { ok: true, cleared: true };
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
schema: {
|
|
94
|
+
name: "navigate",
|
|
95
|
+
description: "Navigate the storefront to a page: the cart, checkout, home, or a specific product page (pass its id when target is 'product').",
|
|
96
|
+
parameters: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
target: { type: "string", enum: ["checkout", "cart", "home", "product"] },
|
|
100
|
+
id: { type: "string" }
|
|
101
|
+
},
|
|
102
|
+
required: ["target"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async handler(args) {
|
|
106
|
+
const target = String(args.target ?? "").toLowerCase();
|
|
107
|
+
if (!["checkout", "cart", "home", "product"].includes(target)) {
|
|
108
|
+
return { error: "unknown_target", target };
|
|
109
|
+
}
|
|
110
|
+
await navigate(target, args.id != null ? String(args.id) : void 0);
|
|
111
|
+
return { ok: true, navigated: target };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { referenceTools };
|
|
118
|
+
//# sourceMappingURL=reference.js.map
|
|
119
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reference/tools.ts"],"names":[],"mappings":"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","file":"reference.js","sourcesContent":["import type { ToolDefinition } from '../tools/types'\nimport type { RagBackend } from '../rag/types'\n\n/** Cart operations the reference cart tools drive. Implement over your own state (store, signal, ...). */\nexport interface CartHandlers {\n add(item: { id: string; title: string; price: number }, quantity: number): void | Promise<void>\n remove(id: string): void | Promise<void>\n view(): CartView | Promise<CartView>\n clear(): void | Promise<void>\n}\nexport interface CartView {\n cart: { id: string; title: string; price: number; quantity: number }[]\n total: number\n}\n\n/** Move the storefront to a page. `target` is one of checkout|cart|home|product; `id` for product. */\nexport type NavigateHandler = (target: string, id?: string) => void | Promise<void>\n\nexport interface ReferenceToolsConfig {\n rag: RagBackend\n cart: CartHandlers\n navigate: NavigateHandler\n}\n\n/**\n * The 7 canonical tools the v1.0.0 model was trained on, wired to your handlers. Schemas (names,\n * descriptions, order) are byte-identical to training; results use the trained shapes. Retrieval is\n * RAG-as-a-tool; cart acts by `id`; navigate is read-only by construction.\n */\nexport function referenceTools(cfg: ReferenceToolsConfig): ToolDefinition[] {\n const { rag, cart, navigate } = cfg\n return [\n {\n schema: {\n name: 'search_catalog',\n description:\n 'Full-text search the product catalog. Returns matching items with their id, title, price, ' +\n 'availability (in stock or not), and a short snippet. Use it for products, prices, and ' +\n 'stock/availability.',\n parameters: {\n type: 'object',\n properties: { query: { type: 'string' }, max_price: { type: 'number' } },\n required: ['query'],\n },\n },\n async handler(args) {\n const query = String(args.query ?? '')\n const maxPrice = args.max_price != null ? Number(args.max_price) : undefined\n return { results: await rag.searchCatalog(query, { maxPrice }) }\n },\n },\n {\n schema: {\n name: 'search_knowledge',\n description:\n 'Full-text search the knowledge base: buying guides, how-to, care, and policies. Use it for ' +\n 'how-to and policy questions - not for prices or stock (use the catalog for those).',\n parameters: {\n type: 'object',\n properties: { query: { type: 'string' } },\n required: ['query'],\n },\n },\n async handler(args) {\n return { results: await rag.searchKnowledge(String(args.query ?? '')) }\n },\n },\n {\n schema: {\n name: 'add_to_cart',\n description: 'Add a catalog item to the cart by its id.',\n parameters: {\n type: 'object',\n properties: { id: { type: 'string' }, quantity: { type: 'integer' } },\n required: ['id'],\n },\n },\n async handler(args) {\n const id = String(args.id ?? '')\n const quantity = Number(args.quantity ?? 1)\n const item = rag.getItem ? await rag.getItem(id) : { id, title: id, price: 0, in_stock: true }\n if (!item) return { error: 'not_found', id }\n if (!item.in_stock) return { error: 'out_of_stock', id }\n await cart.add({ id: item.id, title: item.title, price: item.price ?? 0 }, quantity)\n return { ok: true, added: { id: item.id, title: item.title, quantity } }\n },\n },\n {\n schema: {\n name: 'remove_from_cart',\n description: 'Remove an item from the cart by its id.',\n parameters: {\n type: 'object',\n properties: { id: { type: 'string' } },\n required: ['id'],\n },\n },\n async handler(args) {\n const id = String(args.id ?? '')\n await cart.remove(id)\n return { ok: true, removed: id }\n },\n },\n {\n schema: {\n name: 'view_cart',\n description: 'Show the current cart contents.',\n parameters: { type: 'object', properties: {} },\n },\n async handler() {\n return await cart.view()\n },\n },\n {\n schema: {\n name: 'clear_cart',\n description: 'Empty the cart.',\n parameters: { type: 'object', properties: {} },\n },\n async handler() {\n await cart.clear()\n return { ok: true, cleared: true }\n },\n },\n {\n schema: {\n name: 'navigate',\n description:\n 'Navigate the storefront to a page: the cart, checkout, home, or a specific product page ' +\n \"(pass its id when target is 'product').\",\n parameters: {\n type: 'object',\n properties: {\n target: { type: 'string', enum: ['checkout', 'cart', 'home', 'product'] },\n id: { type: 'string' },\n },\n required: ['target'],\n },\n },\n async handler(args) {\n const target = String(args.target ?? '').toLowerCase()\n if (!['checkout', 'cart', 'home', 'product'].includes(target)) {\n return { error: 'unknown_target', target }\n }\n await navigate(target, args.id != null ? String(args.id) : undefined)\n return { ok: true, navigated: target }\n },\n },\n ]\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Frozen model-facing result shapes (what the model was trained to read from `search_*` tools). */
|
|
2
|
+
interface CatalogResult {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
snippet: string;
|
|
6
|
+
price: number | null;
|
|
7
|
+
in_stock: boolean;
|
|
8
|
+
attrs: Record<string, unknown>;
|
|
9
|
+
score: number;
|
|
10
|
+
}
|
|
11
|
+
interface KnowledgeResult {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
snippet: string;
|
|
15
|
+
score: number;
|
|
16
|
+
}
|
|
17
|
+
/** Minimal catalog item used to resolve a cart action (add_to_cart). */
|
|
18
|
+
interface CatalogItemLite {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
price: number | null;
|
|
22
|
+
in_stock: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A retrieval backend behind the `search_*` tools. The model is retriever-agnostic - any
|
|
26
|
+
* implementation works as long as it returns the frozen result shapes above.
|
|
27
|
+
*/
|
|
28
|
+
interface RagBackend {
|
|
29
|
+
searchCatalog(query: string, opts?: {
|
|
30
|
+
maxPrice?: number;
|
|
31
|
+
k?: number;
|
|
32
|
+
}): Promise<CatalogResult[]>;
|
|
33
|
+
searchKnowledge(query: string, k?: number): Promise<KnowledgeResult[]>;
|
|
34
|
+
/** A bounded `Title [id]; ...` hint for the system prompt (optional). */
|
|
35
|
+
hint?(n?: number): Promise<string> | string;
|
|
36
|
+
/** Resolve a catalog item by id, for cart actions (optional). */
|
|
37
|
+
getItem?(id: string): Promise<CatalogItemLite | null> | CatalogItemLite | null;
|
|
38
|
+
}
|
|
39
|
+
/** Raw index rows for the in-browser MiniSearch backend. */
|
|
40
|
+
interface CatalogItem {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
group?: string;
|
|
44
|
+
price: number | null;
|
|
45
|
+
in_stock: boolean;
|
|
46
|
+
summary: string;
|
|
47
|
+
text?: string;
|
|
48
|
+
}
|
|
49
|
+
interface KnowledgeItem {
|
|
50
|
+
id: string;
|
|
51
|
+
title: string;
|
|
52
|
+
text: string;
|
|
53
|
+
}
|
|
54
|
+
interface RagIndex {
|
|
55
|
+
catalog: CatalogItem[];
|
|
56
|
+
knowledge: KnowledgeItem[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type { CatalogItemLite as C, KnowledgeResult as K, RagBackend as R, RagIndex as a, CatalogResult as b, CatalogItem as c, KnowledgeItem as d };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface ToolSchema {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
parameters: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
type ToolHandler = (args: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
11
|
+
interface ToolDefinition {
|
|
12
|
+
schema: ToolSchema;
|
|
13
|
+
handler: ToolHandler;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type { ToolSchema as T, ToolHandler as a, ToolDefinition as b };
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/lajosbencz/frontend-agent.git",
|
|
11
|
+
"directory": "packages/frontend-agent"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/lajosbencz/frontend-agent",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/lajosbencz/frontend-agent/issues"
|
|
16
|
+
},
|
|
17
|
+
"author": "Lajos Bencz",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"lfm2",
|
|
20
|
+
"llm",
|
|
21
|
+
"agent",
|
|
22
|
+
"tool-calling",
|
|
23
|
+
"rag",
|
|
24
|
+
"wllama",
|
|
25
|
+
"edge",
|
|
26
|
+
"on-device",
|
|
27
|
+
"browser"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./rag": {
|
|
38
|
+
"types": "./dist/rag.d.ts",
|
|
39
|
+
"import": "./dist/rag.js"
|
|
40
|
+
},
|
|
41
|
+
"./reference": {
|
|
42
|
+
"types": "./dist/reference.d.ts",
|
|
43
|
+
"import": "./dist/reference.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@wllama/wllama": ">=3.5.0",
|
|
53
|
+
"minisearch": ">=7.0.0",
|
|
54
|
+
"stemmer": ">=2.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"@wllama/wllama": { "optional": true },
|
|
58
|
+
"minisearch": { "optional": true },
|
|
59
|
+
"stemmer": { "optional": true }
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@wllama/wllama": "^3.5.1",
|
|
63
|
+
"minisearch": "^7.2.0",
|
|
64
|
+
"stemmer": "^2.0.1",
|
|
65
|
+
"tsup": "^8.5.1",
|
|
66
|
+
"typescript": "^7.0.2",
|
|
67
|
+
"vitest": "^4.1.10"
|
|
68
|
+
}
|
|
69
|
+
}
|