@tanstack-ai-browser/prompt-api 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -2
  3. package/dist/adapter.d.ts +160 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +414 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +7 -0
  9. package/dist/limit.d.ts +119 -0
  10. package/dist/limit.d.ts.map +1 -0
  11. package/dist/limit.js +203 -0
  12. package/dist/messages.d.ts +20 -0
  13. package/dist/messages.d.ts.map +1 -0
  14. package/dist/messages.js +95 -0
  15. package/dist/model-meta.d.ts +21 -0
  16. package/dist/model-meta.d.ts.map +1 -0
  17. package/dist/model-meta.js +18 -0
  18. package/dist/model.d.ts +28 -0
  19. package/dist/model.d.ts.map +1 -0
  20. package/dist/model.js +53 -0
  21. package/dist/reasoning.d.ts +42 -0
  22. package/dist/reasoning.d.ts.map +1 -0
  23. package/dist/reasoning.js +113 -0
  24. package/dist/sessions.d.ts +48 -0
  25. package/dist/sessions.d.ts.map +1 -0
  26. package/dist/sessions.js +275 -0
  27. package/dist/shared-load.d.ts +48 -0
  28. package/dist/shared-load.d.ts.map +1 -0
  29. package/dist/shared-load.js +103 -0
  30. package/dist/store.d.ts +8 -0
  31. package/dist/store.d.ts.map +1 -0
  32. package/dist/store.js +17 -0
  33. package/dist/tools.d.ts +50 -0
  34. package/dist/tools.d.ts.map +1 -0
  35. package/dist/tools.js +314 -0
  36. package/dist/types.d.ts +81 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +13 -0
  39. package/package.json +60 -4
  40. package/src/adapter.test.ts +571 -0
  41. package/src/adapter.ts +522 -0
  42. package/src/index.ts +13 -0
  43. package/src/messages.ts +102 -0
  44. package/src/model-meta.ts +35 -0
  45. package/src/model.ts +75 -0
  46. package/src/tools.test.ts +129 -0
  47. package/src/tools.ts +311 -0
  48. package/src/types.ts +110 -0
package/dist/tools.js ADDED
@@ -0,0 +1,314 @@
1
+ const DELIMITERS = [
2
+ { start: "```tool_call", end: "```" },
3
+ { start: "```tool-call", end: "```" },
4
+ { start: "<tool_call>", end: "</tool_call>" },
5
+ { start: "<|tool_call>", end: "<tool_call|>" },
6
+ { start: "<|tool_call_start|>", end: "<|tool_call_end|>" }
7
+ ];
8
+ /** A Python-style call, `[name(...)]`. */
9
+ const PYTHON_CALL = /\[(\w+)\(/;
10
+ const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
11
+ /**
12
+ * The system prompt with the tools and how to call them; unchanged without
13
+ * tools.
14
+ */
15
+ export function toolSystemPrompt(system, tools) {
16
+ if (tools.length === 0)
17
+ return system;
18
+ const definitions = tools.map((tool) => ({
19
+ name: tool.name,
20
+ description: tool.description ?? "No description provided.",
21
+ parameters: tool.inputSchema ?? { type: "object", properties: {} }
22
+ }));
23
+ const instructions = `You are a helpful AI assistant with access to tools.
24
+
25
+ # Available Tools
26
+ ${JSON.stringify(definitions, null, 2)}
27
+
28
+ # Tool Calling Instructions
29
+ Only request one tool call at a time. Wait for tool results before asking for another tool.
30
+
31
+ To call a tool, output JSON in this exact format inside a \`\`\`tool_call code fence:
32
+
33
+ \`\`\`tool_call
34
+ {"name": "tool_name", "arguments": {"param1": "value1", "param2": "value2"}}
35
+ \`\`\`
36
+
37
+ Tool responses will be provided in \`\`\`tool_result fences. Each line contains JSON like:
38
+ \`\`\`tool_result
39
+ {"id": "call_123", "name": "tool_name", "result": {...}, "error": false}
40
+ \`\`\`
41
+ Use the \`result\` payload (and treat \`error\` as a boolean flag) when continuing the conversation.
42
+
43
+ Important:
44
+ - Use exact tool and parameter names from the schema above
45
+ - Arguments must be a valid JSON object matching the tool's parameters
46
+ - You can include brief reasoning before or after the tool call
47
+ - If no tool is needed, respond directly without tool_call fences`;
48
+ return system.trim() ? `${system.trim()}\n\n${instructions}` : instructions;
49
+ }
50
+ /** Earlier tool calls in the conversation, as the model would have written them. */
51
+ export function toolCallFence(calls) {
52
+ const lines = calls.map((call) => {
53
+ let args;
54
+ try {
55
+ args = JSON.parse(call.function.arguments || "{}");
56
+ }
57
+ catch {
58
+ args = call.function.arguments;
59
+ }
60
+ return JSON.stringify({ name: call.function.name, arguments: args, ...(call.id && { id: call.id }) });
61
+ });
62
+ return `\`\`\`tool_call\n${lines.join("\n")}\n\`\`\``;
63
+ }
64
+ /** A tool's result line for a ```tool_result fence. */
65
+ export function toolResultLine(id, name, content, error) {
66
+ let result = error ?? content;
67
+ if (error === undefined) {
68
+ try {
69
+ result = JSON.parse(content);
70
+ }
71
+ catch {
72
+ // Plain text.
73
+ }
74
+ }
75
+ return JSON.stringify({ name, result: result ?? null, error: error !== undefined, ...(id && { id }) });
76
+ }
77
+ /** Tool results as the model reads them. */
78
+ export const toolResultFence = (lines) => `\`\`\`tool_result\n${lines.join("\n")}\n\`\`\``;
79
+ /**
80
+ * The first tool call in a fence's body: a JSON object (or an array of them,
81
+ * or one per line) with `name` and `arguments` (or `parameters`), the
82
+ * arguments possibly a JSON string; else, as the whole body, a Python-style
83
+ * call, `[name(key="v")]`, or `call:name{key:value}`.
84
+ */
85
+ export function parseToolCall(body) {
86
+ const text = body.trim();
87
+ const candidates = [];
88
+ try {
89
+ const value = JSON.parse(text);
90
+ candidates.push(...(Array.isArray(value) ? value : [value]));
91
+ }
92
+ catch {
93
+ for (const line of text.split("\n")) {
94
+ try {
95
+ candidates.push(JSON.parse(line));
96
+ }
97
+ catch {
98
+ // Not JSON.
99
+ }
100
+ }
101
+ }
102
+ for (const call of candidates) {
103
+ if (!isObject(call) || typeof call.name !== "string" || !call.name)
104
+ continue;
105
+ let args = call.arguments ?? call.parameters ?? {};
106
+ if (typeof args === "string") {
107
+ try {
108
+ args = JSON.parse(args);
109
+ }
110
+ catch {
111
+ // Left as the model wrote it.
112
+ }
113
+ }
114
+ return { name: call.name, arguments: args };
115
+ }
116
+ // `call:name{key:value,…}`
117
+ const colon = /^call:(\w+)\{([^}]*)\}$/.exec(text);
118
+ if (colon)
119
+ return { name: colon[1], arguments: parseColonArguments(colon[2]) };
120
+ // Python style, `[name(key="value")]` or `name(key="value")`
121
+ return parsePythonCall(text);
122
+ }
123
+ /** `key:value,…` pairs, values read as numbers, booleans or null where they are. */
124
+ function parseColonArguments(text) {
125
+ const args = {};
126
+ for (const pair of text.split(",")) {
127
+ const at = pair.indexOf(":");
128
+ if (at <= 0)
129
+ continue;
130
+ const key = pair.slice(0, at).trim();
131
+ const raw = pair.slice(at + 1).trim();
132
+ const number = Number(raw);
133
+ args[key] =
134
+ raw === "true" ? true : raw === "false" ? false : raw === "null" ? null : raw !== "" && !Number.isNaN(number) ? number : raw;
135
+ }
136
+ return args;
137
+ }
138
+ /** The index of the bracket that closes the one at `open`, past quoted text, or -1. */
139
+ function closing(text, open) {
140
+ const pairs = { "(": ")", "[": "]", "{": "}" };
141
+ const stack = [];
142
+ let quote = "";
143
+ for (let i = open; i < text.length; i++) {
144
+ const c = text[i];
145
+ if (quote) {
146
+ if (c === "\\")
147
+ i++;
148
+ else if (c === quote)
149
+ quote = "";
150
+ }
151
+ else if (c === '"' || c === "'")
152
+ quote = c;
153
+ else if (pairs[c])
154
+ stack.push(pairs[c]);
155
+ else if (c === stack.at(-1)) {
156
+ stack.pop();
157
+ if (stack.length === 0)
158
+ return i;
159
+ }
160
+ }
161
+ return -1;
162
+ }
163
+ /** `text` split at its top-level commas (not inside quotes or brackets). */
164
+ function splitTopLevel(text) {
165
+ const parts = [];
166
+ let depth = 0;
167
+ let quote = "";
168
+ let from = 0;
169
+ for (let i = 0; i < text.length; i++) {
170
+ const c = text[i];
171
+ if (quote) {
172
+ if (c === "\\")
173
+ i++;
174
+ else if (c === quote)
175
+ quote = "";
176
+ }
177
+ else if (c === '"' || c === "'")
178
+ quote = c;
179
+ else if ("([{".includes(c))
180
+ depth++;
181
+ else if (")]}".includes(c))
182
+ depth--;
183
+ else if (c === "," && depth === 0) {
184
+ parts.push(text.slice(from, i));
185
+ from = i + 1;
186
+ }
187
+ }
188
+ parts.push(text.slice(from));
189
+ return parts.map((part) => part.trim()).filter(Boolean);
190
+ }
191
+ /** A Python literal (string, number, True/False/None, list, dict) as a value. */
192
+ function pythonValue(raw) {
193
+ const text = raw.trim();
194
+ if (text === "True")
195
+ return true;
196
+ if (text === "False")
197
+ return false;
198
+ if (text === "None")
199
+ return null;
200
+ if (/^(["']).*\1$/s.test(text))
201
+ return text.slice(1, -1).replace(/\\(["'\\])/g, "$1");
202
+ const json = text
203
+ .replace(/'((?:[^'\\]|\\.)*)'/g, (_, inner) => JSON.stringify(inner.replace(/\\'/g, "'")))
204
+ .replace(/\bTrue\b/g, "true")
205
+ .replace(/\bFalse\b/g, "false")
206
+ .replace(/\bNone\b/g, "null");
207
+ try {
208
+ return JSON.parse(json);
209
+ }
210
+ catch {
211
+ return text;
212
+ }
213
+ }
214
+ /** `text` as one Python-style call: `name(key=value, …)`, maybe in brackets. */
215
+ function parsePythonCall(text) {
216
+ const match = /^(\[?\s*)(\w+)\(/.exec(text);
217
+ if (!match)
218
+ return undefined;
219
+ const open = match[0].length - 1;
220
+ const close = closing(text, open);
221
+ if (close === -1 || text.slice(close + 1).trim() !== (match[1] ? "]" : ""))
222
+ return undefined;
223
+ const args = {};
224
+ for (const part of splitTopLevel(text.slice(open + 1, close))) {
225
+ const at = part.indexOf("=");
226
+ if (at <= 0)
227
+ continue;
228
+ args[part.slice(0, at).trim()] = pythonValue(part.slice(at + 1));
229
+ }
230
+ return { name: match[2], arguments: args };
231
+ }
232
+ /** Characters at the end of `text` that could start a fence. */
233
+ function partialFenceStart(text) {
234
+ for (let size = Math.min(text.length, Math.max(...DELIMITERS.map((d) => d.start.length)) - 1); size > 0; size--) {
235
+ const end = text.slice(-size);
236
+ if (DELIMITERS.some((d) => d.start.startsWith(end)))
237
+ return size;
238
+ }
239
+ return 0;
240
+ }
241
+ /**
242
+ * Reads a streamed reply: `push` each piece and get back the text that can
243
+ * be shown now; text inside a ```tool_call fence (or another call's
244
+ * delimiters: `<tool_call>`, `<|tool_call>`, `<|tool_call_start|>`, or a
245
+ * Python-style `[name(…)]`) is held back. The first fence that closes with
246
+ * a call in it ends the reply (stop generating then): `call` is the call,
247
+ * and `trailing` the text after it. A fence without a call, a call to a tool
248
+ * not in `names` (when given), and other code fences, are text. `end` gives
249
+ * the rest, an unclosed fence as text.
250
+ */
251
+ export function toolCallReader(names) {
252
+ let buffer = "";
253
+ // The delimiter of the call being read; a Python-style call is read whole.
254
+ let start;
255
+ let call;
256
+ // Text after the call, in the piece that closed it.
257
+ let trailing = "";
258
+ const read = () => {
259
+ let shown = "";
260
+ while (!call) {
261
+ if (!start) {
262
+ const python = PYTHON_CALL.exec(buffer);
263
+ const found = [
264
+ ...DELIMITERS.map((d) => ({ d, at: buffer.indexOf(d.start) })),
265
+ ...(python ? [{ d: { start: python[0], end: ")]", python: true }, at: python.index }] : [])
266
+ ]
267
+ .filter((f) => f.at !== -1)
268
+ .sort((a, b) => a.at - b.at)[0];
269
+ if (!found) {
270
+ const keep = partialFenceStart(buffer);
271
+ shown += buffer.slice(0, buffer.length - keep);
272
+ buffer = buffer.slice(buffer.length - keep);
273
+ return shown;
274
+ }
275
+ shown += buffer.slice(0, found.at);
276
+ buffer = buffer.slice(found.at);
277
+ start = found.d;
278
+ }
279
+ const close = buffer.indexOf(start.end, start.start.length);
280
+ if (close === -1)
281
+ return shown;
282
+ const fence = buffer.slice(0, close + start.end.length);
283
+ const parsed = parseToolCall(start.python ? fence : fence.slice(start.start.length, close));
284
+ call = parsed && (!names || names.includes(parsed.name)) ? parsed : undefined;
285
+ buffer = buffer.slice(fence.length);
286
+ start = undefined;
287
+ if (call)
288
+ trailing = buffer;
289
+ else
290
+ shown += fence;
291
+ }
292
+ return shown;
293
+ };
294
+ return {
295
+ get call() {
296
+ return call;
297
+ },
298
+ /** Text that came after the call: shown after it. */
299
+ get trailing() {
300
+ return trailing;
301
+ },
302
+ push(piece) {
303
+ buffer += piece;
304
+ return read();
305
+ },
306
+ end() {
307
+ if (call)
308
+ return "";
309
+ const rest = buffer;
310
+ buffer = "";
311
+ return rest;
312
+ }
313
+ };
314
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The parts of the Prompt API (https://github.com/webmachinelearning/prompt-api)
3
+ * this package uses. TypeScript's DOM library has no types for it yet.
4
+ * Shapes follow the spec and `@types/dom-chromium-ai`, kept here instead of
5
+ * depending on that package: it declares `LanguageModel` as a global (in
6
+ * every app that uses this package). Tools are left out: Chrome's tool use
7
+ * doesn't work on web pages, so the adapter calls tools by prompt
8
+ * (`tools.ts`). Names follow the spec: `contextUsage`,
9
+ * `contextWindow` and `contextoverflow` (web pages no longer have the old
10
+ * `inputUsage`, `inputQuota` and `quotaoverflow`).
11
+ */
12
+ export type Availability = "unavailable" | "downloadable" | "downloading" | "available";
13
+ /** A download's progress, from 0 to 1. */
14
+ export type DownloadProgressCallback = (progress: number) => void;
15
+ /** Sampling presets, for web pages (the raw `temperature` and `topK` are for extensions). */
16
+ export type SamplingMode = "most-predictable" | "predictable" | "slightly-predictable" | "balanced" | "slightly-creative" | "creative" | "most-creative";
17
+ export type LanguageModelMessageType = "text" | "image" | "audio";
18
+ export type LanguageModelExpected = {
19
+ type: LanguageModelMessageType;
20
+ languages?: Array<string>;
21
+ };
22
+ /**
23
+ * A piece of a message: text, an image (`Blob`, `ImageBitmap`, canvas...)
24
+ * or audio (`Blob`, `AudioBuffer`...).
25
+ */
26
+ export type LanguageModelContent = {
27
+ type: LanguageModelMessageType;
28
+ value: unknown;
29
+ };
30
+ export type LanguageModelMessage = {
31
+ role: "system" | "user" | "assistant";
32
+ content: string | Array<LanguageModelContent>;
33
+ };
34
+ /** Options `availability()` takes, which `create()` takes too. */
35
+ export type LanguageModelCoreOptions = {
36
+ expectedInputs?: Array<LanguageModelExpected>;
37
+ expectedOutputs?: Array<LanguageModelExpected>;
38
+ /** Web pages get it only with Chrome's Sampling Parameters origin trial. */
39
+ samplingMode?: SamplingMode;
40
+ /** Extensions only; web pages ignore them. Chrome refuses them together with `samplingMode`. */
41
+ temperature?: number;
42
+ topK?: number;
43
+ };
44
+ export type LanguageModelCreateOptions = LanguageModelCoreOptions & {
45
+ initialPrompts?: Array<LanguageModelMessage>;
46
+ /** Aborting it destroys the session, during `create()` or after. */
47
+ signal?: AbortSignal;
48
+ monitor?: (monitor: EventTarget) => void;
49
+ };
50
+ export type LanguageModelPromptOptions = {
51
+ signal?: AbortSignal;
52
+ /** A JSON Schema (or RegExp) the reply must match. */
53
+ responseConstraint?: object;
54
+ omitResponseConstraintInput?: boolean;
55
+ };
56
+ export type LanguageModelInput = string | Array<LanguageModelMessage>;
57
+ export interface LanguageModelSession extends EventTarget {
58
+ /** Tokens the session holds. */
59
+ readonly contextUsage: number;
60
+ /** Tokens it can hold. */
61
+ readonly contextWindow: number;
62
+ /** The preset the session samples with; null when made with raw parameters. */
63
+ readonly samplingMode?: SamplingMode | null;
64
+ prompt(input: LanguageModelInput, options?: LanguageModelPromptOptions): Promise<string>;
65
+ /** The reply's text, in pieces as it's written. */
66
+ promptStreaming(input: LanguageModelInput, options?: LanguageModelPromptOptions): ReadableStream<string> & AsyncIterable<string>;
67
+ measureContextUsage(input: LanguageModelInput, options?: LanguageModelPromptOptions): Promise<number>;
68
+ destroy(): void;
69
+ }
70
+ export type LanguageModelStatic = {
71
+ availability(options?: LanguageModelCoreOptions): Promise<Availability>;
72
+ create(options?: LanguageModelCreateOptions): Promise<LanguageModelSession>;
73
+ };
74
+ /** The `downloadprogress` event: `loaded` goes from 0 to 1. */
75
+ export type DownloadProgressEvent = Event & {
76
+ loaded: number;
77
+ total?: number;
78
+ };
79
+ /** The page's `LanguageModel`, or undefined where the browser has none. */
80
+ export declare const languageModel: () => LanguageModelStatic | undefined;
81
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The parts of the Prompt API (https://github.com/webmachinelearning/prompt-api)
3
+ * this package uses. TypeScript's DOM library has no types for it yet.
4
+ * Shapes follow the spec and `@types/dom-chromium-ai`, kept here instead of
5
+ * depending on that package: it declares `LanguageModel` as a global (in
6
+ * every app that uses this package). Tools are left out: Chrome's tool use
7
+ * doesn't work on web pages, so the adapter calls tools by prompt
8
+ * (`tools.ts`). Names follow the spec: `contextUsage`,
9
+ * `contextWindow` and `contextoverflow` (web pages no longer have the old
10
+ * `inputUsage`, `inputQuota` and `quotaoverflow`).
11
+ */
12
+ /** The page's `LanguageModel`, or undefined where the browser has none. */
13
+ export const languageModel = () => globalThis.LanguageModel;
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@tanstack-ai-browser/prompt-api",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "TanStack AI adapter for the browser's built-in language model (the Prompt API: Gemini Nano in Chrome, Phi-4-mini in Edge)",
5
+ "license": "MIT",
6
+ "author": "daveycodez",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/daveycodez/tanstack-ai-browser.git",
10
+ "directory": "packages/prompt-api"
11
+ },
12
+ "homepage": "https://github.com/daveycodez/tanstack-ai-browser/tree/main/packages/prompt-api#readme",
13
+ "bugs": "https://github.com/daveycodez/tanstack-ai-browser/issues",
14
+ "keywords": [
15
+ "tanstack-ai",
16
+ "tanstack",
17
+ "ai",
18
+ "llm",
19
+ "prompt-api",
20
+ "gemini-nano",
21
+ "phi-4-mini",
22
+ "built-in-ai",
23
+ "browser",
24
+ "on-device",
25
+ "local-ai"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.js",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ "./package.json": "./package.json",
34
+ ".": {
35
+ "@tanstack-ai-browser/source": "./src/index.ts",
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js",
38
+ "default": "./dist/index.js"
39
+ }
40
+ },
41
+ "files": [
42
+ "src",
43
+ "dist",
44
+ "!**/*.tsbuildinfo"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "nx": {
50
+ "name": "prompt-api"
51
+ },
52
+ "scripts": {
53
+ "test": "node --import @swc-node/register/esm-register --test \"src/**/*.test.ts\""
54
+ },
55
+ "peerDependencies": {
56
+ "@tanstack/ai": "^1.0.0"
57
+ },
58
+ "devDependencies": {
59
+ "@tanstack/ai": "1.4.0",
60
+ "esbuild": "0.28.2"
61
+ }
62
+ }