@qvac/sdk 0.15.0 → 0.16.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 (102) hide show
  1. package/NOTICE +31 -45
  2. package/dist/client/api/ocr.d.ts +9 -0
  3. package/dist/client/api/ocr.js +9 -0
  4. package/dist/client/api/translate.js +6 -17
  5. package/dist/client/api/video.d.ts +7 -4
  6. package/dist/client/api/video.js +7 -4
  7. package/dist/client/api/vla.d.ts +14 -10
  8. package/dist/client/api/vla.js +14 -10
  9. package/dist/client/rpc/rpc-error.js +7 -1
  10. package/dist/examples/diffusion-ideogram.d.ts +2 -0
  11. package/dist/examples/diffusion-ideogram.js +108 -0
  12. package/dist/examples/diffusion-img2vid-ltx.d.ts +2 -0
  13. package/dist/examples/diffusion-img2vid-ltx.js +98 -0
  14. package/dist/examples/diffusion-txt2vid-ltx.d.ts +2 -0
  15. package/dist/examples/diffusion-txt2vid-ltx.js +92 -0
  16. package/dist/examples/logging-streaming.js +5 -1
  17. package/dist/examples/transcription/parakeet-tdt-filesystem-chunked.d.ts +2 -0
  18. package/dist/examples/transcription/parakeet-tdt-filesystem-chunked.js +203 -0
  19. package/dist/examples/vla-groot.d.ts +2 -0
  20. package/dist/examples/vla-groot.js +118 -0
  21. package/dist/models/registry/models.d.ts +5711 -9637
  22. package/dist/models/registry/models.js +2747 -6886
  23. package/dist/models/update-models/naming.js +5 -1
  24. package/dist/schemas/batch-completion-stream.d.ts +4 -4
  25. package/dist/schemas/common.d.ts +385 -4
  26. package/dist/schemas/common.js +3 -1
  27. package/dist/schemas/completion-stream.d.ts +186 -2
  28. package/dist/schemas/completion-stream.js +67 -0
  29. package/dist/schemas/constants-registry.d.ts +67 -0
  30. package/dist/schemas/constants-registry.js +51 -0
  31. package/dist/schemas/delete-cache.js +7 -3
  32. package/dist/schemas/engine-addon-map.d.ts +1 -0
  33. package/dist/schemas/engine-addon-map.js +1 -1
  34. package/dist/schemas/finetune.js +6 -3
  35. package/dist/schemas/get-loaded-model-info.d.ts +16 -0
  36. package/dist/schemas/get-loaded-model-info.js +14 -6
  37. package/dist/schemas/llamacpp-config.d.ts +24 -4
  38. package/dist/schemas/llamacpp-config.js +1 -6
  39. package/dist/schemas/load-model.d.ts +1380 -60
  40. package/dist/schemas/load-model.js +6 -3
  41. package/dist/schemas/plugin.d.ts +4 -4
  42. package/dist/schemas/plugin.js +4 -4
  43. package/dist/schemas/reload-config.js +3 -1
  44. package/dist/schemas/sdcpp-config.d.ts +239 -0
  45. package/dist/schemas/sdcpp-config.js +116 -10
  46. package/dist/schemas/sdk-config.d.ts +790 -40
  47. package/dist/schemas/sdk-errors-server.js +3 -1
  48. package/dist/schemas/text-to-speech.d.ts +14 -0
  49. package/dist/schemas/text-to-speech.js +10 -4
  50. package/dist/schemas/tools.d.ts +1 -1
  51. package/dist/schemas/tools.js +2 -1
  52. package/dist/schemas/translate.js +12 -6
  53. package/dist/schemas/vla.d.ts +10 -0
  54. package/dist/schemas/vla.js +31 -11
  55. package/dist/scripts/contract/build-contract.d.ts +38 -0
  56. package/dist/scripts/contract/build-contract.js +620 -0
  57. package/dist/scripts/contract/build-error-codes.d.ts +17 -0
  58. package/dist/scripts/contract/build-error-codes.js +17 -0
  59. package/dist/scripts/contract/build-model-type-maps.d.ts +22 -0
  60. package/dist/scripts/contract/build-model-type-maps.js +16 -0
  61. package/dist/scripts/contract/build-models-registry.d.ts +17 -0
  62. package/dist/scripts/contract/build-models-registry.js +31 -0
  63. package/dist/scripts/contract/plugin-contract.d.ts +23 -0
  64. package/dist/scripts/contract/plugin-contract.js +28 -0
  65. package/dist/scripts/export-contract.d.ts +2 -0
  66. package/dist/scripts/export-contract.js +27 -0
  67. package/dist/scripts/export-plugin-contract.d.ts +2 -0
  68. package/dist/scripts/export-plugin-contract.js +18 -0
  69. package/dist/server/bare/ops/translate.js +16 -3
  70. package/dist/server/bare/plugins/bci-whispercpp-transcription/plugin.d.ts +2 -1
  71. package/dist/server/bare/plugins/ggml-ocr/plugin.js +2 -23
  72. package/dist/server/bare/plugins/ggml-ocr/resolve-config.d.ts +6 -0
  73. package/dist/server/bare/plugins/ggml-ocr/resolve-config.js +73 -0
  74. package/dist/server/bare/plugins/ggml-vla/plugin.d.ts +5 -0
  75. package/dist/server/bare/plugins/ggml-vla/plugin.js +1 -1
  76. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +11 -22
  77. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.d.ts +8 -0
  78. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.js +8 -0
  79. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +8 -3
  80. package/dist/server/bare/plugins/llamacpp-embedding/plugin.d.ts +6 -1
  81. package/dist/server/bare/plugins/sdcpp-generation/ops/video.d.ts +3 -1
  82. package/dist/server/bare/plugins/sdcpp-generation/ops/video.js +31 -2
  83. package/dist/server/bare/plugins/sdcpp-generation/plugin.d.ts +129 -1
  84. package/dist/server/bare/plugins/sdcpp-generation/plugin.js +94 -22
  85. package/dist/server/bare/plugins/tts-ggml/ops/text-to-speech.js +2 -16
  86. package/dist/server/bare/plugins/tts-ggml/plugin.d.ts +6 -0
  87. package/dist/server/bare/plugins/tts-ggml/plugin.js +3 -1
  88. package/dist/server/bare/types/addon-responses.d.ts +2 -0
  89. package/dist/server/bare/utils/tts-stats.d.ts +8 -4
  90. package/dist/server/bare/utils/tts-stats.js +6 -0
  91. package/dist/server/rpc/create-server.js +23 -1
  92. package/dist/server/rpc/handler-registry.js +5 -0
  93. package/dist/server/rpc/handlers/completion-orchestrate.d.ts +42 -0
  94. package/dist/server/rpc/handlers/completion-orchestrate.js +190 -0
  95. package/dist/server/rpc/handlers/get-loaded-model-info.js +8 -2
  96. package/dist/server/rpc/handlers/index.d.ts +2 -0
  97. package/dist/server/rpc/handlers/index.js +2 -0
  98. package/dist/server/rpc/method-shapes.d.ts +51 -0
  99. package/dist/server/rpc/method-shapes.js +48 -0
  100. package/dist/server/rpc/profiling/operation-metrics.js +20 -0
  101. package/dist/server/utils/cache/auto-history.js +13 -9
  102. package/package.json +16 -13
@@ -0,0 +1,16 @@
1
+ import { ModelTypeAliases } from '../../schemas/model-types.js';
2
+ import { ENGINE_TO_ADDON, LEGACY_ENGINE_TO_CANONICAL } from '../../schemas/engine-addon-map.js';
3
+ function sortedEntries(record) {
4
+ const entries = Object.entries(record);
5
+ entries.sort(function (a, b) {
6
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
7
+ });
8
+ return Object.fromEntries(entries);
9
+ }
10
+ export function buildModelTypeMaps() {
11
+ return {
12
+ aliasToCanonical: sortedEntries(ModelTypeAliases),
13
+ engineToAddon: sortedEntries(ENGINE_TO_ADDON),
14
+ legacyEngineToCanonical: sortedEntries(LEGACY_ENGINE_TO_CANONICAL)
15
+ };
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { ModelConstant } from '../../models/registry/index';
2
+ /**
3
+ * JSON export of every named model constant in `@/models/registry`
4
+ * (`QWEN3_600M_INST_Q4`, `BCI_EMBEDDER`, ...) — the same constants JS
5
+ * consumers import directly and pass as `modelSrc`
6
+ * (`loadModel({ modelSrc: QWEN3_600M_INST_Q4, ... })`). Only the ~440
7
+ * promoted-to-a-named-export entries are included, not the full ~780-entry
8
+ * `models` array — entries without a name aren't meant for direct import in
9
+ * JS either.
10
+ *
11
+ * This is a separate artifact from schema.json/manifest.json: it's a static
12
+ * data catalog, not part of the RPC wire contract, but it lives alongside
13
+ * them in contract/ for the same reason — a language-neutral build artifact
14
+ * downstream client generators (starting with Python) consume.
15
+ */
16
+ export declare function buildModelsRegistry(): Record<string, ModelConstant>;
17
+ //# sourceMappingURL=build-models-registry.d.ts.map
@@ -0,0 +1,31 @@
1
+ import * as registry from '../../models/registry/index.js';
2
+ function isModelConstant(value) {
3
+ return (value !== null &&
4
+ typeof value === 'object' &&
5
+ !Array.isArray(value) &&
6
+ typeof value.src === 'string');
7
+ }
8
+ /**
9
+ * JSON export of every named model constant in `@/models/registry`
10
+ * (`QWEN3_600M_INST_Q4`, `BCI_EMBEDDER`, ...) — the same constants JS
11
+ * consumers import directly and pass as `modelSrc`
12
+ * (`loadModel({ modelSrc: QWEN3_600M_INST_Q4, ... })`). Only the ~440
13
+ * promoted-to-a-named-export entries are included, not the full ~780-entry
14
+ * `models` array — entries without a name aren't meant for direct import in
15
+ * JS either.
16
+ *
17
+ * This is a separate artifact from schema.json/manifest.json: it's a static
18
+ * data catalog, not part of the RPC wire contract, but it lives alongside
19
+ * them in contract/ for the same reason — a language-neutral build artifact
20
+ * downstream client generators (starting with Python) consume.
21
+ */
22
+ export function buildModelsRegistry() {
23
+ const namespace = registry;
24
+ const entries = Object.entries(namespace).filter(function (entry) {
25
+ return isModelConstant(entry[1]);
26
+ });
27
+ entries.sort(function (a, b) {
28
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
29
+ });
30
+ return Object.fromEntries(entries);
31
+ }
@@ -0,0 +1,23 @@
1
+ import type { QvacPlugin } from '../../schemas/index';
2
+ export interface PluginHandlerContract {
3
+ name: string;
4
+ streaming: boolean;
5
+ requestSchema: unknown;
6
+ responseSchema: unknown;
7
+ }
8
+ export interface PluginContract {
9
+ modelType: string;
10
+ displayName?: string;
11
+ handlers: PluginHandlerContract[];
12
+ }
13
+ /**
14
+ * Wire contract of one registered plugin: JSON Schema per handler, in the
15
+ * same wire shape the built-in contract export uses (requests as input
16
+ * shape, responses as output shape). Custom plugins register at runtime, so
17
+ * unlike the built-in contract this isn't emitted by `contract:export` —
18
+ * plugin authors (or app builds) run `scripts/export-plugin-contract.ts` on
19
+ * their plugin module and feed the result to the Python generator's
20
+ * `--plugin` input to get typed clients for their handlers.
21
+ */
22
+ export declare function buildPluginContract(plugin: QvacPlugin): PluginContract;
23
+ //# sourceMappingURL=plugin-contract.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { toWireJsonSchema } from './build-contract.js';
2
+ /**
3
+ * Wire contract of one registered plugin: JSON Schema per handler, in the
4
+ * same wire shape the built-in contract export uses (requests as input
5
+ * shape, responses as output shape). Custom plugins register at runtime, so
6
+ * unlike the built-in contract this isn't emitted by `contract:export` —
7
+ * plugin authors (or app builds) run `scripts/export-plugin-contract.ts` on
8
+ * their plugin module and feed the result to the Python generator's
9
+ * `--plugin` input to get typed clients for their handlers.
10
+ */
11
+ export function buildPluginContract(plugin) {
12
+ const handlers = Object.entries(plugin.handlers).map(function ([name, handler]) {
13
+ return {
14
+ name,
15
+ streaming: handler.streaming === true,
16
+ requestSchema: toWireJsonSchema(handler.requestSchema, 'input', `${plugin.modelType}.${name}.request`),
17
+ responseSchema: toWireJsonSchema(handler.responseSchema, 'output', `${plugin.modelType}.${name}.response`)
18
+ };
19
+ });
20
+ handlers.sort(function (a, b) {
21
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
22
+ });
23
+ return {
24
+ modelType: plugin.modelType,
25
+ ...(plugin.displayName && { displayName: plugin.displayName }),
26
+ handlers
27
+ };
28
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=export-contract.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { contractDir, renderContractFiles } from './contract/build-contract.js';
4
+ const checkOnly = process.argv.includes('--check');
5
+ const files = await renderContractFiles();
6
+ await mkdir(contractDir, { recursive: true });
7
+ let stale = false;
8
+ for (const [name, content] of Object.entries(files)) {
9
+ const filePath = fileURLToPath(new URL(name, contractDir));
10
+ if (checkOnly) {
11
+ const existing = await readFile(filePath, 'utf8').catch(function () {
12
+ return null;
13
+ });
14
+ if (existing !== content) {
15
+ console.error(`contract/${name} is ${existing === null ? 'missing' : 'out of date'}`);
16
+ stale = true;
17
+ }
18
+ }
19
+ else {
20
+ await writeFile(filePath, content);
21
+ console.log(`wrote contract/${name}`);
22
+ }
23
+ }
24
+ if (stale) {
25
+ console.error("Contract artifacts are stale. Run 'bun run contract:export' and commit the result.");
26
+ process.exit(1);
27
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=export-plugin-contract.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { buildPluginContract } from './contract/plugin-contract.js';
4
+ // Usage: tsx scripts/export-plugin-contract.ts <plugin-module> <out.json>
5
+ // The plugin module's default export must be a definePlugin(...) value.
6
+ const [modulePath, outPath] = process.argv.slice(2);
7
+ if (!modulePath || !outPath) {
8
+ console.error('usage: export-plugin-contract <plugin-module> <out.json>');
9
+ process.exit(1);
10
+ }
11
+ const imported = (await import(pathToFileURL(modulePath).href));
12
+ if (!imported.default) {
13
+ console.error(`${modulePath} has no default export`);
14
+ process.exit(1);
15
+ }
16
+ const contract = buildPluginContract(imported.default);
17
+ await writeFile(outPath, JSON.stringify(contract, null, 2) + '\n');
18
+ console.log(`wrote ${outPath} (${contract.handlers.length} handlers)`);
@@ -52,10 +52,10 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
52
52
  });
53
53
  import { getModelEntry } from '../registry/model-registry.js';
54
54
  import { translateServerParamsSchema, normalizeModelType, ModelType, AFRICAN_LANGUAGES_MAP } from '../../../schemas/index.js';
55
- import { getLangName } from '@qvac/langdetect-text';
55
+ import { getLangName, detectOne } from '@qvac/langdetect-text';
56
56
  import { nowMs } from '../../../profiling/index.js';
57
57
  import { buildStreamResult } from '../../../profiling/model-execution.js';
58
- import { ModelIsDelegatedError, ModelNotFoundError, ModelTypeMismatchError } from '../../../utils/errors-server.js';
58
+ import { ModelIsDelegatedError, ModelNotFoundError, ModelTypeMismatchError, TranslationFailedError } from '../../../utils/errors-server.js';
59
59
  import { getRequestRegistry, withRequestContext } from '../runtime/index.js';
60
60
  import { generateServerRequestId } from '../runtime/request-id.js';
61
61
  import { getServerLogger } from '../../../logging/index.js';
@@ -101,9 +101,22 @@ export async function* translate(params, requestId) {
101
101
  }
102
102
  }
103
103
  const isLlm = canonicalModelType === ModelType.llamacppCompletion;
104
- const from = isLlm ? params.from : undefined;
104
+ let from = isLlm ? params.from : undefined;
105
105
  const to = isLlm ? params.to : undefined;
106
106
  const context = isLlm ? params.context : undefined;
107
+ // Auto-detect the source language when the caller didn't pass `from` (LLM
108
+ // only). This used to run in each client; moving it here gives every
109
+ // language binding one detector instead of each shipping its own (lingua in
110
+ // Python vs langdetect-text in JS drifted on the same input). Store the
111
+ // detected code — not the language name — so the explicit-`from` and
112
+ // detected paths feed getLanguage() identically.
113
+ if (isLlm && !from) {
114
+ const detected = detectOne(text);
115
+ if (detected.code === 'und' || detected.language === 'Undetermined') {
116
+ throw new TranslationFailedError("Could not detect the source language. Please specify the 'from' parameter explicitly.");
117
+ }
118
+ from = detected.code;
119
+ }
107
120
  const afriquePrompt = isLlm && (isAfrican(from) || isAfrican(to));
108
121
  translateServerParamsSchema.parse(params);
109
122
  const fromLanguage = getLanguage(from);
@@ -1,3 +1,4 @@
1
+ import bciAddonLogging from '@qvac/bci-whispercpp/addonLogging';
1
2
  import { type CreateModelParams, type PluginModelResult } from '../../../../schemas/index';
2
3
  import { resolveBciConfig } from './resolve-config';
3
4
  export declare const bciPlugin: {
@@ -164,7 +165,7 @@ export declare const bciPlugin: {
164
165
  }, import("zod/v4/core").$strip>>;
165
166
  };
166
167
  logging: {
167
- module: import("@qvac/bci-whispercpp/addonLogging").AddonLogging;
168
+ module: typeof bciAddonLogging;
168
169
  namespace: "bci-whispercpp-transcription";
169
170
  };
170
171
  };
@@ -1,23 +1,11 @@
1
1
  import { definePlugin, defineHandler, ocrStreamRequestSchema, ocrStreamResponseSchema, ModelType, ocrConfigSchema, ADDON_OCR } from '../../../../schemas/index.js';
2
2
  import { ModelLoadFailedError } from '../../../../utils/errors-server.js';
3
- import { hyperdriveUrlSchema } from '../../../../schemas/load-model.js';
4
3
  import { createStreamLogger, registerAddonLogger } from '../../../../logging/index.js';
5
4
  import ocrAddonLogging from '@qvac/ocr-ggml/addonLogging.js';
6
5
  import { OcrGgml } from '@qvac/ocr-ggml';
7
6
  import { ocr } from './ops/ocr-stream.js';
8
7
  import { attachModelExecutionMs } from '../../../../profiling/model-execution.js';
9
- import { OCR_CRAFT } from '../../../../models/registry/index.js';
10
- const OCR_DETECTOR_FILENAME = 'craft_mlt_25k.gguf';
11
- function deriveDetectorSource(modelSrc) {
12
- if (modelSrc.startsWith('pear://')) {
13
- const { key } = hyperdriveUrlSchema.parse(modelSrc);
14
- return `pear://${key}/${OCR_DETECTOR_FILENAME}`;
15
- }
16
- if (modelSrc.startsWith('registry://')) {
17
- return OCR_CRAFT.src;
18
- }
19
- return undefined;
20
- }
8
+ import { resolveOcrConfig } from './resolve-config.js';
21
9
  function createOCRModel(modelId, detectorPath, recognizerPath, ocrConfig) {
22
10
  const logger = createStreamLogger(modelId, ModelType.ggmlOcr);
23
11
  registerAddonLogger(modelId, ModelType.ggmlOcr, logger);
@@ -66,16 +54,7 @@ export const ocrPlugin = definePlugin({
66
54
  addonPackage: ADDON_OCR,
67
55
  loadConfigSchema: ocrConfigSchema,
68
56
  async resolveConfig(cfg, ctx) {
69
- const { detectorModelSrc, ...ocrConfig } = cfg;
70
- const detectorSrc = detectorModelSrc ?? deriveDetectorSource(ctx.modelSrc);
71
- if (!detectorSrc) {
72
- throw new ModelLoadFailedError('Detector model required for OCR. Use a hyperdrive source or provide detectorModelSrc');
73
- }
74
- const detectorModelPath = await ctx.resolveModelPath(detectorSrc);
75
- return {
76
- config: ocrConfig,
77
- artifacts: { detectorModelPath }
78
- };
57
+ return resolveOcrConfig(cfg, ctx);
79
58
  },
80
59
  createModel(params) {
81
60
  const ocrConfig = (params.modelConfig ?? {});
@@ -0,0 +1,6 @@
1
+ import { type OCRConfig, type ResolveContext, type ResolveResult } from '../../../../schemas/index';
2
+ type OcrPipelineType = NonNullable<OCRConfig['pipelineType']>;
3
+ export declare const DETECTOR_FILENAMES: Record<OcrPipelineType, string>;
4
+ export declare function resolveOcrConfig(cfg: OCRConfig, ctx: ResolveContext): Promise<ResolveResult<Record<string, unknown>, 'detectorModelPath'>>;
5
+ export {};
6
+ //# sourceMappingURL=resolve-config.d.ts.map
@@ -0,0 +1,73 @@
1
+ import {} from '../../../../schemas/index.js';
2
+ import { ModelLoadFailedError } from '../../../../utils/errors-server.js';
3
+ import { hyperdriveUrlSchema } from '../../../../schemas/load-model.js';
4
+ import { OCR_CRAFT, OCR_DOCTR_1 } from '../../../../models/registry/index.js';
5
+ // Detector weights paired with each pipeline. EasyOCR runs CRAFT
6
+ // detection, DocTR runs DBNet detection — the addon cannot load one
7
+ // pipeline's detector into the other.
8
+ export const DETECTOR_FILENAMES = {
9
+ easyocr: 'craft_mlt_25k.gguf',
10
+ doctr: 'db_mobilenet_v3_large.gguf'
11
+ };
12
+ const DETECTOR_REGISTRY_SOURCES = {
13
+ easyocr: OCR_CRAFT.src,
14
+ doctr: OCR_DOCTR_1.src
15
+ };
16
+ // The addon defaults to the EasyOCR pipeline, so a DocTR recognizer
17
+ // loaded without an explicit `pipelineType` dies deep in the EasyOCR
18
+ // CRNN weight loader with a raw missing-tensor error (QVAC-22514).
19
+ // Recognize the known DocTR registry artifacts by name and opt in to
20
+ // the DocTR pipeline automatically.
21
+ function inferPipelineType(recognizerSrc) {
22
+ const path = srcPathname(recognizerSrc);
23
+ const filename = path.split('/').pop() ?? '';
24
+ if (filename === 'crnn_mobilenet_v3_small.gguf' || path.includes('/gguf/doctr')) {
25
+ return 'doctr';
26
+ }
27
+ return undefined;
28
+ }
29
+ function srcPathname(src) {
30
+ return (src.split(/[?#]/)[0] ?? '').toLowerCase();
31
+ }
32
+ function srcToString(src) {
33
+ return typeof src === 'string' ? src : src.src;
34
+ }
35
+ // The registry still lists legacy `.onnx` OCR artifacts under the
36
+ // "@qvac/ocr-onnx" engine; the ggml-ocr addon can only open GGUF, so a
37
+ // raw "failed to open GGUF" surfaces mid-load. Fail fast with the
38
+ // supported configurations instead (QVAC-22514).
39
+ function assertGgufSource(src, role) {
40
+ if (!srcPathname(src).endsWith('.onnx'))
41
+ return;
42
+ throw new ModelLoadFailedError(`ggml-ocr only loads GGUF models, but the ${role} source points to an ONNX file: ${src}. ` +
43
+ 'The .onnx OCR registry entries are legacy artifacts of the removed "onnx-ocr" engine. ' +
44
+ 'Supported registry configurations: EasyOCR (default) — modelSrc: OCR_LATIN.src (detector OCR_CRAFT auto-derived); ' +
45
+ 'DocTR — modelSrc: OCR_DOCTR.src (pipelineType and detector OCR_DOCTR_1 auto-derived).');
46
+ }
47
+ function deriveDetectorSource(modelSrc, pipelineType) {
48
+ if (modelSrc.startsWith('pear://')) {
49
+ const { key } = hyperdriveUrlSchema.parse(modelSrc);
50
+ return `pear://${key}/${DETECTOR_FILENAMES[pipelineType]}`;
51
+ }
52
+ if (modelSrc.startsWith('registry://')) {
53
+ return DETECTOR_REGISTRY_SOURCES[pipelineType];
54
+ }
55
+ return undefined;
56
+ }
57
+ export async function resolveOcrConfig(cfg, ctx) {
58
+ const { detectorModelSrc, ...ocrConfig } = cfg;
59
+ assertGgufSource(ctx.modelSrc, 'recognizer');
60
+ const pipelineType = ocrConfig.pipelineType ?? inferPipelineType(ctx.modelSrc) ?? 'easyocr';
61
+ const detectorSrc = detectorModelSrc ?? deriveDetectorSource(ctx.modelSrc, pipelineType);
62
+ if (!detectorSrc) {
63
+ throw new ModelLoadFailedError(`Detector model required for OCR (${pipelineType} pipeline expects ` +
64
+ `${DETECTOR_FILENAMES[pipelineType]}). Use a hyperdrive or registry ` +
65
+ 'source, or provide detectorModelSrc');
66
+ }
67
+ assertGgufSource(srcToString(detectorSrc), 'detector');
68
+ const detectorModelPath = await ctx.resolveModelPath(detectorSrc);
69
+ return {
70
+ config: { ...ocrConfig, pipelineType },
71
+ artifacts: { detectorModelPath }
72
+ };
73
+ }
@@ -53,6 +53,11 @@ export declare const vlaPlugin: {
53
53
  discrete: "discrete";
54
54
  continuous: "continuous";
55
55
  }>>;
56
+ imageInputMode: import("zod").ZodOptional<import("zod").ZodEnum<{
57
+ pixels: "pixels";
58
+ patches: "patches";
59
+ }>>;
60
+ imagePatchElems: import("zod").ZodOptional<import("zod").ZodNumber>;
56
61
  }, import("zod/v4/core").$strip>;
57
62
  backendName: import("zod").ZodNullable<import("zod").ZodString>;
58
63
  }, import("zod/v4/core").$strip>>;
@@ -16,7 +16,7 @@ function wrapVlaModel(inner, loadOpts) {
16
16
  }
17
17
  export const vlaPlugin = definePlugin({
18
18
  modelType: ModelType.ggmlVla,
19
- displayName: 'VLA (SmolVLA / π₀.₅ ggml)',
19
+ displayName: 'VLA (SmolVLA / π₀.₅ / GR00T ggml)',
20
20
  addonPackage: ADDON_VLA,
21
21
  loadConfigSchema: vlaConfigSchema,
22
22
  createModel(params) {
@@ -2,7 +2,7 @@ import { TOOLS_MODE } from '../../../../../schemas/tools.js';
2
2
  import { logCacheDisabled, logCacheInit, logCacheSave, logMessagesToAddon } from './cache-logger.js';
3
3
  import { extractSystemPrompt, getCurrentCacheInfo } from '../../../ops/kv-cache-utils.js';
4
4
  import { getModel, getModelConfig } from '../../../registry/model-registry.js';
5
- import { decideCachedHistorySlice } from './kv-cache-state.js';
5
+ import { decideCachedHistorySlice, shouldCommitCachedTurn } from './kv-cache-state.js';
6
6
  import { createKvCacheSession, generateConfigHash } from './kv-cache-session.js';
7
7
  import { appendToolsToHistory, detectToolDialect, prependToolsToHistory } from '../../../../utils/tool-integration.js';
8
8
  import { parseToolCalls } from '../../../../utils/tools/index.js';
@@ -15,22 +15,6 @@ import { buildStreamResult } from '../../../../../profiling/model-execution.js';
15
15
  import { normalizeCompletionStats } from './completion-stats.js';
16
16
  import fs from 'bare-fs';
17
17
  const logger = getServerLogger();
18
- /**
19
- * Decide whether a completed turn earned the right to record its kv-cache
20
- * boundary. A `savedCount` is only safe to write when the turn ran to
21
- * completion AND produced at least one token — anything else (cancelled
22
- * mid-decode, zero-token reply, early EOS) leaves the on-disk cache file
23
- * in an unknown state relative to `history.length + 1`, and a stale entry
24
- * would slice the next turn's history down to an empty payload.
25
- *
26
- * Replaces the pre-0.11.0 `shouldRecordSavedCount(wasCancelled, ...)` with
27
- * a signal-driven check that reads directly from the request's
28
- * `AbortSignal`. The local helper keeps the call sites in
29
- * `completion-stream.ts` honest without importing the registry every time.
30
- */
31
- function shouldCommitTurn(signal, producedTokens) {
32
- return !signal.aborted && producedTokens;
33
- }
34
18
  function transformMessage(message) {
35
19
  const transformed = [];
36
20
  // Check if it's a tool definition (has type: "function")
@@ -338,11 +322,17 @@ export async function* completion(params, opts) {
338
322
  /* cacheExists */ true, history, dynamicTools ? tools : undefined);
339
323
  logMessagesToAddon(messagesToSend, 'PROMPT_SEND');
340
324
  const result = yield* processModelResponse(model, messagesToSend, tools, mergedGenerationParams, { cacheKey: turn.cachePath, saveCacheToDisk: true }, dialect);
325
+ const shouldCommitTurn = shouldCommitCachedTurn({
326
+ aborted: signal.aborted,
327
+ producedTokens: result.producedTokens,
328
+ generatedTokens: result.stats?.generatedTokens,
329
+ predict: mergedGenerationParams?.predict ?? modelConfig.predict
330
+ });
341
331
  if (typeof kvCache === 'string') {
342
332
  // Custom-key path: the addon wrote the new cache state inline at
343
333
  // the same path. Either commit (records the boundary, suppresses
344
334
  // rollback) or fall through to the deferred rollback.
345
- if (shouldCommitTurn(signal, result.producedTokens)) {
335
+ if (shouldCommitTurn) {
346
336
  await session.commitTurn(turn, {
347
337
  kind: 'static',
348
338
  messageCount: history.length + 1
@@ -362,10 +352,9 @@ export async function* completion(params, opts) {
362
352
  logger.warn(`[kv-cache] Auto cache tool-call turn; rolling back to avoid disk leak. path=${turn.cachePath}`);
363
353
  return result;
364
354
  }
365
- if (!shouldCommitTurn(signal, result.producedTokens)) {
366
- // Cancelled or zero-token turn — the addon wrote the file but its
367
- // contents don't correspond to a clean turn boundary. Let the
368
- // deferred rollback unlink it.
355
+ if (!shouldCommitTurn) {
356
+ // Cancelled, zero-token, or budget-exhausted turns do not establish
357
+ // a trustworthy message boundary.
369
358
  return result;
370
359
  }
371
360
  const savedHistory = buildAutoCacheSaveHistory(history.map((msg) => ({
@@ -32,6 +32,13 @@ export interface HistorySliceDecision {
32
32
  */
33
33
  clearStaleCount: boolean;
34
34
  }
35
+ interface CacheCommitContext {
36
+ aborted: boolean;
37
+ producedTokens: boolean;
38
+ generatedTokens?: number | undefined;
39
+ predict?: number | undefined;
40
+ }
41
+ export declare function shouldCommitCachedTurn(context: CacheCommitContext): boolean;
35
42
  /**
36
43
  * Pure slice decision for `prepareMessagesForCache`.
37
44
  *
@@ -46,4 +53,5 @@ export interface HistorySliceDecision {
46
53
  * than handing the model an empty payload.
47
54
  */
48
55
  export declare function decideCachedHistorySlice(savedCount: number, cacheExists: boolean, history: HistoryMessage[]): HistorySliceDecision;
56
+ export {};
49
57
  //# sourceMappingURL=kv-cache-state.d.ts.map
@@ -14,6 +14,14 @@
14
14
  * layers (saved counts, init flags, on-disk files). Only the pure
15
15
  * slice-decision helper remains here.
16
16
  */
17
+ export function shouldCommitCachedTurn(context) {
18
+ const { aborted, producedTokens, generatedTokens, predict } = context;
19
+ const stoppedByBudget = predict !== undefined &&
20
+ predict > 0 &&
21
+ generatedTokens !== undefined &&
22
+ generatedTokens >= predict;
23
+ return !aborted && producedTokens && !stoppedByBudget;
24
+ }
17
25
  /**
18
26
  * Pure slice decision for `prepareMessagesForCache`.
19
27
  *
@@ -15,7 +15,12 @@ export declare const llmPlugin: {
15
15
  predict: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodLiteral<-1>, import("zod").ZodLiteral<-2>, import("zod").ZodNumber]>>;
16
16
  system_prompt: import("zod").ZodOptional<import("zod").ZodString>;
17
17
  no_mmap: import("zod").ZodOptional<import("zod").ZodBoolean>;
18
- verbosity: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodLiteral<0>, import("zod").ZodLiteral<1>, import("zod").ZodLiteral<2>, import("zod").ZodLiteral<3>]>>;
18
+ verbosity: import("zod").ZodOptional<import("zod").ZodEnum<{
19
+ readonly ERROR: 0;
20
+ readonly WARN: 1;
21
+ readonly INFO: 2;
22
+ readonly DEBUG: 3;
23
+ }>>;
19
24
  presence_penalty: import("zod").ZodOptional<import("zod").ZodNumber>;
20
25
  frequency_penalty: import("zod").ZodOptional<import("zod").ZodNumber>;
21
26
  repeat_penalty: import("zod").ZodOptional<import("zod").ZodNumber>;
@@ -220,7 +225,7 @@ export declare const llmPlugin: {
220
225
  integer: "integer";
221
226
  }>;
222
227
  description: import("zod").ZodOptional<import("zod").ZodString>;
223
- enum: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
228
+ enum: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber, import("zod").ZodBoolean, import("zod").ZodNull]>>>;
224
229
  }, import("zod/v4/core").$strip>>;
225
230
  required: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
226
231
  }, import("zod/v4/core").$strip>;
@@ -343,7 +348,7 @@ export declare const llmPlugin: {
343
348
  integer: "integer";
344
349
  }>;
345
350
  description: import("zod").ZodOptional<import("zod").ZodString>;
346
- enum: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
351
+ enum: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodUnion<readonly [import("zod").ZodString, import("zod").ZodNumber, import("zod").ZodBoolean, import("zod").ZodNull]>>>;
347
352
  }, import("zod/v4/core").$strip>>;
348
353
  required: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
349
354
  }, import("zod/v4/core").$strip>;
@@ -37,7 +37,12 @@ export declare const embeddingsPlugin: {
37
37
  row: "row";
38
38
  }>>;
39
39
  tensorSplit: import("zod").ZodOptional<import("zod").ZodString>;
40
- verbosity: import("zod").ZodOptional<import("zod").ZodUnion<readonly [import("zod").ZodLiteral<0>, import("zod").ZodLiteral<1>, import("zod").ZodLiteral<2>, import("zod").ZodLiteral<3>]>>;
40
+ verbosity: import("zod").ZodOptional<import("zod").ZodEnum<{
41
+ readonly ERROR: 0;
42
+ readonly WARN: 1;
43
+ readonly INFO: 2;
44
+ readonly DEBUG: 3;
45
+ }>>;
41
46
  openclCacheDir: import("zod").ZodOptional<import("zod").ZodString>;
42
47
  }, import("zod/v4/core").$strip>;
43
48
  createModel(params: CreateModelParams): PluginModelResult;
@@ -1,3 +1,5 @@
1
- import type { VideoRequest, VideoStreamResponse } from '../../../../../schemas/sdcpp-config';
1
+ import { VideoStableDiffusion } from '@qvac/diffusion-cpp';
2
+ import { type VideoRequest, type VideoStreamResponse } from '../../../../../schemas/sdcpp-config';
3
+ export declare function markLtxVideoModel(model: VideoStableDiffusion): void;
2
4
  export declare function video(request: VideoRequest): AsyncGenerator<VideoStreamResponse>;
3
5
  //# sourceMappingURL=video.d.ts.map
@@ -55,8 +55,32 @@ import { getServerLogger } from '../../../../../logging/index.js';
55
55
  import { getModel, getModelEntry } from '../../../registry/model-registry.js';
56
56
  import { getRequestRegistry, withRequestContext } from '../../../runtime/index.js';
57
57
  import { generateServerRequestId } from '../../../runtime/request-id.js';
58
- import { ModelOperationNotSupportedError } from '../../../../../utils/errors-server.js';
58
+ import { ModelOperationNotSupportedError, PluginRequestValidationFailedError } from '../../../../../utils/errors-server.js';
59
+ import { formatZodError } from '../../../../../utils/zod-error.js';
59
60
  import { ModelType } from '../../../../../schemas/index.js';
61
+ import { ltxVideoRequestSchema } from '../../../../../schemas/sdcpp-config.js';
62
+ const ltxVideoModels = new WeakSet();
63
+ export function markLtxVideoModel(model) {
64
+ ltxVideoModels.add(model);
65
+ }
66
+ function parseLtxVideoRequest(request) {
67
+ const result = ltxVideoRequestSchema.safeParse(request);
68
+ if (!result.success) {
69
+ throw new PluginRequestValidationFailedError('videoStream', formatZodError(result.error));
70
+ }
71
+ }
72
+ // The addon reports `hasAudio` as a numeric flag (1/0); the SDK surfaces it as
73
+ // a boolean so consumers get a readable `stats.hasAudio` instead of a magic
74
+ // number. Everything else passes through and is validated client-side.
75
+ function toVideoStats(stats) {
76
+ if (!stats)
77
+ return undefined;
78
+ const { hasAudio, ...rest } = stats;
79
+ return {
80
+ ...rest,
81
+ ...(hasAudio !== undefined && { hasAudio: hasAudio !== 0 })
82
+ };
83
+ }
60
84
  // The diffusion plugin instantiates `VideoStableDiffusion` when the model is
61
85
  // loaded with `modelConfig.mode === "video"` and `ImgStableDiffusion` (or
62
86
  // `EsrganUpscaler`) otherwise. Those have no video `run()` shape, so we refuse
@@ -80,6 +104,8 @@ export async function* video(request) {
80
104
  }), true);
81
105
  const requestLogger = withRequestContext(getServerLogger(), ctx);
82
106
  const model = asVideoModel(getModel(request.modelId), request.modelId);
107
+ if (ltxVideoModels.has(model))
108
+ parseLtxVideoRequest(request);
83
109
  const onAbort = () => {
84
110
  model.cancel().catch((err) => {
85
111
  requestLogger.warn(`[cancel] model.cancel() rejected during abort for modelId=${request.modelId}: ${err instanceof Error ? err.message : String(err)}`);
@@ -148,6 +174,9 @@ export async function* video(request) {
148
174
  ...(request.vae_tile_overlap !== undefined && {
149
175
  vae_tile_overlap: request.vae_tile_overlap
150
176
  }),
177
+ ...(request.temporal_tiling !== undefined && {
178
+ temporal_tiling: request.temporal_tiling
179
+ }),
151
180
  ...(request.cache_mode !== undefined && { cache_mode: request.cache_mode }),
152
181
  ...(request.cache_preset !== undefined && {
153
182
  cache_preset: request.cache_preset
@@ -188,7 +217,7 @@ export async function* video(request) {
188
217
  yield {
189
218
  type: 'videoStream',
190
219
  done: true,
191
- stats: responseWithStats.stats ?? undefined
220
+ stats: toVideoStats(responseWithStats.stats)
192
221
  };
193
222
  }
194
223
  catch (e_1) {