@qvac/sdk 0.20.0 → 0.21.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.
- package/NOTICE +134 -66
- package/README.md +39 -3
- package/dist/examples/advisory-model-fit.d.ts +17 -16
- package/dist/examples/advisory-model-fit.js +18 -18
- package/dist/examples/asr/parakeet-indic-conformer-filesystem.js +3 -3
- package/dist/examples/asr/parakeet-unified-coreml.d.ts +2 -0
- package/dist/examples/asr/parakeet-unified-coreml.js +113 -0
- package/dist/examples/assess-model-fit.js +9 -4
- package/dist/examples/model-fit-ui/server.js +5 -2
- package/dist/examples/rpc-inference.d.ts +2 -0
- package/dist/examples/rpc-inference.js +51 -0
- package/dist/examples/rpc-server.d.ts +2 -0
- package/dist/examples/rpc-server.js +32 -0
- package/dist/examples/tools/llamacpp-deferred-tools.d.ts +2 -0
- package/dist/examples/tools/llamacpp-deferred-tools.js +77 -0
- package/dist/examples/tts/audio8.js +4 -0
- package/dist/examples/tts/moss-dialogue.d.ts +2 -0
- package/dist/examples/tts/moss-dialogue.js +63 -0
- package/dist/examples/tts/moss.d.ts +2 -0
- package/dist/examples/tts/moss.js +71 -0
- package/dist/scripts/contract/build-contract.d.ts +2 -1
- package/dist/scripts/contract/build-contract.js +4 -1
- package/dist/scripts/contract/build-numeric-constants.d.ts +15 -0
- package/dist/scripts/contract/build-numeric-constants.js +13 -0
- package/dist/scripts/contract/method-shapes.d.ts +3 -0
- package/dist/scripts/contract/method-shapes.js +3 -0
- package/dist/scripts/enforce-inference-versions.d.ts +18 -0
- package/dist/scripts/enforce-inference-versions.js +158 -0
- package/dist/scripts/generate-bare-kit-runtimes.d.ts +2 -0
- package/dist/scripts/generate-bare-kit-runtimes.js +106 -0
- package/dist/src/client/api/assess-model-fit.d.ts +11 -5
- package/dist/src/client/api/assess-model-fit.js +30 -8
- package/dist/src/client/api/bci-transcribe.d.ts +4 -1
- package/dist/src/client/api/get-system-resources.d.ts +8 -0
- package/dist/src/client/api/get-system-resources.js +8 -0
- package/dist/src/client/api/index.d.ts +1 -0
- package/dist/src/client/api/index.js +1 -0
- package/dist/src/client/api/rpc-server.d.ts +20 -0
- package/dist/src/client/api/rpc-server.js +45 -0
- package/dist/src/client/api/text-to-speech.d.ts +9 -0
- package/dist/src/client/api/text-to-speech.js +9 -0
- package/dist/src/client/api/transcribe.d.ts +21 -15
- package/dist/src/client/api/transcribe.js +65 -20
- package/dist/src/client/rpc/rpc-error.js +11 -7
- package/dist/src/commands/bundle/entry-gen.d.ts +2 -2
- package/dist/src/commands/bundle/entry-gen.js +8 -4
- package/dist/src/commands/bundle/index.d.ts +20 -0
- package/dist/src/commands/bundle/index.js +88 -12
- package/dist/src/commands/bundle/plugins.js +1 -1
- package/dist/src/commands/command-logger.d.ts +6 -0
- package/dist/src/commands/command-logger.js +12 -0
- package/dist/src/commands/host-prebuilds/index.d.ts +71 -0
- package/dist/src/commands/host-prebuilds/index.js +227 -0
- package/dist/src/commands/host-prebuilds/package-manager.d.ts +54 -0
- package/dist/src/commands/host-prebuilds/package-manager.js +278 -0
- package/dist/src/commands/index.d.ts +10 -1
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/verify/abi.d.ts +22 -2
- package/dist/src/commands/verify/abi.js +49 -27
- package/dist/src/commands/verify/addon-source.d.ts +22 -0
- package/dist/src/commands/verify/addon-source.js +69 -3
- package/dist/src/commands/verify/bare-kit-runtime.d.ts +38 -0
- package/dist/src/commands/verify/bare-kit-runtime.js +201 -0
- package/dist/src/commands/verify/bare-kit-runtimes.d.ts +12 -0
- package/dist/src/commands/verify/bare-kit-runtimes.js +108 -0
- package/dist/src/commands/verify/bundle-source.js +16 -15
- package/dist/src/commands/verify/engines-advice.d.ts +73 -0
- package/dist/src/commands/verify/engines-advice.js +271 -0
- package/dist/src/commands/verify/index.d.ts +22 -0
- package/dist/src/commands/verify/index.js +151 -13
- package/dist/src/commands/verify/node-modules-source.js +43 -39
- package/dist/src/commands/verify/prebuilds.d.ts +19 -0
- package/dist/src/commands/verify/prebuilds.js +52 -13
- package/dist/src/expo/plugins/index.d.ts +2 -0
- package/dist/src/expo/plugins/withMobileBundle.d.ts +15 -5
- package/dist/src/expo/plugins/withMobileBundle.js +68 -42
- package/dist/src/expo/plugins/withQvacSDK.d.ts +7 -1
- package/dist/src/expo/plugins/withQvacSDK.js +6 -3
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.js +4 -2
- package/dist/src/schemas/sdk-errors-client.d.ts +2 -0
- package/dist/src/schemas/sdk-errors-client.js +16 -0
- package/dist/src/schemas/sdk-errors-server.d.ts +1 -0
- package/dist/src/schemas/sdk-errors-server.js +5 -0
- package/dist/src/utils/errors-client.d.ts +15 -0
- package/dist/src/utils/errors-client.js +26 -0
- package/dist/src/utils/errors-server.d.ts +1 -0
- package/dist/src/utils/errors-server.js +1 -0
- package/dist/src/utils/mcp-adapter.js +7 -1
- package/dist/src/utils/tool-helpers.d.ts +4 -0
- package/dist/src/utils/tool-helpers.js +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.d.ts +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.js +1 -0
- package/dist/src/worker/rpc-server-provider.d.ts +3 -0
- package/dist/src/worker/rpc-server-provider.js +1 -0
- package/package.json +31 -27
- package/dist/scripts/enforce-inference-peer-dependencies.d.ts +0 -2
- package/dist/scripts/enforce-inference-peer-dependencies.js +0 -39
- package/dist/src/deprecate.d.ts +0 -2
- package/dist/src/deprecate.js +0 -9
- package/dist/src/worker/legacy-worker-entry.d.ts +0 -2
- package/dist/src/worker/legacy-worker-entry.js +0 -3
- package/dist/src/worker/worker-core.d.ts +0 -7
- package/dist/src/worker/worker-core.js +0 -8
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Advisory
|
|
2
|
+
* Advisory model fit check.
|
|
3
3
|
*
|
|
4
|
-
* Before a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Before a load, the SDK runs the fitter belonging to the engine that would
|
|
5
|
+
* run it and projects whether the exact configuration it is about to load will
|
|
6
|
+
* fit in device memory. Desktop runs it in a disposable Bare child; mobile,
|
|
7
|
+
* which cannot spawn one, runs it on a worker thread.
|
|
7
8
|
*
|
|
8
9
|
* The result is ADVISORY. It never blocks a load. `does-not-fit` is logged and
|
|
9
10
|
* the ordinary load path runs unchanged. Crashes, timeouts, malformed
|
|
10
11
|
* responses, unsupported configurations, and internal errors all resolve to
|
|
11
|
-
* "no evidence" and are equally non-blocking.
|
|
12
|
-
*
|
|
12
|
+
* "no evidence" and are equally non-blocking. No verdict changes the load;
|
|
13
|
+
* `getLoadedModelInfo` returns it as `fitProbe`.
|
|
13
14
|
*
|
|
14
15
|
* The verdict is emitted on the SDK server log stream, not to stdout, so this
|
|
15
16
|
* example subscribes to `loggingStream({ id: SDK_LOG_ID })` and reprints the
|
|
@@ -19,9 +20,8 @@
|
|
|
19
20
|
* What fits on this machine (Apple M4 Pro, 24 GiB unified memory)
|
|
20
21
|
* ---------------------------------------------------------------------------
|
|
21
22
|
*
|
|
22
|
-
* Measured
|
|
23
|
-
*
|
|
24
|
-
* fitter budgets against what the machine can actually keep resident
|
|
23
|
+
* Measured at the default 1024 MiB margin. The fitter budgets against what the
|
|
24
|
+
* machine can actually keep resident
|
|
25
25
|
* (total − wired − compressor: 17.4 GiB on this machine at idle), not the raw
|
|
26
26
|
* RAM figure.
|
|
27
27
|
*
|
|
@@ -30,10 +30,12 @@
|
|
|
30
30
|
* gpt-oss-20B Q4_K_M 10.8 GiB @ 32k ctx
|
|
31
31
|
* gte-large fp16 0.6 GiB (embedding, context pinned to 512)
|
|
32
32
|
*
|
|
33
|
-
* PROJECTED NOT TO FIT —
|
|
33
|
+
* PROJECTED NOT TO FIT — with `gpu_layers: 99` pinned
|
|
34
34
|
* gpt-oss-20B Q4_K_M 10.8 GiB @ 128k ctx with an f32 KV cache ← below
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
*
|
|
36
|
+
* These rows were measured with `gpu_layers` pinned at 99. Loads no longer pin
|
|
37
|
+
* the layer count, and the `does-not-fit` row has not been re-measured
|
|
38
|
+
* unpinned.
|
|
37
39
|
*
|
|
38
40
|
* gpt-oss-20B at 128k context FITS with the default KV cache and DOES NOT FIT
|
|
39
41
|
* once `cache-type-k`/`cache-type-v` are set to `f32`. Same model, same
|
|
@@ -42,10 +44,9 @@
|
|
|
42
44
|
*
|
|
43
45
|
* Two boundaries worth understanding when reading verdicts:
|
|
44
46
|
*
|
|
45
|
-
* 1. The verdict answers for a PLACEMENT, not a model.
|
|
46
|
-
*
|
|
47
|
-
* means "not at this placement".
|
|
48
|
-
* to move layers to the CPU side instead.
|
|
47
|
+
* 1. The verdict answers for a PLACEMENT, not a model. With `gpu_layers`
|
|
48
|
+
* unset, the fitter may move layers to the CPU side; setting it pins the
|
|
49
|
+
* layer count, and `does-not-fit` then means "not at this placement".
|
|
49
50
|
* 2. A `does-not-fit` configuration can still RUN on macOS when the OS
|
|
50
51
|
* compresses and pages hard enough — and a `fits` configuration right at
|
|
51
52
|
* the boundary can still fail at first decode under memory pressure.
|
|
@@ -98,8 +99,7 @@ try {
|
|
|
98
99
|
// Unloaded before the next phase, so the second verdict is measured on an
|
|
99
100
|
// idle machine and stays comparable to the fixture tables above. Leaving it
|
|
100
101
|
// loaded would shift the verdict: the check reserves resident weight bytes
|
|
101
|
-
// through the fit margin, and
|
|
102
|
-
// system-wide wired memory.
|
|
102
|
+
// through the fit margin, and the fitter also sees system-wide wired memory.
|
|
103
103
|
await unloadModel({ modelId: smallModelId, clearStorage: false });
|
|
104
104
|
watchVerdicts();
|
|
105
105
|
// 2. A load the fitter projects NOT to fit. The point of this example is that
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* bun run examples/asr/parakeet-indic-conformer-filesystem.ts <wav-file> <language> [gguf]
|
|
6
6
|
*
|
|
7
|
-
* Loads a single GGUF checkpoint (`
|
|
7
|
+
* Loads a single GGUF checkpoint (`PARAKEET_INDIC_CONFORMER_600M_Q8_0` by
|
|
8
8
|
* default) and transcribes with the batch `transcribe` API. `language` is
|
|
9
9
|
* required (e.g. `hi`, `ta`) because Indic Conformer CTC masks the vocab
|
|
10
10
|
* with `parakeet.ctc.lang_*` ranges. English Parakeet CTC ignores this field.
|
|
11
11
|
*
|
|
12
12
|
* Audio should be 16 kHz mono PCM in a WAV container.
|
|
13
13
|
*/
|
|
14
|
-
import { loadModel, unloadModel, transcribe,
|
|
14
|
+
import { loadModel, unloadModel, transcribe, PARAKEET_INDIC_CONFORMER_600M_Q8_0 } from '@qvac/sdk';
|
|
15
15
|
const args = process.argv.slice(2);
|
|
16
16
|
if (!args[0] || !args[1]) {
|
|
17
17
|
console.error('Usage: bun run examples/asr/parakeet-indic-conformer-filesystem.ts ' +
|
|
@@ -22,7 +22,7 @@ if (!args[0] || !args[1]) {
|
|
|
22
22
|
}
|
|
23
23
|
const audioFilePath = args[0];
|
|
24
24
|
const language = args[1];
|
|
25
|
-
const parakeetModelSrc = args[2] ??
|
|
25
|
+
const parakeetModelSrc = args[2] ?? PARAKEET_INDIC_CONFORMER_600M_Q8_0;
|
|
26
26
|
try {
|
|
27
27
|
console.log('▸ Loading Indic Conformer CTC model...');
|
|
28
28
|
const modelId = await loadModel({
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcribe a WAV file with Parakeet Unified and its Core ML encoder sidecar.
|
|
3
|
+
*
|
|
4
|
+
* From packages/sdk:
|
|
5
|
+
* bun run examples/asr/parakeet-unified-coreml.ts \
|
|
6
|
+
* /path/to/parakeet-unified-en-0.6b.q4_0.gguf [16-kHz-wav-file]
|
|
7
|
+
*
|
|
8
|
+
* The GGUF must be a regenerated Unified artifact. The five compiled Core ML
|
|
9
|
+
* files are downloaded through the SDK registry and staged beside the GGUF.
|
|
10
|
+
* The default audio is examples/audio/sample-16khz.wav.
|
|
11
|
+
*/
|
|
12
|
+
import { downloadAsset, getModelInfo, loadModel, transcribe, unloadModel } from '@qvac/sdk';
|
|
13
|
+
import { link, mkdir, mkdtemp, open, rm, symlink } from 'node:fs/promises';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { basename, join, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
const MODEL_STEM = 'parakeet-unified-en-0.6b';
|
|
18
|
+
const COREML_FILES = [
|
|
19
|
+
['PARAKEET_UNIFIED_COREMLDATA', 'analytics/coremldata.bin'],
|
|
20
|
+
['PARAKEET_UNIFIED_COREMLDATA_1', 'coremldata.bin'],
|
|
21
|
+
['PARAKEET_UNIFIED_METADATA', 'metadata.json'],
|
|
22
|
+
['PARAKEET_UNIFIED_MODEL', 'model.mil'],
|
|
23
|
+
['PARAKEET_UNIFIED_WEIGHT', 'weights/weight.bin']
|
|
24
|
+
];
|
|
25
|
+
const defaultAudioPath = fileURLToPath(new URL('../audio/sample-16khz.wav', import.meta.url));
|
|
26
|
+
const modelPath = process.argv[2];
|
|
27
|
+
const audioPath = process.argv[3] ?? defaultAudioPath;
|
|
28
|
+
async function checkModel(path) {
|
|
29
|
+
const name = basename(path);
|
|
30
|
+
if (!/^parakeet-unified-en-0\.6b\.(?:f16|q4_0|q8_0)\.gguf$/.test(name)) {
|
|
31
|
+
throw new Error(`Expected a regenerated Unified GGUF, got: ${name}`);
|
|
32
|
+
}
|
|
33
|
+
const file = await open(path, 'r');
|
|
34
|
+
try {
|
|
35
|
+
const header = Buffer.alloc(1024 * 1024);
|
|
36
|
+
const { bytesRead } = await file.read(header, 0, header.length, 0);
|
|
37
|
+
if (!header.subarray(0, bytesRead).includes('parakeet.unified.left_context_frames')) {
|
|
38
|
+
throw new Error(`Unified GGUF lacks the new streaming metadata: ${path}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await file.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function stageModel(path) {
|
|
46
|
+
if (process.platform !== 'darwin')
|
|
47
|
+
throw new Error('Core ML requires macOS');
|
|
48
|
+
await checkModel(path);
|
|
49
|
+
const dir = await mkdtemp(join(tmpdir(), 'qvac-unified-coreml-'));
|
|
50
|
+
try {
|
|
51
|
+
const modelSrc = join(dir, basename(path));
|
|
52
|
+
await symlink(resolve(path), modelSrc);
|
|
53
|
+
const bundleDir = join(dir, `${MODEL_STEM}-encoder.mlmodelc`);
|
|
54
|
+
for (const [name, relativePath] of COREML_FILES) {
|
|
55
|
+
const info = await getModelInfo({ name });
|
|
56
|
+
if (!info.isCached) {
|
|
57
|
+
if (!info.registrySource || !info.registryPath) {
|
|
58
|
+
throw new Error(`Registry location missing for ${name}`);
|
|
59
|
+
}
|
|
60
|
+
console.log(`▸ Downloading Core ML ${relativePath}...`);
|
|
61
|
+
await downloadAsset({ assetSrc: `registry://${info.registrySource}/${info.registryPath}` });
|
|
62
|
+
}
|
|
63
|
+
const cached = await getModelInfo({ name });
|
|
64
|
+
const sourcePath = cached.cacheFiles.find((file) => file.isCached)?.path;
|
|
65
|
+
if (!sourcePath)
|
|
66
|
+
throw new Error(`Core ML file is missing from the SDK cache: ${name}`);
|
|
67
|
+
const targetPath = join(bundleDir, relativePath);
|
|
68
|
+
await mkdir(join(targetPath, '..'), { recursive: true });
|
|
69
|
+
await link(sourcePath, targetPath);
|
|
70
|
+
}
|
|
71
|
+
return { modelSrc, dir };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
await rm(dir, { recursive: true, force: true });
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function main() {
|
|
79
|
+
if (!modelPath) {
|
|
80
|
+
throw new Error('Usage: bun run examples/asr/parakeet-unified-coreml.ts ' +
|
|
81
|
+
'<regenerated-unified-gguf> [16-kHz-wav-file]');
|
|
82
|
+
}
|
|
83
|
+
let staged;
|
|
84
|
+
let modelId;
|
|
85
|
+
try {
|
|
86
|
+
staged = await stageModel(modelPath);
|
|
87
|
+
console.log('▸ Loading Parakeet Unified with its Core ML encoder sidecar...');
|
|
88
|
+
modelId = await loadModel({
|
|
89
|
+
modelSrc: staged.modelSrc,
|
|
90
|
+
modelType: 'parakeet-transcription',
|
|
91
|
+
modelConfig: { useGPU: true }
|
|
92
|
+
});
|
|
93
|
+
console.log('▸ Transcribing audio...');
|
|
94
|
+
const text = await transcribe({ modelId, audioChunk: audioPath });
|
|
95
|
+
if (!text.trim())
|
|
96
|
+
throw new Error('Parakeet Unified returned an empty transcription');
|
|
97
|
+
console.log(text);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
try {
|
|
101
|
+
if (modelId)
|
|
102
|
+
await unloadModel({ modelId });
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
if (staged)
|
|
106
|
+
await rm(staged.dir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
main().catch((error) => {
|
|
111
|
+
console.error('✖', error);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
});
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* call either way. Show it as "can't say", never as "no".
|
|
12
12
|
*/
|
|
13
13
|
import { assessModelFit, QWEN3_600M_INST_Q4, QWEN3_1_7B_INST_Q4, QWEN3_4B_INST_Q4_K_M, QWEN3_8B_INST_Q4_K_M, QWEN3_8_27B_MULTIMODAL_UD_Q8_K_XL } from '@qvac/sdk';
|
|
14
|
-
//
|
|
15
|
-
|
|
14
|
+
// Sizes the assessment the way it sizes the load: the estimator reads the
|
|
15
|
+
// context out of the config `loadModel` would be given.
|
|
16
|
+
const CONTEXT_TOKENS = 8192;
|
|
16
17
|
// A ladder ending well past what a laptop has, so one screen shows every verdict.
|
|
17
18
|
const CANDIDATES = [
|
|
18
19
|
QWEN3_600M_INST_Q4,
|
|
@@ -31,7 +32,11 @@ function gib(bytes) {
|
|
|
31
32
|
}
|
|
32
33
|
try {
|
|
33
34
|
const result = await assessModelFit({
|
|
34
|
-
models: CANDIDATES.map((model) => ({
|
|
35
|
+
models: CANDIDATES.map((model) => ({
|
|
36
|
+
modelSrc: model,
|
|
37
|
+
modelType: 'llamacpp-completion',
|
|
38
|
+
modelConfig: { ctx_size: CONTEXT_TOKENS }
|
|
39
|
+
})),
|
|
35
40
|
// Declared for aggregation only, not a scheduling instruction: 'sequential'
|
|
36
41
|
// counts the largest operation peak, 'concurrent' counts one per model.
|
|
37
42
|
execution: 'sequential',
|
|
@@ -49,7 +54,7 @@ try {
|
|
|
49
54
|
const placement = result.assumptions.find((line) => line.includes('assumed to execute on'));
|
|
50
55
|
if (placement)
|
|
51
56
|
console.log(`▸ ${placement}`);
|
|
52
|
-
console.log(`\n▸ At ${
|
|
57
|
+
console.log(`\n▸ At ${CONTEXT_TOKENS} tokens of context`);
|
|
53
58
|
for (const [index, model] of result.models.entries()) {
|
|
54
59
|
const mark = VERDICT_MARK[model.verdict];
|
|
55
60
|
const size = gib(CANDIDATES[index].expectedSize).padStart(9);
|
|
@@ -102,9 +102,12 @@ function sendJson(response, status, payload) {
|
|
|
102
102
|
async function estimate(body) {
|
|
103
103
|
const names = Array.isArray(body['names']) ? body['names'] : [];
|
|
104
104
|
const contextTokens = Number(body['contextTokens'] ?? 8192);
|
|
105
|
-
const workload = { kind: 'llm', contextTokens };
|
|
106
105
|
return assessModelFit({
|
|
107
|
-
models: names.map((name) => ({
|
|
106
|
+
models: names.map((name) => ({
|
|
107
|
+
modelSrc: byName(name),
|
|
108
|
+
modelType: 'llamacpp-completion',
|
|
109
|
+
modelConfig: { ctx_size: contextTokens }
|
|
110
|
+
})),
|
|
108
111
|
// Declared for aggregation only. 'sequential' counts the largest operation
|
|
109
112
|
// peak; 'concurrent' counts one per model.
|
|
110
113
|
execution: 'sequential',
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// npx tsx rpc-inference.ts my-private-rpc-group
|
|
2
|
+
import { discoverRpcServers, getRpcDeviceMap, loadModel, completion, unloadModel, close, LLAMA_3_2_1B_INST_Q4_0 } from '@qvac/sdk';
|
|
3
|
+
try {
|
|
4
|
+
const topic = process.argv[2];
|
|
5
|
+
if (!topic)
|
|
6
|
+
throw new Error('Usage: rpc-inference.ts <topic>');
|
|
7
|
+
// Prefer server RDMA offers before assigning aliases. Transport is negotiated at load time.
|
|
8
|
+
const selected = (await discoverRpcServers({ topic, timeoutMs: 5000 }))
|
|
9
|
+
.sort((a, b) => Number(b.rdmaAvailable) - Number(a.rdmaAvailable) || a.url.localeCompare(b.url))
|
|
10
|
+
.slice(0, 2);
|
|
11
|
+
if (selected.length !== 2)
|
|
12
|
+
throw new Error('Two idle RPC servers are required');
|
|
13
|
+
const devices = getRpcDeviceMap(selected);
|
|
14
|
+
if (!devices.length)
|
|
15
|
+
throw new Error('The selected servers expose no devices');
|
|
16
|
+
console.error(`▸ Using ${devices.length} devices across ${selected.length} servers`);
|
|
17
|
+
const modelId = await loadModel({
|
|
18
|
+
modelSrc: LLAMA_3_2_1B_INST_Q4_0,
|
|
19
|
+
modelConfig: {
|
|
20
|
+
device: 'gpu',
|
|
21
|
+
'rpc-servers': selected.map((server) => server.url).join(','),
|
|
22
|
+
devices: devices.map((device) => device.alias).join(','),
|
|
23
|
+
'split-mode': 'layer',
|
|
24
|
+
'tensor-split': devices.map(() => '1').join(',')
|
|
25
|
+
},
|
|
26
|
+
onProgress: (progress) => {
|
|
27
|
+
process.stderr.write(`\r▸ Downloading ${progress.percentage.toFixed(0)}%`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
process.stderr.write('\n');
|
|
32
|
+
const run = completion({
|
|
33
|
+
modelId,
|
|
34
|
+
history: [{ role: 'user', content: 'Explain distributed inference in one sentence.' }],
|
|
35
|
+
stream: true
|
|
36
|
+
});
|
|
37
|
+
for await (const token of run.tokenStream)
|
|
38
|
+
process.stdout.write(token);
|
|
39
|
+
process.stdout.write('\n');
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await unloadModel({ modelId });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('✖', error);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await close();
|
|
51
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Rebuild the worker with
|
|
2
|
+
// rpcServerProvider: '@qvac/sdk/ggml-rpc-server/provider' in qvac.config.json.
|
|
3
|
+
// npx tsx rpc-server.ts 10.0.0.2 my-private-rpc-group
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { startRpcServer, stopRpcServer, close } from '@qvac/sdk';
|
|
6
|
+
try {
|
|
7
|
+
const host = process.argv[2];
|
|
8
|
+
const topic = process.argv[3];
|
|
9
|
+
if (!host || !topic)
|
|
10
|
+
throw new Error('Usage: rpc-server.ts <private-ipv4> <topic>');
|
|
11
|
+
const server = await startRpcServer({
|
|
12
|
+
host,
|
|
13
|
+
allowNonLoopbackHost: true,
|
|
14
|
+
discoveryTopic: topic
|
|
15
|
+
});
|
|
16
|
+
const input = createInterface({ input: process.stdin, output: process.stderr });
|
|
17
|
+
try {
|
|
18
|
+
console.error(`▸ Serving ${server.url}; RDMA capable: ${server.rdmaCapable}`);
|
|
19
|
+
await input.question('▸ Press Enter to stop after clients unload their models.\n');
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
input.close();
|
|
23
|
+
await stopRpcServer({ serverId: server.serverId });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('✖', error);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await close();
|
|
32
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Deferred tool loading: a large tool set stays out of the prompt until the
|
|
2
|
+
// model asks for it. Only `get_weather` and the built-in `tool_search` (whose
|
|
3
|
+
// description carries a name-and-description catalog of the rest) are in the
|
|
4
|
+
// initial prompt; the deferred schemas arrive in the conversation when the
|
|
5
|
+
// model searches for them.
|
|
6
|
+
import { TOOL_SEARCH_NAME, completion, executeToolSearch, loadModel, unloadModel, QWEN3_1_7B_INST_Q4 } from '@qvac/sdk';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { weatherSchema, horoscopeSchema, mockExecute } from './shared.js';
|
|
9
|
+
const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'get_weather',
|
|
12
|
+
description: 'Get current weather for a city',
|
|
13
|
+
parameters: weatherSchema
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'get_horoscope',
|
|
17
|
+
description: "Get today's horoscope for an astrological sign",
|
|
18
|
+
parameters: horoscopeSchema,
|
|
19
|
+
deferLoading: true,
|
|
20
|
+
group: 'astrology'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'get_moon_phase',
|
|
24
|
+
description: 'Get the current phase of the moon',
|
|
25
|
+
parameters: z.object({ date: z.string().describe('ISO date').optional() }),
|
|
26
|
+
deferLoading: true,
|
|
27
|
+
group: 'astrology'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
try {
|
|
31
|
+
const modelId = await loadModel({
|
|
32
|
+
modelSrc: QWEN3_1_7B_INST_Q4,
|
|
33
|
+
modelConfig: { ctx_size: 4096, tools: true }
|
|
34
|
+
});
|
|
35
|
+
console.log(`▸ Model loaded. Model ID: ${modelId}`);
|
|
36
|
+
const history = [
|
|
37
|
+
{
|
|
38
|
+
role: 'system',
|
|
39
|
+
content: 'You are a helpful assistant. Search for a tool before calling it.'
|
|
40
|
+
},
|
|
41
|
+
{ role: 'user', content: "What's my horoscope for Aquarius?" }
|
|
42
|
+
];
|
|
43
|
+
console.log('\n▸ Turn 1 — only get_weather and tool_search are in the prompt\n');
|
|
44
|
+
const first = completion({ modelId, history, stream: true, tools });
|
|
45
|
+
for await (const token of first.tokenStream)
|
|
46
|
+
process.stdout.write(token);
|
|
47
|
+
const calls = await first.toolCalls;
|
|
48
|
+
const search = calls.find((call) => call.name === TOOL_SEARCH_NAME);
|
|
49
|
+
if (!search) {
|
|
50
|
+
console.log('\n\n▸ The model answered without searching; nothing more to show.');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`\n\n▸ The model searched: ${JSON.stringify(search.arguments)}`);
|
|
54
|
+
// Running the search is the SDK's job, not a tool handler's. The result is
|
|
55
|
+
// an ordinary tool message: appending it is what makes the definitions
|
|
56
|
+
// callable on the next turn.
|
|
57
|
+
history.push({ role: 'assistant', content: await first.text });
|
|
58
|
+
history.push({
|
|
59
|
+
role: 'tool',
|
|
60
|
+
content: executeToolSearch(tools, search.arguments, history)
|
|
61
|
+
});
|
|
62
|
+
console.log('▸ Definitions appended. Turn 2 — the model can now call them directly.\n');
|
|
63
|
+
const second = completion({ modelId, history, stream: true, tools });
|
|
64
|
+
for await (const token of second.tokenStream)
|
|
65
|
+
process.stdout.write(token);
|
|
66
|
+
for (const call of await second.toolCalls) {
|
|
67
|
+
console.log(`\n\n▸ Tool call: ${call.name}(${JSON.stringify(call.arguments)})`);
|
|
68
|
+
console.log(`▸ Result: ${mockExecute(call.name, call.arguments)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log('\n\n▸ Completed!');
|
|
72
|
+
await unloadModel({ modelId, clearStorage: false });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('✖', error);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
@@ -4,6 +4,8 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
|
|
|
4
4
|
// primary modelSrc is the LM GGUF; the codec decoder loads via modelConfig.
|
|
5
5
|
// For zero-shot voice cloning also pass audio8CodecEncoderModelSrc plus
|
|
6
6
|
// referenceAudioSrc/referenceText (a recording and its exact transcript).
|
|
7
|
+
// On macOS and iOS the decoder downloads with its Core ML bundle, which runs
|
|
8
|
+
// the codec's synthesis stack; `stats.codecOnCoreml` reports whether it did.
|
|
7
9
|
// Only a fallback: the engine reports the rate it actually produced, and
|
|
8
10
|
// `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
|
|
9
11
|
const AUDIO8_SAMPLE_RATE = 44100;
|
|
@@ -35,7 +37,9 @@ try {
|
|
|
35
37
|
});
|
|
36
38
|
const audioBuffer = await result.buffer;
|
|
37
39
|
const sampleRate = (await result.sampleRate) ?? AUDIO8_SAMPLE_RATE;
|
|
40
|
+
const stats = await result.stats;
|
|
38
41
|
console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
|
|
42
|
+
console.log(`▸ Codec ran on ${stats?.codecOnCoreml === 1 ? 'Core ML' : 'ggml'}`);
|
|
39
43
|
console.log('▸ Saving audio to file...');
|
|
40
44
|
createWav(audioBuffer, sampleRate, 'audio8-output.wav');
|
|
41
45
|
console.log('▸ Audio saved to audio8-output.wav');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { loadModel, textToSpeech, unloadModel, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
|
|
2
|
+
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
|
|
3
|
+
// MOSS-TTSD (GGML): multi-speaker dialogue in one pass, each voice cloned from
|
|
4
|
+
// a 24 kHz reference recording. The model continues the references, so the
|
|
5
|
+
// text opens with what each recording says under its speaker tag, followed by
|
|
6
|
+
// the lines to generate; only the new lines come out as audio. A dialogue is
|
|
7
|
+
// never split into sentences, so `stream: true` delivers the native chunks of
|
|
8
|
+
// the whole conversation (set `streamChunkTokens` to get them while it is
|
|
9
|
+
// still being generated).
|
|
10
|
+
//
|
|
11
|
+
// The MOSS-TTSD backbone is not in the QVAC Registry yet, so it is a local
|
|
12
|
+
// file; the codec halves it shares with MOSS-TTS download from the registry.
|
|
13
|
+
// Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"
|
|
14
|
+
const [backbone, s1Audio, s1Text, s2Audio, s2Text] = process.argv.slice(2);
|
|
15
|
+
if (!backbone || !s1Audio || !s1Text || !s2Audio || !s2Text) {
|
|
16
|
+
console.error('Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Only a fallback: the engine reports the rate it actually produced.
|
|
20
|
+
const MOSS_SAMPLE_RATE = 24000;
|
|
21
|
+
try {
|
|
22
|
+
const modelId = await loadModel({
|
|
23
|
+
modelSrc: backbone,
|
|
24
|
+
modelType: 'tts-ggml',
|
|
25
|
+
modelConfig: {
|
|
26
|
+
ttsEngine: 'moss',
|
|
27
|
+
language: 'en',
|
|
28
|
+
mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
|
|
29
|
+
mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16,
|
|
30
|
+
// One recording per speaker, in the order the text tags them.
|
|
31
|
+
dialogueReferenceSrcs: [s1Audio, s2Audio],
|
|
32
|
+
streamChunkTokens: 25,
|
|
33
|
+
useGPU: true
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
console.log(`▸ Model loaded: ${modelId}`);
|
|
37
|
+
console.log('▸ Synthesizing the dialogue...');
|
|
38
|
+
const result = textToSpeech({
|
|
39
|
+
modelId,
|
|
40
|
+
text: `[S1] ${s1Text} [S2] ${s2Text} ` +
|
|
41
|
+
'[S1] Did the build finish? [S2] Yes, every test passed. [S1] Great, ship it.',
|
|
42
|
+
stream: true
|
|
43
|
+
});
|
|
44
|
+
const samples = [];
|
|
45
|
+
for await (const sample of result.bufferStream)
|
|
46
|
+
samples.push(sample);
|
|
47
|
+
const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
|
|
48
|
+
console.log(`▸ Dialogue complete: ${samples.length} samples`);
|
|
49
|
+
createWav(samples, sampleRate, 'moss-dialogue-output.wav');
|
|
50
|
+
console.log('▸ Audio saved to moss-dialogue-output.wav');
|
|
51
|
+
console.log('▸ Playing audio...');
|
|
52
|
+
const audioData = int16ArrayToBuffer(samples);
|
|
53
|
+
const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
|
|
54
|
+
playAudio(wavBuffer);
|
|
55
|
+
console.log('▸ Audio playback complete');
|
|
56
|
+
await unloadModel({ modelId });
|
|
57
|
+
console.log('▸ Model unloaded');
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error('✖', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { loadModel, textToSpeech, unloadModel, TTS_DELAY_LLM_MOSS_TTS_F16, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
|
|
2
|
+
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
|
|
3
|
+
// MOSS (GGML): OpenMOSS MOSS-TTS v1.5 Delay, an 8B backbone (about 17 GB)
|
|
4
|
+
// plus a codec, so desktop only. The primary modelSrc is the backbone; the
|
|
5
|
+
// codec decoder loads via modelConfig, and the codec encoder is only needed to
|
|
6
|
+
// clone a voice from a 24 kHz reference recording (no transcript needed).
|
|
7
|
+
//
|
|
8
|
+
// MOSS streams natively: with `streamChunkTokens` set, `stream: true` emits a
|
|
9
|
+
// chunk every that-many codec frames (12.5 per second) while the backbone is
|
|
10
|
+
// still generating, and the text is synthesized as one utterance, so the
|
|
11
|
+
// `[pause 1.0s]` marker below and `durationTokens` apply to all of it.
|
|
12
|
+
//
|
|
13
|
+
// Uses registry model constants — downloads automatically from QVAC Registry.
|
|
14
|
+
// Usage: node moss.ts [referenceAudio.wav]
|
|
15
|
+
const [referenceAudioSrc] = process.argv.slice(2);
|
|
16
|
+
// Only a fallback: the engine reports the rate it actually produced.
|
|
17
|
+
const MOSS_SAMPLE_RATE = 24000;
|
|
18
|
+
try {
|
|
19
|
+
const modelId = await loadModel({
|
|
20
|
+
modelSrc: TTS_DELAY_LLM_MOSS_TTS_F16,
|
|
21
|
+
modelConfig: {
|
|
22
|
+
ttsEngine: 'moss',
|
|
23
|
+
language: 'en',
|
|
24
|
+
mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
|
|
25
|
+
...(referenceAudioSrc
|
|
26
|
+
? { mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16, referenceAudioSrc }
|
|
27
|
+
: {}),
|
|
28
|
+
streamChunkTokens: 25,
|
|
29
|
+
useGPU: true,
|
|
30
|
+
seed: 7
|
|
31
|
+
},
|
|
32
|
+
onProgress: (p) => {
|
|
33
|
+
const mb = (n) => (n / 1e6).toFixed(1);
|
|
34
|
+
const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
|
|
35
|
+
process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
|
|
36
|
+
if (p.percentage >= 100)
|
|
37
|
+
process.stderr.write('\n');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
console.log(`▸ Model loaded: ${modelId}`);
|
|
41
|
+
console.log('▸ Streaming MOSS Text-to-Speech...');
|
|
42
|
+
const started = Date.now();
|
|
43
|
+
const result = textToSpeech({
|
|
44
|
+
modelId,
|
|
45
|
+
text: 'Hold on [pause 1.0s] here it comes: speech that starts playing while the rest is still being generated.',
|
|
46
|
+
stream: true
|
|
47
|
+
});
|
|
48
|
+
const samples = [];
|
|
49
|
+
for await (const sample of result.bufferStream) {
|
|
50
|
+
if (samples.length === 0)
|
|
51
|
+
console.log(`▸ First audio after ${Date.now() - started} ms`);
|
|
52
|
+
samples.push(sample);
|
|
53
|
+
}
|
|
54
|
+
const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
|
|
55
|
+
const stats = await result.stats;
|
|
56
|
+
console.log(`▸ TTS complete: ${samples.length} samples, ${stats?.generatedFrames ?? '?'} codec frames, RTF ${stats?.realTimeFactor?.toFixed(2) ?? '?'}`);
|
|
57
|
+
createWav(samples, sampleRate, 'moss-output.wav');
|
|
58
|
+
console.log('▸ Audio saved to moss-output.wav');
|
|
59
|
+
console.log('▸ Playing audio...');
|
|
60
|
+
const audioData = int16ArrayToBuffer(samples);
|
|
61
|
+
const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
|
|
62
|
+
playAudio(wavBuffer);
|
|
63
|
+
console.log('▸ Audio playback complete');
|
|
64
|
+
await unloadModel({ modelId });
|
|
65
|
+
console.log('▸ Model unloaded');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('✖', error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
@@ -20,7 +20,7 @@ export declare function buildContract(): {
|
|
|
20
20
|
condition: string;
|
|
21
21
|
responseSchema: string;
|
|
22
22
|
};
|
|
23
|
-
name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
|
|
23
|
+
name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "startRpcServer" | "stopRpcServer" | "discoverRpcServers" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
|
|
24
24
|
callShape: "duplex" | "request-reply" | "server-stream";
|
|
25
25
|
requestSchema: string;
|
|
26
26
|
responseSchema: string;
|
|
@@ -33,6 +33,7 @@ export declare function renderContractFiles(): Promise<{
|
|
|
33
33
|
'models.json': string;
|
|
34
34
|
'model-type-maps.json': string;
|
|
35
35
|
'error-codes.json': string;
|
|
36
|
+
'numeric-constants.json': string;
|
|
36
37
|
}>;
|
|
37
38
|
export {};
|
|
38
39
|
//# sourceMappingURL=build-contract.d.ts.map
|
|
@@ -7,6 +7,7 @@ import { constantsRegistry } from './constants-registry.js';
|
|
|
7
7
|
import { buildModelsRegistry } from './build-models-registry.js';
|
|
8
8
|
import { buildModelTypeMaps } from './build-model-type-maps.js';
|
|
9
9
|
import { buildErrorCodes } from './build-error-codes.js';
|
|
10
|
+
import { buildNumericConstants } from './build-numeric-constants.js';
|
|
10
11
|
export const contractDir = new URL('../../contract/', import.meta.url);
|
|
11
12
|
export const callShapeByHandlerType = {
|
|
12
13
|
reply: 'request-reply',
|
|
@@ -610,11 +611,13 @@ export async function renderContractFiles() {
|
|
|
610
611
|
const modelsRegistry = buildModelsRegistry();
|
|
611
612
|
const modelTypeMaps = buildModelTypeMaps();
|
|
612
613
|
const errorCodes = buildErrorCodes();
|
|
614
|
+
const numericConstants = buildNumericConstants();
|
|
613
615
|
return {
|
|
614
616
|
'schema.json': await formatJson(schemaDocument, 'schema.json'),
|
|
615
617
|
'manifest.json': await formatJson(manifest, 'manifest.json'),
|
|
616
618
|
'models.json': await formatJson(modelsRegistry, 'models.json'),
|
|
617
619
|
'model-type-maps.json': await formatJson(modelTypeMaps, 'model-type-maps.json'),
|
|
618
|
-
'error-codes.json': await formatJson(errorCodes, 'error-codes.json')
|
|
620
|
+
'error-codes.json': await formatJson(errorCodes, 'error-codes.json'),
|
|
621
|
+
'numeric-constants.json': await formatJson(numericConstants, 'numeric-constants.json')
|
|
619
622
|
};
|
|
620
623
|
}
|