Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
2d432e6
chore: depend on published llm-llamacpp 0.41.0
donriddo Aug 11, 2026
845f460
feat[api]: run completions and batches concurrently per model
donriddo Aug 11, 2026
3159d25
doc: describe multi-job admission and per-request cancel in lifecycle…
donriddo Aug 11, 2026
ea95a2a
fix[api]: keep disk-KV-cache completions in the shared admission lane
donriddo Aug 12, 2026
8bd15d1
test: cover concurrent completion overlap and same-cache-key serializ…
donriddo Aug 12, 2026
34aac4d
fix[api]: run finetune as an exclusive writer over the completion lane
donriddo Aug 12, 2026
34eb6e2
fix[api]: cancel LLM translate and batch runs per job, not model-wide
donriddo Aug 12, 2026
77ba2d4
fix[api]: serialize auto KV-cache writes per resolved path, abortably
donriddo Aug 12, 2026
93d119a
test: exercise per-job cancel isolation and concurrent decoding
donriddo Aug 12, 2026
1c92f57
doc: note finetune exclusivity and per-job cancel in lifecycle rules
donriddo Aug 12, 2026
7fd29b5
chore: tighten inline comments in the multi-job cancel and KV-cache p…
donriddo Aug 12, 2026
bb78ea5
fix[api]: floor admission caps to integer slot counts
donriddo Aug 12, 2026
0fbf76e
fix[api]: stop an aborted queued finetune or translate before native …
donriddo Aug 12, 2026
d853e59
chore: apply prettier formatting to multi-job files
donriddo Aug 12, 2026
a9e81b4
fix[api]: make auto-cache commit target-lock abortable and held throu…
donriddo Aug 12, 2026
3268296
fix[api]: expose requestId on the translate and finetune client handles
donriddo Aug 12, 2026
21d7d61
fix[api]: cancel a request before begin without waiting for a saturat…
donriddo Aug 12, 2026
ccb35dc
fix[api]: close double slot-release and same-tick duplicate-id races …
donriddo Aug 12, 2026
c80f978
fix[api]: reject unsafe cache keys in deleteCache and auto-cache markers
donriddo Aug 12, 2026
8c20c2a
fix[api]: keep begin() admission on one microtask so abort forwarding…
donriddo Aug 12, 2026
aa30060
fix[api]: surface any error during an aborted completion as cancellation
donriddo Aug 12, 2026
85a996d
fix[api]: ref-count finetune runtime state so overlapping runs report…
donriddo Aug 12, 2026
c32fdcd
fix[api]: case-fold the KV-cache lock key
donriddo Aug 12, 2026
2e3b257
fix[api]: pin the lane reader cap so a larger-cap request can't over-…
donriddo Aug 12, 2026
4bf3583
chore: bump NOTICE llm-llamacpp to 0.41.0
donriddo Aug 12, 2026
0d8ea97
fix[api]: reject non-canonical custom cache keys at the turn boundary
donriddo Aug 12, 2026
bd8a90f
fix[api]: cancel in-flight and queued requests when a model is unloaded
donriddo Aug 12, 2026
b00e959
doc: reconcile lifecycle primitives and the multi-job example with cu…
donriddo Aug 12, 2026
36cd654
chore: revert manual NOTICE version bump
donriddo Aug 12, 2026
f801fea
test: assert cancel isolation and auto-cache concurrency at token gra…
donriddo Aug 12, 2026
e44e53f
doc: point the finetune requestId at cancel({ requestId })
donriddo Aug 12, 2026
c66b473
chore: apply prettier formatting to KV-cache and path-security files
donriddo Aug 12, 2026
2c57438
fix[api]: reject an unsafe modelId in keyed KV-cache deletion
donriddo Aug 12, 2026
5ac3d82
doc: reconcile per-job cancel wording in the lifecycle docs
donriddo Aug 12, 2026
72d8a37
doc: state the last-writer-wins contract for deleteKvCacheState
donriddo Aug 12, 2026
24d1728
doc: make the multi-job example prove cancel isolation
donriddo Aug 12, 2026
ef223a9
fix[api]: drain a model's in-flight requests before unloading it
donriddo Aug 12, 2026
9ec8068
doc: document the named/auto cache key-collision exception
donriddo Aug 12, 2026
714b836
fix[api]: block admission for a model being unloaded, not just drain it
donriddo Aug 13, 2026
c4fc6fb
doc: make the multi-job example require typed cancellation to pass
donriddo Aug 13, 2026
f421688
fix[api]: bail NMT translate before native work when its context is a…
donriddo Aug 13, 2026
e183619
fix[api]: drain in-flight disposals and cancel+drain on worker shutdown
donriddo Aug 13, 2026
734e054
refactor: drop the global shutdown admission barrier
donriddo Aug 13, 2026
4210b38
fix[api]: settle the translate stats promise when the stream ends early
donriddo Aug 13, 2026
f0d9940
refactor: key the KV-cache write lock on the resolved path directly
donriddo Aug 13, 2026
2892ef4
refactor: drop redundant indirection in translate and plugin
donriddo Aug 13, 2026
935312a
doc: stop restating the shared-lane rules in the system doc
donriddo Aug 13, 2026
dc69617
refactor: fold model-drain cancel + barrier-lift into one scoped call
donriddo Aug 13, 2026
93ae084
fix[api]: reject null-byte cache keys in isSafeCacheKey as a predicate
donriddo Aug 13, 2026
a398e47
fix[api]: omit modelExecutionMs on the aborted completion path
donriddo Aug 13, 2026
d79bfb9
fix[api]: soft-cancel LLM translate instead of throwing a server-side…
donriddo Aug 13, 2026
b71a86e
fix[api]: reject new operation RPCs once the worker is shutting down
donriddo Aug 13, 2026
36d4c10
fix[api]: make the shutting-down lifecycle state terminal
donriddo Aug 13, 2026
1489c10
fix[api]: restore the case-folded KV-cache lock key; document limits
donriddo Aug 13, 2026
d17588c
fix[api]: keep shutdown terminal across an in-flight suspend/resume
donriddo Aug 13, 2026
72023a3
chore: regenerate contract schema for the shuttingDown lifecycle state
donriddo Aug 13, 2026
92af293
test: exercise the NMT scalar (run) path under the unload barrier
donriddo Aug 13, 2026
5806e99
chore: regenerate python client for shuttingDown lifecycle state
donriddo Aug 13, 2026
72fe167
Merge upstream/main into feat/sdk-multi-job-batching
donriddo Aug 13, 2026
3c24387
test: a begin racing withModelDraining starts aborted
donriddo Aug 13, 2026
dea3c35
fix[api]: a begin racing worker shutdown starts aborted
donriddo Aug 13, 2026
b5eedc4
fix[api]: reject begin() during terminal shutdown instead of aborting it
donriddo Aug 13, 2026
43f2a97
fix[api]: arm a cleanup watchdog before runCleanup in the direct worker
donriddo Aug 13, 2026
3e7d107
fix[api]: widen the cleanup watchdog to 30s so it can't force-kill sl…
donriddo Aug 13, 2026
88f0e70
fix[api]: replace the pre-cleanup SIGKILL watchdog with a non-fatal w…
donriddo Aug 13, 2026
6ffbb13
revert: leave the shutdown cleanup watchdog as-is, no pre-cleanup dea…
donriddo Aug 13, 2026
589fb40
fix[api]: recreate a kv-cache parent dir a peer's rollback pruned bef…
donriddo Aug 14, 2026
21821d3
fix[api]: surface a real completion error that races a cancel instead…
donriddo Aug 14, 2026
fe39346
fix[api]: key kv-cache init-state by resolved path and harden cache d…
donriddo Aug 14, 2026
fb77d33
fix[api]: track terminal shutdown internally, not as a public lifecyc…
donriddo Aug 14, 2026
eba6541
doc: document the shared llama.cpp admission lane's bounded wait queue
donriddo Aug 14, 2026
0ddd7b5
fix[api]: skip kv-cache priming for a turn aborted before it holds th…
donriddo Aug 14, 2026
dd5bece
fix[api]: swallow an addon cancellation racing an aborted LLM translate
donriddo Aug 14, 2026
7063474
feat[python]: thread and expose a request id on translate
donriddo Aug 14, 2026
08d0ec9
test[api]: give the cancellation and kv-cache e2e checks real teeth
donriddo Aug 14, 2026
5ffb3ef
fix[api]: report a blocked lifecycle op against the shutdown state
donriddo Aug 14, 2026
d811aad
fix[api]: release native loggers only after draining in-flight requests
donriddo Aug 14, 2026
105dfd0
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 14, 2026
80250bd
doc: align cancellation lifecycle descriptions with runtime behavior
donriddo Aug 14, 2026
72c1a3d
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 14, 2026
7ca0457
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 14, 2026
f125af2
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 14, 2026
3429c91
test[api]: interleave cached and plain submissions in the mixed-overl…
donriddo Aug 14, 2026
1752722
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 14, 2026
1f1cd5f
fix[api]: remove test-only reset hooks from shipped worker modules
donriddo Aug 15, 2026
6921839
fix[api]: hold a request id reserved through its async disposal
donriddo Aug 15, 2026
3a0a1f6
fix[api]: harden kv-cache lock cleanup and keep path helpers private
donriddo Aug 15, 2026
88a6850
fix[api]: cancel via the SDK abort signal, not addon error shape
donriddo Aug 15, 2026
41d9aef
test[api]: prove cached-vs-cached native concurrency with a cached-on…
donriddo Aug 15, 2026
7022e4c
test[api]: prove survivor progress after the cancel ack in the example
donriddo Aug 15, 2026
d86a1ea
doc: await the async registry begin in the lifecycle examples
donriddo Aug 15, 2026
f9e469b
doc: restore the path-security re-export comment
donriddo Aug 15, 2026
b21c3e3
fix[api]: preserve a context-overflow error racing translate cancella…
donriddo Aug 15, 2026
c04e2c5
fix[api]: revalidate a queued finetune after exclusive-lane admission
donriddo Aug 15, 2026
5c6340d
revert[api]: remove the worker-shutdown request drain
donriddo Aug 15, 2026
715ce46
Merge remote-tracking branch 'upstream/main' into feat/sdk-multi-job-…
donriddo Aug 17, 2026
c3bd9d0
doc: correct async begin() examples and document the finetune availab…
donriddo Aug 17, 2026
957103b
Merge branch 'main' into feat/sdk-multi-job-batching
donriddo Aug 18, 2026
19deed3
Merge branch 'main' into feat/sdk-multi-job-batching
donriddo Aug 18, 2026
a2bc158
chore[notask]: bump speech addons to latest published
donriddo Aug 19, 2026
ced4980
chore[notask]: bump tts-ggml to 0.7.4
donriddo Aug 19, 2026
175735b
Merge upstream/main into feat/sdk-multi-job-batching
donriddo Aug 19, 2026
5027b59
fix[api]: pause only an admitted finetune, never a queued one
donriddo Aug 19, 2026
7f49487
fix: release the auto-cache target ref when commit setup throws
donriddo Aug 19, 2026
2be4585
fix[api]: pause cancels queued finetune peers, not just readers
donriddo Aug 19, 2026
bc93d71
test: record kv-cache decode window per token, not at stream close
donriddo Aug 19, 2026
431ccb4
fix[api]: reject a second finetune per model and simplify pause
donriddo Aug 19, 2026
f58a0db
test: strengthen the concurrency-proof e2e and clarify request-permit…
donriddo Aug 19, 2026
275a09c
test: gate concurrency proof on the engine signal, not client token w…
donriddo Aug 19, 2026
a0faf52
fix[api]: close the finetune pause runtime-ref and cancel-handoff races
donriddo Aug 19, 2026
bdf754d
Merge upstream/main into feat/sdk-multi-job-batching
donriddo Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .cursor/rules/sdk/docs/kv-cache-system.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ Anonymous caches created by `kvCache: true` are tracked with internal marker met
- Least-recently-used auto caches are removed when their total size exceeds 512 MiB on React Native or 4 GiB on other runtimes.
- Cache paths used by active turns are excluded from eviction.

Named string-key caches are not inferred from their directory names, are not subject to automatic retention, and remain caller-managed through `deleteCache()`. Generated caches from older SDK versions become managed when they are next reused; operators can use `deleteCache({ all: true })` for a one-time removal of unreachable legacy caches.
Named string-key caches are not inferred from their directory names and remain caller-managed through `deleteCache()`. Retention acts only on cache directories that carry an auto-cache marker, and a marker is written only on an auto (`kvCache: true`) turn β€” a purely named cache never gets one. The one exception: a named key chosen to exactly equal a generated auto key (16 lowercase hex characters) resolves to the *same* directory as that auto cache, so it shares the auto cache's lock and its retention. Avoid that shape for named keys you want to manage independently. Generated caches from older SDK versions become managed when they are next reused; operators can use `deleteCache({ all: true })` for a one-time removal of unreachable legacy caches.

**Case-only key variants are unsupported.** Two named keys that differ only in case (e.g. `"Session"` and `"session"`) are the same file on case-insensitive filesystems (default macOS/Windows). The per-cache-path write lock case-folds its key so such variants still serialise and can't corrupt one file, but the other bookkeeping (active-path refs, saved-message counts, retention's directory matching) stays case-sensitive β€” so retention may not recognise a case-variant alias as active. Pick a single canonical case per named key.

## Key Implementation Files

Expand Down Expand Up @@ -139,6 +141,8 @@ if (shouldCommit) await session.commitTurn(turn, ...); // suppresses the deferr

`commitTurn` flips an internal flag on the turn handle; `rollback` reads the flag and short-circuits. The happy path commits and the deferred rollback becomes a no-op. Every other exit path (cancel, zero-token, addon error, rename failure, tool-call turn) lets `scope[Symbol.asyncDispose]` run the deferred rollback, which atomically unlinks the `.bin` file, deletes the `initializedCaches` entry, and forgets the `cachedMessageCounts` entry.

**Late-commit vs cancel is intentional, not a race.** A commit only proceeds past its abort re-check for a turn that fully generated and whose file passed `verifySaveAndRecord`. If a cancel lands during the async commit (after that check, during rename/verify), the commit finishes: a **complete, verified** turn is persisted while the operation reports cancelled. This is by design β€” the persisted cache matches the tokens the client already received, and committing fully-generated verified work beats discarding it. A strict "cancel wins" rollback here would need target-aware cleanup and risks deleting valid completed state, so it is not done.

The maps that backed `clearCacheRegistry` and `cachedMessageCounts` are **private to `kv-cache-session.ts`**. No other module reads or writes them.

### Cache Initialization (primeIfMissing) and Addon Non-Transactional Save
Expand Down
14 changes: 6 additions & 8 deletions .cursor/rules/sdk/docs/request-lifecycle-system.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Extend the `CancelTarget` discriminated union in `request-registry.ts`. Today su

### What's the relationship to the addon's `cancel`?

The addon (`@qvac/llm-llamacpp` and friends) exposes a synchronous `cancel(jobId?)` that signals the C++ side to stop decoding ASAP. The SDK wires `signal.addEventListener("abort", onAbort, { once: true })` so a registry cancel fires `addon.cancel()` at the binding leaf β€” but the SDK's source of truth for "was this cancelled" is still `signal.aborted`, not addon state. This keeps the SDK decoupled from per-addon cancel semantics; an addon without a `cancel()` method still gets best-effort cancellation through the SDK's signal-propagated termination of the streaming loop.
Addon cancellation differs by handler capability. Request-scoped llama.cpp completion, batch completion, and LLM translate route an aborted registry context to that run's `response.cancel()`. Model-scoped handlers wire the signal to the addon's model-wide `cancel()`, while handlers with no cancel surface stop observing the result and let native work finish. In every case the SDK's source of truth for "was this cancelled" remains `signal.aborted`, not addon state.

### Why declare cancel capability on the plugin manifest?

Expand All @@ -141,19 +141,17 @@ The truth-table cell for each built-in plugin is pinned by `test/unit/plugin-can

### Why a concurrency policy on the registry?

The llama.cpp addon owns one KV-cache + one decode loop per model. Two concurrent `completionStream` requests against the same model interleave their token streams on the same logical session β€” observably "the wrong tokens for my prompt" from the client's point of view, with the cache layer also drifting because two `KvCacheSession.beginTurn(...)` calls would race for the same `cachePath`.
`@qvac/llm-llamacpp` now admits multiple concurrent `run()` calls per model β€” each its own native job with its own tokens β€” so completions no longer interleave on one session. The registry caps admission at the model's `parallel` and the KV-cache layer serializes only genuine same-file turns via a per-cache-path lock in `KvCacheSession`; turns on different cache keys decode concurrently.

The registry exposes an admission-control hook so callers don't have to externally enforce "one completion per model":

```typescript
registry.policy({ kind: "completion", maxConcurrentPerModel: 1, onOverflow: "queue" });
```

`maxConcurrentPerModel` is the per-`(lane, modelId)` slot count (`1` today β€” the single-context reality; raise it to the addon's batching width once continuous batching lands). `onOverflow` decides what an over-capacity `begin(...)` does: `"queue"` (the default) **serializes** β€” the second `begin({ kind: "completion", modelId: "m1" })` **waits FIFO** behind the in-flight one; `"reject"` throws `RequestRejectedByPolicyError` (52420) immediately (the legacy `oneAtATimePerModel` behavior). On the queue path, `begin` rejects only when the per-model wait queue is already at its `maxQueueDepthPerModel` cap (default 64), or when a waiter exceeds an optional `queueTimeoutMs`. The error is structured (carries `requestId`, `kind`, `modelId`, `reason`) rather than a generic 500, so clients can `instanceof` narrow and surface "model saturated" to the user.
`maxConcurrentPerModel` is the per-`(lane, modelId)` **top-level request-permit** count. The completion/batch handlers pass the loaded model's own `parallel` as that request cap; the registered `1` is only the fallback when a caller supplies none. A multi-prompt batch still consumes one permit rather than one permit per native sequence, so the addon's scheduler remains authoritative for actual sequence occupancy. `onOverflow` decides what an over-capacity `begin(...)` does: `"queue"` (the default) **serializes** β€” the second `begin({ kind: "completion", modelId: "m1" })` **waits FIFO** behind the in-flight one; `"reject"` throws `RequestRejectedByPolicyError` (52420) immediately (the legacy `oneAtATimePerModel` behavior). On the queue path, `begin` rejects only when the per-model wait queue is already at its `maxQueueDepthPerModel` cap (default 64), or when a waiter exceeds an optional `queueTimeoutMs`. The error is structured (carries `requestId`, `kind`, `modelId`, `reason`) rather than a generic 500, so clients can `instanceof` narrow and surface "model saturated" to the user.

The worker singleton wires `completion` **and** `batchCompletion` with `maxConcurrentPerModel: 1, onOverflow: "queue"` and a shared `llamacppCompletion` lane (`sharedSlotGroup`), so the two kinds contend for one slot per model β€” see "Shared slot groups" in `request-lifecycle-primitives.mdc`. It also gives `audiogen` its own one-slot FIFO lane because ACE-Step replaces the active response when a second same-model run starts.

Other kinds (`embeddings`, `transcribe`, `translate`, `finetune`) intentionally do **not** register a policy: those addons tolerate concurrent requests against the same model (embeddings is genuinely concurrent on the addon side; the streaming kinds share the addon's job-state but the registry's per-request lifecycle is sufficient for cancel routing without admission control). Per-kind policies can be registered if and when an addon's concurrency invariants change.
The worker singleton registers the admission policies: `completion`, `batchCompletion`, and LLM `translate` share the `llamacppCompletion` lane as readers capped at the model's `parallel`, `finetune` joins it as an exclusive writer, and `embeddings` / `transcribe` / NMT `translate` stay ungated. See "Shared slot groups" in `request-lifecycle-primitives.mdc` for the full lane rules (this doc doesn't restate them). `audiogen` gets its own one-slot FIFO lane because ACE-Step replaces the active response when a second same-model run starts.

The policy runs **before** controller / scope allocation in `begin(...)`, so a rejected `begin` leaves no registry entry behind. Disposing the holder of the in-flight slot releases admission for the next request β€” `await using` makes this automatic.

Expand Down Expand Up @@ -197,13 +195,13 @@ Test coverage: `same-tick cancel-before-begin retroactively aborts the later beg
| `server/bare/ops/cancel.ts` | Broad-cancel op: pure registry walk, legacy addon-cancel fallback removed in 0.11.0 |
| `server/rpc/handlers/cancelHandler.ts` | RPC entry point: 2-arm `request` / `broad` dispatch (5-arm union collapsed in 0.11.0). Targeted `request` goes through `RequestRegistry.cancel({ requestId })` plus an optional `markClearCacheForRequest(...)` for downloads; `broad` delegates to `server/bare/ops/cancel.ts` |
| `server/rpc/handlers/delete-cache.ts` | Delegates to `deleteKvCacheState(...)` β€” zero direct references to the three KV-cache layers |
| `server/bare/plugins/llamacpp-completion/plugin.ts` | Reference plugin manifest; declares `cancel: { scope: "model", hard: true }`; builds `withRequestContext(...)` once per request and threads it into `completion(...)`; `finetune` declares `{ scope: "model", hard: true }`; `translate` handler threads `requestId` into the shared bare op |
| `server/bare/plugins/llamacpp-completion/plugin.ts` | Reference plugin manifest; `completion`/`batchCompletion`/`translate` declare `cancel: { scope: "request", hard: true }` (per-job `response.cancel()`); `finetune` declares `{ scope: "model", hard: true }` (global cancel, made safe by exclusive admission); builds `withRequestContext(...)` once per request |
| `server/bare/plugins/llamacpp-completion/ops/completion-stream.ts` | Reference implementation of the canonical handler shape; uses `KvCacheSession`; accepts a request-scoped `logger` |
| `server/bare/plugins/llamacpp-completion/ops/finetune.ts` | `startFinetune` opens a registry context (kind `"finetune"`), wires `signal β†’ model.cancel()`, registers `scope.defer` cleanups for `clearFinetuneRuntimeState` + `handle.removeListener`; `cancelFinetune` is a thin wrapper over `registry.cancel({ modelId, kind: "finetune" })` |
| `server/bare/plugins/llamacpp-completion/ops/kv-cache-session.ts` | `KvCacheSession` factory + `deleteKvCacheState`: single owner of the three KV-cache layers; accepts a per-instance logger |
| `server/bare/ops/embed.ts` | Registry-routed (kind `"embeddings"`, `scope: "model", hard: true`); accepts an optional `requestId`, falls back to a server-generated UUID |
| `server/bare/ops/transcribe.ts` | `transcribe` + `transcribeStream` registry-routed (kind `"transcribe"`); `restorePrompt` runs via `scope.defer`; per-iteration `if (ctx.signal.aborted) break;` in the response loop |
| `server/bare/ops/translate.ts` | Registry-routed (kind `"translate"`); LLM-translate branch wires `signal β†’ addon.cancel()` (hard); NMT-translate branch is soft-cancel only (the `nmtcpp-translation.translate` handler declares `scope: "none"`); covers the `runBatch(...)` early-return path |
| `server/bare/ops/translate.ts` | Registry-routed (kind `"translate"`); LLM translate routes per-request cancellation through `response.cancel()`; NMT translate is soft-cancel only (the `nmtcpp-translation.translate` handler declares `scope: "none"`); covers the `runBatch(...)` early-return path |
| `server/bare/plugins/{llamacpp-embedding,whispercpp-transcription,parakeet-transcription,nmtcpp-translation,onnx-tts,ggml-ocr,sdcpp-generation}/plugin.ts` | Built-in plugin manifests β€” each handler declares its `cancel: { scope, hard? }` truth-table row; inference handlers thread `requestId` from the request envelope into the shared bare ops |
| `schemas/plugin.ts` | `PluginHandlerCancel` / `PluginHandlerCancelScope` types + runtime schema validation on `pluginHandlerDefinitionRuntimeSchema` |
| `client/api/completion-stream.ts` | Client-side construction of `InferenceCancelledError` on `stopReason: "cancelled"` |
Expand Down
Loading
Loading