@tanstack-ai-browser/webgllm 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.
- package/LICENSE +21 -0
- package/README.md +121 -2
- package/dist/adapter.d.ts +150 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +173 -0
- package/dist/catalog.d.ts +25 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +7 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/model-memory.d.ts +46 -0
- package/dist/model-memory.d.ts.map +1 -0
- package/dist/model-memory.js +79 -0
- package/dist/model-meta.d.ts +7 -0
- package/dist/model-meta.d.ts.map +1 -0
- package/dist/model-meta.js +5 -0
- package/dist/models.d.ts +93 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +274 -0
- package/package.json +65 -4
- package/src/adapter.test.ts +100 -0
- package/src/adapter.ts +291 -0
- package/src/catalog.ts +35 -0
- package/src/fake-webgllm.test.ts +236 -0
- package/src/index.ts +11 -0
- package/src/unavailable.test.ts +10 -0
- package/src/wrapper.test.ts +146 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 daveycodez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tanstack-ai-browser/webgllm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [TanStack AI](https://tanstack.com/ai) adapter for [webgllm](https://webgllm.dev): LFM3 1.7B, LFM2.5, Liquid's vision models (LFM3-VL and LFM2.5-VL) and Qwen3.5 2B running in the browser on WebGL 2, in a Web Worker on an OffscreenCanvas. It runs with or without WebGPU; where there's no WebGPU (Firefox on Android, older iPhones) it still reads prompts on the GPU.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @tanstack-ai-browser/webgllm @tanstack/ai
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { chat } from "@tanstack/ai"
|
|
11
|
+
import { webgllmText } from "@tanstack-ai-browser/webgllm"
|
|
12
|
+
|
|
13
|
+
const stream = chat({
|
|
14
|
+
adapter: webgllmText("LiquidAI/LFM3-1.7B-GGUF"),
|
|
15
|
+
messages,
|
|
16
|
+
threadId
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It needs WebGL 2 with float render targets (`EXT_color_buffer_float`) and OffscreenCanvas in workers, which current Chrome, Firefox and Safari have; an adapter's `availability()` says `"unavailable"` where they're missing.
|
|
21
|
+
|
|
22
|
+
The package is shaped like `@tanstack/ai-openai`'s text adapter. The root has `webgllmText(model, config?)`, `WebGllmTextAdapter` (with the runtime methods `availability`, `create` and `destroy`) and the types `WebGllmConfig`, `WebGllmModel`, `WebGllmTextModelOptions`, `Availability` and `DownloadProgressCallback`, and the catalog (below).
|
|
23
|
+
|
|
24
|
+
The engine is the [`webgllm`](https://www.npmjs.com/package/webgllm) package, behind OpenAI's Chat Completions API in the page. The adapter is TanStack's Chat Completions adapter (`@tanstack/openai-base`) with the shared `WebGLLM` instance as its client, so messages, tools, structured output and the stream are TanStack's; it adds loading the model, webgllm's reasoning levels and thinking budget, and its options. Tool-call parsing and repair, thinking, guided tool choice and schema-constrained answers are webgllm's own. See [webgllm.dev](https://webgllm.dev) and its README for how the engine works, its memory and speed.
|
|
25
|
+
|
|
26
|
+
## The adapter
|
|
27
|
+
|
|
28
|
+
`webgllmText(model, config?)` takes:
|
|
29
|
+
|
|
30
|
+
- `contextLength`: most tokens a chat may hold, prompt and reply together; the model's whole context by default (LFM3: 262,144), never more. Smaller takes less memory. Changing it loads the model again.
|
|
31
|
+
- `worker`: a Web Worker of your own to run the model in (see A worker of your own).
|
|
32
|
+
|
|
33
|
+
There are no callbacks of its own: thinking, text, tool calls and structured output all come back as TanStack's standard stream events, so an app can swap this adapter for TanStack's OpenAI or OpenRouter adapter without other changes.
|
|
34
|
+
|
|
35
|
+
What maps to what:
|
|
36
|
+
|
|
37
|
+
| TanStack AI | webgllm request |
|
|
38
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
39
|
+
| `systemPrompts`, `messages` (tool calls, results, thinking) | `messages` (a failed tool's error as its result) |
|
|
40
|
+
| `tools` (their `inputSchema`) | `tools` (function `parameters`) |
|
|
41
|
+
| `toolChoice` (`auto`, `none`, `required`, a tool) | `tool_choice`; with `none` webgllm offers the model no tools |
|
|
42
|
+
| `outputSchema` | `response_format: { type: "json_schema" }` |
|
|
43
|
+
| `reasoning` level / `budgetTokens` | `reasoning_effort` (`none` or `high`) / `reasoning.max_tokens` |
|
|
44
|
+
| `promptCache: "none"` | `cache_prompt: false` |
|
|
45
|
+
| `modelOptions` | see below |
|
|
46
|
+
|
|
47
|
+
`modelOptions` are webgllm's own request fields, sent as given, as TanStack's adapters do with their provider's fields: OpenAI's names (`max_completion_tokens`, thinking included; `temperature`, `top_p`, `presence_penalty`, `stop`, `seed`, `parallel_tool_calls`, false keeping only the first tool call) and webgllm's own (`top_k`, `min_p`, `repetition_penalty`, and `repair_tool_calls`, false skipping its tool-call repair). `frequency_penalty` isn't supported: anything but 0 throws webgllm's `unsupported-parameter` error. Their type is webgllm's `ChatCompletionCreateParamsBase`, without the fields the adapter writes from `chat()`'s own options (messages, tools, the output schema, streaming, reasoning).
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
chat({ adapter: webgllmText("LiquidAI/LFM3-1.7B-GGUF"), messages, modelOptions: { max_completion_tokens: 1024, temperature: 0.2 } })
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Unset sampling fields take the model card's values (webgllm's), and there's no output cap unless you set one. Events come back as TanStack's Chat Completions adapter writes them: reasoning, text and tool call events, and `RUN_FINISHED` with the finish reason and token usage. Timings aren't in it: measure them from the stream, as for any adapter. A model that fails to load ends the run with a `RUN_ERROR` whose `code` is webgllm's (`download-failed`, `storage-full`, `webgl2-unavailable`); other failures are TanStack's `RUN_ERROR`. Aborting the request stops the generation and ends the stream without one.
|
|
54
|
+
|
|
55
|
+
## A worker of your own
|
|
56
|
+
|
|
57
|
+
webgllm starts a worker for each model it loads. To run the model in one you start (bundled your way, or shared with your own code), write the worker with webgllm's handler and give it to the adapter:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// worker.ts
|
|
61
|
+
import { WebGLLMWorkerHandler } from "@tanstack-ai-browser/webgllm"
|
|
62
|
+
|
|
63
|
+
const handler = new WebGLLMWorkerHandler()
|
|
64
|
+
self.onmessage = (event) => handler.onmessage(event)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module" })
|
|
69
|
+
const adapter = webgllmText("LiquidAI/LFM3-1.7B-GGUF", { worker })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`WebGLLMWorkerHandler` is webgllm's own, exported here too. The worker holds one model: adapters given the same worker share it, and loading another model replaces the one there once its running reply ends. `destroy()` frees the model's GPU memory and leaves the worker running. If the worker fails (its script doesn't load, or it crashes), the call waiting on it rejects with webgllm's `worker-failed` error (webgllm 0.3.1 and later). Without a worker, webgllm keeps several models loaded at once, a worker each, up to its memory budget.
|
|
73
|
+
|
|
74
|
+
## Thinking and structured output
|
|
75
|
+
|
|
76
|
+
`chat({ outputSchema })` sends the schema as given in `response_format: json_schema`, as an OpenAI call would; webgllm constrains the answer to the schema while it's written. With `stream: true` it streams as TanStack's standard events, the raw JSON as `TEXT_MESSAGE_CONTENT` deltas and a `CUSTOM` `structured-output.complete` event with the object; `stream: false` resolves to the validated object.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const stream = chat({
|
|
80
|
+
adapter: webgllmText("LiquidAI/LFM3-1.7B-GGUF"),
|
|
81
|
+
messages,
|
|
82
|
+
outputSchema: MoveSchema, // a Standard Schema (Zod, …) or JSON Schema
|
|
83
|
+
stream: true
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
for await (const event of stream) {
|
|
87
|
+
if (event.type === "REASONING_MESSAGE_CONTENT") showThinking(event.delta)
|
|
88
|
+
if (event.type === "TEXT_MESSAGE_CONTENT") showPartialJson(event.delta)
|
|
89
|
+
if (event.type === "CUSTOM" && event.name === "structured-output.complete") play(event.value.object)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A failure is a `RUN_ERROR`, as in a chat. Aborting stops the generation and ends the stream without one.
|
|
94
|
+
|
|
95
|
+
## Images
|
|
96
|
+
|
|
97
|
+
Models with a vision encoder read images: Qwen3.5 2B (its ONNX build since webgllm 0.2.3, its GGUF build with its projector since 0.3.3), and Liquid's vision models, LFM3-VL 2B and LFM2.5-VL 3B, 1.6B and 450M, each in Liquid's GGUF and ONNX builds (webgllm 0.3.3 and later). The adapter's `inputModalities` is `["text", "image"]` for them, from the catalog's `supports.input`; `undefined` for a model the catalog doesn't list. Send image parts in a user message, as data (base64) or URLs: they go to webgllm as OpenAI's `image_url`, with `detail` (`auto` by default) setting how many pixels it reads. webgllm loads the vision encoder only for a request with an image and frees it after, so text chats cost nothing more. An image to a model without one throws webgllm's `unsupported-parameter` error.
|
|
98
|
+
|
|
99
|
+
## Models and loading
|
|
100
|
+
|
|
101
|
+
The root has the catalog too, webgllm's own re-exported under the adapter's names: `WEBGLLM_CHAT_MODELS` (webgllm's `WEBGL_TEXT_MODELS`) lists the models webgllm runs and `WEBGLLM_MODEL_META` (its `WEBGL_MODEL_META`) has each one's files, capabilities and sampling. `WEBGLLM_MODEL_REASONING` has each model's reasoning levels in TanStack's shape, for `supportedReasoningLevels()`. Their types are `WebGllmChatModel`, `WebGllmModelMeta` and `WebGllmModelReasoningByName`. LFM3 thinks by default and Qwen3.5 doesn't; `chat({ reasoning })` switches it.
|
|
102
|
+
|
|
103
|
+
`webgllmText` also takes any Hugging Face repo webgllm can resolve (a supported architecture, in ONNX or GGUF), or the URL of a model's `.gguf` file or `.onnx` graph with its tokenizer files in the same folder: webgllm reads its files and sampling from there. `chat({ reasoning })` then sends `reasoning_effort` `"none"` or `"high"`, which webgllm applies where the model's chat template can switch thinking.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { webgllmText } from "@tanstack-ai-browser/webgllm"
|
|
107
|
+
|
|
108
|
+
const adapter = webgllmText("LiquidAI/LFM3-1.7B-GGUF", { contextLength: 8192 })
|
|
109
|
+
await adapter.availability() // "unavailable", "downloadable", "downloading" or "available" (loaded)
|
|
110
|
+
await adapter.create({ onDownloadProgress: (progress) => console.log(progress) }) // ahead of time; else the first chat loads it
|
|
111
|
+
await adapter.destroy() // frees its GPU memory
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The methods have the Prompt API's values and meanings. `availability()` is `"unavailable"` where the engine can't run (checked once per page: webgllm's check makes a WebGL context), `"downloading"` while this page downloads the model (however the load started: `create` or an adapter's first chat), `"available"` when the model is loaded or every file it loads is in webgllm's storage (`webgllm.models.downloaded()`, which reads the Origin Private File System only), else `"downloadable"`. It makes no request (no `webgllm.models.retrieve()`, which asks Hugging Face for file sizes) and starts no worker. `create({ onDownloadProgress?, signal }?)` reports the download, 0 to 1, then 1 once the model is ready (at once if it already is); it resolves to the adapter, and rejects on failure (fine to leave unawaited). Aborting `signal` rejects that call at once with an `AbortError` and stops its progress reports; when no other caller (another call, or a chat waiting for the model) still waits on the load, webgllm cancels it and the model is left unloaded (`availability()` is back to `"downloadable"`). The config's `onDownloadProgress` hears a download the first chat starts too. Adapters for the same model share it, so `destroy()` on one unloads it for all of them.
|
|
115
|
+
|
|
116
|
+
## Memory
|
|
117
|
+
|
|
118
|
+
The page shares one `WebGLLM` instance, which keeps models loaded while they fit its memory budget and unloads the least recently used past it. See webgllm's README for its budget.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A TanStack AI text adapter for webgllm: LFM3, LFM2.5 and Qwen3.5 on
|
|
3
|
+
* WebGL 2 in a Web Worker, for browsers without WebGPU, reading images
|
|
4
|
+
* where the model has a vision encoder. webgllm answers
|
|
5
|
+
* OpenAI's Chat Completions API in the page, so this is TanStack's Chat
|
|
6
|
+
* Completions adapter with the page's `WebGLLM` instance as its client (or
|
|
7
|
+
* the one for the adapter's own `worker`):
|
|
8
|
+
* messages, tools, structured output and the stream (text, thinking, tool
|
|
9
|
+
* calls) are TanStack's. This file adds what's webgllm's own: loading the
|
|
10
|
+
* model before a call, and its reasoning levels and thinking budget.
|
|
11
|
+
*/
|
|
12
|
+
import type { AdapterYieldChunk, ModelReasoning, TextOptions, TokenUsage } from "@tanstack/ai";
|
|
13
|
+
import type { StructuredOutputOptions, StructuredOutputResult } from "@tanstack/ai/adapters";
|
|
14
|
+
import { OpenAIBaseChatCompletionsTextAdapter } from "@tanstack/openai-base";
|
|
15
|
+
import type { OpenAIClientLike } from "@tanstack/openai-base";
|
|
16
|
+
import { WebGLLM } from "webgllm";
|
|
17
|
+
import type { ChatCompletionCreateParamsBase } from "webgllm";
|
|
18
|
+
import type { WebGllmChatModel, WebGllmModelReasoningByName } from "./catalog.js";
|
|
19
|
+
/**
|
|
20
|
+
* Where a model stands in this browser, with the Prompt API's values
|
|
21
|
+
* (`LanguageModel.availability()`).
|
|
22
|
+
*/
|
|
23
|
+
export type Availability = "unavailable" | "downloadable" | "downloading" | "available";
|
|
24
|
+
/** A download's progress, from 0 to 1. */
|
|
25
|
+
export type DownloadProgressCallback = (progress: number) => void;
|
|
26
|
+
/** The page's `WebGLLM`. Exported for tests. */
|
|
27
|
+
export declare const webgllm: WebGLLM;
|
|
28
|
+
/**
|
|
29
|
+
* Per-call `modelOptions`: webgllm's own request fields (OpenAI's names, and
|
|
30
|
+
* its `top_k`, `min_p`, `repetition_penalty` and `repair_tool_calls`), sent
|
|
31
|
+
* as given. The fields the adapter writes from `chat()`'s own options
|
|
32
|
+
* (messages, tools, the output schema, streaming, reasoning) are left out.
|
|
33
|
+
*/
|
|
34
|
+
export type WebGllmTextModelOptions = Omit<ChatCompletionCreateParamsBase, "model" | "messages" | "stream" | "stream_options" | "tools" | "tool_choice" | "response_format" | "reasoning_effort" | "reasoning">;
|
|
35
|
+
/** Options for `webgllmText`. */
|
|
36
|
+
export type WebGllmConfig = {
|
|
37
|
+
/**
|
|
38
|
+
* Most tokens a chat may hold, prompt and reply together: the model's
|
|
39
|
+
* whole context length by default, never more. A smaller limit takes less
|
|
40
|
+
* memory and refuses longer chats sooner. Changing it loads the model again.
|
|
41
|
+
*/
|
|
42
|
+
contextLength?: number;
|
|
43
|
+
/**
|
|
44
|
+
* A Web Worker of your own to run the model in, in place of the ones
|
|
45
|
+
* webgllm starts (webgllm's `worker` option): a module whose `onmessage`
|
|
46
|
+
* goes to webgllm's `WebGLLMWorkerHandler` (exported here too). It holds
|
|
47
|
+
* one model, so adapters given the same worker share it and loading
|
|
48
|
+
* another model replaces the one there; `destroy()` frees the model and
|
|
49
|
+
* leaves the worker running.
|
|
50
|
+
*/
|
|
51
|
+
worker?: Worker;
|
|
52
|
+
/**
|
|
53
|
+
* The model's download progress, 0 to 1, when a call (`chat()`, say)
|
|
54
|
+
* starts its download; then 1 once it's ready. To load it ahead of time
|
|
55
|
+
* with progress, see `create`.
|
|
56
|
+
*/
|
|
57
|
+
onDownloadProgress?: DownloadProgressCallback;
|
|
58
|
+
};
|
|
59
|
+
/** A model's reasoning levels: a listed model's, none for a listed one that doesn't reason, else the switch. */
|
|
60
|
+
type ResolveReasoning<TModel extends string> = TModel extends keyof WebGllmModelReasoningByName ? WebGllmModelReasoningByName[TModel] : TModel extends WebGllmChatModel ? never : {
|
|
61
|
+
levels: "off" | "high";
|
|
62
|
+
budget: false;
|
|
63
|
+
};
|
|
64
|
+
/** A model the catalog lists, any Hugging Face repo webgllm can resolve, or the URL of a `.gguf` file or `.onnx` graph. */
|
|
65
|
+
export type WebGllmModel = WebGllmChatModel | (string & {});
|
|
66
|
+
type MetadataByModality = {
|
|
67
|
+
text: unknown;
|
|
68
|
+
image: unknown;
|
|
69
|
+
audio: unknown;
|
|
70
|
+
video: unknown;
|
|
71
|
+
document: unknown;
|
|
72
|
+
};
|
|
73
|
+
/** A Chat Completions request, as the base adapter writes it. */
|
|
74
|
+
type CreateParams = Parameters<OpenAIClientLike["chat"]["completions"]["create"]>[0];
|
|
75
|
+
export declare class WebGllmTextAdapter<TModel extends WebGllmModel> extends OpenAIBaseChatCompletionsTextAdapter<TModel, WebGllmTextModelOptions, ReadonlyArray<"text" | "image">, MetadataByModality, ReadonlyArray<string>, ResolveReasoning<TModel>> {
|
|
76
|
+
readonly name = "webgllm";
|
|
77
|
+
readonly api = "webgllm";
|
|
78
|
+
/**
|
|
79
|
+
* What the model reads, from webgllm's catalog: images too for a model
|
|
80
|
+
* with a vision encoder (Qwen3.5's ONNX build). Not known for a model the
|
|
81
|
+
* catalog doesn't list (webgllm's `models.retrieve()` says, once loaded).
|
|
82
|
+
*/
|
|
83
|
+
readonly inputModalities: ReadonlyArray<"text" | "image"> | undefined;
|
|
84
|
+
private readonly contextLength;
|
|
85
|
+
private readonly onDownloadProgress;
|
|
86
|
+
/** The `WebGLLM` the model loads in: the page's, or the adapter's own worker's. */
|
|
87
|
+
private readonly engine;
|
|
88
|
+
constructor(config: WebGllmConfig, model: TModel);
|
|
89
|
+
/**
|
|
90
|
+
* Where the model stands in this browser, with the Prompt API's values:
|
|
91
|
+
* "unavailable" where the engine can't run (WebGL 2 with float render
|
|
92
|
+
* targets, in a worker), "downloading" while this page downloads it,
|
|
93
|
+
* "available" when it's loaded or its files are in webgllm's storage,
|
|
94
|
+
* else "downloadable". It reads storage only.
|
|
95
|
+
*/
|
|
96
|
+
availability(): Promise<Availability>;
|
|
97
|
+
/**
|
|
98
|
+
* Loads the model for this adapter's `contextLength` (downloading it the
|
|
99
|
+
* first time), with the download's progress, 0 to 1, then 1 once it's
|
|
100
|
+
* ready. Resolves to this adapter; rejects if it can't load (fine to leave
|
|
101
|
+
* unawaited). `chat()` doesn't need it: its first call loads the model.
|
|
102
|
+
*/
|
|
103
|
+
create(options?: {
|
|
104
|
+
onDownloadProgress?: DownloadProgressCallback;
|
|
105
|
+
signal?: AbortSignal;
|
|
106
|
+
}): Promise<this>;
|
|
107
|
+
/** Unloads the model, freeing its GPU memory. Its files stay. Never rejects. */
|
|
108
|
+
destroy(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Loads the model for this adapter's context length; webgllm returns at
|
|
111
|
+
* once when it's loaded for it, and shares a load between its callers.
|
|
112
|
+
*/
|
|
113
|
+
private load;
|
|
114
|
+
chatStream(options: TextOptions<WebGllmTextModelOptions>): AsyncIterable<AdapterYieldChunk>;
|
|
115
|
+
structuredOutputStream(options: StructuredOutputOptions<WebGllmTextModelOptions>): AsyncIterable<AdapterYieldChunk>;
|
|
116
|
+
structuredOutput(options: StructuredOutputOptions<WebGllmTextModelOptions>): Promise<StructuredOutputResult<unknown>>;
|
|
117
|
+
/** The request, with `modelOptions` as given, the thinking budget and the prompt cache. */
|
|
118
|
+
/**
|
|
119
|
+
* The engine's usage, as TanStack converts it, but with a cached count of 0
|
|
120
|
+
* kept, which TanStack's conversion leaves out: 0 means the whole prompt was
|
|
121
|
+
* read, where no count means the engine didn't say.
|
|
122
|
+
*/
|
|
123
|
+
protected chatCompletionsTokenUsage(usage: {
|
|
124
|
+
prompt_tokens_details?: {
|
|
125
|
+
cached_tokens?: number;
|
|
126
|
+
} | null;
|
|
127
|
+
} | null | undefined, source?: unknown): TokenUsage | undefined;
|
|
128
|
+
protected mapOptionsToRequest(options: TextOptions): CreateParams & {
|
|
129
|
+
stream: true;
|
|
130
|
+
};
|
|
131
|
+
protected modelReasoning(model: string): ModelReasoning | undefined;
|
|
132
|
+
/** webgllm streams thinking as `reasoning_content`. */
|
|
133
|
+
protected extractReasoning(chunk: unknown): {
|
|
134
|
+
text: string;
|
|
135
|
+
} | undefined;
|
|
136
|
+
/** Schemas as given: webgllm constrains to any JSON Schema, without OpenAI's strict-mode rewrite. */
|
|
137
|
+
protected makeStructuredOutputCompatible(schema: Record<string, any>): Record<string, any>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A TanStack AI text adapter for a webgllm model on WebGL 2, for browsers
|
|
141
|
+
* without WebGPU (Firefox on Android, for one).
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* chat({ adapter: webgllmText("LiquidAI/LFM3-1.7B-GGUF"), messages, threadId })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export declare function webgllmText<TModel extends WebGllmModel>(model: TModel, config?: WebGllmConfig): WebGllmTextAdapter<TModel>;
|
|
149
|
+
export {};
|
|
150
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { resolveReasoning } from "@tanstack/ai/adapter-internals";
|
|
2
|
+
import { OpenAIBaseChatCompletionsTextAdapter } from "@tanstack/openai-base";
|
|
3
|
+
import { WebGLLM } from "webgllm";
|
|
4
|
+
import { modelMeta, WEBGLLM_MODEL_META } from "./catalog.js";
|
|
5
|
+
/** A `WebGLLM` (running in `worker`, if given), and the models downloading in it, from its events. */
|
|
6
|
+
function createEngine(worker) {
|
|
7
|
+
const webgllm = new WebGLLM(worker && { worker });
|
|
8
|
+
const downloading = new Set();
|
|
9
|
+
webgllm.addEventListener("progress", ({ model, phase, cached }) => {
|
|
10
|
+
if (phase === "download" && !cached)
|
|
11
|
+
downloading.add(model);
|
|
12
|
+
});
|
|
13
|
+
for (const type of ["load", "error", "unload"]) {
|
|
14
|
+
webgllm.addEventListener(type, ({ model }) => downloading.delete(model));
|
|
15
|
+
}
|
|
16
|
+
return { webgllm, downloading };
|
|
17
|
+
}
|
|
18
|
+
/** The page's engine, shared by every adapter without a worker of its own. */
|
|
19
|
+
const pageEngine = createEngine();
|
|
20
|
+
/** The page's `WebGLLM`. Exported for tests. */
|
|
21
|
+
export const webgllm = pageEngine.webgllm;
|
|
22
|
+
/** The engines in the callers' own workers: one a worker, as each worker's handler holds one model. */
|
|
23
|
+
const workerEngines = new WeakMap();
|
|
24
|
+
/** An adapter's engine: its own worker's (shared by every adapter given that worker), else the page's. */
|
|
25
|
+
function engineFor(worker) {
|
|
26
|
+
if (!worker)
|
|
27
|
+
return pageEngine;
|
|
28
|
+
let engine = workerEngines.get(worker);
|
|
29
|
+
if (!engine)
|
|
30
|
+
workerEngines.set(worker, (engine = createEngine(worker)));
|
|
31
|
+
return engine;
|
|
32
|
+
}
|
|
33
|
+
let supported;
|
|
34
|
+
/** Whether the engine runs here, checked once: webgllm's check makes a WebGL context. */
|
|
35
|
+
const engineRuns = () => (supported ??= WebGLLM.isSupported());
|
|
36
|
+
/** The download's share of webgllm's load progress (downloading is its first 90%). */
|
|
37
|
+
const downloadShare = ({ progress, loadedBytes, totalBytes }) => totalBytes ? Math.min((loadedBytes ?? 0) / totalBytes, 1) : Math.min(progress / 0.9, 1);
|
|
38
|
+
/** The switch for a model the catalog doesn't list: webgllm's `reasoning_effort`, a hint it ignores where it can't apply. */
|
|
39
|
+
const UNLISTED_REASONING = { levels: { off: "none", high: "high" }, budget: false };
|
|
40
|
+
const listed = (model) => Object.prototype.hasOwnProperty.call(WEBGLLM_MODEL_META, model);
|
|
41
|
+
const signalOf = (options) => options.abortController?.signal ?? options.request?.signal ?? undefined;
|
|
42
|
+
export class WebGllmTextAdapter extends OpenAIBaseChatCompletionsTextAdapter {
|
|
43
|
+
name = "webgllm";
|
|
44
|
+
api = "webgllm";
|
|
45
|
+
/**
|
|
46
|
+
* What the model reads, from webgllm's catalog: images too for a model
|
|
47
|
+
* with a vision encoder (Qwen3.5's ONNX build). Not known for a model the
|
|
48
|
+
* catalog doesn't list (webgllm's `models.retrieve()` says, once loaded).
|
|
49
|
+
*/
|
|
50
|
+
inputModalities;
|
|
51
|
+
contextLength;
|
|
52
|
+
onDownloadProgress;
|
|
53
|
+
/** The `WebGLLM` the model loads in: the page's, or the adapter's own worker's. */
|
|
54
|
+
engine;
|
|
55
|
+
constructor(config, model) {
|
|
56
|
+
const engine = engineFor(config.worker);
|
|
57
|
+
// Chat Completions (`chat.completions.create`) is all the adapter calls.
|
|
58
|
+
super(model, "webgllm", engine.webgllm, { strictFallbackWarning: false });
|
|
59
|
+
this.engine = engine;
|
|
60
|
+
this.contextLength = config.contextLength;
|
|
61
|
+
this.onDownloadProgress = config.onDownloadProgress;
|
|
62
|
+
this.inputModalities = listed(model) ? modelMeta(model).supports.input : undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Where the model stands in this browser, with the Prompt API's values:
|
|
66
|
+
* "unavailable" where the engine can't run (WebGL 2 with float render
|
|
67
|
+
* targets, in a worker), "downloading" while this page downloads it,
|
|
68
|
+
* "available" when it's loaded or its files are in webgllm's storage,
|
|
69
|
+
* else "downloadable". It reads storage only.
|
|
70
|
+
*/
|
|
71
|
+
async availability() {
|
|
72
|
+
const { webgllm, downloading } = this.engine;
|
|
73
|
+
if (!engineRuns())
|
|
74
|
+
return "unavailable";
|
|
75
|
+
if (downloading.has(this.model))
|
|
76
|
+
return "downloading";
|
|
77
|
+
const stored = await webgllm.models.downloaded(this.model).catch(() => false);
|
|
78
|
+
return stored ? "available" : "downloadable";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Loads the model for this adapter's `contextLength` (downloading it the
|
|
82
|
+
* first time), with the download's progress, 0 to 1, then 1 once it's
|
|
83
|
+
* ready. Resolves to this adapter; rejects if it can't load (fine to leave
|
|
84
|
+
* unawaited). `chat()` doesn't need it: its first call loads the model.
|
|
85
|
+
*/
|
|
86
|
+
create(options = {}) {
|
|
87
|
+
const ready = this.load(options.signal, options.onDownloadProgress).then(() => this);
|
|
88
|
+
ready.catch(() => undefined);
|
|
89
|
+
return ready;
|
|
90
|
+
}
|
|
91
|
+
/** Unloads the model, freeing its GPU memory. Its files stay. Never rejects. */
|
|
92
|
+
destroy() {
|
|
93
|
+
return this.engine.webgllm.unloadModel(this.model).catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Loads the model for this adapter's context length; webgllm returns at
|
|
97
|
+
* once when it's loaded for it, and shares a load between its callers.
|
|
98
|
+
*/
|
|
99
|
+
async load(signal, onDownloadProgress) {
|
|
100
|
+
const report = (progress) => {
|
|
101
|
+
this.onDownloadProgress?.(progress);
|
|
102
|
+
onDownloadProgress?.(progress);
|
|
103
|
+
};
|
|
104
|
+
await this.engine.webgllm.loadModel(this.model, {
|
|
105
|
+
...(this.contextLength !== undefined && { contextLength: this.contextLength }),
|
|
106
|
+
...(signal && { signal }),
|
|
107
|
+
onProgress: (progress) => {
|
|
108
|
+
if (progress.phase === "download" && !progress.cached)
|
|
109
|
+
report(downloadShare(progress));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
report(1);
|
|
113
|
+
}
|
|
114
|
+
async *chatStream(options) {
|
|
115
|
+
await this.load(signalOf(options));
|
|
116
|
+
yield* super.chatStream(options);
|
|
117
|
+
}
|
|
118
|
+
async *structuredOutputStream(options) {
|
|
119
|
+
await this.load(signalOf(options.chatOptions));
|
|
120
|
+
yield* super.structuredOutputStream(options);
|
|
121
|
+
}
|
|
122
|
+
async structuredOutput(options) {
|
|
123
|
+
await this.load(signalOf(options.chatOptions));
|
|
124
|
+
return super.structuredOutput(options);
|
|
125
|
+
}
|
|
126
|
+
/** The request, with `modelOptions` as given, the thinking budget and the prompt cache. */
|
|
127
|
+
/**
|
|
128
|
+
* The engine's usage, as TanStack converts it, but with a cached count of 0
|
|
129
|
+
* kept, which TanStack's conversion leaves out: 0 means the whole prompt was
|
|
130
|
+
* read, where no count means the engine didn't say.
|
|
131
|
+
*/
|
|
132
|
+
chatCompletionsTokenUsage(usage, source) {
|
|
133
|
+
const result = super.chatCompletionsTokenUsage(usage, source);
|
|
134
|
+
const cachedTokens = usage?.prompt_tokens_details?.cached_tokens;
|
|
135
|
+
if (!result || cachedTokens !== 0)
|
|
136
|
+
return result;
|
|
137
|
+
return { ...result, promptTokensDetails: { ...result.promptTokensDetails, cachedTokens } };
|
|
138
|
+
}
|
|
139
|
+
mapOptionsToRequest(options) {
|
|
140
|
+
const request = super.mapOptionsToRequest(options);
|
|
141
|
+
const resolved = resolveReasoning(options.reasoning, this.modelReasoning(this.model));
|
|
142
|
+
const budget = resolved?.value === "high" ? resolved.budgetTokens : undefined;
|
|
143
|
+
return Object.assign(request, {
|
|
144
|
+
...(budget !== undefined && { reasoning: { max_tokens: budget } }),
|
|
145
|
+
...(options.promptCache?.retention === "none" && { cache_prompt: false })
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
modelReasoning(model) {
|
|
149
|
+
return (listed(model) ? modelMeta(model).reasoning : UNLISTED_REASONING);
|
|
150
|
+
}
|
|
151
|
+
/** webgllm streams thinking as `reasoning_content`. */
|
|
152
|
+
extractReasoning(chunk) {
|
|
153
|
+
const text = chunk.choices?.[0]?.delta
|
|
154
|
+
?.reasoning_content;
|
|
155
|
+
return text ? { text } : undefined;
|
|
156
|
+
}
|
|
157
|
+
/** Schemas as given: webgllm constrains to any JSON Schema, without OpenAI's strict-mode rewrite. */
|
|
158
|
+
makeStructuredOutputCompatible(schema) {
|
|
159
|
+
return schema;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A TanStack AI text adapter for a webgllm model on WebGL 2, for browsers
|
|
164
|
+
* without WebGPU (Firefox on Android, for one).
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* chat({ adapter: webgllmText("LiquidAI/LFM3-1.7B-GGUF"), messages, threadId })
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export function webgllmText(model, config) {
|
|
172
|
+
return new WebGllmTextAdapter(config ?? {}, model);
|
|
173
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The models webgllm runs: webgllm's own catalog, re-exported under this
|
|
3
|
+
* adapter's names, with their reasoning levels in TanStack AI's shape (for
|
|
4
|
+
* `chat({ reasoning })` and `supportedReasoningLevels()`).
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelReasoning } from "@tanstack/ai";
|
|
7
|
+
import type { ModelReasoningCapability } from "@tanstack/ai/adapter-internals";
|
|
8
|
+
import type { WebGlModelMeta, WebGlModelReasoning, WebGlTextModel } from "webgllm";
|
|
9
|
+
export { WEBGL_TEXT_MODELS as WEBGLLM_CHAT_MODELS, WEBGL_MODEL_META as WEBGLLM_MODEL_META } from "webgllm";
|
|
10
|
+
export type { WebGlTextModel as WebGllmChatModel, WebGlModelMeta as WebGllmModelMeta } from "webgllm";
|
|
11
|
+
type WebGllmChatModel = WebGlTextModel;
|
|
12
|
+
/**
|
|
13
|
+
* The reasoning levels of each model that thinks, exactly as its catalog
|
|
14
|
+
* entry gives them: LFM3 and Qwen3.5 switch between `off` and `high`, the
|
|
15
|
+
* LFM2.5 Thinking builds always think (`high` only). Models that don't think
|
|
16
|
+
* aren't in it.
|
|
17
|
+
*/
|
|
18
|
+
export type WebGllmModelReasoningByName = {
|
|
19
|
+
[K in WebGllmChatModel as WebGlModelReasoning<K> extends undefined ? never : K]: ModelReasoningCapability<NonNullable<WebGlModelReasoning<K>>>;
|
|
20
|
+
};
|
|
21
|
+
/** Reasoning data of each model (`false`: it doesn't think), for `supportedReasoningLevels()`. */
|
|
22
|
+
export declare const WEBGLLM_MODEL_REASONING: Readonly<Record<WebGllmChatModel, ModelReasoning>>;
|
|
23
|
+
/** A model's catalog entry. */
|
|
24
|
+
export declare const modelMeta: (model: WebGllmChatModel) => WebGlModelMeta;
|
|
25
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAGlF,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,gBAAgB,IAAI,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAC1G,YAAY,EAAE,cAAc,IAAI,gBAAgB,EAAE,cAAc,IAAI,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAErG,KAAK,gBAAgB,GAAG,cAAc,CAAA;AAEtC;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;KACvC,CAAC,IAAI,gBAAgB,IAAI,mBAAmB,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG,KAAK,GAAG,CAAC,GAAG,wBAAwB,CACvG,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CACpC;CACF,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,uBAAuB,EAE/B,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAA;AAEvD,+BAA+B;AAC/B,eAAO,MAAM,SAAS,UAAW,gBAAgB,KAAG,cAAyC,CAAA"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { WEBGL_MODEL_META, WEBGL_TEXT_MODELS } from "webgllm";
|
|
2
|
+
// webgllm's catalog as it is: the models it runs, and each one's entry by name.
|
|
3
|
+
export { WEBGL_TEXT_MODELS as WEBGLLM_CHAT_MODELS, WEBGL_MODEL_META as WEBGLLM_MODEL_META } from "webgllm";
|
|
4
|
+
/** Reasoning data of each model (`false`: it doesn't think), for `supportedReasoningLevels()`. */
|
|
5
|
+
export const WEBGLLM_MODEL_REASONING = Object.fromEntries(WEBGL_TEXT_MODELS.map((model) => [model, WEBGL_MODEL_META[model].reasoning ?? false]));
|
|
6
|
+
/** A model's catalog entry. */
|
|
7
|
+
export const modelMeta = (model) => WEBGL_MODEL_META[model];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack AI adapter for webgllm: LFM3, LFM2.5 and Qwen3.5 in the browser
|
|
3
|
+
* on WebGL 2, for browsers without WebGPU (Firefox on Android, for one).
|
|
4
|
+
* The model catalog (model-meta.ts) is exported here too.
|
|
5
|
+
*/
|
|
6
|
+
export { webgllmText, WebGllmTextAdapter } from "./adapter.js";
|
|
7
|
+
export type { Availability, DownloadProgressCallback, WebGllmConfig, WebGllmModel, WebGllmTextModelOptions } from "./adapter.js";
|
|
8
|
+
export { WebGLLMWorkerHandler } from "webgllm";
|
|
9
|
+
export { WEBGLLM_CHAT_MODELS, WEBGLLM_MODEL_META, WEBGLLM_MODEL_REASONING } from "./catalog.js";
|
|
10
|
+
export type { WebGllmChatModel, WebGllmModelMeta, WebGllmModelReasoningByName } from "./catalog.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EAAE,YAAY,EAAE,wBAAwB,EAAE,aAAa,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAEhI,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack AI adapter for webgllm: LFM3, LFM2.5 and Qwen3.5 in the browser
|
|
3
|
+
* on WebGL 2, for browsers without WebGPU (Firefox on Android, for one).
|
|
4
|
+
* The model catalog (model-meta.ts) is exported here too.
|
|
5
|
+
*/
|
|
6
|
+
export { webgllmText, WebGllmTextAdapter } from "./adapter.js";
|
|
7
|
+
// webgllm's own worker handler, for a worker of your own (`worker` in the adapter's config).
|
|
8
|
+
export { WebGLLMWorkerHandler } from "webgllm";
|
|
9
|
+
export { WEBGLLM_CHAT_MODELS, WEBGLLM_MODEL_META, WEBGLLM_MODEL_REASONING } from "./catalog.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory the page's models take, shared by this project's runtimes
|
|
3
|
+
* (Transformers.js, WebGL, wllama), each of which loads models in its own
|
|
4
|
+
* worker. Before a runtime loads a model it claims the memory: other
|
|
5
|
+
* runtimes' least recently used models are unloaded until everything fits
|
|
6
|
+
* the budget. A phone can't hold two 1 GB models (an iPhone Safari tab has
|
|
7
|
+
* 2 GB), and small models still stay loaded side by side.
|
|
8
|
+
*
|
|
9
|
+
* The packages each carry this file and share one registry through
|
|
10
|
+
* globalThis, so they find each other even when bundled apart. Keep the
|
|
11
|
+
* copies identical (packages/{transformers-js,webgl,wllama}/src).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The default budget: 1.5 GB on iOS (a Safari tab gets 2 GB, the page and
|
|
15
|
+
* Safari take the rest), half the device's memory where the browser says
|
|
16
|
+
* (Chrome: up to 8 GB), else 4 GB.
|
|
17
|
+
*/
|
|
18
|
+
export declare function defaultMemoryBudget(): number;
|
|
19
|
+
/**
|
|
20
|
+
* Sets how many bytes the page's models may take together, across this
|
|
21
|
+
* project's runtimes, from the next load on; undefined for the default.
|
|
22
|
+
*/
|
|
23
|
+
export declare function setMemoryBudget(bytes: number | undefined): void;
|
|
24
|
+
/**
|
|
25
|
+
* Makes room for `owner`'s model of about `bytes`, unloading other runtimes'
|
|
26
|
+
* models, least recently used first, until it fits the budget (models kept
|
|
27
|
+
* loaded stay), then records it in place of `owner`'s previous one.
|
|
28
|
+
* Resolves to false, recording nothing, if `signal` aborted meanwhile.
|
|
29
|
+
*/
|
|
30
|
+
export declare function claimMemory(model: {
|
|
31
|
+
owner: string;
|
|
32
|
+
model: string;
|
|
33
|
+
bytes: number;
|
|
34
|
+
keepLoaded: boolean;
|
|
35
|
+
unload: () => unknown;
|
|
36
|
+
/**
|
|
37
|
+
* The load the claim is for: once it's aborted (canceled or replaced),
|
|
38
|
+
* nothing more is unloaded for it and nothing is recorded.
|
|
39
|
+
*/
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}): Promise<boolean>;
|
|
42
|
+
/** Marks `owner`'s model as just used (for least recently used order). */
|
|
43
|
+
export declare function touchMemory(owner: string): void;
|
|
44
|
+
/** Forgets `owner`'s model, once it's unloaded or failed to load. */
|
|
45
|
+
export declare function releaseMemory(owner: string): void;
|
|
46
|
+
//# sourceMappingURL=model-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-memory.d.ts","sourceRoot":"","sources":["../src/model-memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6BH;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,QAExD;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,MAAM,OAAO,CAAA;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBnB;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,QAGxC;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,QAE1C"}
|