@degreesign/chat 0.6.2 → 0.7.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/README.md CHANGED
@@ -40,8 +40,6 @@ package loads one model, streams one reply and reports status.
40
40
  capped by the prompt budgets.
41
41
  - Audio: a user turn can carry an audio URL, sent only to a model with audio
42
42
  support and capped by the prompt budgets.
43
- - Tools: the model may call Web search, arithmetic and currency built-ins, plus
44
- any tool the host supplies, in the same reply.
45
43
  - Reusable: the loaded model stays in memory for the next call.
46
44
  - Typed: written in TypeScript with declarations for every export.
47
45
  - Portable: importing and calling works in browsers, workers, React and Node.
@@ -61,11 +59,11 @@ yarn add @degreesign/chat
61
59
  pnpm add @degreesign/chat
62
60
  ```
63
61
 
64
- `@wllama/wllama`, `@wllama/wllama-compat`, `@litert-lm/core` and
65
- `@degreesign/utils` are build-time dependencies. The engines are bundled into
66
- the published build as dynamic chunks and their wasm assets ship beside them,
67
- so a consumer installs nothing extra and resolves no engine import. Only the
68
- model weights are downloaded at runtime.
62
+ `@wllama/wllama`, `@wllama/wllama-compat` and `@litert-lm/core` are build-time
63
+ dependencies. The engines are bundled into the published build as dynamic
64
+ chunks and their wasm assets ship beside them, so a consumer installs nothing
65
+ extra and resolves no engine import. Only the model weights are downloaded at
66
+ runtime.
69
67
 
70
68
  The build emits:
71
69
 
@@ -128,30 +126,33 @@ only to a model with vision support, center-crops each image to a square,
128
126
  resizes it to 288 x 288, and caps the number with `limits.maxImages`. The
129
127
  WebGPU engine asks LiteRT for the smallest Gemma 4 vision budget (70 tokens,
130
128
  630 patches), so it encodes the crop at 384 x 384 instead of upscaling it to
131
- 768 x 768. The CPU engine uses the 288 crop directly. On a phone or tablet,
132
- Gemma runs the text-only browser build, so images there go through
133
- `qwen3_vl_2b` instead.
129
+ 768 x 768. The CPU engine uses the 288 crop directly. On a phone or tablet a
130
+ Gemma request resolves to `gemma4_e2b_limited`, the text-only browser build, so
131
+ images there go through `qwen3_vl_2b` instead.
134
132
 
135
133
  Attach a recording with `audio: [dataUrl]`. Gemma 4 E2B accepts WAV, MP3 and
136
134
  FLAC on the desktop WebGPU engine and loads its audio encoder on demand. A short
137
135
  16 kHz mono WAV is the best input. `limits.maxAudio` caps the clips (default 1).
138
136
  A model without audio drops the clip when the turn also has text; an audio-only
139
- turn reports `Model does not accept audio`. The phone build is text only, so
140
- audio follows the same rule as images there. The package does not transcode, so
141
- the host supplies one of those formats.
137
+ turn reports `Model does not accept audio`. On a phone or tablet
138
+ `gemma4_e2b_limited` is text only, so audio follows the same rule as images
139
+ there. The package does not transcode, so the host supplies one of those
140
+ formats.
142
141
 
143
142
  The package never writes storage, so the host appends the user message before
144
143
  the call and appends the assistant message after the last `onResponse`.
145
144
 
146
145
  ## Enable WebGPU
147
146
 
148
- A request without `model` picks `gemma4_e2b` when the browser exposes a WebGPU
149
- adapter and `qwen3_vl_2b` when it does not. A phone or tablet asking for Gemma
150
- gets `gemma-4-E2B-it-gpu.litertlm`, the 2.0 GB browser build, which is text
151
- only. A desktop gets the 2.6 GB multimodal build with vision and audio. Hosts
152
- that need images on mobile pass `qwen3_vl_2b`; audio is desktop only. The GPU
153
- engine is far faster on a desktop, so check the browser before blaming the
154
- package:
147
+ A request without `model` picks Gemma when the browser exposes a WebGPU adapter
148
+ and `qwen3_vl_2b` when it does not. A phone or tablet asking for Gemma gets
149
+ `gemma4_e2b_limited`, the 2.0 GB text-only browser build
150
+ (`gemma-4-E2B-it-gpu.litertlm`); a desktop gets `gemma4_e2b`, the 2.6 GB
151
+ multimodal build with vision and audio. Once a model is requested or resolved,
152
+ every status snapshot reports the build that runs in `model`, so a host that
153
+ asks for `gemma4_e2b` on a phone reads `gemma4-e2b-limited` back. Hosts that
154
+ need images on mobile pass `qwen3_vl_2b`; audio is desktop only. The GPU engine
155
+ is far faster on a desktop, so check the browser before blaming the package:
155
156
 
156
157
  ```js
157
158
  // Run in the console of the page that loads the package
@@ -215,8 +216,8 @@ budget, 70 tokens and 630 patches, so the encoder runs one small signature and
215
216
  the crop is resized to 384 x 384 rather than the full 768 x 768. If the error
216
217
  still appears, close other GPU tabs, unload the model, or switch to the CPU
217
218
  model. A lost device needs a page or model reload; later calls cannot reuse it.
218
- A phone or tablet uses the text-only browser build, so it never starts the
219
- vision or audio encoder.
219
+ A phone or tablet resolves Gemma to the text-only browser build, so it never
220
+ starts the vision or audio encoder.
220
221
 
221
222
  ## Public API
222
223
 
@@ -241,12 +242,6 @@ requested model is already loaded, no download or load happens.
241
242
  | `onResponse` | Receives the accumulated reply text as it streams. Required. |
242
243
  | `onSpeed?` | Receives the current generation speed in tokens per second. |
243
244
  | `onStatus?` | Receives a `ChatStatusSnapshot` on every state change. |
244
- | `builtins?` | Built-in tools to attach, all three by default; `false` attaches none. |
245
- | `websearch?` | `WebSearchConfig` for the built-in search, provider, language and limits. |
246
- | `tools?` | Extra `ChatTool` entries, appended after the built-ins. |
247
- | `onToolCall?` | Receives a `ChatToolCall` before a tool runs. |
248
- | `onToolResult?` | Receives a `ChatToolResult` when a tool returns or throws. |
249
- | `maxToolRounds?` | Ceiling on tool rounds in one reply, default 4, clamped 1 to 25. |
250
245
 
251
246
  Without `model` the package picks the GPU build when the browser provides a
252
247
  WebGPU adapter and the CPU build when it does not. The budget defaults are 250
@@ -294,143 +289,33 @@ on failure. A preload owns no cancel function; a new load replaces it.
294
289
 
295
290
  ## Tools
296
291
 
297
- Gemma 4 E2B on the WebGPU engine can call tools. A reply may contain a tool
298
- call, the package runs it, feeds the result back and lets the model answer, all
299
- inside the one `createChat` call. Qwen3-VL 2B and the CPU engine never receive
300
- tools: `ChatStatusSnapshot.supportsTools` reports `false` there, the built-ins
301
- are dropped and a host tool ends the call with `Model does not accept tools`.
302
-
303
- Three built-ins ship and all three attach by default:
304
-
305
- | `BuiltinTool` | What it does |
306
- | ------------- | ------------ |
307
- | `websearch` | Searches Wikipedia by default, or DuckDuckGo, and returns a few hits. |
308
- | `calculate` | Evaluates an arithmetic expression with a safe parser. |
309
- | `currency` | Quotes and converts fiat, crypto and metals with the 24-hour change. |
292
+ Three built-in tools ship as descriptors. They are not connected to the model:
293
+ the package never declares a tool to an engine and never runs one by itself.
310
294
 
311
295
  ```ts
312
- import { BuiltinTool, WebSearchProvider, createChat } from "@degreesign/chat";
296
+ import { builtinTools, BuiltinTool } from '@degreesign/chat';
313
297
 
314
- const cancel = createChat({
315
- messages,
316
- // all three by default; narrow or disable them here
317
- builtins: [BuiltinTool.calculate, BuiltinTool.currency],
318
- websearch: { provider: WebSearchProvider.wikipedia, language: "en", limit: 3 },
319
- onToolCall: (call) => renderToolStart(call),
320
- onToolResult: (result) => renderToolEnd(result),
321
- onResponse: (text) => renderReply(text),
322
- });
298
+ const [calculation] = builtinTools([BuiltinTool.calculate]);
299
+ const answer = await calculation.execute({ expression: `6*7` });
323
300
  ```
324
301
 
325
- `builtins: false` disables every built-in. A tool is only offered to the model
326
- when the model accepts tools, so the default set costs nothing on the CPU
327
- engine.
328
-
329
- ### Custom tools
330
-
331
- `ChatInput.tools` is the extension point, with no registration and no global
332
- state. Each mention is appended after the built-ins and offered in the same
333
- conversation.
302
+ | Export | Description |
303
+ | ------ | ----------- |
304
+ | `builtinTools(names, config?)` | Returns the requested tools in the order given. |
305
+ | `calculateTool` | Evaluates an arithmetic expression, fully local. |
306
+ | `currencyTool` | Converts between fiat, metals and coins through the CoinExams feed. |
307
+ | `webSearchTool(config?)` | Searches Wikipedia by default, or DuckDuckGo. |
308
+ | `BuiltinTool` | `websearch`, `calculate` or `currency`. |
309
+ | `WebSearchProvider` | `wikipedia` or `duckduckgo`. |
334
310
 
335
- ```ts
336
- import { createChat } from "@degreesign/chat";
337
-
338
- const cancel = createChat({
339
- messages,
340
- tools: [
341
- {
342
- name: "lookup_order",
343
- description: "Read one order from the shop.",
344
- parameters: {
345
- type: "object",
346
- properties: {
347
- id: { type: "string", description: "Order id" },
348
- },
349
- required: ["id"],
350
- },
351
- execute: async ({ id }) => await shop.order(String(id)),
352
- },
353
- ],
354
- onToolCall: (call) => renderToolStart(call),
355
- onToolResult: (result) => renderToolEnd(result),
356
- onResponse: (text) => renderReply(text),
357
- });
358
- ```
311
+ Each tool has `name`, `description`, `parameters` (`ChatToolSchema`) and
312
+ `execute(args)`, which returns a JSON-safe `ChatToolValue` or throws. `ChatTool`
313
+ is that whole shape, and `ChatToolCall` / `ChatToolResult` describe a call and
314
+ its outcome for a host that records them.
359
315
 
360
- - `name` is required and unique. A host tool named `websearch`, `calculate` or
361
- `currency` replaces that built-in.
362
- - `description` and `parameters` reach the model as written.
363
- - `execute` receives the parsed arguments and may return a value or a promise.
364
- A thrown error becomes a tool error the model reads, and `onToolResult`
365
- receives `error`.
366
- - Tools and callbacks are captured when the conversation is created. Rebuilding
367
- the array with the same functions and declarations reuses the conversation;
368
- changing a declaration or a function opens a new one and pays one history
369
- prefill.
370
- - The package cannot abort a running `execute`. A cancel stops generation and
371
- discards its result.
372
- - The package neither clamps nor times out a host result. Keep it small: three
373
- declarations already cost roughly 200 to 250 of the 4096 context tokens.
374
-
375
- ### Search
376
-
377
- `websearch` sends one query and returns a short list of `title`, `url` and
378
- `snippet`.
379
-
380
- | `WebSearchConfig` | Default | Meaning |
381
- | ----------------- | ------- | ------- |
382
- | `provider` | Wikipedia | `WebSearchProvider.wikipedia` or `.duckduckgo`. |
383
- | `language` | `en` | Wikipedia edition, two or three letters plus an optional region. |
384
- | `limit` | 3 | Hits asked of the provider, clamped 1 to 20. |
385
- | `snippetLength` | 400 | Characters kept per snippet. |
386
- | `timeout` | 8000 | Request budget in milliseconds. |
387
-
388
- Wikipedia is the default because its REST search has CORS `*`, no key and a
389
- predictable result shape. DuckDuckGo returns an instant answer and related
390
- topics, and many queries have neither, so it can return an empty list. Both
391
- endpoints are public and keyless.
392
-
393
- ### Calculator
394
-
395
- `calculate` accepts `+`, `-`, `*`, `/`, `%`, `^` and parentheses, and returns
396
- `{ expression, result }`.
397
-
398
- - `×`, `÷` and `−` are normalized, and thousands commas and spaces are removed.
399
- - No implicit multiplication: write `2*(3+4)`.
400
- - No functions: use `^0.5` for a square root.
401
- - `%` is a postfix division by 100, so `200*15%` is 30 and `200+10%` is 200.1.
402
- - Unbalanced parentheses, division by zero, an unknown character, an expression
403
- over 200 characters and a non-finite result are errors.
404
- - The parser is hand written. The package never uses `eval` or `new Function`.
405
-
406
- ### Currency
407
-
408
- `currency` reads one public price file published by [CoinExams](https://coinexams.com)
409
- and answers from it.
410
-
411
- - Every rate is quoted against `EUR`, which is always 1.
412
- - Fiat and metals come from the fiat section. Crypto comes from the coin
413
- section, which wins when a symbol appears in both.
414
- - Metals are `XAU`, `XAG`, `XPT` and `XPD`, quoted per gram. A zero or missing
415
- quote is unavailable.
416
- - Arguments are `from` (required), `to` (default `EUR`) and `amount` (default 1).
417
- - A result carries `from`, `to`, `amount`, `rate`, `result`, `updatedAt` and
418
- `stale`, plus `previousRate`, `change` and `changePercent` when the feed has a
419
- 24-hour value for both symbols.
420
-
421
- The feed is cached on the device under `degreesign-chat-prices-v1` in
422
- `localStorage`. Fiat and metals refresh at most hourly, crypto at most every
423
- five minutes, and concurrent calls share one fetch. A failed refresh keeps the
424
- previous snapshot, serves it with `stale: true` and waits 30 seconds before the
425
- next attempt. `navigator.onLine === false` skips the fetch and serves the cache.
426
- With no cache and no network the tool reports `Prices unavailable offline`.
427
-
428
- ### Privacy
429
-
430
- Search sends the model's query to the chosen provider, and currency fetches the
431
- public price file. Nothing else leaves the device, and no key is sent. To keep a
432
- call fully local, pass `builtins: [BuiltinTool.calculate]`, which computes on
433
- device.
316
+ `webSearchTool` takes `WebSearchConfig`: `provider`, a Wikipedia `language`
317
+ edition, `limit` results, `snippetLength` and a `timeout` in milliseconds. The
318
+ search and currency tools fetch over the network; `calculate` does not.
434
319
 
435
320
  ## Status
436
321
 
@@ -438,12 +323,12 @@ device.
438
323
 
439
324
  | Field | Description |
440
325
  | ----- | ----------- |
326
+ | `model?` | The build that runs after the mobile swap. |
441
327
  | `availability` | `ChatStatus.online`, `.away` or `.offline`, derived from `status`. |
442
328
  | `status` | `LlmStatus`: `idle`, `downloading`, `ready`, `error` or `unsupported`. |
443
329
  | `backend` | `LlmBackend.cpu`, `.gpu` or `.none`. |
444
- | `supportsImages` | Whether the requested model accepts images. |
445
- | `supportsAudio` | Whether the requested model accepts audio. |
446
- | `supportsTools` | Whether the requested model accepts tool declarations. |
330
+ | `supportsImages` | Whether the model accepts images on this device. |
331
+ | `supportsAudio` | Whether the model accepts audio on this device. |
447
332
  | `ready` | Whether a model is loaded and accepts a reply. |
448
333
  | `loading` | Whether a download or load is in flight. |
449
334
  | `requesting` | Whether a reply is streaming right now. |
@@ -458,12 +343,6 @@ A stream that never produces its first chunk ends with an error after 180
458
343
  seconds instead of waiting forever. Once a chunk has arrived, 30 seconds of
459
344
  silence ends the stream and keeps the text already delivered.
460
345
 
461
- A lost WebGPU device is rebuilt once: the package drops the dead engine and its
462
- device, reloads the LiteRT wasm, retries the turn on the next smaller context
463
- window (4096, 2048, 1024) and reports the reply normally. When no rebuild is
464
- possible the runtime ends as `error` with `backend: none`, so the host can fall
465
- back to the CPU model.
466
-
467
346
  ## Logging
468
347
 
469
348
  Console output stays off by default. Pass `logging: true` in a call to receive
@@ -473,15 +352,18 @@ without it goes quiet again.
473
352
 
474
353
  ## Models
475
354
 
476
- | `ChatModelId` | Name | Reasoning | Images | Audio | Tools | CPU | WebGPU |
477
- | ------------- | ---- | --------- | ------ | ----- | ----- | --- | ------ |
478
- | `qwen3_vl_2b` | Qwen3-VL 2B | Yes | Yes | No | No | Yes | No |
479
- | `gemma4_e2b` | Gemma 4 E2B | Yes | Desktop | Desktop | Yes | No | Yes |
355
+ | `ChatModelId` | Name | Reasoning | Images | Audio | CPU | WebGPU |
356
+ | ------------- | ---- | --------- | ------ | ----- | --- | ------ |
357
+ | `qwen3_vl_2b` | Qwen3-VL 2B | Yes | Yes | No | Yes | No |
358
+ | `gemma4_e2b` | Gemma 4 E2B | Yes | Yes | Yes | No | Yes |
359
+ | `gemma4_e2b_limited` | Gemma 4 E2B limited | Yes | No | No | No | Mobile |
480
360
 
481
- A phone or tablet asking for Gemma gets the browser build,
482
- `gemma-4-E2B-it-gpu.litertlm`, which is text only. It has no vision or audio
483
- encoder, so `supportsImages` and `supportsAudio` report `false` there. Use
484
- `qwen3_vl_2b` for images on mobile.
361
+ A phone or tablet asking for any Gemma id gets `gemma4_e2b_limited`,
362
+ `gemma-4-E2B-it-gpu.litertlm`, which is text only. A desktop asking for the
363
+ limited id gets the full `gemma4_e2b` build instead. The build that actually
364
+ runs is always the one in `ChatStatusSnapshot.model`. The limited build has no
365
+ vision or audio encoder, so `supportsImages` and `supportsAudio` report `false`
366
+ there. Use `qwen3_vl_2b` for images on mobile.
485
367
 
486
368
  The first call downloads the model before its engine loads it. Later calls
487
369
  reuse the loaded runtime. Only one model stays in memory. An interrupted
@@ -496,7 +378,7 @@ Gemma 4 WebGPU path. Use the GPU model when images need to be fast.
496
378
 
497
379
  Gemma 4 E2B downloads `gemma-4-E2B-it.litertlm`, about 2.6 GB, on a desktop. It
498
380
  runs on the WebGPU engine with its vision and audio encoders in the same file,
499
- so an image or audio prompt does not download a projector. A phone or tablet
381
+ so an image or audio prompt does not download a projector. `gemma4_e2b_limited`
500
382
  instead downloads the 2.0 GB `gemma-4-E2B-it-gpu.litertlm`, the precompiled
501
383
  GPU_ARTISAN build Google ships for browsers. The smaller file is what keeps a
502
384
  mobile tab alive at the end of the model load.
@@ -684,16 +566,9 @@ const useReply = () => {
684
566
  | `ChatStatusSnapshot` | Availability, engine status, backend, progress and request state. |
685
567
  | `LlmLimits` | Token, prompt, image, audio and thread budgets, all overridable. |
686
568
  | `WllamaAssets` | Wasm, compat wasm and compat worker URLs. |
687
- | `ChatTool` | One tool: `name`, `description`, `parameters` and `execute`. |
688
- | `ChatToolSchema` | The JSON-schema subset a tool parameter declares. |
689
- | `ChatToolValue` | A JSON-safe argument or result value. |
690
- | `ChatToolCall` | One model call: `id`, `name` and parsed `arguments`. |
691
- | `ChatToolResult` | A finished call, with `result` or `error`. |
692
- | `WebSearchConfig` | Provider, language, limit, snippet length and timeout. |
693
- | `WebSearchResult` | One hit: `title`, `url` and `snippet`. |
694
569
 
695
570
  The public enums are `ChatModelId`, `LlmRole`, `ChatStatus`, `LlmStatus`,
696
- `LlmBackend`, `LlmPhase`, `BuiltinTool` and `WebSearchProvider`.
571
+ `LlmBackend` and `LlmPhase`.
697
572
 
698
573
  ## Architecture
699
574
 
@@ -717,22 +592,23 @@ No. The host passes the full prompt on every call and keeps the reply.
717
592
 
718
593
  Yes, on models with vision support. Put image URLs in a user turn's `images`
719
594
  array. Qwen3-VL 2B accepts images on the CPU engine and Gemma 4 E2B accepts
720
- images on the desktop WebGPU engine. On a phone or tablet Gemma runs the
721
- text-only browser build, so use `qwen3_vl_2b` for mobile images. Each image is
722
- center-cropped to a square and resized to 288 x 288 before the engine sees it.
723
- The WebGPU engine encodes that crop at 384 x 384 with the smallest Gemma 4
724
- vision budget. A model without vision drops images when the turn also has text;
725
- an image-only turn reports `Model does not accept images`.
595
+ images on the desktop WebGPU engine. On a phone or tablet Gemma resolves to
596
+ `gemma4_e2b_limited`, the text-only browser build, so use `qwen3_vl_2b` for
597
+ mobile images. Each image is center-cropped to a square and resized to 288 x 288
598
+ before the engine sees it. The WebGPU engine encodes that crop at 384 x 384
599
+ with the smallest Gemma 4 vision budget. A model without vision drops images
600
+ when the turn also has text; an image-only turn reports
601
+ `Model does not accept images`.
726
602
 
727
603
  ### Does it support audio?
728
604
 
729
605
  Yes, on models with audio support. Put an audio URL in a user turn's `audio`
730
606
  array. Gemma 4 E2B accepts WAV, MP3 and FLAC on the desktop WebGPU engine, and a
731
607
  short 16 kHz mono WAV is the best input. `limits.maxAudio` caps the clips
732
- (default 1). Audio is desktop only; the mobile browser build has no audio
733
- encoder. The package does not transcode, so the host supplies one of those
734
- formats. A model without audio drops the clip when the turn also has text; an
735
- audio-only turn reports `Model does not accept audio`.
608
+ (default 1). Audio is desktop only; `gemma4_e2b_limited` has no audio encoder.
609
+ The package does not transcode, so the host supplies one of those formats. A
610
+ model without audio drops the clip when the turn also has text; an audio-only
611
+ turn reports `Model does not accept audio`.
736
612
 
737
613
  ### Does it need a server?
738
614
 
@@ -741,11 +617,10 @@ includes no server component.
741
617
 
742
618
  ### Does it have runtime dependencies?
743
619
 
744
- No runtime dependencies. `@wllama/wllama`, `@wllama/wllama-compat`,
745
- `@litert-lm/core` and `@degreesign/utils` are build-time dependencies, bundled
746
- into the dynamic engine chunks, the chat bundle and their wasm assets, so
747
- consumers resolve no engine import and download no engine code beyond the chunk
748
- they use.
620
+ No runtime dependencies. `@wllama/wllama`, `@wllama/wllama-compat` and
621
+ `@litert-lm/core` are build-time dependencies, bundled into the dynamic engine
622
+ chunks and their wasm assets, so consumers resolve no engine import and
623
+ download no engine code beyond the chunk they use.
749
624
 
750
625
  ### Where are the models stored?
751
626
 
@@ -760,7 +635,7 @@ Qwen3-VL 2B supports reasoning and images on the CPU. Gemma 4 E2B supports
760
635
  reasoning, images and audio on desktop WebGPU, with much faster media prompts
761
636
  but a larger download and a slower first compile. Use Qwen for broad browser
762
637
  support and for mobile images. Use Gemma on a desktop with Chromium WebGPU, or
763
- on a phone for text with the smaller browser build.
638
+ on a phone for text, where it resolves to `gemma4_e2b_limited`.
764
639
 
765
640
  ## Licensing
766
641