@qvac/sdk 0.10.2 → 0.11.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 (200) hide show
  1. package/NOTICE +92 -54
  2. package/dist/client/api/cancel.d.ts +24 -32
  3. package/dist/client/api/cancel.js +58 -31
  4. package/dist/client/api/client-request-id.d.ts +17 -0
  5. package/dist/client/api/client-request-id.js +27 -0
  6. package/dist/client/api/completion-stream.d.ts +1 -1
  7. package/dist/client/api/completion-stream.js +34 -3
  8. package/dist/client/api/diffusion.d.ts +46 -0
  9. package/dist/client/api/diffusion.js +50 -1
  10. package/dist/client/api/download-asset.d.ts +12 -2
  11. package/dist/client/api/download-asset.js +21 -3
  12. package/dist/client/api/embed.d.ts +8 -4
  13. package/dist/client/api/embed.js +14 -1
  14. package/dist/client/api/get-model-info.d.ts +1 -1
  15. package/dist/client/api/index.d.ts +1 -0
  16. package/dist/client/api/index.js +1 -0
  17. package/dist/client/api/load-model.d.ts +12 -4
  18. package/dist/client/api/load-model.js +25 -1
  19. package/dist/client/api/rag.d.ts +9 -3
  20. package/dist/client/api/rag.js +23 -3
  21. package/dist/client/api/transcribe.d.ts +20 -8
  22. package/dist/client/api/transcribe.js +58 -4
  23. package/dist/client/api/unload-model.d.ts +5 -2
  24. package/dist/client/api/unload-model.js +9 -4
  25. package/dist/client/api/upscale.d.ts +45 -0
  26. package/dist/client/api/upscale.js +99 -0
  27. package/dist/client/rpc/expo-rpc-client.js +2 -1
  28. package/dist/client/rpc/node-rpc-client.js +18 -7
  29. package/dist/client/rpc/rpc-client.d.ts +2 -2
  30. package/dist/client/rpc/rpc-client.js +10 -4
  31. package/dist/client/rpc/rpc-error.d.ts +9 -0
  32. package/dist/client/rpc/rpc-error.js +92 -0
  33. package/dist/examples/cancel-by-request-id.d.ts +38 -0
  34. package/dist/examples/cancel-by-request-id.js +103 -0
  35. package/dist/examples/diffusion-esrgan-upscale.d.ts +2 -0
  36. package/dist/examples/diffusion-esrgan-upscale.js +78 -0
  37. package/dist/examples/download-with-cancel.js +10 -10
  38. package/dist/examples/embed-multi-gpu.d.ts +2 -0
  39. package/dist/examples/embed-multi-gpu.js +46 -0
  40. package/dist/examples/llamacpp-multi-gpu.d.ts +2 -0
  41. package/dist/examples/llamacpp-multi-gpu.js +57 -0
  42. package/dist/examples/rag/rag-hyperdb/cancellation.js +20 -17
  43. package/dist/examples/tools/llamacpp-tools-gemma4.d.ts +2 -0
  44. package/dist/examples/tools/llamacpp-tools-gemma4.js +76 -0
  45. package/dist/examples/tools/llamacpp-tools-qwen35.d.ts +2 -0
  46. package/dist/examples/tools/llamacpp-tools-qwen35.js +70 -0
  47. package/dist/examples/transcription/whispercpp-microphone-conversation.d.ts +2 -0
  48. package/dist/examples/transcription/whispercpp-microphone-conversation.js +102 -0
  49. package/dist/examples/tts/supertonic.js +1 -1
  50. package/dist/expo/plugins/withMobileBundle.d.ts +20 -4
  51. package/dist/expo/plugins/withMobileBundle.js +44 -6
  52. package/dist/index.d.ts +5 -2
  53. package/dist/index.js +21 -1
  54. package/dist/models/registry/models.d.ts +186 -318
  55. package/dist/models/registry/models.js +1522 -1667
  56. package/dist/pear/pre.d.ts +3 -0
  57. package/dist/pear/pre.js +3 -0
  58. package/dist/schemas/cancel.d.ts +167 -28
  59. package/dist/schemas/cancel.js +119 -19
  60. package/dist/schemas/common.d.ts +427 -208
  61. package/dist/schemas/common.js +3 -1
  62. package/dist/schemas/completion-event.d.ts +14 -0
  63. package/dist/schemas/completion-event.js +11 -1
  64. package/dist/schemas/completion-stream.d.ts +15 -1
  65. package/dist/schemas/completion-stream.js +25 -7
  66. package/dist/schemas/download-asset.d.ts +15 -9
  67. package/dist/schemas/download-asset.js +17 -6
  68. package/dist/schemas/embed.d.ts +1 -0
  69. package/dist/schemas/embed.js +5 -0
  70. package/dist/schemas/error.d.ts +31 -0
  71. package/dist/schemas/error.js +29 -1
  72. package/dist/schemas/finetune.d.ts +2 -0
  73. package/dist/schemas/finetune.js +5 -0
  74. package/dist/schemas/get-model-info.d.ts +6 -6
  75. package/dist/schemas/llamacpp-config.d.ts +54 -8
  76. package/dist/schemas/llamacpp-config.js +12 -0
  77. package/dist/schemas/load-model.d.ts +1911 -1260
  78. package/dist/schemas/load-model.js +15 -0
  79. package/dist/schemas/model-src-utils.d.ts +14 -14
  80. package/dist/schemas/model-types.d.ts +10 -10
  81. package/dist/schemas/ocr.d.ts +3 -3
  82. package/dist/schemas/plugin.d.ts +40 -1
  83. package/dist/schemas/plugin.js +10 -1
  84. package/dist/schemas/rag.d.ts +9 -0
  85. package/dist/schemas/rag.js +17 -9
  86. package/dist/schemas/registry.d.ts +30 -30
  87. package/dist/schemas/reload-config.d.ts +14 -14
  88. package/dist/schemas/sdcpp-config.d.ts +131 -17
  89. package/dist/schemas/sdcpp-config.js +163 -1
  90. package/dist/schemas/sdk-config.d.ts +649 -271
  91. package/dist/schemas/sdk-errors-client.d.ts +1 -0
  92. package/dist/schemas/sdk-errors-client.js +5 -0
  93. package/dist/schemas/sdk-errors-server.d.ts +5 -0
  94. package/dist/schemas/sdk-errors-server.js +25 -0
  95. package/dist/schemas/text-to-speech.d.ts +78 -78
  96. package/dist/schemas/transcription-config.d.ts +24 -24
  97. package/dist/schemas/transcription.d.ts +56 -2
  98. package/dist/schemas/transcription.js +27 -0
  99. package/dist/schemas/translate.d.ts +2 -0
  100. package/dist/schemas/translate.js +13 -2
  101. package/dist/schemas/translation-config.d.ts +40 -40
  102. package/dist/schemas/unload-model.d.ts +2 -1
  103. package/dist/schemas/unload-model.js +4 -1
  104. package/dist/scripts/run-unit-tests.js +15 -2
  105. package/dist/server/bare/ops/cancel.d.ts +27 -1
  106. package/dist/server/bare/ops/cancel.js +36 -10
  107. package/dist/server/bare/ops/embed.d.ts +3 -3
  108. package/dist/server/bare/ops/embed.js +144 -28
  109. package/dist/server/bare/ops/kv-cache-utils.d.ts +0 -7
  110. package/dist/server/bare/ops/kv-cache-utils.js +8 -47
  111. package/dist/server/bare/ops/transcribe.d.ts +10 -5
  112. package/dist/server/bare/ops/transcribe.js +196 -41
  113. package/dist/server/bare/ops/translate.d.ts +1 -1
  114. package/dist/server/bare/ops/translate.js +197 -88
  115. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.d.ts +12 -3
  116. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +170 -172
  117. package/dist/server/bare/plugins/llamacpp-completion/ops/finetune.js +125 -21
  118. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.d.ts +154 -0
  119. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.js +448 -0
  120. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.d.ts +21 -73
  121. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.js +18 -101
  122. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +30 -3
  123. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +157 -85
  124. package/dist/server/bare/plugins/llamacpp-completion/transform.d.ts +10 -0
  125. package/dist/server/bare/plugins/llamacpp-completion/transform.js +34 -0
  126. package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +7 -0
  127. package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +10 -1
  128. package/dist/server/bare/plugins/nmtcpp-translation/plugin.d.ts +17 -15
  129. package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +5 -1
  130. package/dist/server/bare/plugins/onnx-ocr/plugin.d.ts +3 -3
  131. package/dist/server/bare/plugins/onnx-ocr/plugin.js +3 -0
  132. package/dist/server/bare/plugins/onnx-tts/plugin.d.ts +39 -39
  133. package/dist/server/bare/plugins/onnx-tts/plugin.js +6 -0
  134. package/dist/server/bare/plugins/parakeet-transcription/plugin.d.ts +29 -21
  135. package/dist/server/bare/plugins/parakeet-transcription/plugin.js +4 -1
  136. package/dist/server/bare/plugins/sdcpp-generation/ops/diffusion.js +8 -0
  137. package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.d.ts +3 -0
  138. package/dist/server/bare/plugins/sdcpp-generation/ops/upscale.js +40 -0
  139. package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +91 -16
  140. package/dist/server/bare/plugins/sdcpp-generation/plugin.js +97 -7
  141. package/dist/server/bare/plugins/whispercpp-transcription/plugin.d.ts +22 -3
  142. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +50 -14
  143. package/dist/server/bare/rag-hyperdb/index.d.ts +1 -1
  144. package/dist/server/bare/rag-hyperdb/index.js +1 -1
  145. package/dist/server/bare/rag-hyperdb/rag-operation-manager.d.ts +26 -4
  146. package/dist/server/bare/rag-hyperdb/rag-operation-manager.js +65 -28
  147. package/dist/server/bare/runtime/disposable-scope.d.ts +36 -0
  148. package/dist/server/bare/runtime/disposable-scope.js +69 -0
  149. package/dist/server/bare/runtime/index.d.ts +8 -0
  150. package/dist/server/bare/runtime/index.js +3 -0
  151. package/dist/server/bare/runtime/request-context.d.ts +49 -0
  152. package/dist/server/bare/runtime/request-context.js +1 -0
  153. package/dist/server/bare/runtime/request-id.d.ts +12 -0
  154. package/dist/server/bare/runtime/request-id.js +14 -0
  155. package/dist/server/bare/runtime/request-registry-singleton.d.ts +4 -0
  156. package/dist/server/bare/runtime/request-registry-singleton.js +27 -0
  157. package/dist/server/bare/runtime/request-registry.d.ts +136 -0
  158. package/dist/server/bare/runtime/request-registry.js +297 -0
  159. package/dist/server/bare/runtime/with-request-context.d.ts +22 -0
  160. package/dist/server/bare/runtime/with-request-context.js +53 -0
  161. package/dist/server/bare/types/addon-responses.d.ts +11 -1
  162. package/dist/server/rpc/handler-registry.js +26 -7
  163. package/dist/server/rpc/handlers/cancel-delegated.js +28 -31
  164. package/dist/server/rpc/handlers/cancelHandler.d.ts +30 -1
  165. package/dist/server/rpc/handlers/cancelHandler.js +54 -22
  166. package/dist/server/rpc/handlers/delete-cache.d.ts +9 -0
  167. package/dist/server/rpc/handlers/delete-cache.js +13 -15
  168. package/dist/server/rpc/handlers/download-asset.js +133 -40
  169. package/dist/server/rpc/handlers/index.d.ts +2 -0
  170. package/dist/server/rpc/handlers/index.js +2 -0
  171. package/dist/server/rpc/handlers/load-model/download-manager.d.ts +60 -2
  172. package/dist/server/rpc/handlers/load-model/download-manager.js +141 -8
  173. package/dist/server/rpc/handlers/load-model/handler.js +213 -98
  174. package/dist/server/rpc/handlers/load-model/http.js +3 -3
  175. package/dist/server/rpc/handlers/load-model/hyperdrive.js +1 -1
  176. package/dist/server/rpc/handlers/load-model/registry.js +1 -1
  177. package/dist/server/rpc/handlers/load-model/resolve-session.d.ts +20 -1
  178. package/dist/server/rpc/handlers/load-model/resolve-session.js +6 -5
  179. package/dist/server/rpc/handlers/load-model/resolve.d.ts +3 -2
  180. package/dist/server/rpc/handlers/load-model/resolve.js +9 -6
  181. package/dist/server/rpc/handlers/load-model/types.d.ts +24 -0
  182. package/dist/server/rpc/handlers/rag.js +133 -15
  183. package/dist/server/rpc/handlers/upscale-stream.d.ts +3 -0
  184. package/dist/server/rpc/handlers/upscale-stream.js +4 -0
  185. package/dist/server/utils/completion-normalizer.js +8 -0
  186. package/dist/server/utils/tools/dialect.js +4 -0
  187. package/dist/server/utils/tools/parser.js +16 -1
  188. package/dist/server/utils/tools/parsers/gemma4native.d.ts +4 -0
  189. package/dist/server/utils/tools/parsers/gemma4native.js +60 -0
  190. package/dist/server/utils/tools/parsers/qwen35.d.ts +4 -0
  191. package/dist/server/utils/tools/parsers/qwen35.js +120 -0
  192. package/dist/utils/aggregate-events.d.ts +9 -0
  193. package/dist/utils/aggregate-events.js +18 -3
  194. package/dist/utils/decorate-promise.d.ts +34 -0
  195. package/dist/utils/decorate-promise.js +35 -0
  196. package/dist/utils/errors-client.d.ts +3 -0
  197. package/dist/utils/errors-client.js +5 -0
  198. package/dist/utils/errors-server.d.ts +85 -0
  199. package/dist/utils/errors-server.js +97 -0
  200. package/package.json +20 -15
@@ -0,0 +1,136 @@
1
+ import { type AbortSignal } from "bare-abort-controller";
2
+ import type { RequestContext, RequestKind } from "./request-context";
3
+ import type { Logger } from "../../../logging/types";
4
+ /**
5
+ * Outcome the caller declares when terminating a request through
6
+ * `registry.end(...)`. The registry maps it to a terminal `RequestState`
7
+ * before disposing the scope so observers see a coherent final state.
8
+ */
9
+ export type RequestOutcome = "completed" | "failed" | "cancelled";
10
+ export interface BeginOpts {
11
+ /** Stable identity. Caller-provided so the client and server agree. */
12
+ requestId: string;
13
+ kind: RequestKind;
14
+ modelId?: string;
15
+ /**
16
+ * Optional parent abort signal — typically the worker-level "shutdown"
17
+ * signal. When the parent aborts, the request's own signal aborts too.
18
+ * Composes through a `addEventListener("abort", ...)` hook so cancelling
19
+ * the parent does not require iterating the registry.
20
+ */
21
+ parentSignal?: AbortSignal;
22
+ }
23
+ export interface CancelByRequestId {
24
+ requestId: string;
25
+ reason?: string;
26
+ }
27
+ export interface CancelByModelId {
28
+ modelId: string;
29
+ kind?: RequestKind;
30
+ reason?: string;
31
+ }
32
+ export type CancelTarget = CancelByRequestId | CancelByModelId;
33
+ /**
34
+ * Per-kind admission rule. Kinds without a registered policy have no
35
+ * admission control (every `begin(...)` is accepted as long as the
36
+ * request id is unique).
37
+ */
38
+ export interface ConcurrencyPolicy {
39
+ kind: RequestKind;
40
+ /**
41
+ * When true, at most one in-flight request per `(kind, modelId)`
42
+ * tuple — a second `begin(...)` rejects with
43
+ * `RequestRejectedByPolicyError`. Requests without a `modelId` are
44
+ * unaffected.
45
+ */
46
+ oneAtATimePerModel: boolean;
47
+ }
48
+ /**
49
+ * `ManagedRequestContext` is the value `begin(...)` returns. It extends
50
+ * `RequestContext` with an async-dispose method so handlers can write:
51
+ *
52
+ * await using ctx = registry.begin({ ... });
53
+ *
54
+ * On dispose the scope unwinds (LIFO cleanup) and the registry slot is
55
+ * freed. If the handler doesn't override `ctx.state` before unwinding,
56
+ * the registry derives the terminal state from `signal.aborted` —
57
+ * `"cancelled"` when an abort was recorded, `"completed"` otherwise.
58
+ */
59
+ export interface ManagedRequestContext extends RequestContext {
60
+ [Symbol.asyncDispose](): Promise<void>;
61
+ }
62
+ export interface RequestRegistry {
63
+ /**
64
+ * Open a new request. Throws:
65
+ * - `RequestIdConflictError` if `requestId` is already present
66
+ * (UUIDv4 collision is astronomically unlikely; the guard exists
67
+ * so a buggy client retry sending the same id can't silently
68
+ * overwrite an in-flight request).
69
+ * - `RequestRejectedByPolicyError` if a concurrency policy was
70
+ * registered for `opts.kind` and the new request would violate
71
+ * it (e.g. `oneAtATimePerModel` rejects when another request
72
+ * with the same `(kind, modelId)` pair is already in flight).
73
+ */
74
+ begin(opts: BeginOpts): ManagedRequestContext;
75
+ /**
76
+ * Register or replace the concurrency policy for a `RequestKind`.
77
+ * Subsequent `begin(...)` calls for that kind run the policy before
78
+ * allocating a controller / scope. One policy per kind — calling
79
+ * twice replaces the previous declaration.
80
+ *
81
+ * @example
82
+ * r.policy({ kind: "completion", oneAtATimePerModel: true });
83
+ * await using a = r.begin({ requestId: "r-1", kind: "completion", modelId: "m1" });
84
+ * r.begin({ requestId: "r-2", kind: "completion", modelId: "m1" });
85
+ * // → throws RequestRejectedByPolicyError (code 52420)
86
+ */
87
+ policy(opts: ConcurrencyPolicy): void;
88
+ /** Look up an in-flight request by id. */
89
+ get(requestId: string): RequestContext | null;
90
+ /**
91
+ * Snapshot of currently-tracked requests. Useful for diagnostics /
92
+ * structured logs ("which requests are in flight right now?"). Returns
93
+ * a fresh array; mutations on it are not observed by the registry.
94
+ */
95
+ list(): RequestContext[];
96
+ /**
97
+ * Cancel matching requests. Returns the number of contexts whose abort
98
+ * was triggered by *this* call (already-cancelled contexts are skipped
99
+ * so callers can rely on the count to log "n requests cancelled" once).
100
+ *
101
+ * For `{ modelId }` and an optional `kind`, cancels every active
102
+ * request that matches the predicate. This is the broad-cancel path
103
+ * the pre-registry `cancel({ modelId })` API maps to.
104
+ */
105
+ cancel(target: CancelTarget): number;
106
+ /**
107
+ * Cancel every active request — the worker-shutdown / model-unload
108
+ * sweep. The reason is forwarded to each request as the abort reason
109
+ * so handler logs can distinguish a normal cancel from a sweep.
110
+ * Resolves once all targeted contexts have flipped to `"cancelling"`;
111
+ * scope unwinding still happens on each handler's own dispose path.
112
+ */
113
+ cancelAll(reason: "shutdown" | "modelUnload"): Promise<void>;
114
+ /**
115
+ * Mark a request finished and dispose its scope. Equivalent to
116
+ * `await ctx[Symbol.asyncDispose]()` with an explicit outcome.
117
+ * Idempotent — calling `end` after a scope dispose is a no-op.
118
+ */
119
+ end(requestId: string, outcome: RequestOutcome): Promise<void>;
120
+ }
121
+ export declare function createRequestRegistry(options?: {
122
+ /** Defaults to `getServerLogger()`. Tests inject a stub. */
123
+ logger?: Logger;
124
+ }): RequestRegistry;
125
+ /**
126
+ * Test-only knobs exported for `request-registry.test.ts` so the bound
127
+ * assertions can pin the documented limits without re-reading them via
128
+ * fragile string comparison. **Not part of the public SDK surface.**
129
+ *
130
+ * @internal
131
+ */
132
+ export declare const __requestRegistryTestHooks: {
133
+ cancelBeforeBeginMaxEntries: number;
134
+ cancelBeforeBeginTtlMs: number;
135
+ };
136
+ //# sourceMappingURL=request-registry.d.ts.map
@@ -0,0 +1,297 @@
1
+ import { AbortController } from "bare-abort-controller";
2
+ import { createDisposableScope, } from "./disposable-scope.js";
3
+ import { RequestIdConflictError, RequestRejectedByPolicyError, } from "../../../utils/errors-server.js";
4
+ import { getServerLogger } from "../../../logging/index.js";
5
+ /**
6
+ * Tuning knobs for the "cancelled-before-begin" bookkeeping set.
7
+ *
8
+ * The race window is bounded by the client-to-server round-trip: a
9
+ * `cancel({ requestId })` issued by the client at the same time as the
10
+ * matching `completion(...)` either lands first (and we need to remember
11
+ * it long enough for the server's `begin(...)` to follow) or lands
12
+ * second (and we never touch this set). 30 seconds is overkill for a
13
+ * 500ms round-trip but gives slow networks / pause-the-debugger
14
+ * scenarios enough slack while still bounding worst-case retention.
15
+ *
16
+ * The size cap protects against a buggy or malicious client firing a
17
+ * stream of cancels for ids that never get a `begin(...)` follow-up —
18
+ * each `cancel({ requestId })` that doesn't match an in-flight context
19
+ * inserts one entry, so without a cap the worker would grow the map
20
+ * unbounded. At the cap, the oldest entry is evicted.
21
+ *
22
+ * Tweak with care: both bounds appear in the registry race test
23
+ * (`bounded cancel-before-begin set does not grow past its cap`).
24
+ */
25
+ const CANCEL_BEFORE_BEGIN_MAX_ENTRIES = 128;
26
+ const CANCEL_BEFORE_BEGIN_TTL_MS = 30_000;
27
+ export function createRequestRegistry(options) {
28
+ const entries = new Map();
29
+ const policies = new Map();
30
+ const logger = options?.logger ?? getServerLogger();
31
+ function logLifecycle(event, ctx, durationMs) {
32
+ const modelId = ctx.modelId !== undefined ? ctx.modelId : "-";
33
+ const base = `[request-lifecycle] ${event} requestId=${ctx.requestId} kind=${ctx.kind} modelId=${modelId} state=${ctx.state}`;
34
+ const line = durationMs !== undefined ? `${base} durationMs=${durationMs}` : base;
35
+ // `failed` end emits at `warn` so log shippers can alert on
36
+ // `level>=warn` for this prefix without parsing `state=failed`
37
+ // out of the message body. Everything else stays at `info`.
38
+ if (event === "end" && ctx.state === "failed") {
39
+ logger.warn(line);
40
+ }
41
+ else {
42
+ logger.info(line);
43
+ }
44
+ }
45
+ /**
46
+ * "Cancelled-before-begin" tripwire. A
47
+ * `cancel({ requestId })` whose target isn't yet in `entries` records
48
+ * the id here; the subsequent `begin({ requestId: <same id> })` then
49
+ * aborts the new controller before returning. Map order is insertion
50
+ * order — the iterator's first key is the oldest entry, which makes
51
+ * the size-cap eviction free.
52
+ *
53
+ * Invariants:
54
+ * - Every read path (`begin`, `cancel`-by-id) calls
55
+ * `pruneCancelBeforeBeginExpired()` first so a 30s+ stale entry
56
+ * never decides a fresh `begin(...)`.
57
+ * - Insertion enforces the size cap by evicting the oldest entry
58
+ * when at capacity — a malicious client cannot grow this map
59
+ * unbounded.
60
+ * - On a successful `begin(...)` match, the entry is consumed
61
+ * (removed) so a second `begin(...)` with the same id (which
62
+ * would itself throw `RequestIdConflictError`) doesn't see a
63
+ * phantom pre-cancel.
64
+ */
65
+ const cancelledBeforeBegin = new Map();
66
+ function pruneCancelBeforeBeginExpired(now = Date.now()) {
67
+ if (cancelledBeforeBegin.size === 0)
68
+ return;
69
+ const cutoff = now - CANCEL_BEFORE_BEGIN_TTL_MS;
70
+ for (const [id, entry] of cancelledBeforeBegin) {
71
+ if (entry.at > cutoff)
72
+ break; // Insertion order ⇒ rest are newer.
73
+ cancelledBeforeBegin.delete(id);
74
+ }
75
+ }
76
+ function recordCancelBeforeBegin(requestId, reason) {
77
+ const now = Date.now();
78
+ pruneCancelBeforeBeginExpired(now);
79
+ // Re-canceling an id that is already tracked refreshes its TTL but
80
+ // keeps the original reason — the first cancel "won" the race.
81
+ if (cancelledBeforeBegin.has(requestId)) {
82
+ const existing = cancelledBeforeBegin.get(requestId);
83
+ cancelledBeforeBegin.delete(requestId);
84
+ cancelledBeforeBegin.set(requestId, { ...existing, at: now });
85
+ return;
86
+ }
87
+ if (cancelledBeforeBegin.size >= CANCEL_BEFORE_BEGIN_MAX_ENTRIES) {
88
+ const oldest = cancelledBeforeBegin.keys().next().value;
89
+ if (oldest !== undefined)
90
+ cancelledBeforeBegin.delete(oldest);
91
+ }
92
+ cancelledBeforeBegin.set(requestId, reason !== undefined ? { at: now, reason } : { at: now });
93
+ }
94
+ function consumeCancelBeforeBegin(requestId) {
95
+ pruneCancelBeforeBeginExpired();
96
+ const entry = cancelledBeforeBegin.get(requestId);
97
+ if (!entry)
98
+ return undefined;
99
+ cancelledBeforeBegin.delete(requestId);
100
+ return entry;
101
+ }
102
+ function cancelEntry(entry, reason) {
103
+ if (entry.controller.signal.aborted)
104
+ return false;
105
+ entry.ctx.state = "cancelling";
106
+ entry.controller.abort(reason);
107
+ logLifecycle("cancel", entry.ctx);
108
+ return true;
109
+ }
110
+ async function disposeEntry(entry, outcome) {
111
+ if (entry.scope.disposed)
112
+ return;
113
+ entry.ctx.state = outcome;
114
+ entry.detachParent();
115
+ logLifecycle("end", entry.ctx, Date.now() - entry.startedAt);
116
+ // Pull the entry out before unwinding so observers (e.g. a `cancel(...)`
117
+ // racing with dispose) don't see a half-disposed context.
118
+ entries.delete(entry.ctx.requestId);
119
+ await entry.scope[Symbol.asyncDispose]();
120
+ }
121
+ function begin(opts) {
122
+ if (entries.has(opts.requestId)) {
123
+ throw new RequestIdConflictError(opts.requestId);
124
+ }
125
+ // Admission control runs before allocation so a rejected begin
126
+ // leaves no controller / scope behind.
127
+ const policy = policies.get(opts.kind);
128
+ if (policy && policy.oneAtATimePerModel && opts.modelId !== undefined) {
129
+ for (const existing of entries.values()) {
130
+ if (existing.ctx.kind !== opts.kind)
131
+ continue;
132
+ if (existing.ctx.modelId !== opts.modelId)
133
+ continue;
134
+ throw new RequestRejectedByPolicyError(opts.requestId, opts.kind, opts.modelId, `another ${opts.kind} request (${existing.ctx.requestId}) is already running on this model`);
135
+ }
136
+ }
137
+ const controller = new AbortController();
138
+ const scope = createDisposableScope();
139
+ // Stop-button race close. If a
140
+ // `cancel({ requestId })` already arrived for this id, abort the
141
+ // new controller before observers can subscribe to it. The
142
+ // tripwire entry is consumed so a later, separate `begin(...)`
143
+ // with the same id is unaffected (in practice ids are UUIDv4 and
144
+ // never reused; this guard just keeps the contract self-
145
+ // consistent under retries).
146
+ const preCancel = consumeCancelBeforeBegin(opts.requestId);
147
+ if (preCancel) {
148
+ controller.abort(preCancel.reason);
149
+ }
150
+ let detachParent = () => { };
151
+ if (opts.parentSignal) {
152
+ const parent = opts.parentSignal;
153
+ if (parent.aborted) {
154
+ controller.abort(parent.reason);
155
+ }
156
+ else {
157
+ const onParentAbort = () => controller.abort(parent.reason);
158
+ parent.addEventListener("abort", onParentAbort, { once: true });
159
+ detachParent = () => parent.removeEventListener("abort", onParentAbort);
160
+ }
161
+ }
162
+ const ctx = {
163
+ requestId: opts.requestId,
164
+ kind: opts.kind,
165
+ modelId: opts.modelId,
166
+ signal: controller.signal,
167
+ scope,
168
+ // Land the context in `cancelling` from the outset whenever the
169
+ // controller was already aborted by `begin(...)` itself — either
170
+ // the Stop-button race (`preCancel`) or a `parentSignal` that was
171
+ // already aborted at begin time. Both branches abort the
172
+ // controller above, so without this guard observers would see a
173
+ // momentarily-`running` context with an already-aborted signal.
174
+ state: preCancel || opts.parentSignal?.aborted ? "cancelling" : "running",
175
+ };
176
+ const entry = {
177
+ ctx,
178
+ controller,
179
+ scope,
180
+ detachParent,
181
+ startedAt: Date.now(),
182
+ };
183
+ entries.set(opts.requestId, entry);
184
+ logLifecycle("begin", ctx);
185
+ return {
186
+ get requestId() {
187
+ return ctx.requestId;
188
+ },
189
+ get kind() {
190
+ return ctx.kind;
191
+ },
192
+ get modelId() {
193
+ return ctx.modelId;
194
+ },
195
+ get signal() {
196
+ return ctx.signal;
197
+ },
198
+ get scope() {
199
+ return ctx.scope;
200
+ },
201
+ get state() {
202
+ return ctx.state;
203
+ },
204
+ set state(next) {
205
+ ctx.state = next;
206
+ },
207
+ [Symbol.asyncDispose]: async () => {
208
+ await disposeEntry(entry, derivedTerminalState(ctx));
209
+ },
210
+ };
211
+ }
212
+ function get(requestId) {
213
+ return entries.get(requestId)?.ctx ?? null;
214
+ }
215
+ function list() {
216
+ return Array.from(entries.values(), (e) => e.ctx);
217
+ }
218
+ function cancel(target) {
219
+ let cancelled = 0;
220
+ if ("requestId" in target) {
221
+ const entry = entries.get(target.requestId);
222
+ if (entry) {
223
+ if (cancelEntry(entry, target.reason))
224
+ cancelled++;
225
+ return cancelled;
226
+ }
227
+ // Stop-button race: the client beat its own
228
+ // `begin(...)`. Record the cancel so the next matching `begin`
229
+ // aborts immediately. The return value stays 0 — no in-flight
230
+ // request was matched, which is still the truth — but the
231
+ // *effective* cancel will land when the begin arrives.
232
+ recordCancelBeforeBegin(target.requestId, target.reason);
233
+ return cancelled;
234
+ }
235
+ for (const entry of entries.values()) {
236
+ if (entry.ctx.modelId !== target.modelId)
237
+ continue;
238
+ if (target.kind && entry.ctx.kind !== target.kind)
239
+ continue;
240
+ if (cancelEntry(entry, target.reason))
241
+ cancelled++;
242
+ }
243
+ return cancelled;
244
+ }
245
+ function cancelAll(reason) {
246
+ for (const entry of entries.values()) {
247
+ cancelEntry(entry, reason);
248
+ }
249
+ // The interface returns Promise<void> so we can later make this an
250
+ // async sweep that awaits per-handler scope unwinding (e.g. join on
251
+ // the disposers). Today every handler unwinds on its own dispose
252
+ // path, so the function only needs to fire-and-forget the abort.
253
+ return Promise.resolve();
254
+ }
255
+ async function end(requestId, outcome) {
256
+ const entry = entries.get(requestId);
257
+ if (!entry)
258
+ return;
259
+ await disposeEntry(entry, outcome);
260
+ }
261
+ function policy(opts) {
262
+ policies.set(opts.kind, opts);
263
+ }
264
+ return {
265
+ begin,
266
+ get,
267
+ list,
268
+ cancel,
269
+ cancelAll,
270
+ end,
271
+ policy,
272
+ // Test-only: lets the registry race tests assert the bound
273
+ // invariants on the internal "cancelled-before-begin" set without
274
+ // exposing it as a public surface. Kept off the `RequestRegistry`
275
+ // interface (typed via the augmented return type below) so handler
276
+ // code can't depend on it accidentally.
277
+ __cancelBeforeBeginSize: () => cancelledBeforeBegin.size,
278
+ };
279
+ }
280
+ /**
281
+ * Test-only knobs exported for `request-registry.test.ts` so the bound
282
+ * assertions can pin the documented limits without re-reading them via
283
+ * fragile string comparison. **Not part of the public SDK surface.**
284
+ *
285
+ * @internal
286
+ */
287
+ export const __requestRegistryTestHooks = {
288
+ cancelBeforeBeginMaxEntries: CANCEL_BEFORE_BEGIN_MAX_ENTRIES,
289
+ cancelBeforeBeginTtlMs: CANCEL_BEFORE_BEGIN_TTL_MS,
290
+ };
291
+ function derivedTerminalState(ctx) {
292
+ if (ctx.state === "failed")
293
+ return "failed";
294
+ if (ctx.signal.aborted || ctx.state === "cancelled")
295
+ return "cancelled";
296
+ return "completed";
297
+ }
@@ -0,0 +1,22 @@
1
+ import type { Logger } from "../../../logging/types";
2
+ export interface RequestLogContext {
3
+ requestId: string;
4
+ kind: string;
5
+ modelId: string | undefined;
6
+ }
7
+ /**
8
+ * Wraps `logger` so every emit is prefixed with
9
+ * `[request-lifecycle <kind> requestId=<id> modelId=<modelId>] `
10
+ * (the `modelId=...` segment is dropped when absent). The wrapper is a
11
+ * thin shim: `setLevel` / `getLevel` / `addTransport` / `setConsoleOutput`
12
+ * pass through to the underlying logger, and transport callbacks receive
13
+ * the prefixed message.
14
+ *
15
+ * @example
16
+ * await using ctx = registry.begin({ requestId, kind: "completion", modelId });
17
+ * const log = withRequestContext(getServerLogger(), ctx);
18
+ * log.info("decoding token 7");
19
+ * // → "[request-lifecycle completion requestId=<id> modelId=<id>] decoding token 7"
20
+ */
21
+ export declare function withRequestContext(logger: Logger, ctx: RequestLogContext): Logger;
22
+ //# sourceMappingURL=with-request-context.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Wraps `logger` so every emit is prefixed with
3
+ * `[request-lifecycle <kind> requestId=<id> modelId=<modelId>] `
4
+ * (the `modelId=...` segment is dropped when absent). The wrapper is a
5
+ * thin shim: `setLevel` / `getLevel` / `addTransport` / `setConsoleOutput`
6
+ * pass through to the underlying logger, and transport callbacks receive
7
+ * the prefixed message.
8
+ *
9
+ * @example
10
+ * await using ctx = registry.begin({ requestId, kind: "completion", modelId });
11
+ * const log = withRequestContext(getServerLogger(), ctx);
12
+ * log.info("decoding token 7");
13
+ * // → "[request-lifecycle completion requestId=<id> modelId=<id>] decoding token 7"
14
+ */
15
+ export function withRequestContext(logger, ctx) {
16
+ const prefix = ctx.modelId !== undefined
17
+ ? `[request-lifecycle ${ctx.kind} requestId=${ctx.requestId} modelId=${ctx.modelId}] `
18
+ : `[request-lifecycle ${ctx.kind} requestId=${ctx.requestId}] `;
19
+ function pick(method) {
20
+ switch (method) {
21
+ case "error":
22
+ return logger.error;
23
+ case "warn":
24
+ return logger.warn;
25
+ case "info":
26
+ return logger.info;
27
+ case "debug":
28
+ return logger.debug;
29
+ case "trace":
30
+ return logger.trace;
31
+ }
32
+ }
33
+ function emit(method, args) {
34
+ const sink = pick(method);
35
+ if (args.length === 0) {
36
+ sink(prefix);
37
+ return;
38
+ }
39
+ const [first, ...rest] = args;
40
+ sink(prefix + String(first), ...rest);
41
+ }
42
+ return {
43
+ error: (...args) => emit("error", args),
44
+ warn: (...args) => emit("warn", args),
45
+ info: (...args) => emit("info", args),
46
+ debug: (...args) => emit("debug", args),
47
+ trace: (...args) => emit("trace", args),
48
+ setLevel: (level) => logger.setLevel(level),
49
+ getLevel: () => logger.getLevel(),
50
+ addTransport: (transport) => logger.addTransport(transport),
51
+ setConsoleOutput: (enabled) => logger.setConsoleOutput(enabled),
52
+ };
53
+ }
@@ -60,8 +60,18 @@ export interface TranscribeAddonSegment {
60
60
  toAppend?: boolean;
61
61
  id?: number;
62
62
  }
63
+ export interface TranscribeAddonVadEvent {
64
+ type: "vad";
65
+ speaking: boolean;
66
+ probability: number;
67
+ }
68
+ export interface TranscribeAddonEndOfTurnEvent {
69
+ type: "endOfTurn";
70
+ silenceDurationMs: number;
71
+ }
72
+ export type TranscribeAddonOutput = Array<TranscribeAddonSegment> | TranscribeAddonVadEvent | TranscribeAddonEndOfTurnEvent;
63
73
  export interface TranscribeResponse {
64
74
  stats?: TranscribeStats;
65
- iterate(): AsyncIterable<Array<TranscribeAddonSegment>>;
75
+ iterate(): AsyncIterable<TranscribeAddonOutput>;
66
76
  }
67
77
  //# sourceMappingURL=addon-responses.d.ts.map
@@ -27,6 +27,7 @@ import { handleFinetune } from "./handlers/finetune.js";
27
27
  import { handleHeartbeatDelegated } from "./handlers/heartbeat-delegated.js";
28
28
  import { handleCancelDelegated } from "./handlers/cancel-delegated.js";
29
29
  import { handleDiffusionStream } from "./handlers/diffusion-stream.js";
30
+ import { handleUpscaleStream } from "./handlers/upscale-stream.js";
30
31
  import { handlePluginInvoke, handlePluginInvokeStream, } from "./handlers/plugin-invoke.js";
31
32
  import { handleModelRegistryList, handleModelRegistrySearch, handleModelRegistryGetModel, } from "./handlers/registry.js";
32
33
  import { handleSuspend } from "./handlers/suspend.js";
@@ -48,16 +49,33 @@ function isModelDelegated(request) {
48
49
  const entry = getModelEntry(request.modelId);
49
50
  return entry?.isDelegated ?? false;
50
51
  }
52
+ /**
53
+ * Should the cancel be forwarded to a delegated provider?
54
+ *
55
+ * After the 0.11.0 wire-schema collapse the cancel envelope has two
56
+ * operations:
57
+ *
58
+ * - `request` — targeted cancel by `requestId`. Always handled
59
+ * locally: the worker-singleton `RequestRegistry` is the source of
60
+ * truth for active requests (delegated handlers register their own
61
+ * requests on it the same way local handlers do), so a `requestId`
62
+ * cancel always lands on the right worker without needing a hop
63
+ * through the provider. Returning `false` here keeps the cancel on
64
+ * the local cancel handler, where it routes through the registry
65
+ * and (for downloads) the `markClearCacheForRequest` helper.
66
+ *
67
+ * - `broad` — abort every in-flight request on a model. Forwarded to
68
+ * the delegated provider iff the targeted model itself is
69
+ * delegated; the provider then runs the same broad-cancel sweep
70
+ * server-side. Local broad cancels for non-delegated models stay
71
+ * on this worker.
72
+ */
51
73
  function isCancelDelegated(request) {
52
74
  if (request.type !== "cancel")
53
75
  return false;
54
- if (request.operation === "inference" || request.operation === "embeddings") {
55
- return isModelDelegated(request);
56
- }
57
- if (request.operation === "downloadAsset") {
58
- return !!request.delegate;
59
- }
60
- return false;
76
+ if (request.operation !== "broad")
77
+ return false;
78
+ return isModelDelegated(request);
61
79
  }
62
80
  export const registry = {
63
81
  // Simple Reply handlers
@@ -104,6 +122,7 @@ export const registry = {
104
122
  textToSpeechStream: { type: "duplex", handler: handleTextToSpeechStream },
105
123
  ocrStream: { type: "stream", handler: handleOCRStream },
106
124
  diffusionStream: { type: "stream", handler: handleDiffusionStream },
125
+ upscaleStream: { type: "stream", handler: handleUpscaleStream },
107
126
  pluginInvokeStream: { type: "stream", handler: handlePluginInvokeStream },
108
127
  // Handlers with delegation support
109
128
  loadModel: {
@@ -3,38 +3,35 @@ import { getModelEntry } from "../../bare/registry/model-registry.js";
3
3
  import { getRPC } from "../../bare/delegate-rpc-client.js";
4
4
  import { send } from "../delegate-transport.js";
5
5
  const logger = getServerLogger();
6
+ /**
7
+ * Resolve the delegated provider for a cancel request, if any.
8
+ *
9
+ * After the 0.11.0 wire-schema collapse the cancel envelope has only
10
+ * two operations. Only `broad` cancels delegate at the cancel layer —
11
+ * see `isCancelDelegated` in `handler-registry.ts` for the policy and
12
+ * the rationale.
13
+ *
14
+ * The targeted `request` arm is handled locally because the registry
15
+ * is worker-singleton and already holds the entry for delegated
16
+ * requests (the delegated handler registers its own context on the
17
+ * provider-facing side). For pre-0.11.0 behaviour where a `requestId`
18
+ * cancel against a delegated model needed to round-trip to the
19
+ * provider, hold onto the delegated `loadModel(...).requestId` and
20
+ * fire a broad cancel against the model id instead.
21
+ */
6
22
  function resolveDelegationTarget(request) {
7
- if (request.operation === "inference" || request.operation === "embeddings") {
8
- const entry = getModelEntry(request.modelId);
9
- if (!entry?.isDelegated) {
10
- return null;
11
- }
12
- const target = {
13
- providerPublicKey: entry.delegated.providerPublicKey,
14
- };
15
- if (entry.delegated.timeout !== undefined) {
16
- target.timeout = entry.delegated.timeout;
17
- }
18
- return target;
19
- }
20
- if (request.operation === "downloadAsset" && request.delegate) {
21
- const target = {
22
- providerPublicKey: request.delegate.providerPublicKey,
23
- };
24
- if (request.delegate.timeout !== undefined) {
25
- target.timeout = request.delegate.timeout;
26
- }
27
- return target;
28
- }
29
- return null;
30
- }
31
- function toProviderCancelRequest(request) {
32
- if (request.operation !== "downloadAsset") {
33
- return request;
23
+ if (request.operation !== "broad")
24
+ return null;
25
+ const entry = getModelEntry(request.modelId);
26
+ if (!entry?.isDelegated)
27
+ return null;
28
+ const target = {
29
+ providerPublicKey: entry.delegated.providerPublicKey,
30
+ };
31
+ if (entry.delegated.timeout !== undefined) {
32
+ target.timeout = entry.delegated.timeout;
34
33
  }
35
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
36
- const { delegate: _delegate, ...providerRequest } = request;
37
- return providerRequest;
34
+ return target;
38
35
  }
39
36
  export async function handleCancelDelegated(request, options) {
40
37
  const target = resolveDelegationTarget(request);
@@ -55,7 +52,7 @@ export async function handleCancelDelegated(request, options) {
55
52
  if (options?.profilingMeta) {
56
53
  delegateOpts.profilingMeta = options.profilingMeta;
57
54
  }
58
- await send(toProviderCancelRequest(request), rpc, delegateOpts);
55
+ await send(request, rpc, delegateOpts);
59
56
  return { type: "cancel", success: true };
60
57
  }
61
58
  catch (error) {