@qvac/sdk 0.17.1 → 0.18.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 (154) hide show
  1. package/NOTICE +124 -64
  2. package/README.md +19 -0
  3. package/dist/client/api/audio-gen.d.ts +17 -0
  4. package/dist/client/api/audio-gen.js +17 -0
  5. package/dist/client/api/finetune.d.ts +2 -0
  6. package/dist/client/api/finetune.js +7 -1
  7. package/dist/client/api/index.d.ts +1 -1
  8. package/dist/client/api/index.js +1 -1
  9. package/dist/client/api/load-model.d.ts +8 -0
  10. package/dist/client/api/transcribe.d.ts +19 -2
  11. package/dist/client/api/transcribe.js +66 -27
  12. package/dist/client/api/translate.d.ts +1 -0
  13. package/dist/client/api/translate.js +40 -19
  14. package/dist/client/api/vla.d.ts +42 -1
  15. package/dist/client/api/vla.js +50 -1
  16. package/dist/commands/bundle/entry-gen.d.ts +4 -0
  17. package/dist/commands/bundle/entry-gen.js +6 -0
  18. package/dist/commands/bundle/index.js +19 -12
  19. package/dist/examples/asr/parakeet-indic-conformer-filesystem.d.ts +2 -0
  20. package/dist/examples/asr/parakeet-indic-conformer-filesystem.js +52 -0
  21. package/dist/examples/audiogen/generate-cover.d.ts +2 -0
  22. package/dist/examples/audiogen/generate-cover.js +100 -0
  23. package/dist/examples/llamacpp-fallback.d.ts +2 -0
  24. package/dist/examples/llamacpp-fallback.js +34 -0
  25. package/dist/examples/multi-job-completion.d.ts +28 -0
  26. package/dist/examples/multi-job-completion.js +128 -0
  27. package/dist/examples/profiling/basic.js +32 -1
  28. package/dist/examples/system-resources.d.ts +2 -0
  29. package/dist/examples/system-resources.js +86 -0
  30. package/dist/examples/tts/audio8.d.ts +2 -0
  31. package/dist/examples/tts/audio8.js +54 -0
  32. package/dist/examples/tts/cosyvoice3.d.ts +2 -0
  33. package/dist/examples/tts/cosyvoice3.js +52 -0
  34. package/dist/examples/vla-groot.js +92 -49
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.js +2 -2
  37. package/dist/models/registry/models.d.ts +782 -6
  38. package/dist/models/registry/models.js +2664 -1859
  39. package/dist/schemas/audio-gen.d.ts +103 -0
  40. package/dist/schemas/audio-gen.js +135 -4
  41. package/dist/schemas/common.d.ts +550 -11
  42. package/dist/schemas/constants-registry.d.ts +18 -4
  43. package/dist/schemas/constants-registry.js +16 -3
  44. package/dist/schemas/index.d.ts +1 -1
  45. package/dist/schemas/index.js +1 -1
  46. package/dist/schemas/llamacpp-config.d.ts +10 -10
  47. package/dist/schemas/llamacpp-config.js +12 -5
  48. package/dist/schemas/load-model.d.ts +3678 -323
  49. package/dist/schemas/load-model.js +21 -3
  50. package/dist/schemas/sdk-config.d.ts +60 -24
  51. package/dist/schemas/sdk-errors-server.d.ts +1 -0
  52. package/dist/schemas/sdk-errors-server.js +5 -0
  53. package/dist/schemas/text-to-speech.d.ts +1086 -110
  54. package/dist/schemas/text-to-speech.js +194 -4
  55. package/dist/schemas/tools.d.ts +0 -16
  56. package/dist/schemas/tools.js +0 -15
  57. package/dist/schemas/transcription-config.d.ts +2 -0
  58. package/dist/schemas/transcription-config.js +10 -4
  59. package/dist/schemas/transcription.d.ts +3 -0
  60. package/dist/schemas/vla.d.ts +83 -0
  61. package/dist/schemas/vla.js +105 -2
  62. package/dist/scripts/link-workspace-inference.d.ts +2 -0
  63. package/dist/scripts/link-workspace-inference.js +31 -0
  64. package/dist/server/bare/ops/cancel.d.ts +1 -1
  65. package/dist/server/bare/ops/cancel.js +1 -1
  66. package/dist/server/bare/ops/kv-cache-retention.js +11 -0
  67. package/dist/server/bare/ops/kv-cache-utils.d.ts +1 -1
  68. package/dist/server/bare/ops/kv-cache-utils.js +40 -12
  69. package/dist/server/bare/ops/transcribe.d.ts +2 -2
  70. package/dist/server/bare/ops/transcribe.js +54 -48
  71. package/dist/server/bare/ops/translate-stats.d.ts +5 -0
  72. package/dist/server/bare/ops/translate-stats.js +11 -0
  73. package/dist/server/bare/ops/translate.js +66 -38
  74. package/dist/server/bare/ops/unload-model.js +30 -24
  75. package/dist/server/bare/plugins/audiogen-ggml/ops/audio-gen-input.d.ts +20 -0
  76. package/dist/server/bare/plugins/audiogen-ggml/ops/audio-gen-input.js +153 -0
  77. package/dist/server/bare/plugins/audiogen-ggml/ops/audio-gen-stream.js +52 -1
  78. package/dist/server/bare/plugins/audiogen-ggml/plugin.d.ts +28 -0
  79. package/dist/server/bare/plugins/ggml-ocr/plugin.js +3 -1
  80. package/dist/server/bare/plugins/ggml-vla/ops/vla-set-embodiment.d.ts +3 -0
  81. package/dist/server/bare/plugins/ggml-vla/ops/vla-set-embodiment.js +9 -0
  82. package/dist/server/bare/plugins/ggml-vla/plugin.d.ts +37 -0
  83. package/dist/server/bare/plugins/ggml-vla/plugin.js +21 -4
  84. package/dist/server/bare/plugins/llamacpp-completion/ops/batch-completion-stream.js +16 -15
  85. package/dist/server/bare/plugins/llamacpp-completion/ops/cache-logger.d.ts +1 -1
  86. package/dist/server/bare/plugins/llamacpp-completion/ops/cache-logger.js +2 -2
  87. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stats.d.ts +6 -0
  88. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stats.js +10 -0
  89. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.d.ts +1 -0
  90. package/dist/server/bare/plugins/llamacpp-completion/ops/completion-stream.js +131 -123
  91. package/dist/server/bare/plugins/llamacpp-completion/ops/finetune.js +110 -20
  92. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.d.ts +49 -23
  93. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-session.js +308 -143
  94. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.d.ts +1 -0
  95. package/dist/server/bare/plugins/llamacpp-completion/ops/kv-cache-state.js +2 -2
  96. package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +6 -6
  97. package/dist/server/bare/plugins/llamacpp-completion/plugin.js +55 -36
  98. package/dist/server/bare/plugins/llamacpp-completion/transform.js +1 -7
  99. package/dist/server/bare/plugins/llamacpp-embedding/plugin.js +2 -3
  100. package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +6 -5
  101. package/dist/server/bare/plugins/parakeet-transcription/plugin.js +27 -16
  102. package/dist/server/bare/plugins/tts-ggml/ops/parler-options.js +23 -3
  103. package/dist/server/bare/plugins/tts-ggml/plugin.d.ts +302 -4
  104. package/dist/server/bare/plugins/tts-ggml/plugin.js +131 -0
  105. package/dist/server/bare/plugins/whispercpp-transcription/plugin.js +48 -39
  106. package/dist/server/bare/registry/model-registry.d.ts +1 -0
  107. package/dist/server/bare/runtime/request-registry-singleton.js +29 -15
  108. package/dist/server/bare/runtime/request-registry.d.ts +27 -0
  109. package/dist/server/bare/runtime/request-registry.js +246 -76
  110. package/dist/server/bare/types/addon-responses.d.ts +4 -12
  111. package/dist/server/rpc/handlers/load-model/handler.js +2 -2
  112. package/dist/server/rpc/handlers/load-model/resolve-session.d.ts +1 -1
  113. package/dist/server/rpc/handlers/load-model/resolve-session.js +3 -3
  114. package/dist/server/rpc/handlers/load-model/resolve.d.ts +12 -2
  115. package/dist/server/rpc/handlers/load-model/resolve.js +90 -12
  116. package/dist/server/utils/audio/decoder.d.ts +37 -1
  117. package/dist/server/utils/audio/decoder.js +81 -51
  118. package/dist/server/utils/config-transform.d.ts +3 -0
  119. package/dist/server/utils/config-transform.js +6 -0
  120. package/dist/server/utils/index.d.ts +0 -1
  121. package/dist/server/utils/index.js +0 -1
  122. package/dist/server/utils/shard-utils.d.ts +1 -0
  123. package/dist/server/utils/shard-utils.js +14 -0
  124. package/dist/server/utils/tool-integration.d.ts +3 -10
  125. package/dist/server/utils/tool-integration.js +3 -12
  126. package/dist/utils/errors-server.d.ts +3 -0
  127. package/dist/utils/errors-server.js +5 -0
  128. package/package.json +12 -14
  129. package/dist/examples/llamacpp-dynamic-tools.d.ts +0 -2
  130. package/dist/examples/llamacpp-dynamic-tools.js +0 -152
  131. package/dist/models/update-models/codegen.d.ts +0 -3
  132. package/dist/models/update-models/codegen.js +0 -196
  133. package/dist/models/update-models/companions.d.ts +0 -30
  134. package/dist/models/update-models/companions.js +0 -296
  135. package/dist/models/update-models/history.d.ts +0 -24
  136. package/dist/models/update-models/history.js +0 -107
  137. package/dist/models/update-models/index.d.ts +0 -2
  138. package/dist/models/update-models/index.js +0 -123
  139. package/dist/models/update-models/naming.d.ts +0 -4
  140. package/dist/models/update-models/naming.js +0 -396
  141. package/dist/models/update-models/processing.d.ts +0 -8
  142. package/dist/models/update-models/processing.js +0 -65
  143. package/dist/models/update-models/registry.d.ts +0 -5
  144. package/dist/models/update-models/registry.js +0 -87
  145. package/dist/models/update-models/shards.d.ts +0 -4
  146. package/dist/models/update-models/shards.js +0 -57
  147. package/dist/models/update-models/types.d.ts +0 -88
  148. package/dist/models/update-models/types.js +0 -1
  149. package/dist/models/update-models/utils.d.ts +0 -3
  150. package/dist/models/update-models/utils.js +0 -21
  151. package/dist/server/bare/plugins/llamacpp-completion/ops/batch-cancelled.d.ts +0 -23
  152. package/dist/server/bare/plugins/llamacpp-completion/ops/batch-cancelled.js +0 -30
  153. package/dist/server/utils/expand-gguf-shards.d.ts +0 -3
  154. package/dist/server/utils/expand-gguf-shards.js +0 -22
@@ -5,10 +5,17 @@ import { getServerLogger } from '../../../logging/index.js';
5
5
  const logger = getServerLogger();
6
6
  const AUTO_CACHE_MARKER_PREFIX = '.auto-cache-';
7
7
  const AUTO_CACHE_MARKER_PATTERN = /^\.auto-cache-([a-f0-9]{16})$/;
8
+ // SDK-generated auto-cache keys are 16 hex chars. Marker ops gate on this so a
9
+ // caller-provided key never gets a marker and the raw-key marker path stays
10
+ // inside the cache root.
11
+ const AUTO_CACHE_KEY_PATTERN = /^[a-f0-9]{16}$/;
8
12
  function getAutoCacheMarkerPath(cacheKey) {
9
13
  return path.join(getKVCacheDir(), `${AUTO_CACHE_MARKER_PREFIX}${cacheKey}`);
10
14
  }
11
15
  export async function markAutoCacheKey(cacheKey) {
16
+ // Only generated 16-hex auto keys get markers (see AUTO_CACHE_KEY_PATTERN).
17
+ if (!AUTO_CACHE_KEY_PATTERN.test(cacheKey))
18
+ return;
12
19
  try {
13
20
  await fsPromises.writeFile(getAutoCacheMarkerPath(cacheKey), '');
14
21
  }
@@ -17,6 +24,8 @@ export async function markAutoCacheKey(cacheKey) {
17
24
  }
18
25
  }
19
26
  export async function removeAutoCacheMarker(cacheKey) {
27
+ if (!AUTO_CACHE_KEY_PATTERN.test(cacheKey))
28
+ return;
20
29
  try {
21
30
  await fsPromises.unlink(getAutoCacheMarkerPath(cacheKey));
22
31
  }
@@ -25,6 +34,8 @@ export async function removeAutoCacheMarker(cacheKey) {
25
34
  }
26
35
  }
27
36
  export async function removeAutoCacheMarkerIfMissing(cacheKey) {
37
+ if (!AUTO_CACHE_KEY_PATTERN.test(cacheKey))
38
+ return;
28
39
  try {
29
40
  await fsPromises.access(path.join(getKVCacheDir(), cacheKey));
30
41
  }
@@ -12,7 +12,7 @@ export declare function getCurrentCacheInfo(modelId: string, configHash: string,
12
12
  cachePath: string;
13
13
  }>;
14
14
  export declare function renameCacheFile(oldPath: string, newPath: string): Promise<boolean>;
15
- export declare function pruneEmptyCacheDirectories(cacheFilePath: string): Promise<void>;
15
+ export declare function pruneEmptyCacheDirectories(cacheFilePath: string, activePaths?: readonly string[]): Promise<void>;
16
16
  export declare function deleteCache(options: {
17
17
  all: true;
18
18
  } | {
@@ -3,6 +3,7 @@ import { promises as fsPromises } from 'bare-fs';
3
3
  import path from 'bare-path';
4
4
  import { getAutoCacheLookupHistory, getKVCacheDir, validateAndJoinPath } from '../../../server/utils/index.js';
5
5
  import { getServerLogger } from '../../../logging/index.js';
6
+ import { PathTraversalError } from '../../../utils/errors-server.js';
6
7
  import { Buffer } from 'bare-buffer';
7
8
  import { markAutoCacheKey } from '../../../server/bare/ops/kv-cache-retention.js';
8
9
  const logger = getServerLogger();
@@ -14,20 +15,28 @@ export function extractSystemPrompt(messages) {
14
15
  const systemMessage = messages.find((msg) => msg.role === 'system');
15
16
  return systemMessage ? systemMessage.content : null;
16
17
  }
17
- function getToolNamesForHash(tools) {
18
- if (!Array.isArray(tools))
19
- return [];
20
- return tools
21
- .map((t) => t.name)
22
- .filter((n) => typeof n === 'string')
23
- .sort();
18
+ // Cache hash based on the system prompt + complete tool definitions.
19
+ // Callers pass tools only when the tool block is written into the cache and
20
+ // left there, so a different tool set gets its own cache instead of reusing a
21
+ // prefix that holds the old block. Every prompt-affecting field participates,
22
+ // not just the name: canonical serialization avoids cache misses caused only
23
+ // by object-key insertion order, while tool-array order is preserved because
24
+ // that is the order sent to the model.
25
+ function canonicalizeHashInput(value) {
26
+ if (Array.isArray(value))
27
+ return value.map(canonicalizeHashInput);
28
+ if (typeof value !== 'object' || value === null)
29
+ return value;
30
+ const canonical = {};
31
+ for (const key of Object.keys(value).sort()) {
32
+ canonical[key] = canonicalizeHashInput(value[key]);
33
+ }
34
+ return canonical;
24
35
  }
25
- // Cache hash based on system prompt + tool names
26
- // Different tools = different cache (tools anchored in first message, protected from n_discarded)
27
36
  export function generateConfigHash(systemPrompt, tools) {
28
37
  const hash = crypto.createHash('sha-256');
29
- const toolNames = getToolNamesForHash(tools);
30
- hash.update(Buffer.from(JSON.stringify({ systemPrompt, toolNames }), 'utf8'));
38
+ const canonicalConfig = JSON.stringify(canonicalizeHashInput({ systemPrompt, tools: Array.isArray(tools) ? tools : [] }));
39
+ hash.update(Buffer.from(canonicalConfig, 'utf8'));
31
40
  return hash.digest('hex').substring(0, 16);
32
41
  }
33
42
  export function generateCacheKey(messages) {
@@ -88,11 +97,15 @@ export async function renameCacheFile(oldPath, newPath) {
88
97
  return false;
89
98
  }
90
99
  }
91
- export async function pruneEmptyCacheDirectories(cacheFilePath) {
100
+ export async function pruneEmptyCacheDirectories(cacheFilePath, activePaths = []) {
92
101
  const cacheDir = getKVCacheDir();
93
102
  const cacheDirPrefix = `${cacheDir}${path.sep}`;
94
103
  let currentDirectory = path.dirname(cacheFilePath);
95
104
  while (currentDirectory.startsWith(cacheDirPrefix)) {
105
+ // Keep a directory another in-flight turn still holds (its .bin isn't on disk yet).
106
+ const childPrefix = `${currentDirectory}${path.sep}`;
107
+ if (activePaths.some((p) => p.startsWith(childPrefix)))
108
+ return;
96
109
  try {
97
110
  await fsPromises.rmdir(currentDirectory);
98
111
  }
@@ -113,8 +126,23 @@ export async function deleteCache(options) {
113
126
  await fsPromises.mkdir(cacheDir, { recursive: true });
114
127
  return cacheDir;
115
128
  }
129
+ // validateAndJoinPath sanitizes the key (strips traversal) and rejects
130
+ // escapes, so nested and other in-root keys resolve normally. A key that
131
+ // resolves to the cache root would wipe every cache — reject it BEFORE joining
132
+ // any modelId, so an empty key plus a modelId can't delete a real key dir
133
+ // under the root.
134
+ const cacheRoot = path.resolve(cacheDir);
116
135
  const kvCacheDir = validateAndJoinPath(cacheDir, options.kvCacheKey);
136
+ if (path.resolve(kvCacheDir) === cacheRoot) {
137
+ throw new PathTraversalError(options.kvCacheKey, cacheDir);
138
+ }
139
+ // Omitting modelId means "delete every model under the key"; a PROVIDED but
140
+ // empty/dot modelId collapses back to that same dir and would silently broaden
141
+ // the delete — reject it.
117
142
  const targetDir = options.modelId !== undefined ? validateAndJoinPath(kvCacheDir, options.modelId) : kvCacheDir;
143
+ if (options.modelId !== undefined && path.resolve(targetDir) === path.resolve(kvCacheDir)) {
144
+ throw new PathTraversalError(options.modelId, kvCacheDir);
145
+ }
118
146
  await fsPromises.rm(targetDir, { recursive: true, force: true });
119
147
  return targetDir;
120
148
  }
@@ -15,6 +15,6 @@ export interface TranscribeStreamOpts {
15
15
  vadRunIntervalMs?: number;
16
16
  parakeetStreamingConfig?: ParakeetStreamingRunConfig;
17
17
  }
18
- export declare function transcribeStream(modelId: string, audioInputStream: AsyncIterable<Buffer>, prompt: string | undefined, metadata: true, opts?: TranscribeStreamOpts, requestId?: string): AsyncGenerator<TranscribeSegment | TranscribeStreamEvent, void, void>;
19
- export declare function transcribeStream(modelId: string, audioInputStream: AsyncIterable<Buffer>, prompt?: string, metadata?: boolean, opts?: TranscribeStreamOpts, requestId?: string): AsyncGenerator<string | TranscribeStreamEvent, void, void>;
18
+ export declare function transcribeStream(modelId: string, audioInputStream: AsyncIterable<Buffer>, prompt: string | undefined, metadata: true, opts?: TranscribeStreamOpts, requestId?: string): AsyncGenerator<TranscribeSegment | TranscribeStreamEvent, TranscribeReturn, void>;
19
+ export declare function transcribeStream(modelId: string, audioInputStream: AsyncIterable<Buffer>, prompt?: string, metadata?: boolean, opts?: TranscribeStreamOpts, requestId?: string): AsyncGenerator<string | TranscribeStreamEvent, TranscribeReturn, void>;
20
20
  //# sourceMappingURL=transcribe.d.ts.map
@@ -100,6 +100,55 @@ async function restorePrompt(modelId, originalConfig) {
100
100
  return;
101
101
  await model.reload(buildWhisperReloadConfig({ ...originalConfig, initial_prompt: '' }));
102
102
  }
103
+ function mapTranscribeStats(stats) {
104
+ return {
105
+ ...(stats?.audioDurationMs !== undefined && {
106
+ audioDuration: stats.audioDurationMs
107
+ }),
108
+ ...(stats?.realTimeFactor !== undefined && {
109
+ realTimeFactor: stats.realTimeFactor
110
+ }),
111
+ ...(stats?.tokensPerSecond !== undefined && {
112
+ tokensPerSecond: stats.tokensPerSecond
113
+ }),
114
+ ...(stats?.totalTokens !== undefined && {
115
+ totalTokens: stats.totalTokens
116
+ }),
117
+ ...(stats?.totalSegments !== undefined && {
118
+ totalSegments: stats.totalSegments
119
+ }),
120
+ ...(stats?.whisperEncodeMs !== undefined && {
121
+ whisperEncodeTime: stats.whisperEncodeMs
122
+ }),
123
+ ...(stats?.whisperDecodeMs !== undefined && {
124
+ whisperDecodeTime: stats.whisperDecodeMs
125
+ }),
126
+ ...(stats?.encoderMs !== undefined && {
127
+ encoderTime: stats.encoderMs
128
+ }),
129
+ ...(stats?.decoderMs !== undefined && {
130
+ decoderTime: stats.decoderMs
131
+ }),
132
+ ...(stats?.melSpecMs !== undefined && {
133
+ melSpecTime: stats.melSpecMs
134
+ }),
135
+ ...(stats?.backendDevice !== undefined && {
136
+ backendDevice: stats.backendDevice
137
+ }),
138
+ ...(stats?.backendId !== undefined && {
139
+ backendId: stats.backendId
140
+ }),
141
+ ...(stats?.gpuUnsupported !== undefined && {
142
+ gpuUnsupported: stats.gpuUnsupported
143
+ }),
144
+ ...(stats?.gpuMemTotalMb !== undefined && {
145
+ gpuMemTotalMb: stats.gpuMemTotalMb
146
+ }),
147
+ ...(stats?.gpuMemFreeMb !== undefined && {
148
+ gpuMemFreeMb: stats.gpuMemFreeMb
149
+ })
150
+ };
151
+ }
103
152
  export async function* transcribe(params, requestId) {
104
153
  const env_1 = { stack: [], error: void 0, hasError: false };
105
154
  try {
@@ -173,54 +222,7 @@ export async function* transcribe(params, requestId) {
173
222
  }
174
223
  }
175
224
  const modelExecutionMs = nowMs() - modelStart;
176
- const stats = {
177
- ...(response.stats?.audioDurationMs !== undefined && {
178
- audioDuration: response.stats.audioDurationMs
179
- }),
180
- ...(response.stats?.realTimeFactor !== undefined && {
181
- realTimeFactor: response.stats.realTimeFactor
182
- }),
183
- ...(response.stats?.tokensPerSecond !== undefined && {
184
- tokensPerSecond: response.stats.tokensPerSecond
185
- }),
186
- ...(response.stats?.totalTokens !== undefined && {
187
- totalTokens: response.stats.totalTokens
188
- }),
189
- ...(response.stats?.totalSegments !== undefined && {
190
- totalSegments: response.stats.totalSegments
191
- }),
192
- ...(response.stats?.whisperEncodeMs !== undefined && {
193
- whisperEncodeTime: response.stats.whisperEncodeMs
194
- }),
195
- ...(response.stats?.whisperDecodeMs !== undefined && {
196
- whisperDecodeTime: response.stats.whisperDecodeMs
197
- }),
198
- ...(response.stats?.encoderMs !== undefined && {
199
- encoderTime: response.stats.encoderMs
200
- }),
201
- ...(response.stats?.decoderMs !== undefined && {
202
- decoderTime: response.stats.decoderMs
203
- }),
204
- ...(response.stats?.melSpecMs !== undefined && {
205
- melSpecTime: response.stats.melSpecMs
206
- }),
207
- ...(response.stats?.backendDevice !== undefined && {
208
- backendDevice: response.stats.backendDevice
209
- }),
210
- ...(response.stats?.backendId !== undefined && {
211
- backendId: response.stats.backendId
212
- }),
213
- ...(response.stats?.gpuUnsupported !== undefined && {
214
- gpuUnsupported: response.stats.gpuUnsupported
215
- }),
216
- ...(response.stats?.gpuMemTotalMb !== undefined && {
217
- gpuMemTotalMb: response.stats.gpuMemTotalMb
218
- }),
219
- ...(response.stats?.gpuMemFreeMb !== undefined && {
220
- gpuMemFreeMb: response.stats.gpuMemFreeMb
221
- })
222
- };
223
- return buildStreamResult(modelExecutionMs, stats);
225
+ return buildStreamResult(modelExecutionMs, mapTranscribeStats(response.stats));
224
226
  }
225
227
  catch (e_1) {
226
228
  env_1.error = e_1;
@@ -286,6 +288,7 @@ export async function* transcribeStream(modelId, audioInputStream, prompt, metad
286
288
  ctx.signal.removeEventListener('abort', onAbort);
287
289
  });
288
290
  const runOpts = buildRunStreamingOpts(engineType, opts);
291
+ const modelStart = nowMs();
289
292
  const response = await model.runStreaming(audioInputStream, runOpts);
290
293
  for await (const output of response.iterate()) {
291
294
  if (ctx.signal.aborted)
@@ -316,6 +319,9 @@ export async function* transcribeStream(modelId, audioInputStream, prompt, metad
316
319
  yield* emitSegment(segment, metadata, silenceMarker);
317
320
  }
318
321
  }
322
+ await response.await();
323
+ const modelExecutionMs = nowMs() - modelStart;
324
+ return buildStreamResult(modelExecutionMs, mapTranscribeStats(response.stats));
319
325
  }
320
326
  catch (e_2) {
321
327
  env_2.error = e_2;
@@ -0,0 +1,5 @@
1
+ import type { TranslationStats } from '../../../schemas/index.js';
2
+ import type { NmtStats } from '../../../server/bare/types/addon-responses.js';
3
+ export declare const NMT_SECONDS_TO_MS = 1000;
4
+ export declare function buildNmtTranslationStats(stats: Partial<NmtStats> | undefined): TranslationStats;
5
+ //# sourceMappingURL=translate-stats.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const NMT_SECONDS_TO_MS = 1000;
2
+ export function buildNmtTranslationStats(stats) {
3
+ return {
4
+ ...(stats?.totalTime !== undefined && { totalTime: stats.totalTime * NMT_SECONDS_TO_MS }),
5
+ ...(stats?.totalTokens !== undefined && { totalTokens: stats.totalTokens }),
6
+ ...(stats?.decodeTime !== undefined && { decodeTime: stats.decodeTime * NMT_SECONDS_TO_MS }),
7
+ ...(stats?.encodeTime !== undefined && { encodeTime: stats.encodeTime * NMT_SECONDS_TO_MS }),
8
+ ...(stats?.TPS !== undefined && { tokensPerSecond: stats.TPS }),
9
+ ...(stats?.TTFT !== undefined && { timeToFirstToken: stats.TTFT })
10
+ };
11
+ }
@@ -55,9 +55,12 @@ import { translateServerParamsSchema, normalizeModelType, ModelType, AFRICAN_LAN
55
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 { buildNmtTranslationStats } from '../../../server/bare/ops/translate-stats.js';
58
59
  import { ModelIsDelegatedError, ModelNotFoundError, ModelTypeMismatchError, TranslationFailedError } from '../../../utils/errors-server.js';
59
60
  import { getRequestRegistry, withRequestContext } from '../../../server/bare/runtime/index.js';
61
+ import { isAddonContextOverflowError } from '../../../server/bare/plugins/llamacpp-completion/ops/context-overflow.js';
60
62
  import { generateServerRequestId } from '../../../server/bare/runtime/request-id.js';
63
+ import { getModelParallel } from '../../../server/utils/index.js';
61
64
  import { getServerLogger } from '../../../logging/index.js';
62
65
  export function getLanguage(code) {
63
66
  if (!code)
@@ -121,38 +124,47 @@ export async function* translate(params, requestId) {
121
124
  const afriquePrompt = isLlm && (isAfrican(from) || isAfrican(to));
122
125
  const fromLanguage = getLanguage(from);
123
126
  const toLanguage = getLanguage(to);
124
- // Open a request-scoped lifecycle for both engine branches. LLM-
125
- // translate inherits llamacpp-completion's `{ scope: "model",
126
- // hard: true }` cancel surface; NMT-translate inherits nmtcpp's
127
- // `{ scope: "none" }` — so the addon-cancel wiring below only
128
- // engages on the LLM path. NMT cancel is purely soft: the loop
129
- // exits on `signal.aborted`, scope unwinds, and the addon may run
130
- // to completion in the background — acceptable because the result
131
- // is dropped either way.
127
+ // Open a request-scoped lifecycle for both engine branches. The LLM
128
+ // path cancels only its own run response (wired below), so a peer
129
+ // completion on the same model keeps running; NMT-translate has no
130
+ // addon cancel — the loop exits on `signal.aborted`, scope unwinds,
131
+ // and the addon may run to completion in the background, which is
132
+ // fine because the result is dropped either way.
132
133
  const ctx = __addDisposableResource(env_1, await getRequestRegistry().begin({
133
134
  requestId: requestId ?? generateServerRequestId(),
134
135
  kind: 'translate',
135
- modelId
136
+ modelId,
137
+ // LLM translate joins the completion lane (its cap = the model's own
138
+ // `parallel`); NMT passes no cap and stays ungated on its own model.
139
+ ...(isLlm && {
140
+ maxConcurrentPerModel: getModelParallel(entry.local.config)
141
+ })
136
142
  }), true);
137
143
  const requestLogger = withRequestContext(getServerLogger(), ctx);
144
+ // Cancel only this run's response, not the addon's global cancel, so a peer
145
+ // completion keeps running.
146
+ let activeResponse = null;
147
+ const cancelActive = () => {
148
+ activeResponse?.cancel().catch((err) => {
149
+ requestLogger.warn(`[cancel] translate response.cancel() rejected during abort for modelId=${modelId}: ${err instanceof Error ? err.message : String(err)}`);
150
+ });
151
+ };
138
152
  if (isLlm) {
139
- const onAbort = () => {
140
- const addon = model.addon;
141
- if (addon?.cancel) {
142
- addon.cancel.call(addon).catch((err) => {
143
- requestLogger.warn(`[cancel] addon.cancel() rejected during abort for modelId=${modelId}: ${err instanceof Error ? err.message : String(err)}`);
144
- });
145
- }
146
- };
147
- ctx.signal.addEventListener('abort', onAbort, { once: true });
153
+ ctx.signal.addEventListener('abort', cancelActive, { once: true });
148
154
  if (ctx.signal.aborted)
149
- onAbort();
155
+ cancelActive();
150
156
  ctx.scope.defer(() => {
151
- ctx.signal.removeEventListener('abort', onAbort);
157
+ ctx.signal.removeEventListener('abort', cancelActive);
158
+ activeResponse = null;
152
159
  });
153
160
  }
154
161
  // Check if input is an array and model type is NMT
155
162
  if (Array.isArray(text) && canonicalModelType === ModelType.nmtcppTranslation) {
163
+ // A cancel that landed before native work (e.g. the model is being unloaded)
164
+ // must not start the batch. Bail before runBatch, dropping the result.
165
+ if (ctx.signal.aborted) {
166
+ return { modelExecutionMs: 0 };
167
+ }
156
168
  // Use runBatch for batch processing
157
169
  const modelStart = nowMs();
158
170
  const translations = await model.runBatch(text);
@@ -188,6 +200,15 @@ export async function* translate(params, requestId) {
188
200
  : `${context ? `${context}. ` : ''}Translate the following text from ${fromLanguage} into ${toLanguage}. Only output the translation, nothing else.\n\n${fromLanguage}: ${singleText}\n${toLanguage}:`
189
201
  }
190
202
  ];
203
+ // A translate cancelled before native work (queued-cancel, or the model being
204
+ // unloaded) must not call run(): LLM could decode against an exclusive finetune
205
+ // holding the lane, and either engine would run against a model being torn
206
+ // down. Bail with an empty soft-cancel result for both engines — the server
207
+ // never throws InferenceCancelledError (that error is client-constructed and
208
+ // would cross the RPC as a generic error).
209
+ if (ctx.signal.aborted) {
210
+ return { modelExecutionMs: 0 };
211
+ }
191
212
  const modelStart = nowMs();
192
213
  let response;
193
214
  if (canonicalModelType === ModelType.llamacppCompletion &&
@@ -199,14 +220,34 @@ export async function* translate(params, requestId) {
199
220
  else {
200
221
  response = await model.run(input);
201
222
  }
223
+ if (isLlm) {
224
+ activeResponse = response;
225
+ // A cancel that landed while `run(...)` was admitting still applies.
226
+ if (ctx.signal.aborted)
227
+ cancelActive();
228
+ }
202
229
  // Check if the response has an iterate method (like LLM models)
203
230
  if (canonicalModelType === ModelType.llamacppCompletion &&
204
231
  typeof response.iterate === 'function') {
205
232
  const llmResponse = response;
206
- for await (const token of llmResponse.iterate()) {
207
- if (ctx.signal.aborted)
208
- break;
209
- yield token;
233
+ try {
234
+ for await (const token of llmResponse.iterate()) {
235
+ if (ctx.signal.aborted)
236
+ break;
237
+ yield token;
238
+ }
239
+ }
240
+ catch (err) {
241
+ // A context-overflow rejection is a real terminal condition, not a
242
+ // cancellation — surface it even under an aborted signal, as completion does.
243
+ if (isAddonContextOverflowError(err))
244
+ throw err;
245
+ // The request signal is the SDK-owned cancellation contract. Addon
246
+ // rejection shapes differ for active and queued native sequences, so once
247
+ // cancellation is accepted the translate ends cleanly regardless of that
248
+ // transport detail; without an abort, every error still propagates.
249
+ if (!ctx.signal.aborted)
250
+ throw err;
210
251
  }
211
252
  const modelExecutionMs = nowMs() - modelStart;
212
253
  const stats = {
@@ -228,20 +269,7 @@ export async function* translate(params, requestId) {
228
269
  yield token;
229
270
  }
230
271
  const modelExecutionMs = nowMs() - modelStart;
231
- const stats = {
232
- ...(nmtResponse.stats?.totalTime !== undefined && { totalTime: nmtResponse.stats.totalTime }),
233
- ...(nmtResponse.stats?.totalTokens !== undefined && {
234
- totalTokens: nmtResponse.stats.totalTokens
235
- }),
236
- ...(nmtResponse.stats?.decodeTime !== undefined && {
237
- decodeTime: nmtResponse.stats.decodeTime
238
- }),
239
- ...(nmtResponse.stats?.encodeTime !== undefined && {
240
- encodeTime: nmtResponse.stats.encodeTime
241
- }),
242
- ...(nmtResponse.stats?.TPS !== undefined && { tokensPerSecond: nmtResponse.stats.TPS }),
243
- ...(nmtResponse.stats?.TTFT !== undefined && { timeToFirstToken: nmtResponse.stats.TTFT })
244
- };
272
+ const stats = buildNmtTranslationStats(nmtResponse.stats);
245
273
  return buildStreamResult(modelExecutionMs, stats);
246
274
  }
247
275
  catch (e_1) {
@@ -1,6 +1,7 @@
1
1
  import { promises as fsPromises } from 'bare-fs';
2
2
  import path from 'bare-path';
3
3
  import { unregisterModel } from '../../../server/bare/registry/model-registry.js';
4
+ import { getRequestRegistry } from '../../../server/bare/runtime/index.js';
4
5
  import { unregisterAllLoggingStreams } from '../../../server/bare/registry/logging-stream-registry.js';
5
6
  import { clearFinetuneRuntimeState } from '../../../server/bare/plugins/llamacpp-completion/ops/finetune.js';
6
7
  import { unregisterAddonLogger, getServerLogger } from '../../../logging/index.js';
@@ -15,31 +16,36 @@ export async function unloadModel(params) {
15
16
  if (!entry) {
16
17
  throw new ModelNotLoadedError(modelId);
17
18
  }
18
- clearFinetuneRuntimeState(modelId);
19
- if (!entry.isDelegated) {
20
- if (entry.local.model.unload) {
21
- await entry.local.model.unload();
22
- }
23
- if (clearStorage && entry.local.path) {
24
- const modelPath = entry.local.path;
25
- const modelFileName = path.basename(modelPath);
26
- const shardInfo = detectShardedModel(modelFileName);
27
- if (shardInfo.isSharded) {
28
- const shardDir = path.dirname(modelPath);
29
- await fsPromises.rm(shardDir, { recursive: true, force: true });
30
- logger.info(`Sharded model storage cleared: ${shardDir}`);
19
+ // Cancel and drain the model's requests, then tear down under the admission
20
+ // barrier so nothing goes active against a model being freed. The barrier is
21
+ // lifted for us when this resolves.
22
+ await getRequestRegistry().withModelDraining(modelId, async () => {
23
+ clearFinetuneRuntimeState(modelId);
24
+ if (!entry.isDelegated) {
25
+ if (entry.local.model.unload) {
26
+ await entry.local.model.unload();
31
27
  }
32
- else {
33
- const target = getClearStorageTarget(modelPath);
34
- await fsPromises.rm(target.path, {
35
- recursive: target.kind === 'directory',
36
- force: true
37
- });
38
- logger.info(`Model storage cleared (${target.kind}): ${target.path}`);
28
+ if (clearStorage && entry.local.path) {
29
+ const modelPath = entry.local.path;
30
+ const modelFileName = path.basename(modelPath);
31
+ const shardInfo = detectShardedModel(modelFileName);
32
+ if (shardInfo.isSharded) {
33
+ const shardDir = path.dirname(modelPath);
34
+ await fsPromises.rm(shardDir, { recursive: true, force: true });
35
+ logger.info(`Sharded model storage cleared: ${shardDir}`);
36
+ }
37
+ else {
38
+ const target = getClearStorageTarget(modelPath);
39
+ await fsPromises.rm(target.path, {
40
+ recursive: target.kind === 'directory',
41
+ force: true
42
+ });
43
+ logger.info(`Model storage cleared (${target.kind}): ${target.path}`);
44
+ }
39
45
  }
40
46
  }
41
- }
42
- unregisterAddonLogger(modelId);
43
- unregisterAllLoggingStreams(modelId);
44
- logger.info(`Model ${modelId} unloaded`);
47
+ unregisterAddonLogger(modelId);
48
+ unregisterAllLoggingStreams(modelId);
49
+ logger.info(`Model ${modelId} unloaded`);
50
+ });
45
51
  }
@@ -0,0 +1,20 @@
1
+ import { type AudioGenAudioInput } from '../../../../../schemas/audio-gen.js';
2
+ export type AudioGenAudioInputName = 'referenceAudio' | 'sourceAudio';
3
+ /**
4
+ * Resolve a reference/source audio input into the interleaved stereo 48 kHz
5
+ * Float32 PCM the ACE-Step engine consumes.
6
+ *
7
+ * - `base64` inputs (client `Buffer`/`Uint8Array`) must already be raw
8
+ * interleaved stereo 48 kHz Float32 LE PCM; they are handed over as-is
9
+ * (a copy is made only when the bytes are not 4-byte aligned).
10
+ * - `filePath` inputs with a decodable extension (`.wav`, `.mp3`, `.m4a`,
11
+ * `.ogg`, `.flac`, `.aac`) are decoded through the SDK's FFmpeg decoder to
12
+ * 48 kHz mono float PCM and duplicated onto both channels. Any other
13
+ * extension is streamed in as raw interleaved stereo 48 kHz Float32 LE PCM.
14
+ *
15
+ * Every path is bounded by `AUDIOGEN_INPUT_MAX_SECONDS` and rejects
16
+ * non-finite samples, so malformed or oversized input fails with
17
+ * `InvalidAudioInputError` before the engine is invoked.
18
+ */
19
+ export declare function resolveAudioGenPcm(input: AudioGenAudioInput, name: AudioGenAudioInputName): Promise<Float32Array>;
20
+ //# sourceMappingURL=audio-gen-input.d.ts.map