@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,620 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { z } from 'zod';
3
+ import prettier from 'prettier';
4
+ import { requestSchema, responseSchema } from '../../schemas/common.js';
5
+ import { methodShapes } from '../../server/rpc/method-shapes.js';
6
+ import { constantsRegistry } from '../../schemas/constants-registry.js';
7
+ import { buildModelsRegistry } from './build-models-registry.js';
8
+ import { buildModelTypeMaps } from './build-model-type-maps.js';
9
+ import { buildErrorCodes } from './build-error-codes.js';
10
+ export const contractDir = new URL('../../contract/', import.meta.url);
11
+ export const callShapeByHandlerType = {
12
+ reply: 'request-reply',
13
+ stream: 'server-stream',
14
+ duplex: 'duplex'
15
+ };
16
+ /**
17
+ * Wire `type` literal(s) of a request/response schema. Union members all
18
+ * carry the same literal (one method can have several request variants),
19
+ * pipes are read on their input side, intersections on both sides.
20
+ */
21
+ function wireTypesOf(schema) {
22
+ if (schema instanceof z.ZodObject) {
23
+ const typeField = schema.shape['type'];
24
+ if (typeField instanceof z.ZodLiteral) {
25
+ return Array.from(typeField.values).filter(function (value) {
26
+ return typeof value === 'string';
27
+ });
28
+ }
29
+ return [];
30
+ }
31
+ if (schema instanceof z.ZodUnion) {
32
+ const nested = schema.options.flatMap(function (option) {
33
+ return wireTypesOf(option);
34
+ });
35
+ return [...new Set(nested)];
36
+ }
37
+ if (schema instanceof z.ZodPipe) {
38
+ return wireTypesOf(schema.in);
39
+ }
40
+ if (schema instanceof z.ZodIntersection) {
41
+ const nested = [
42
+ ...wireTypesOf(schema.def.left),
43
+ ...wireTypesOf(schema.def.right)
44
+ ];
45
+ return [...new Set(nested)];
46
+ }
47
+ return [];
48
+ }
49
+ function wireTypeOf(schema, context) {
50
+ const types = wireTypesOf(schema);
51
+ if (types.length !== 1) {
52
+ throw new Error(`Expected exactly one wire "type" literal for ${context}, found: [${types.join(', ')}]`);
53
+ }
54
+ return types[0];
55
+ }
56
+ function assertNoRefs(value, defName) {
57
+ if (Array.isArray(value)) {
58
+ for (const item of value)
59
+ assertNoRefs(item, defName);
60
+ return;
61
+ }
62
+ if (value !== null && typeof value === 'object') {
63
+ for (const [key, child] of Object.entries(value)) {
64
+ if (key === '$ref') {
65
+ throw new Error(`Schema for "${defName}" emitted a $ref (${String(child)}); ` +
66
+ 'nesting it under $defs would break the pointer. Extend build-contract.ts to re-root refs.');
67
+ }
68
+ assertNoRefs(child, defName);
69
+ }
70
+ }
71
+ }
72
+ function mergeObjectSchemaParts(parts, defName) {
73
+ const properties = {};
74
+ const required = [];
75
+ for (const part of parts) {
76
+ if (part['type'] !== undefined && part['type'] !== 'object') {
77
+ throw new Error(`Cannot merge a non-object schema member while flattening "${defName}"`);
78
+ }
79
+ for (const [key, value] of Object.entries(part['properties'] ?? {})) {
80
+ if (key in properties && JSON.stringify(properties[key]) !== JSON.stringify(value)) {
81
+ throw new Error(`Conflicting definitions for property "${key}" while flattening "${defName}"`);
82
+ }
83
+ properties[key] = value;
84
+ }
85
+ for (const name of part['required'] ?? []) {
86
+ if (!required.includes(name))
87
+ required.push(name);
88
+ }
89
+ }
90
+ return { type: 'object', properties, required };
91
+ }
92
+ /**
93
+ * Flattens `allOf: [commonFields, { oneOf/anyOf: [...variants] }]` (Zod's
94
+ * `.and()` of an object with a discriminated union) into a plain
95
+ * `oneOf`/`anyOf` with `commonFields` merged into every variant.
96
+ *
97
+ * Needed because JSON-Schema-consuming codegen (verified against
98
+ * datamodel-code-generator, the standard JSON-Schema -> pydantic tool) does
99
+ * not merge `allOf` branches when one of them is itself a union: it silently
100
+ * keeps only the first branch under the schema's title, dropping every
101
+ * variant-specific field. `allOf`-of-a-union is valid JSON Schema, but this
102
+ * is a known rough edge across JSON-Schema tooling generally, so flattening
103
+ * it here makes the contract more robust for every consumer, not a
104
+ * Python-specific workaround.
105
+ *
106
+ * Scoped to the one shape currently in use (exactly one `allOf` branch is a
107
+ * union, the rest are plain object schemas): anything else throws instead of
108
+ * silently emitting a schema some consumer will mis-generate from.
109
+ */
110
+ function flattenAllOfWithUnion(json, defName) {
111
+ const allOf = json['allOf'];
112
+ if (!Array.isArray(allOf))
113
+ return json;
114
+ const isUnionMember = function (member) {
115
+ return (member !== null &&
116
+ typeof member === 'object' &&
117
+ (Array.isArray(member['oneOf']) ||
118
+ Array.isArray(member['anyOf'])));
119
+ };
120
+ const unionMembers = allOf.filter(isUnionMember);
121
+ const plainMembers = allOf.filter(function (member) {
122
+ return !isUnionMember(member);
123
+ });
124
+ if (unionMembers.length !== 1) {
125
+ throw new Error(`Schema for "${defName}" uses allOf with ${unionMembers.length} union branches; ` +
126
+ 'only the single-union-branch shape is handled. Extend flattenAllOfWithUnion in build-contract.ts.');
127
+ }
128
+ const unionMember = unionMembers[0];
129
+ const unionKey = Array.isArray(unionMember['oneOf']) ? 'oneOf' : 'anyOf';
130
+ const arms = unionMember[unionKey];
131
+ const flattened = { ...json };
132
+ delete flattened['allOf'];
133
+ flattened[unionKey] = arms.map(function (arm) {
134
+ return mergeObjectSchemaParts([...plainMembers, arm], defName);
135
+ });
136
+ return flattened;
137
+ }
138
+ function isSchemaObject(node) {
139
+ return node !== null && typeof node === 'object' && !Array.isArray(node);
140
+ }
141
+ function unionArmsOf(node) {
142
+ if (Array.isArray(node['oneOf']))
143
+ return node['oneOf'];
144
+ if (Array.isArray(node['anyOf']))
145
+ return node['anyOf'];
146
+ return undefined;
147
+ }
148
+ /**
149
+ * Collapses `oneOf`/`anyOf` arrays that carry exactly one arm into that arm
150
+ * directly, recursively from the leaves up. A single-arm union discriminates
151
+ * nothing, so naming it as a union (wrapper title + arm title) forces the
152
+ * arm into a name distinct from a wrapper it's identical to — e.g.
153
+ * `reloadConfigRequestSchema = z.union([reloadConfigWhisperRequestSchema])`
154
+ * produced both a `ReloadConfigRequest` (the wrapper) and a
155
+ * `ReloadConfigRequest2` (the lone arm) for what is really one type.
156
+ * Collapsing first means there's only ever one node here for titling to see.
157
+ * The wrapper's own explicit `title` (its `.meta()`), if any, wins over the
158
+ * arm's — it's the intentional public name for the schema export.
159
+ */
160
+ function collapseSingleMemberUnions(node) {
161
+ if (!isSchemaObject(node))
162
+ return node;
163
+ const properties = node['properties'];
164
+ if (isSchemaObject(properties)) {
165
+ for (const key of Object.keys(properties)) {
166
+ properties[key] = collapseSingleMemberUnions(properties[key]);
167
+ }
168
+ }
169
+ if (node['items'] !== undefined) {
170
+ node['items'] = collapseSingleMemberUnions(node['items']);
171
+ }
172
+ if (isSchemaObject(node['additionalProperties'])) {
173
+ node['additionalProperties'] = collapseSingleMemberUnions(node['additionalProperties']);
174
+ }
175
+ const unionKey = Array.isArray(node['oneOf'])
176
+ ? 'oneOf'
177
+ : Array.isArray(node['anyOf'])
178
+ ? 'anyOf'
179
+ : undefined;
180
+ if (!unionKey)
181
+ return node;
182
+ const arms = node[unionKey].map(collapseSingleMemberUnions);
183
+ if (arms.length !== 1) {
184
+ node[unionKey] = arms;
185
+ return node;
186
+ }
187
+ const arm = arms[0];
188
+ const wrapperTitle = typeof node['title'] === 'string' ? node['title'] : undefined;
189
+ const wrapperRest = { ...node };
190
+ delete wrapperRest[unionKey];
191
+ if (!isSchemaObject(arm))
192
+ return wrapperTitle ? wrapperRest : arm;
193
+ const merged = { ...wrapperRest, ...arm };
194
+ if (wrapperTitle)
195
+ merged['title'] = wrapperTitle;
196
+ return merged;
197
+ }
198
+ function constValueOf(arm, key) {
199
+ const properties = arm['properties'];
200
+ const field = isSchemaObject(properties) ? properties[key] : undefined;
201
+ return isSchemaObject(field) && typeof field['const'] === 'string' ? field['const'] : undefined;
202
+ }
203
+ /**
204
+ * Property key that best discriminates a set of sibling union arms: the
205
+ * `const` string field, wherever present, that splits the arms into the most
206
+ * distinct groups — counting "arms with no const for this key at all" as one
207
+ * more group (they'll be disambiguated some other way; see `nameUnionArms`).
208
+ * Picks the *best* key rather than requiring perfection — two shortcomings
209
+ * otherwise cause bad names:
210
+ *
211
+ * - Requiring every arm to carry the key: `loadModel`'s custom-plugin
212
+ * catch-all arm has no `modelType` const, so demanding full coverage threw
213
+ * away `modelType` for the other 11 arms too.
214
+ * - Requiring the value to never repeat: `completionStream`'s event union
215
+ * has two arms both typed `completionDone` (success vs. error) — demanding
216
+ * perfect uniqueness rejected `type` for all 8 events, even though it
217
+ * cleanly separates the other 6. `nameUnionArms` recurses into the
218
+ * colliding pair to find a secondary key (`stopReason`) instead.
219
+ *
220
+ * Also deliberately not a fixed guess-list of names (`type`/`operation`/...):
221
+ * the wire method discriminator (`type`) is IDENTICAL across every arm of a
222
+ * single method's own operation union (e.g. every `rag` arm has
223
+ * `type: 'rag'`), so guessing by name picked the one field that never
224
+ * discriminates anything and produced `RagRequestRag`, `RagRequestRag2`, ...
225
+ * A key where every arm lands in one single group is rejected outright —
226
+ * it's exactly this "same value everywhere" (or "no arm has it") case.
227
+ */
228
+ function pickDiscriminatorKey(arms) {
229
+ const candidateKeys = new Set();
230
+ for (const arm of arms) {
231
+ const properties = arm['properties'];
232
+ if (isSchemaObject(properties)) {
233
+ for (const key of Object.keys(properties))
234
+ candidateKeys.add(key);
235
+ }
236
+ }
237
+ let bestKey;
238
+ let bestGroupCount = 1; // a key producing only 1 group discriminates nothing
239
+ for (const key of candidateKeys) {
240
+ const values = new Set();
241
+ let anyMissing = false;
242
+ for (const arm of arms) {
243
+ const value = constValueOf(arm, key);
244
+ if (value === undefined)
245
+ anyMissing = true;
246
+ else
247
+ values.add(value);
248
+ }
249
+ const groupCount = values.size + (anyMissing ? 1 : 0);
250
+ if (groupCount > bestGroupCount) {
251
+ bestKey = key;
252
+ bestGroupCount = groupCount;
253
+ }
254
+ }
255
+ return bestKey;
256
+ }
257
+ function ensureUniqueTitle(base, seenTitles) {
258
+ if (!seenTitles.has(base)) {
259
+ seenTitles.add(base);
260
+ return base;
261
+ }
262
+ let suffix = 2;
263
+ while (seenTitles.has(`${base}${suffix}`))
264
+ suffix++;
265
+ const unique = `${base}${suffix}`;
266
+ seenTitles.add(unique);
267
+ return unique;
268
+ }
269
+ /** Whether a schema node is itself substantial enough to need a title: an
270
+ * object, an enum, or a nested union (whose own arms will need names) —
271
+ * as opposed to a bare scalar (`{type: 'string'}`) or `{}` ("any"), which
272
+ * never get titled regardless of position. */
273
+ function needsOwnTitle(node) {
274
+ if (!isSchemaObject(node))
275
+ return false;
276
+ if (unionArmsOf(node))
277
+ return true;
278
+ const isEnum = Array.isArray(node['enum']);
279
+ const isObject = node['type'] === 'object' || isSchemaObject(node['properties']);
280
+ const hasConst = node['const'] !== undefined;
281
+ return (isEnum || isObject) && !hasConst;
282
+ }
283
+ /**
284
+ * Names each arm of a union: by the shared discriminator's value when the
285
+ * arms have one in common; else, if at most one arm actually needs a title
286
+ * (e.g. `string | { repeats: number }` — only the object arm does), that one
287
+ * arm gets the bare prefix with no suffix, since there's nothing to
288
+ * disambiguate; only when neither applies does it fall back to positional
289
+ * naming (1-based: `Foo1`, `Foo2`, ...).
290
+ */
291
+ function nameUnionArms(arms, namePrefix) {
292
+ if (arms.length <= 1) {
293
+ return arms.map(function () {
294
+ return namePrefix;
295
+ });
296
+ }
297
+ const key = pickDiscriminatorKey(arms);
298
+ if (!key) {
299
+ if (arms.filter(needsOwnTitle).length <= 1) {
300
+ return arms.map(function () {
301
+ return namePrefix;
302
+ });
303
+ }
304
+ return arms.map(function (_arm, index) {
305
+ return `${namePrefix}${index + 1}`;
306
+ });
307
+ }
308
+ // Group arms by their value for `key` (arms with no const for `key` share
309
+ // the `undefined` group). A key is only ever picked when it produces more
310
+ // than one group (see pickDiscriminatorKey), so every group here is
311
+ // strictly smaller than `arms` — groups with more than one member recurse
312
+ // to find a secondary discriminator instead of falling straight to
313
+ // `ensureUniqueTitle`'s numeric bump. This is what turns two arms sharing
314
+ // `type: 'completionDone'` into `...CompletionDoneError` (has a `stopReason`
315
+ // const) and bare `...CompletionDone` (the lone arm left without one)
316
+ // instead of `...CompletionDone` / `...CompletionDone2`.
317
+ const groupOrder = [];
318
+ const groups = new Map();
319
+ arms.forEach(function (arm, index) {
320
+ const value = constValueOf(arm, key);
321
+ if (!groups.has(value)) {
322
+ groupOrder.push(value);
323
+ groups.set(value, []);
324
+ }
325
+ groups.get(value)?.push(index);
326
+ });
327
+ const names = new Array(arms.length);
328
+ for (const value of groupOrder) {
329
+ const indexes = groups.get(value);
330
+ const groupPrefix = value !== undefined ? `${namePrefix}${toPascalCase(value)}` : namePrefix;
331
+ if (indexes.length === 1) {
332
+ names[indexes[0]] = groupPrefix;
333
+ continue;
334
+ }
335
+ const subNames = nameUnionArms(indexes.map(function (index) {
336
+ return arms[index];
337
+ }), groupPrefix);
338
+ indexes.forEach(function (armIndex, i) {
339
+ names[armIndex] = subNames[i];
340
+ });
341
+ }
342
+ return names;
343
+ }
344
+ /**
345
+ * Assigns a `title` to `node` (unless it already has one) and recurses into
346
+ * its properties/items/union arms, naming each by its property key or (for
347
+ * discriminated union arms) the arm's discriminator value — e.g.
348
+ * `completionStream.response`'s event union gets
349
+ * `CompletionStreamResponseEventsItemContentDelta` instead of a bare `Events3`.
350
+ *
351
+ * Without this, nested/inline Zod schemas reach codegen (verified against
352
+ * datamodel-code-generator, a JSON-Schema-to-pydantic tool) as
353
+ * positionally-numbered classes (`Stats13`, `Events7`) with no indication of
354
+ * what they represent.
355
+ *
356
+ * If `node` already carries a `title` (a schema author's explicit Zod
357
+ * `.meta({ title: ... })`, e.g. `FinetuneRunRequest`), that title wins over
358
+ * `namePrefix` — both for `node` itself and as the prefix for its own
359
+ * children — instead of being silently discarded the moment `node` also
360
+ * happens to be a union wrapper.
361
+ */
362
+ function titleSchemaNode(node, namePrefix, seenTitles) {
363
+ if (!isSchemaObject(node))
364
+ return;
365
+ const existingTitle = typeof node['title'] === 'string' ? node['title'] : undefined;
366
+ if (existingTitle)
367
+ seenTitles.add(existingTitle);
368
+ const arms = unionArmsOf(node);
369
+ if (arms) {
370
+ const effectivePrefix = existingTitle ?? namePrefix;
371
+ const armNames = nameUnionArms(arms, effectivePrefix);
372
+ arms.forEach(function (arm, index) {
373
+ titleSchemaNode(arm, armNames[index], seenTitles);
374
+ });
375
+ return;
376
+ }
377
+ // If namePrefix collided with an already-assigned title, ensureUniqueTitle
378
+ // bumps it (e.g. `...CompletionDone` -> `...CompletionDone2`). Children
379
+ // must be prefixed with that *final* title, not the pre-bump namePrefix —
380
+ // otherwise a child two levels down silently collides with an unrelated
381
+ // sibling's identically-(pre-bump-)named child instead of its own.
382
+ const assignedTitle = needsOwnTitle(node) && !existingTitle ? ensureUniqueTitle(namePrefix, seenTitles) : undefined;
383
+ if (assignedTitle)
384
+ node['title'] = assignedTitle;
385
+ const effectivePrefix = existingTitle ?? assignedTitle ?? namePrefix;
386
+ const properties = node['properties'];
387
+ if (isSchemaObject(properties)) {
388
+ for (const [key, propSchema] of Object.entries(properties)) {
389
+ titleSchemaNode(propSchema, `${effectivePrefix}${toPascalCase(key)}`, seenTitles);
390
+ }
391
+ }
392
+ if (node['items'] !== undefined) {
393
+ titleSchemaNode(node['items'], `${effectivePrefix}Item`, seenTitles);
394
+ }
395
+ if (isSchemaObject(node['additionalProperties'])) {
396
+ titleSchemaNode(node['additionalProperties'], `${effectivePrefix}Value`, seenTitles);
397
+ }
398
+ }
399
+ /** Entry point for a def's root: the root already has `rootTitle` (assigned
400
+ * by the caller), so only its children need naming — recursing straight into
401
+ * `titleSchemaNode` would treat the root itself as untitled. */
402
+ function titleNestedSchemas(root, rootTitle, seenTitles) {
403
+ const arms = unionArmsOf(root);
404
+ if (arms) {
405
+ const armNames = nameUnionArms(arms, rootTitle);
406
+ arms.forEach(function (arm, index) {
407
+ titleSchemaNode(arm, armNames[index], seenTitles);
408
+ });
409
+ return;
410
+ }
411
+ const properties = root['properties'];
412
+ if (isSchemaObject(properties)) {
413
+ for (const [key, propSchema] of Object.entries(properties)) {
414
+ titleSchemaNode(propSchema, `${rootTitle}${toPascalCase(key)}`, seenTitles);
415
+ }
416
+ }
417
+ if (root['items'] !== undefined) {
418
+ titleSchemaNode(root['items'], `${rootTitle}Item`, seenTitles);
419
+ }
420
+ if (isSchemaObject(root['additionalProperties'])) {
421
+ titleSchemaNode(root['additionalProperties'], `${rootTitle}Value`, seenTitles);
422
+ }
423
+ }
424
+ export function toWireJsonSchema(schema, io, defName) {
425
+ const json = z.toJSONSchema(schema, {
426
+ target: 'draft-2020-12',
427
+ io,
428
+ unrepresentable: 'any'
429
+ });
430
+ delete json['$schema'];
431
+ const flattened = flattenAllOfWithUnion(json, defName);
432
+ const collapsed = collapseSingleMemberUnions(flattened);
433
+ assertNoRefs(collapsed, defName);
434
+ return collapsed;
435
+ }
436
+ function collectByWireType(options, side) {
437
+ const byType = new Map();
438
+ for (const [index, option] of options.entries()) {
439
+ const schema = option;
440
+ const wireType = wireTypeOf(schema, `${side} union member #${index}`);
441
+ if (byType.has(wireType)) {
442
+ throw new Error(`Duplicate ${side} schema for wire type "${wireType}"`);
443
+ }
444
+ byType.set(wireType, schema);
445
+ }
446
+ return byType;
447
+ }
448
+ function refTo(defName) {
449
+ return { $ref: `#/$defs/${defName}` };
450
+ }
451
+ /**
452
+ * PascalCase class name for a wire type, e.g. `loadModel` -> `LoadModel`,
453
+ * `finetune:progress` -> `FinetuneProgress`. Without a `title`, JSON Schema
454
+ * -> codegen (e.g. datamodel-code-generator) falls back to positional names
455
+ * like `Request1Model11` for nested unions — verified empirically against
456
+ * the actual generator, not assumed.
457
+ */
458
+ function toPascalCase(name) {
459
+ return name
460
+ .split(/[:\-_]/)
461
+ .map(function (part) {
462
+ return part.length > 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part;
463
+ })
464
+ .join('');
465
+ }
466
+ function classTitleFor(name, suffix) {
467
+ return `${toPascalCase(name)}${suffix}`;
468
+ }
469
+ /**
470
+ * The 4 methods that can switch from a plain unary reply to a stream of
471
+ * responses when the caller opts in (`request.withProgress === true`):
472
+ * progress events and the final reply both arrive as stream frames,
473
+ * distinguished only by each payload's own `type` — see
474
+ * `server/rpc/handler-selection.ts` (`handlerSupportsProgress`) and
475
+ * `server/rpc/handler-registry.ts` for the source of truth this mirrors.
476
+ *
477
+ * `rag` and `finetune` gate progress further on the request's `operation`
478
+ * (see `ragSupportsProgress`/`finetuneSupportsProgress` in
479
+ * `handler-registry.ts`) — `condition` spells out that extra check so a
480
+ * contract consumer doesn't have to special-case it separately.
481
+ */
482
+ const progressByMethod = {
483
+ loadModel: { condition: 'request.withProgress === true', responseType: 'modelProgress' },
484
+ downloadAsset: { condition: 'request.withProgress === true', responseType: 'modelProgress' },
485
+ rag: {
486
+ condition: "request.withProgress === true && ['ingest', 'saveEmbeddings', 'reindex'].includes(request.operation)",
487
+ responseType: 'rag:progress'
488
+ },
489
+ finetune: {
490
+ condition: "request.withProgress === true && ['start', 'resume', undefined].includes(request.operation)",
491
+ responseType: 'finetune:progress'
492
+ }
493
+ };
494
+ export function buildContract() {
495
+ const methodNames = Object.keys(methodShapes).sort();
496
+ const requestByType = collectByWireType(requestSchema.options, 'request');
497
+ const responseByType = collectByWireType(responseSchema.options, 'response');
498
+ for (const name of methodNames) {
499
+ if (!requestByType.has(name)) {
500
+ throw new Error(`Method "${name}" has no request schema in the request union`);
501
+ }
502
+ if (!responseByType.has(name)) {
503
+ throw new Error(`Method "${name}" has no response schema in the response union`);
504
+ }
505
+ }
506
+ for (const wireType of requestByType.keys()) {
507
+ if (!methodNames.includes(wireType)) {
508
+ throw new Error(`Request schema "${wireType}" has no entry in methodShapes`);
509
+ }
510
+ }
511
+ const responseTypes = [...responseByType.keys()].sort();
512
+ for (const [methodName, progress] of Object.entries(progressByMethod)) {
513
+ if (!responseByType.has(progress.responseType)) {
514
+ throw new Error(`progressByMethod["${methodName}"] points at response type "${progress.responseType}", ` +
515
+ 'which has no schema in the response union');
516
+ }
517
+ }
518
+ const defs = {
519
+ request: {
520
+ title: 'AnyRequest',
521
+ description: 'Any request accepted by the server, in wire (pre-parse) shape.',
522
+ anyOf: methodNames.map(function (name) {
523
+ return refTo(`${name}.request`);
524
+ })
525
+ },
526
+ response: {
527
+ title: 'AnyResponse',
528
+ description: 'Any response emitted by the server, including progress updates and error envelopes.',
529
+ anyOf: responseTypes.map(function (type) {
530
+ return refTo(`${type}.response`);
531
+ })
532
+ }
533
+ };
534
+ const defEntries = [];
535
+ for (const [type, schema] of requestByType) {
536
+ defEntries.push([`${type}.request`, schema, 'input', classTitleFor(type, 'Request')]);
537
+ }
538
+ for (const [type, schema] of responseByType) {
539
+ defEntries.push([`${type}.response`, schema, 'output', classTitleFor(type, 'Response')]);
540
+ }
541
+ // Public constants (@/schemas/constants-registry), merged into the same
542
+ // $defs as every request/response type via the same z.toJSONSchema call —
543
+ // not a separate artifact. `x-enum-varnames` preserves each entry's
544
+ // original key names (`ModelType.llamacppCompletion`, `PluginId.LLM`, ...)
545
+ // through codegen; plain JSON Schema `enum:` only carries values.
546
+ const constantVarNames = new Map();
547
+ for (const [name, schema] of Object.entries(constantsRegistry)) {
548
+ const defName = `constants.${name}`;
549
+ defEntries.push([defName, schema, 'output', name]);
550
+ constantVarNames.set(defName, Object.keys(schema.enum));
551
+ }
552
+ defEntries.sort(function (a, b) {
553
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
554
+ });
555
+ // Seed with every top-level title up front so nested-schema titles (assigned
556
+ // below, per def) can never collide with a top-level def title regardless
557
+ // of iteration order.
558
+ const seenTitles = new Set([
559
+ 'AnyRequest',
560
+ 'AnyResponse',
561
+ ...defEntries.map((entry) => entry[3])
562
+ ]);
563
+ for (const [defName, schema, io, title] of defEntries) {
564
+ const wireSchema = toWireJsonSchema(schema, io, defName);
565
+ titleNestedSchemas(wireSchema, title, seenTitles);
566
+ const varNames = constantVarNames.get(defName);
567
+ defs[defName] = {
568
+ title,
569
+ ...wireSchema,
570
+ ...(varNames && { 'x-enum-varnames': varNames })
571
+ };
572
+ }
573
+ const manifest = {
574
+ methods: methodNames.map(function (name) {
575
+ const progress = progressByMethod[name];
576
+ return {
577
+ name,
578
+ callShape: callShapeByHandlerType[methodShapes[name]],
579
+ requestSchema: `schema.json#/$defs/${name}.request`,
580
+ responseSchema: `schema.json#/$defs/${name}.response`,
581
+ ...(progress && {
582
+ progress: {
583
+ condition: progress.condition,
584
+ responseSchema: `schema.json#/$defs/${progress.responseType}.response`
585
+ }
586
+ })
587
+ };
588
+ })
589
+ };
590
+ const schemaDocument = {
591
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
592
+ title: '@qvac/sdk wire contract',
593
+ description: 'Generated from the SDK Zod schemas by `bun run contract:export`. Do not edit by hand. ' +
594
+ 'Requests use the schema input shape, responses the output shape; runtime-only ' +
595
+ 'refinements and transforms stay server-side.',
596
+ $defs: defs
597
+ };
598
+ return { schemaDocument, manifest };
599
+ }
600
+ async function formatJson(value, fileName) {
601
+ const filePath = fileURLToPath(new URL(fileName, contractDir));
602
+ const config = await prettier.resolveConfig(filePath);
603
+ return prettier.format(JSON.stringify(value, null, 2), {
604
+ ...config,
605
+ parser: 'json'
606
+ });
607
+ }
608
+ export async function renderContractFiles() {
609
+ const { schemaDocument, manifest } = buildContract();
610
+ const modelsRegistry = buildModelsRegistry();
611
+ const modelTypeMaps = buildModelTypeMaps();
612
+ const errorCodes = buildErrorCodes();
613
+ return {
614
+ 'schema.json': await formatJson(schemaDocument, 'schema.json'),
615
+ 'manifest.json': await formatJson(manifest, 'manifest.json'),
616
+ 'models.json': await formatJson(modelsRegistry, 'models.json'),
617
+ 'model-type-maps.json': await formatJson(modelTypeMaps, 'model-type-maps.json'),
618
+ 'error-codes.json': await formatJson(errorCodes, 'error-codes.json')
619
+ };
620
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * JSON export of the SDK error-code registries (name → numeric code) for the
3
+ * server, client, and registry error families. The Python client's typed
4
+ * error classes and its RPC-boundary reconstructor read these instead of
5
+ * hand-copying the numbers, so a code added or renamed on the SDK side surfaces
6
+ * as a `contract:check` / `generate.py --check` failure rather than a Python
7
+ * client that silently can't reconstruct a server error.
8
+ *
9
+ * A separate artifact from schema.json, like models.json / model-type-maps.json.
10
+ */
11
+ export interface ErrorCodes {
12
+ server: Record<string, number>;
13
+ client: Record<string, number>;
14
+ registry: Record<string, number>;
15
+ }
16
+ export declare function buildErrorCodes(): ErrorCodes;
17
+ //# sourceMappingURL=build-error-codes.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { SDK_SERVER_ERROR_CODES } from '../../schemas/sdk-errors-server.js';
2
+ import { SDK_CLIENT_ERROR_CODES } from '../../schemas/sdk-errors-client.js';
3
+ import { REGISTRY_ERROR_CODES } from '../../schemas/sdk-errors-registry.js';
4
+ function sortedEntries(record) {
5
+ const entries = Object.entries(record);
6
+ entries.sort(function (a, b) {
7
+ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
8
+ });
9
+ return Object.fromEntries(entries);
10
+ }
11
+ export function buildErrorCodes() {
12
+ return {
13
+ server: sortedEntries(SDK_SERVER_ERROR_CODES),
14
+ client: sortedEntries(SDK_CLIENT_ERROR_CODES),
15
+ registry: sortedEntries(REGISTRY_ERROR_CODES)
16
+ };
17
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * JSON export of the model-type resolution maps that `loadModel`'s
3
+ * client-side type-inference reads: alias → canonical model type, canonical
4
+ * engine → addon, and legacy engine string → canonical engine.
5
+ *
6
+ * Inference itself stays in each client (a plain string `modelSrc` carries no
7
+ * engine to infer from, so it can't move server-side), but the *data* it reads
8
+ * must not drift between languages — so the maps are exported here, alongside
9
+ * schema.json/models.json, and generated into each client rather than
10
+ * hand-copied. `contract:check` (JS) and `generate.py --check` (Python) then
11
+ * fail the build if a map is edited on one side only.
12
+ *
13
+ * A separate artifact from schema.json for the same reason models.json is: a
14
+ * language-neutral data catalog, not part of the RPC wire contract.
15
+ */
16
+ export interface ModelTypeMaps {
17
+ aliasToCanonical: Record<string, string>;
18
+ engineToAddon: Record<string, string>;
19
+ legacyEngineToCanonical: Record<string, string>;
20
+ }
21
+ export declare function buildModelTypeMaps(): ModelTypeMaps;
22
+ //# sourceMappingURL=build-model-type-maps.d.ts.map