@tanstack-ai-browser/prompt-api 0.0.0-stage → 0.2.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.
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 +413 -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 +59 -3
  40. package/src/adapter.test.ts +571 -0
  41. package/src/adapter.ts +521 -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EACV,0BAA0B,EAC1B,oBAAoB,EACrB,MAAM,YAAY,CAAA;AAwBnB,wCAAwC;AACxC,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE,oBAAoB,CAAA;IAC7B,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAA;IACpB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,MAAM,EAAE,OAAO,CAAA;IACf,gFAAgF;IAChF,UAAU,EAAE,MAAM,OAAO,CAAA;IACzB,+EAA+E;IAC/E,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,KAAK,IAAI,CAAA;IAChD,iFAAiF;IACjF,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAED;;;;GAIG;AACH,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;IAC3B,OAAO,EAAE,0BAA0B,CAAA;IACnC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;IAC/B,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IAClC,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAChD,CAAA;AA6BD,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,CA8D1E;AA4KD;;;;GAIG;AACH,wBAAgB,wBAAwB,SAQvC"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Sessions by conversation. A Prompt API session holds the conversation
3
+ * it has read (the model's cache), so each thread keeps its own: when the
4
+ * next request extends what the session has read (a follow-up, or the step
5
+ * after a tool call), only the new messages are read. Anything else (an
6
+ * edited or regenerated message, another system prompt) starts a new
7
+ * session from the whole conversation.
8
+ */
9
+ import { createLanguageModel } from "./model.js";
10
+ import { keyOf, toLanguageModelMessages } from "./messages.js";
11
+ /** Sessions kept at once, most recently used first; older ones are freed. */
12
+ const MAX_SESSIONS = 4;
13
+ const entries = new Map();
14
+ const preparing = new Map();
15
+ /** Counts unloads: a session created across one isn't cached. */
16
+ let generation = 0;
17
+ const startsWith = (list, start) => start.length <= list.length && start.every((key, i) => list[i] === key);
18
+ /** Serialize base preparation only; independent clones generate concurrently. */
19
+ async function lockPreparation(key, signal) {
20
+ const previous = preparing.get(key) ?? Promise.resolve();
21
+ let unlock = () => undefined;
22
+ const done = new Promise(resolve => { unlock = resolve; });
23
+ const queued = previous.then(() => done);
24
+ preparing.set(key, queued);
25
+ void queued.then(() => { if (preparing.get(key) === queued)
26
+ preparing.delete(key); });
27
+ try {
28
+ await new Promise((resolve, reject) => {
29
+ const cancel = () => reject(signal?.reason);
30
+ signal?.addEventListener("abort", cancel, { once: true });
31
+ void previous.then(() => {
32
+ signal?.removeEventListener("abort", cancel);
33
+ if (signal?.aborted)
34
+ reject(signal.reason);
35
+ else
36
+ resolve();
37
+ });
38
+ });
39
+ return unlock;
40
+ }
41
+ catch (error) {
42
+ // An aborted waiter releases its place without letting later callers pass
43
+ // the earlier preparation that is still in flight.
44
+ unlock();
45
+ throw error;
46
+ }
47
+ }
48
+ export async function leaseSession(options) {
49
+ options.signal?.throwIfAborted();
50
+ const { threadId, isolated, system, prior } = options;
51
+ const setup = JSON.stringify({ system, options: options.options });
52
+ // Separate namespaces prevent structured finalization from replacing a chat/tool
53
+ // session. Stateless calls can share an identical base without inventing a thread.
54
+ const cacheKey = options.retain === false ? undefined : isolated
55
+ ? JSON.stringify(["structured", threadId ?? null, threadId ? null : setup])
56
+ : threadId ? JSON.stringify(["chat", threadId]) : undefined;
57
+ const unlock = isolated && cacheKey !== undefined ? await lockPreparation(cacheKey, options.signal) : undefined;
58
+ try {
59
+ let recreated = false;
60
+ for (;;) {
61
+ const base = await leasePooledSession({ ...options, cacheKey });
62
+ // A non-retained session is already isolated: generate directly on it and
63
+ // destroy it when the call ends, without preparing an extra clone.
64
+ if (!isolated || options.retain === false)
65
+ return base;
66
+ const started = performance.now();
67
+ let copy;
68
+ try {
69
+ copy = await base.session.clone({ signal: options.signal });
70
+ options.signal?.throwIfAborted();
71
+ let overflowed = false;
72
+ const onOverflow = () => { overflowed = true; };
73
+ copy.addEventListener("contextoverflow", onOverflow);
74
+ const baseOverflowed = base.overflowed();
75
+ // Only preparation touches the base. Release it before generation so concurrent
76
+ // callers can clone it too; generated JSON and draft notes never enter it.
77
+ base.release(prior);
78
+ const session = copy;
79
+ let destroyed = false;
80
+ const destroy = () => {
81
+ if (destroyed)
82
+ return;
83
+ destroyed = true;
84
+ session.removeEventListener("contextoverflow", onOverflow);
85
+ session.destroy();
86
+ };
87
+ return {
88
+ ...base,
89
+ session,
90
+ createMs: base.createMs + performance.now() - started,
91
+ overflowed: () => baseOverflowed || overflowed,
92
+ release: destroy,
93
+ discard: destroy
94
+ };
95
+ }
96
+ catch (error) {
97
+ copy?.destroy();
98
+ // Aborting a clone must not destroy its healthy cached parent.
99
+ if (options.signal?.aborted)
100
+ base.release(prior);
101
+ else
102
+ base.discard();
103
+ // A stale browser session can fail at clone() before any generation starts.
104
+ // Recreate that prefix once, just as the streaming path does.
105
+ if (!options.signal?.aborted && base.reused && !recreated && error instanceof DOMException && error.name === "InvalidStateError") {
106
+ recreated = true;
107
+ continue;
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+ }
113
+ finally {
114
+ unlock?.();
115
+ }
116
+ }
117
+ async function leasePooledSession({ cacheKey, system, prior, options, signal, media, onDownloadProgress }) {
118
+ const setup = JSON.stringify({ system, options });
119
+ const keys = prior.map(keyOf);
120
+ const entry = cacheKey ? entries.get(cacheKey) : undefined;
121
+ let miss;
122
+ if (entry) {
123
+ miss = entry.busy
124
+ ? "another call of this thread holds its session"
125
+ : entry.setup !== setup
126
+ ? "the system prompt, tools or options changed"
127
+ : startsWith(keys, entry.history)
128
+ ? undefined
129
+ : "the conversation changed (an edited, regenerated or rewritten message)";
130
+ }
131
+ if (entry && !miss) {
132
+ entry.busy = true;
133
+ // Most recently used last.
134
+ entries.delete(cacheKey);
135
+ entries.set(cacheKey, entry);
136
+ const cachedTokens = entry.session.contextUsage;
137
+ const started = performance.now();
138
+ const gap = prior.slice(entry.history.length);
139
+ entry.overflowed = false;
140
+ try {
141
+ if (gap.length > 0) {
142
+ await entry.session.append(await toLanguageModelMessages(gap, media), {
143
+ signal
144
+ });
145
+ }
146
+ entry.history = keys;
147
+ return {
148
+ ...lease(cacheKey, entry, cachedTokens, performance.now() - started),
149
+ reused: true
150
+ };
151
+ }
152
+ catch (error) {
153
+ drop(cacheKey, entry);
154
+ if (signal?.aborted)
155
+ throw error;
156
+ // The session couldn't take them (it was destroyed, say): start over.
157
+ miss = `the kept session failed (${error instanceof Error ? error.message : String(error)})`;
158
+ }
159
+ }
160
+ // Another run of this thread holds its session (it shouldn't happen):
161
+ // this one gets a session of its own, freed when it ends.
162
+ const shared = !entry?.busy;
163
+ if (entry && shared && cacheKey)
164
+ drop(cacheKey, entry);
165
+ const startedIn = generation;
166
+ const started = performance.now();
167
+ const created = {
168
+ session: await createSession(system, prior, options, signal, media, onDownloadProgress),
169
+ setup,
170
+ history: keys,
171
+ busy: true,
172
+ overflowed: false
173
+ };
174
+ created.session.addEventListener("contextoverflow", () => {
175
+ created.overflowed = true;
176
+ });
177
+ // Unloaded while it was created: the call uses it, then it's freed.
178
+ const kept = cacheKey !== undefined && shared && startedIn === generation;
179
+ if (kept) {
180
+ entries.set(cacheKey, created);
181
+ trimIdleSessions();
182
+ }
183
+ return {
184
+ ...lease(kept ? cacheKey : undefined, created, 0, performance.now() - started),
185
+ reused: false,
186
+ ...(miss && { miss })
187
+ };
188
+ }
189
+ function lease(threadId, entry, cachedTokens, createMs) {
190
+ return {
191
+ session: entry.session,
192
+ cachedTokens,
193
+ createMs,
194
+ overflowed: () => entry.overflowed,
195
+ release: (history) => {
196
+ if (!threadId || entries.get(threadId) !== entry)
197
+ return entry.session.destroy();
198
+ entry.history = history.map(keyOf);
199
+ entry.busy = false;
200
+ trimIdleSessions();
201
+ },
202
+ discard: () => {
203
+ if (threadId)
204
+ drop(threadId, entry);
205
+ else
206
+ entry.session.destroy();
207
+ }
208
+ };
209
+ }
210
+ /** Busy entries can exceed the bound temporarily; evict oldest idle entries on return. */
211
+ function trimIdleSessions() {
212
+ for (const [id, entry] of entries) {
213
+ if (entries.size <= MAX_SESSIONS)
214
+ break;
215
+ if (!entry.busy)
216
+ drop(id, entry);
217
+ }
218
+ }
219
+ function drop(threadId, entry) {
220
+ if (entries.get(threadId) === entry)
221
+ entries.delete(threadId);
222
+ entry.session.destroy();
223
+ }
224
+ /** The conversation without its oldest turn: up to the next user message. */
225
+ function withoutOldestTurn(messages) {
226
+ const next = messages.findIndex((m, i) => i > 0 && m.role === "user");
227
+ return next > 0 ? messages.slice(next) : [];
228
+ }
229
+ /**
230
+ * A new session that has read the system prompt and the conversation. Only
231
+ * the system prompt is an initial prompt, which the browser never drops;
232
+ * the conversation is appended, so when it later outgrows the context the
233
+ * browser drops its oldest turns. If it doesn't fit now, its oldest turns
234
+ * are left out (measured first, not tried) until it does.
235
+ */
236
+ async function createSession(system, prior, options, signal, media, onDownloadProgress) {
237
+ const session = await createLanguageModel({
238
+ ...options,
239
+ ...(system && { initialPrompts: [{ role: "system", content: system }] })
240
+ }, signal, onDownloadProgress);
241
+ try {
242
+ let rest = prior;
243
+ while (rest.length > 0) {
244
+ const input = await toLanguageModelMessages(rest, media);
245
+ const room = session.contextWindow - session.contextUsage;
246
+ const needed = await session.measureContextUsage(input, { signal });
247
+ if (needed <= room) {
248
+ await session.append(input, { signal });
249
+ break;
250
+ }
251
+ rest = withoutOldestTurn(rest);
252
+ }
253
+ return session;
254
+ }
255
+ catch (error) {
256
+ session.destroy();
257
+ throw error;
258
+ }
259
+ }
260
+ /**
261
+ * Frees every session kept, as when the page no longer needs the model.
262
+ * Sessions a call holds, or is still creating, finish that call and are
263
+ * freed when it ends instead of going back in the cache.
264
+ */
265
+ export function destroyPromptApiSessions() {
266
+ generation++;
267
+ for (const [id, entry] of [...entries]) {
268
+ // A busy entry is only detached: its lease sees it's no longer the
269
+ // thread's and destroys the session on release.
270
+ if (entry.busy)
271
+ entries.delete(id);
272
+ else
273
+ drop(id, entry);
274
+ }
275
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The callers waiting on one shared model load: their progress callbacks
3
+ * and how many still wait. A caller whose signal aborts stops waiting at
4
+ * once and hears nothing more; when the last caller still waiting aborts,
5
+ * the load is cancelled. (Kept in step with the same file in the other
6
+ * runtime packages.)
7
+ */
8
+ /** A download's progress, from 0 to 1. */
9
+ type DownloadProgressCallback = (progress: number) => void;
10
+ /** What an aborted `create()` rejects with. */
11
+ export declare function abortError(): DOMException;
12
+ /** Calls a progress callback; one that throws doesn't stop the load. */
13
+ export declare function callProgress(listener: DownloadProgressCallback, progress: number): void;
14
+ export type WaitOptions = {
15
+ onDownloadProgress?: DownloadProgressCallback;
16
+ signal?: AbortSignal;
17
+ /**
18
+ * Whether this caller aborting last cancels the load (true by default).
19
+ * A chat's own wait passes false: it stops waiting, and the download goes
20
+ * on for the next chat.
21
+ */
22
+ cancels?: boolean;
23
+ /** What this caller rejects with when it aborts: an AbortError by default. */
24
+ reason?: (signal: AbortSignal) => unknown;
25
+ };
26
+ export declare class LoadWaiters {
27
+ private readonly cancel;
28
+ /** Each callback, with how many waiting callers use it: called once per report. */
29
+ private readonly callbacks;
30
+ private waiting;
31
+ private settled;
32
+ /** `cancel` stops the load: called when the last caller still waiting aborts. */
33
+ constructor(cancel: () => void);
34
+ /** Reports progress to the callers still waiting. */
35
+ report(progress: number): void;
36
+ /** The load settled (ready, failed or replaced): nothing more to report or cancel. */
37
+ settle(): void;
38
+ /**
39
+ * `load`'s result for one more caller. Aborting `signal` rejects this
40
+ * caller at once and stops its reports; if it was the last caller waiting
41
+ * (and `cancels`), the load is cancelled. The promise is fine to leave
42
+ * unawaited. Check the signal before starting the load: an aborted one
43
+ * rejects here without waiting.
44
+ */
45
+ wait<T>(load: Promise<T>, options?: WaitOptions): Promise<T>;
46
+ }
47
+ export {};
48
+ //# sourceMappingURL=shared-load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-load.d.ts","sourceRoot":"","sources":["../src/shared-load.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,KAAK,wBAAwB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;AAE1D,+CAA+C;AAC/C,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,QAMhF;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kBAAkB,CAAC,EAAE,wBAAwB,CAAA;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAA;CAC1C,CAAA;AAED,qBAAa,WAAW;IAOV,OAAO,CAAC,QAAQ,CAAC,MAAM;IANnC,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8C;IACxE,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,OAAO,CAAQ;IAEvB,iFAAiF;gBACpD,MAAM,EAAE,MAAM,IAAI;IAE/C,qDAAqD;IACrD,MAAM,CAAC,QAAQ,EAAE,MAAM;IAIvB,sFAAsF;IACtF,MAAM;IAMN;;;;;;OAMG;IACH,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;CA8CjE"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The callers waiting on one shared model load: their progress callbacks
3
+ * and how many still wait. A caller whose signal aborts stops waiting at
4
+ * once and hears nothing more; when the last caller still waiting aborts,
5
+ * the load is cancelled. (Kept in step with the same file in the other
6
+ * runtime packages.)
7
+ */
8
+ /** What an aborted `create()` rejects with. */
9
+ export function abortError() {
10
+ return new DOMException("The model load was aborted.", "AbortError");
11
+ }
12
+ /** Calls a progress callback; one that throws doesn't stop the load. */
13
+ export function callProgress(listener, progress) {
14
+ try {
15
+ listener(progress);
16
+ }
17
+ catch (error) {
18
+ console.error(error);
19
+ }
20
+ }
21
+ export class LoadWaiters {
22
+ cancel;
23
+ /** Each callback, with how many waiting callers use it: called once per report. */
24
+ callbacks = new Map();
25
+ waiting = 0;
26
+ settled = false;
27
+ /** `cancel` stops the load: called when the last caller still waiting aborts. */
28
+ constructor(cancel) {
29
+ this.cancel = cancel;
30
+ }
31
+ /** Reports progress to the callers still waiting. */
32
+ report(progress) {
33
+ for (const listener of [...this.callbacks.keys()])
34
+ callProgress(listener, progress);
35
+ }
36
+ /** The load settled (ready, failed or replaced): nothing more to report or cancel. */
37
+ settle() {
38
+ this.settled = true;
39
+ this.callbacks.clear();
40
+ this.waiting = 0;
41
+ }
42
+ /**
43
+ * `load`'s result for one more caller. Aborting `signal` rejects this
44
+ * caller at once and stops its reports; if it was the last caller waiting
45
+ * (and `cancels`), the load is cancelled. The promise is fine to leave
46
+ * unawaited. Check the signal before starting the load: an aborted one
47
+ * rejects here without waiting.
48
+ */
49
+ wait(load, options = {}) {
50
+ const { onDownloadProgress, signal, cancels = true, reason = abortError } = options;
51
+ if (signal?.aborted)
52
+ return quiet(Promise.reject(reason(signal)));
53
+ if (this.settled)
54
+ return quiet(load);
55
+ this.waiting++;
56
+ if (onDownloadProgress) {
57
+ this.callbacks.set(onDownloadProgress, (this.callbacks.get(onDownloadProgress) ?? 0) + 1);
58
+ }
59
+ let left = false;
60
+ const leave = () => {
61
+ if (left)
62
+ return;
63
+ left = true;
64
+ if (this.settled)
65
+ return;
66
+ this.waiting--;
67
+ if (!onDownloadProgress)
68
+ return;
69
+ const count = (this.callbacks.get(onDownloadProgress) ?? 0) - 1;
70
+ if (count > 0)
71
+ this.callbacks.set(onDownloadProgress, count);
72
+ else
73
+ this.callbacks.delete(onDownloadProgress);
74
+ };
75
+ if (!signal) {
76
+ load.then(leave, leave);
77
+ return quiet(load);
78
+ }
79
+ return quiet(new Promise((resolve, reject) => {
80
+ const onAbort = () => {
81
+ leave();
82
+ reject(reason(signal));
83
+ if (cancels && !this.settled && this.waiting === 0)
84
+ this.cancel();
85
+ };
86
+ signal.addEventListener("abort", onAbort, { once: true });
87
+ load.then((value) => {
88
+ signal.removeEventListener("abort", onAbort);
89
+ leave();
90
+ resolve(value);
91
+ }, (error) => {
92
+ signal.removeEventListener("abort", onAbort);
93
+ leave();
94
+ reject(error);
95
+ });
96
+ }));
97
+ }
98
+ }
99
+ /** `promise`, with a rejection that's fine to leave unhandled. */
100
+ function quiet(promise) {
101
+ promise.catch(() => undefined);
102
+ return promise;
103
+ }
@@ -0,0 +1,8 @@
1
+ /** A value that can be read, set and watched. Framework-free. */
2
+ export type Store<T> = {
3
+ get: () => T;
4
+ set: (value: T) => void;
5
+ subscribe: (listener: () => void) => () => void;
6
+ };
7
+ export declare function createStore<T>(initial: T): Store<T>;
8
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI;IACrB,GAAG,EAAE,MAAM,CAAC,CAAA;IACZ,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAA;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAgBnD"}
package/dist/store.js ADDED
@@ -0,0 +1,17 @@
1
+ export function createStore(initial) {
2
+ let value = initial;
3
+ const listeners = new Set();
4
+ return {
5
+ get: () => value,
6
+ set: (next) => {
7
+ value = next;
8
+ listeners.forEach((listener) => listener());
9
+ },
10
+ subscribe: (listener) => {
11
+ listeners.add(listener);
12
+ return () => {
13
+ listeners.delete(listener);
14
+ };
15
+ }
16
+ };
17
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tool calls by prompt. Chrome's Prompt API has no
3
+ * working tool use on web pages (a session that expects tool calls fails to
4
+ * start, and `tools` alone are never called), so the system prompt lists the
5
+ * tools as JSON and asks for a call as JSON in a ```tool_call fence; results
6
+ * go back in a ```tool_result fence. The adapter reads calls out of the
7
+ * streamed text; `chat()` checks them against the tools.
8
+ */
9
+ import type { ModelMessage, Tool } from "@tanstack/ai";
10
+ /** A tool call read from the model's text. */
11
+ export type ParsedToolCall = {
12
+ name: string;
13
+ arguments: unknown;
14
+ };
15
+ /**
16
+ * The system prompt with the tools and how to call them; unchanged without
17
+ * tools.
18
+ */
19
+ export declare function toolSystemPrompt(system: string, tools: ReadonlyArray<Tool>): string;
20
+ /** Earlier tool calls in the conversation, as the model would have written them. */
21
+ export declare function toolCallFence(calls: NonNullable<ModelMessage["toolCalls"]>): string;
22
+ /** A tool's result line for a ```tool_result fence. */
23
+ export declare function toolResultLine(id: string, name: string, content: string, error?: string): string;
24
+ /** Tool results as the model reads them. */
25
+ export declare const toolResultFence: (lines: ReadonlyArray<string>) => string;
26
+ /**
27
+ * The first tool call in a fence's body: a JSON object (or an array of them,
28
+ * or one per line) with `name` and `arguments` (or `parameters`), the
29
+ * arguments possibly a JSON string; else, as the whole body, a Python-style
30
+ * call, `[name(key="v")]`, or `call:name{key:value}`.
31
+ */
32
+ export declare function parseToolCall(body: string): ParsedToolCall | undefined;
33
+ /**
34
+ * Reads a streamed reply: `push` each piece and get back the text that can
35
+ * be shown now; text inside a ```tool_call fence (or another call's
36
+ * delimiters: `<tool_call>`, `<|tool_call>`, `<|tool_call_start|>`, or a
37
+ * Python-style `[name(…)]`) is held back. The first fence that closes with
38
+ * a call in it ends the reply (stop generating then): `call` is the call,
39
+ * and `trailing` the text after it. A fence without a call, a call to a tool
40
+ * not in `names` (when given), and other code fences, are text. `end` gives
41
+ * the rest, an unclosed fence as text.
42
+ */
43
+ export declare function toolCallReader(names?: ReadonlyArray<string>): {
44
+ readonly call: ParsedToolCall | undefined;
45
+ /** Text that came after the call: shown after it. */
46
+ readonly trailing: string;
47
+ push(piece: string): string;
48
+ end(): string;
49
+ };
50
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEtD,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAA;AAiBjE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAiCnF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,GAAG,MAAM,CAWnF;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAUhG;AAED,4CAA4C;AAC5C,eAAO,MAAM,eAAe,UAAW,aAAa,CAAC,MAAM,CAAC,WAAqD,CAAA;AAEjH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAgCtE;AAwGD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC;aA2CpD,IAAI;IAGR,qDAAqD;aACjD,QAAQ;IAGZ,IAAI,QAAQ,MAAM,GAAG,MAAM;IAI3B,GAAG,IAAI,MAAM;EAOhB"}