@qvac/harness 0.8.5 → 0.8.7
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/CHANGELOG.md +1278 -472
- package/README.md +49 -0
- package/dist-lib/index.d.ts +42 -2
- package/dist-lib/index.js +447 -58
- package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-bare.js +22 -25
- package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-node.js +1 -3
- package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
- package/dist-lib/lib/capabilities/audio-resample.js +7 -8
- package/dist-lib/lib/capabilities/completion.d.ts +6 -3
- package/dist-lib/lib/capabilities/completion.js +96 -49
- package/dist-lib/lib/capabilities/download.d.ts +3 -1
- package/dist-lib/lib/capabilities/download.js +1 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
- package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
- package/dist-lib/lib/capabilities/tool-selection.js +2 -4
- package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
- package/dist-lib/lib/capabilities/tool-turns.js +0 -6
- package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +94 -26
- package/dist-lib/lib/connect.d.ts +4 -1
- package/dist-lib/lib/connect.js +16 -1
- package/dist-lib/lib/contract-types.d.ts +12 -0
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
- package/dist-lib/lib/models/context-choice.d.ts +15 -0
- package/dist-lib/lib/models/context-choice.js +55 -0
- package/dist-lib/lib/models/download-gate.d.ts +20 -0
- package/dist-lib/lib/models/download-gate.js +73 -0
- package/dist-lib/lib/models/memory-cap.d.ts +13 -0
- package/dist-lib/lib/models/memory-cap.js +21 -0
- package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
- package/dist-lib/lib/models/model-catalog-constants.js +9 -1
- package/dist-lib/lib/models/model-catalog.d.ts +6 -2
- package/dist-lib/lib/models/model-catalog.js +35 -3
- package/dist-lib/lib/models/model-handle.d.ts +8 -1
- package/dist-lib/lib/models/model-handle.js +20 -2
- package/dist-lib/lib/models/model-list.d.ts +8 -1
- package/dist-lib/lib/models/model-list.js +23 -0
- package/dist-lib/lib/models/model-pool.d.ts +35 -5
- package/dist-lib/lib/models/model-pool.js +97 -32
- package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
- package/dist-lib/lib/models/sdk-faults.js +6 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
- package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
- package/dist-lib/lib/serve.js +23 -3
- package/dist-lib/lib/spawn.js +4 -10
- package/dist-lib/lib/tools/exec.d.ts +4 -1
- package/dist-lib/lib/tools/exec.js +55 -11
- package/dist-lib/lib/tools/generate-image.d.ts +4 -2
- package/dist-lib/lib/tools/generate-image.js +5 -2
- package/dist-lib/lib/tools/generate-music.d.ts +4 -2
- package/dist-lib/lib/tools/generate-music.js +5 -2
- package/dist-lib/lib/tools/office/ops.js +1 -1
- package/dist-lib/lib/tools/office.js +29 -6
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +2 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
- package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
- package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
- package/dist-lib/lib/tools/skills/mention.js +0 -3
- package/dist-lib/lib/tools/translate.d.ts +3 -1
- package/dist-lib/lib/tools/translate.js +6 -1
- package/dist-lib/schema/harness.js +43 -4
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +101 -13
- package/dist-lib/spec/hyperschema/index.js +101 -13
- package/dist-lib/spec/hyperschema/schema.json +62 -1
- package/dist-lib/spec/hyperschema/types.d.ts +20 -2
- package/package.json +8 -10
- package/worker/harness-sidecar.mjs +2 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AUDIOGEN_ACESTEP_5HZ_LM_0_6B_Q8_0, AUDIOGEN_ACESTEP_V15_SFT_Q8_0, AUDIOGEN_ACESTEP_V15_TURBO_Q4_K_M, AUDIOGEN_ACESTEP_V15_TURBO_Q8_0, AUDIOGEN_QWEN3_EMBEDDING_0_6B_Q8_0, AUDIOGEN_VAE_BF16, EMBEDDINGGEMMA_300M_Q4_0, FLUX_2_KLEIN_4B_Q4_0, FLUX_2_KLEIN_4B_VAE, HEALTHCARE_1_7B_MEDICAL_Q4_K_M, HEALTHCARE_4B_MEDICAL_Q4_K_M, MMPROJ_QWEN3_5_2B_MULTIMODAL_Q8_0, MMPROJ_QWEN3_5_4B_MULTIMODAL_Q8_0, MMPROJ_QWEN3_5_9B_MULTIMODAL_F16, MMPROJ_QWEN3_6_27B_MULTIMODAL_F16, MMPROJ_QWEN3_6_35B_A3B_MULTIMODAL_F16, MMPROJ_QWEN3_8_27B_MULTIMODAL_F16, QWEN3_5_2B_MULTIMODAL_Q4_K_M, QWEN3_5_4B_MULTIMODAL_Q4_K_M, QWEN3_5_9B_MULTIMODAL_Q4_K_M, PARAKEET_TDT_0_6B_V3_Q8_0, QWEN3_4B_Q4_K_M, QWEN3_6_27B_MULTIMODAL_Q4_K_XL, QWEN3_6_35B_A3B_MULTIMODAL_Q4_K_M, QWEN3_8_27B_MULTIMODAL_UD_Q4_K_XL, SD_V2_1_1B_Q8_0, SDXL_BASE_1_0_3B_Q4_0, TTS_MULTILINGUAL_SUPERTONIC3_FP16, TTS_MULTILINGUAL_SUPERTONIC3_Q4_0, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0 } from '@qvac/sdk/models';
|
|
1
|
+
import { AUDIOGEN_ACESTEP_5HZ_LM_0_6B_Q8_0, AUDIOGEN_ACESTEP_V15_SFT_Q8_0, AUDIOGEN_ACESTEP_V15_TURBO_Q4_K_M, AUDIOGEN_ACESTEP_V15_TURBO_Q8_0, AUDIOGEN_QWEN3_EMBEDDING_0_6B_Q8_0, AUDIOGEN_VAE_BF16, EMBEDDINGGEMMA_300M_Q4_0, FLUX_2_KLEIN_4B_Q4_0, FLUX_2_KLEIN_4B_VAE, HEALTHCARE_1_7B_MEDICAL_Q4_K_M, HEALTHCARE_4B_MEDICAL_Q4_K_M, MMPROJ_QWEN3_5_2B_MULTIMODAL_Q8_0, MMPROJ_QWEN3_5_4B_MULTIMODAL_Q8_0, MMPROJ_QWEN3_5_9B_MULTIMODAL_F16, MMPROJ_QWEN3_6_27B_MULTIMODAL_F16, MMPROJ_QWEN3_6_35B_A3B_MULTIMODAL_F16, MMPROJ_QWEN3_8_27B_MULTIMODAL_F16, MMPROJ_TERNARY_BONSAI_2_27B_MULTIMODAL_Q8_0, QWEN3_5_2B_MULTIMODAL_Q4_K_M, QWEN3_5_4B_MULTIMODAL_Q4_K_M, QWEN3_5_9B_MULTIMODAL_Q4_K_M, PARAKEET_TDT_0_6B_V3_Q8_0, QWEN3_4B_Q4_K_M, QWEN3_6_27B_MULTIMODAL_Q4_K_XL, QWEN3_6_35B_A3B_MULTIMODAL_Q4_K_M, QWEN3_8_27B_MULTIMODAL_UD_Q4_K_XL, SD_V2_1_1B_Q8_0, SDXL_BASE_1_0_3B_Q4_0, TERNARY_BONSAI_2_27B_MULTIMODAL_PQ2_0, TERNARY_BONSAI_2_27B_MULTIMODAL_PTQ1_0, TTS_MULTILINGUAL_SUPERTONIC3_FP16, TTS_MULTILINGUAL_SUPERTONIC3_Q4_0, TTS_MULTILINGUAL_SUPERTONIC3_Q8_0 } from '@qvac/sdk/models';
|
|
2
2
|
import { detectToolDialect } from './tool-dialect.js';
|
|
3
3
|
import { defaultTtsModelName } from './tts-model-default.js';
|
|
4
|
-
import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName } from './model-catalog-constants.js';
|
|
5
|
-
export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName };
|
|
4
|
+
import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, BONSAI_2_27B_PQ2, BONSAI_2_27B_PTQ1, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName } from './model-catalog-constants.js';
|
|
5
|
+
export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, BONSAI_2_27B_PQ2, BONSAI_2_27B_PTQ1, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName };
|
|
6
6
|
export const LLM_CATALOG = [
|
|
7
7
|
{
|
|
8
8
|
name: QWEN_35_2B,
|
|
@@ -51,6 +51,20 @@ export const LLM_CATALOG = [
|
|
|
51
51
|
weight: QWEN3_8_27B_MULTIMODAL_UD_Q4_K_XL,
|
|
52
52
|
capabilityProfile: 'xhigh',
|
|
53
53
|
projection: MMPROJ_QWEN3_8_27B_MULTIMODAL_F16
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: BONSAI_2_27B_PQ2,
|
|
57
|
+
weight: TERNARY_BONSAI_2_27B_MULTIMODAL_PQ2_0,
|
|
58
|
+
capabilityProfile: 'high',
|
|
59
|
+
projection: MMPROJ_TERNARY_BONSAI_2_27B_MULTIMODAL_Q8_0,
|
|
60
|
+
toolDialect: 'qwen35'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: BONSAI_2_27B_PTQ1,
|
|
64
|
+
weight: TERNARY_BONSAI_2_27B_MULTIMODAL_PTQ1_0,
|
|
65
|
+
capabilityProfile: 'high',
|
|
66
|
+
projection: MMPROJ_TERNARY_BONSAI_2_27B_MULTIMODAL_Q8_0,
|
|
67
|
+
toolDialect: 'qwen35'
|
|
54
68
|
}
|
|
55
69
|
];
|
|
56
70
|
export function defaultProjectionMap() {
|
|
@@ -181,6 +195,16 @@ export function diffusionDownloadMetadata(model, addon) {
|
|
|
181
195
|
assetSizes
|
|
182
196
|
};
|
|
183
197
|
}
|
|
198
|
+
export function assetBytes(src) {
|
|
199
|
+
const assets = [
|
|
200
|
+
...curatedListables().map((entry) => entry.asset),
|
|
201
|
+
...LLM_CATALOG.flatMap((entry) => (entry.projection ? [entry.projection] : [])),
|
|
202
|
+
...DIFFUSION_CATALOG.flatMap(diffusionAssets),
|
|
203
|
+
...AUDIOGEN_CATALOG.flatMap((entry) => [entry.dit, entry.textEnc, entry.lm, entry.vae])
|
|
204
|
+
];
|
|
205
|
+
const custom = customModels().find((entry) => entry.src === src);
|
|
206
|
+
return assets.find((asset) => asset.src === src)?.expectedSize ?? custom?.expectedSize;
|
|
207
|
+
}
|
|
184
208
|
import { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom } from './music-catalog.js';
|
|
185
209
|
export { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom };
|
|
186
210
|
const MUSIC_GENERATION_SKILL = 'music-generation';
|
|
@@ -363,6 +387,11 @@ export function modelAddonOf(model) {
|
|
|
363
387
|
export function residentSrc(model, addon) {
|
|
364
388
|
return curatedModelAsset(model, addon)?.src ?? model;
|
|
365
389
|
}
|
|
390
|
+
export function catalogModelName(model, addon) {
|
|
391
|
+
const known = curatedListables().find((entry) => entry.asset.addon === addon &&
|
|
392
|
+
(entry.name === model || entry.asset.name === model || entry.asset.src === model));
|
|
393
|
+
return known?.name ?? model;
|
|
394
|
+
}
|
|
366
395
|
export const CUSTOM_TASK_TYPES = ['llm'];
|
|
367
396
|
let CUSTOM_MODELS = new Map();
|
|
368
397
|
export function setCustomModels(entries) {
|
|
@@ -378,6 +407,9 @@ export function customModelSrc(name, addon) {
|
|
|
378
407
|
const entry = CUSTOM_MODELS.get(name);
|
|
379
408
|
return entry && (entry.taskType ?? 'llm') === addon ? entry.src : undefined;
|
|
380
409
|
}
|
|
410
|
+
export function toolDialectFor(name) {
|
|
411
|
+
return LLM_CATALOG.find((entry) => entry.name === name)?.toolDialect ?? customToolDialect(name);
|
|
412
|
+
}
|
|
381
413
|
export function customToolDialect(name) {
|
|
382
414
|
const entry = CUSTOM_MODELS.get(name);
|
|
383
415
|
return entry
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { type AbortSignal } from 'bare-abort-controller';
|
|
1
2
|
export interface ModelResource {
|
|
2
3
|
model: string;
|
|
3
4
|
modelId: string;
|
|
4
5
|
ctxSize?: number;
|
|
6
|
+
bytes?: number;
|
|
5
7
|
}
|
|
6
8
|
export interface ModelHandleOptions {
|
|
7
|
-
load(): Promise<ModelResource>;
|
|
9
|
+
load(signal: AbortSignal): Promise<ModelResource>;
|
|
8
10
|
onLoaded?(): void;
|
|
9
11
|
onFailed?(err: unknown): void;
|
|
10
12
|
}
|
|
@@ -15,8 +17,13 @@ export declare class ModelHandle {
|
|
|
15
17
|
private readonly _onFailed;
|
|
16
18
|
private _tracked;
|
|
17
19
|
private _loading;
|
|
20
|
+
private _resource;
|
|
21
|
+
lastUsed: number;
|
|
22
|
+
private _attempt;
|
|
18
23
|
constructor({ load, onLoaded, onFailed }: ModelHandleOptions);
|
|
19
24
|
load(): Promise<ModelResource>;
|
|
20
25
|
get loading(): boolean;
|
|
26
|
+
abandon(): void;
|
|
21
27
|
peek(): Promise<ModelResource> | null;
|
|
28
|
+
get resource(): ModelResource | null;
|
|
22
29
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AbortController } from 'bare-abort-controller';
|
|
2
|
+
import { AbortedError } from '../helpers/race-abort.js';
|
|
1
3
|
import { retryMemo } from '../helpers/retry-memo.js';
|
|
2
4
|
export class ModelHandle {
|
|
3
5
|
_load;
|
|
@@ -6,8 +8,15 @@ export class ModelHandle {
|
|
|
6
8
|
_onFailed;
|
|
7
9
|
_tracked = null;
|
|
8
10
|
_loading = false;
|
|
11
|
+
_resource = null;
|
|
12
|
+
lastUsed = 0;
|
|
13
|
+
_attempt = null;
|
|
9
14
|
constructor({ load, onLoaded, onFailed }) {
|
|
10
|
-
const memo = retryMemo(
|
|
15
|
+
const memo = retryMemo(() => {
|
|
16
|
+
const attempt = new AbortController();
|
|
17
|
+
this._attempt = attempt;
|
|
18
|
+
return load(attempt.signal);
|
|
19
|
+
});
|
|
11
20
|
this._load = memo;
|
|
12
21
|
this._peek = memo.peek;
|
|
13
22
|
this._onLoaded = onLoaded;
|
|
@@ -25,15 +34,24 @@ export class ModelHandle {
|
|
|
25
34
|
report();
|
|
26
35
|
}
|
|
27
36
|
};
|
|
28
|
-
attempt.then(() => settle(() =>
|
|
37
|
+
attempt.then((resource) => settle(() => {
|
|
38
|
+
this._resource = resource;
|
|
39
|
+
this._onLoaded?.();
|
|
40
|
+
}), (err) => settle(() => this._onFailed?.(err)));
|
|
29
41
|
}
|
|
30
42
|
return attempt;
|
|
31
43
|
}
|
|
32
44
|
get loading() {
|
|
33
45
|
return this._loading;
|
|
34
46
|
}
|
|
47
|
+
abandon() {
|
|
48
|
+
this._attempt?.abort(new AbortedError());
|
|
49
|
+
}
|
|
35
50
|
peek() {
|
|
36
51
|
return this._peek();
|
|
37
52
|
}
|
|
53
|
+
get resource() {
|
|
54
|
+
return this._loading ? null : this._resource;
|
|
55
|
+
}
|
|
38
56
|
}
|
|
39
57
|
function noop() { }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as QvacSdk from '@qvac/sdk';
|
|
2
|
-
import type
|
|
2
|
+
import { type ModelAddon, type ModelRegistrySdk } from './model-source.ts';
|
|
3
3
|
import { type ModelTaskType } from './model-task.ts';
|
|
4
4
|
export type ModelType = ModelAddon;
|
|
5
5
|
export interface StaticCatalogEntry {
|
|
@@ -22,6 +22,7 @@ export interface ModelInfo {
|
|
|
22
22
|
actualSize?: number;
|
|
23
23
|
label?: string;
|
|
24
24
|
taskType: ModelTaskType;
|
|
25
|
+
memoryBytes?: number | undefined;
|
|
25
26
|
}
|
|
26
27
|
export type CatalogRow = Omit<ModelInfo, 'isLoading'>;
|
|
27
28
|
export interface ListModelsOptions {
|
|
@@ -54,4 +55,10 @@ export interface CustomModelRowsContext {
|
|
|
54
55
|
}
|
|
55
56
|
export declare function customModelRows({ type, cachedOnly }: ListModelsOptions, { modelsDir, loaded }: CustomModelRowsContext): Promise<CatalogRow[]>;
|
|
56
57
|
export declare function mergeCustomRows(catalog: ReadonlyArray<CatalogRow>, custom: ReadonlyArray<CatalogRow>): CatalogRow[];
|
|
58
|
+
export interface ModelSourceQuery {
|
|
59
|
+
readonly model: string;
|
|
60
|
+
readonly addon: ModelAddon;
|
|
61
|
+
readonly modelsDir: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
export declare function isModelSourceCached(sdk: ModelListSdk, { model, addon, modelsDir }: ModelSourceQuery): Promise<boolean>;
|
|
57
64
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveModelSrc } from './model-source.js';
|
|
1
2
|
import { addonTaskType } from './model-task.js';
|
|
2
3
|
import { isCustomModelCached } from './custom-model-cache.js';
|
|
3
4
|
import { companionRegistryPaths, curatedListables, customModels, musicListables } from './model-catalog.js';
|
|
@@ -153,3 +154,25 @@ export function mergeCustomRows(catalog, custom) {
|
|
|
153
154
|
const shadowed = new Set(custom.map((row) => row.name));
|
|
154
155
|
return [...catalog.filter((row) => !shadowed.has(row.name)), ...custom];
|
|
155
156
|
}
|
|
157
|
+
export async function isModelSourceCached(sdk, { model, addon, modelsDir }) {
|
|
158
|
+
const src = await resolveModelSrc(sdk, model, addon).catch(() => null);
|
|
159
|
+
if (src === null) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const name = registryCatalogName(sdk, src);
|
|
163
|
+
if (name === undefined) {
|
|
164
|
+
return typeof src === 'string' && isCustomModelCached({ name: model, src }, modelsDir);
|
|
165
|
+
}
|
|
166
|
+
const info = await sdk.getModelInfo({ name }).catch(() => null);
|
|
167
|
+
return info?.isCached ?? false;
|
|
168
|
+
}
|
|
169
|
+
function registryCatalogName(sdk, src) {
|
|
170
|
+
if (typeof src !== 'string') {
|
|
171
|
+
return sdk.models.find((entry) => entry.registryPath === src.registryPath)?.name ?? src.name;
|
|
172
|
+
}
|
|
173
|
+
const registryPath = /^registry:\/\/[^/]+\/(.+)$/.exec(src)?.[1];
|
|
174
|
+
if (registryPath === undefined) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
return sdk.models.find((entry) => entry.registryPath === registryPath)?.name;
|
|
178
|
+
}
|
|
@@ -3,14 +3,29 @@ import { ModelHandle } from './model-handle.ts';
|
|
|
3
3
|
import { type ModelSrc } from './context.ts';
|
|
4
4
|
import { type ModelAddon } from './model-source.ts';
|
|
5
5
|
import type { GpuFaultState } from './sdk-faults.ts';
|
|
6
|
+
import type { HoldFn } from './download-gate.ts';
|
|
6
7
|
type SdkModule = typeof import('@qvac/sdk');
|
|
8
|
+
type FitModelType = NonNullable<import('@qvac/sdk').AssessModelFitInput['models'][number]['modelType']>;
|
|
7
9
|
export interface ModelState {
|
|
8
|
-
status: 'loading' | 'downloading' | 'downloaded' | 'ready' | 'error' | 'unloading' | 'unloaded';
|
|
10
|
+
status: 'held' | 'loading' | 'downloading' | 'downloaded' | 'ready' | 'error' | 'unloading' | 'unloaded';
|
|
9
11
|
model: string;
|
|
10
12
|
catalogChanged?: boolean;
|
|
11
13
|
[key: string]: unknown;
|
|
12
14
|
}
|
|
13
15
|
export type NotifyFn = (state: ModelState) => void;
|
|
16
|
+
export interface PendingLoad {
|
|
17
|
+
readonly key: string;
|
|
18
|
+
readonly model: string;
|
|
19
|
+
readonly modelSrc: ModelSrc;
|
|
20
|
+
readonly modelType: FitModelType;
|
|
21
|
+
readonly modelConfig?: Record<string, unknown> | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface Admission {
|
|
24
|
+
readonly bytes: number | undefined;
|
|
25
|
+
release(): void;
|
|
26
|
+
}
|
|
27
|
+
export type BeforeLoad = (load: PendingLoad) => Promise<Admission | undefined>;
|
|
28
|
+
export type ChooseContext = (load: PendingLoad, tier: number) => Promise<number>;
|
|
14
29
|
export interface LlmDeviceConfig {
|
|
15
30
|
device?: 'cpu' | 'gpu';
|
|
16
31
|
gpuLayers?: number;
|
|
@@ -80,6 +95,13 @@ export declare function modelOf(key: string): string;
|
|
|
80
95
|
export declare function releaseModel(pool: PoolLike, key: string): void;
|
|
81
96
|
export declare function loadedModels(pool: PoolLike): LoadedModel[];
|
|
82
97
|
export declare function settledModels(pool: PoolLike): string[];
|
|
98
|
+
export interface SettledEntry {
|
|
99
|
+
readonly key: string;
|
|
100
|
+
readonly refs: number;
|
|
101
|
+
readonly bytes: number | undefined;
|
|
102
|
+
readonly lastUsed: number;
|
|
103
|
+
}
|
|
104
|
+
export declare function settledEntries(pool: PoolLike): SettledEntry[];
|
|
83
105
|
export declare function loadingModels(pool: PoolLike): string[];
|
|
84
106
|
export interface ResidentLease {
|
|
85
107
|
model: string;
|
|
@@ -104,6 +126,7 @@ export interface ModelCapacityOptions extends DeadlineOptions {
|
|
|
104
126
|
}
|
|
105
127
|
export declare function awaitModelCapacity(pool: PoolLike, { cap, deadlineMs, tickMs, signal, evictable }: ModelCapacityOptions): Promise<boolean>;
|
|
106
128
|
export declare function evictModelWithin(pool: PoolLike, key: string, options: DeadlineOptions): Promise<boolean>;
|
|
129
|
+
export declare function evictIdleWithin(pool: PoolLike, key: string, { deadlineMs }: DeadlineOptions): Promise<boolean>;
|
|
107
130
|
export declare function evictModel(pool: PoolLike, model: string, options: DeadlineOptions): Promise<boolean>;
|
|
108
131
|
export type LoadSdk = () => Promise<ModelLifecycleSdk>;
|
|
109
132
|
export type EmbedLifecycleSdk = Pick<SdkModule, 'unloadModel' | 'modelRegistrySearch'> & Partial<CancelSdk> & {
|
|
@@ -134,15 +157,20 @@ export declare function loadClockGuard(model: string, sdk: Partial<CancelSdk>, {
|
|
|
134
157
|
export interface ModelPoolOptions {
|
|
135
158
|
deviceConfig?: LlmDeviceConfig;
|
|
136
159
|
loads?: LoadTracker;
|
|
160
|
+
beforeLoad?: BeforeLoad;
|
|
161
|
+
chooseContext?: ChooseContext;
|
|
162
|
+
hold?: HoldFn;
|
|
137
163
|
}
|
|
138
|
-
export declare function createModelPool(loadSdk: LoadSdk, notify: NotifyFn, { deviceConfig, loads }?: ModelPoolOptions): ModelPuddle;
|
|
164
|
+
export declare function createModelPool(loadSdk: LoadSdk, notify: NotifyFn, { deviceConfig, loads, beforeLoad, chooseContext, hold }?: ModelPoolOptions): ModelPuddle;
|
|
139
165
|
export interface EmbedPoolOptions {
|
|
140
166
|
faults: GpuFaultState;
|
|
141
167
|
batchSize?: number;
|
|
142
168
|
openclCacheDir?: (() => string | undefined) | undefined;
|
|
143
169
|
loads?: LoadTracker;
|
|
170
|
+
beforeLoad?: BeforeLoad;
|
|
171
|
+
hold?: HoldFn;
|
|
144
172
|
}
|
|
145
|
-
export declare function createEmbedPool(loadSdk: LoadEmbedSdk, notify: NotifyFn, { faults, batchSize, openclCacheDir, loads }: EmbedPoolOptions): ModelPuddle;
|
|
173
|
+
export declare function createEmbedPool(loadSdk: LoadEmbedSdk, notify: NotifyFn, { faults, batchSize, openclCacheDir, loads, beforeLoad, hold }: EmbedPoolOptions): ModelPuddle;
|
|
146
174
|
export type VoiceLifecycleSdk = Pick<SdkModule, 'unloadModel' | 'modelRegistrySearch'> & Partial<CancelSdk> & {
|
|
147
175
|
loadModel(params: {
|
|
148
176
|
modelSrc: ModelSrc;
|
|
@@ -153,12 +181,14 @@ export type VoiceLifecycleSdk = Pick<SdkModule, 'unloadModel' | 'modelRegistrySe
|
|
|
153
181
|
};
|
|
154
182
|
export type LoadVoiceSdk = () => Promise<VoiceLifecycleSdk>;
|
|
155
183
|
export interface VoicePoolOptions {
|
|
156
|
-
modelType:
|
|
184
|
+
modelType: FitModelType;
|
|
157
185
|
addon: ModelAddon;
|
|
158
186
|
modelConfig?: Record<string, unknown> | undefined;
|
|
159
187
|
modelConfigFor?: (modelSrc: ModelSrc) => Readonly<Record<string, unknown>> | undefined;
|
|
160
188
|
openclCacheDir?: (() => string | undefined) | undefined;
|
|
161
189
|
loads?: LoadTracker;
|
|
190
|
+
beforeLoad?: BeforeLoad;
|
|
191
|
+
hold?: HoldFn;
|
|
162
192
|
}
|
|
163
|
-
export declare function createVoicePool(loadSdk: LoadVoiceSdk, notify: NotifyFn, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads }: VoicePoolOptions): ModelPuddle;
|
|
193
|
+
export declare function createVoicePool(loadSdk: LoadVoiceSdk, notify: NotifyFn, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads, beforeLoad, hold }: VoicePoolOptions): ModelPuddle;
|
|
164
194
|
export {};
|
|
@@ -11,6 +11,7 @@ import { RUNAWAY_STOP_SEQUENCES } from '../helpers/control-tokens.js';
|
|
|
11
11
|
import { raceTimeout } from '../helpers/race-timeout.js';
|
|
12
12
|
import { resolveModelSrc } from './model-source.js';
|
|
13
13
|
import { errorMessage } from '../guards.js';
|
|
14
|
+
import { AbortedError } from '../helpers/race-abort.js';
|
|
14
15
|
export function createLoadTracker() {
|
|
15
16
|
const inflight = new Set();
|
|
16
17
|
let draining = null;
|
|
@@ -73,10 +74,14 @@ function entryOf(pool, key) {
|
|
|
73
74
|
export function modelOf(key) {
|
|
74
75
|
return key.split('\0')[0];
|
|
75
76
|
}
|
|
77
|
+
let uses = 0;
|
|
76
78
|
export function releaseModel(pool, key) {
|
|
77
79
|
if (pool.destroyed)
|
|
78
80
|
return;
|
|
79
81
|
const handle = entryOf(pool, key)?.value;
|
|
82
|
+
if (handle) {
|
|
83
|
+
handle.lastUsed = ++uses;
|
|
84
|
+
}
|
|
80
85
|
if (handle === undefined || !handle.loading) {
|
|
81
86
|
pool.release(key);
|
|
82
87
|
return;
|
|
@@ -86,8 +91,18 @@ export function releaseModel(pool, key) {
|
|
|
86
91
|
pool.release(key);
|
|
87
92
|
return;
|
|
88
93
|
}
|
|
89
|
-
|
|
94
|
+
const deferred = (deferredReleases.get(handle) ?? 0) + 1;
|
|
95
|
+
deferredReleases.set(handle, deferred);
|
|
96
|
+
if (deferred >= (entryOf(pool, key)?.refs ?? 0)) {
|
|
97
|
+
handle.abandon();
|
|
98
|
+
}
|
|
99
|
+
const settle = () => {
|
|
100
|
+
deferredReleases.set(handle, (deferredReleases.get(handle) ?? 1) - 1);
|
|
101
|
+
releaseSettled(pool, key);
|
|
102
|
+
};
|
|
103
|
+
void pending.then(settle, settle);
|
|
90
104
|
}
|
|
105
|
+
const deferredReleases = new WeakMap();
|
|
91
106
|
function releaseSettled(pool, key) {
|
|
92
107
|
if (!pool.destroyed) {
|
|
93
108
|
pool.release(key);
|
|
@@ -111,6 +126,21 @@ export function settledModels(pool) {
|
|
|
111
126
|
}
|
|
112
127
|
return out;
|
|
113
128
|
}
|
|
129
|
+
export function settledEntries(pool) {
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const entry of pool.resources.values()) {
|
|
132
|
+
const resource = entry.value.resource;
|
|
133
|
+
if (resource !== null) {
|
|
134
|
+
out.push({
|
|
135
|
+
key: entry.id,
|
|
136
|
+
refs: entry.refs,
|
|
137
|
+
bytes: resource.bytes,
|
|
138
|
+
lastUsed: entry.value.lastUsed
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
114
144
|
export function loadingModels(pool) {
|
|
115
145
|
const out = [];
|
|
116
146
|
for (const entry of pool.resources.values()) {
|
|
@@ -120,14 +150,19 @@ export function loadingModels(pool) {
|
|
|
120
150
|
}
|
|
121
151
|
return out;
|
|
122
152
|
}
|
|
123
|
-
function reportedHandle(notify, model, load) {
|
|
153
|
+
function reportedHandle({ notify, model, addon, hold, load }) {
|
|
124
154
|
return new ModelHandle({
|
|
125
|
-
load: () => {
|
|
155
|
+
load: async (signal) => {
|
|
156
|
+
await hold?.(model, addon, signal);
|
|
126
157
|
notify({ status: 'loading', model, catalogChanged: true });
|
|
127
158
|
return load();
|
|
128
159
|
},
|
|
129
160
|
onLoaded: () => notify({ status: 'ready', model, catalogChanged: true }),
|
|
130
|
-
onFailed: (err) =>
|
|
161
|
+
onFailed: (err) => {
|
|
162
|
+
if (err instanceof AbortedError)
|
|
163
|
+
return;
|
|
164
|
+
notify({ status: 'error', model, message: errorMessage(err), catalogChanged: true });
|
|
165
|
+
}
|
|
131
166
|
});
|
|
132
167
|
}
|
|
133
168
|
export async function leaseResidentModel(pool, wanted = () => true) {
|
|
@@ -226,6 +261,10 @@ export async function awaitModelCapacity(pool, { cap, deadlineMs, tickMs = 250,
|
|
|
226
261
|
export function evictModelWithin(pool, key, options) {
|
|
227
262
|
return evictWhereWithin(pool, (candidate) => candidate === key, options);
|
|
228
263
|
}
|
|
264
|
+
export function evictIdleWithin(pool, key, { deadlineMs }) {
|
|
265
|
+
const evicting = evictIdleWhere(pool, (candidate) => candidate === key).then(() => !isResident(pool, key));
|
|
266
|
+
return raceTimeout(evicting, { ms: deadlineMs, fallback: false });
|
|
267
|
+
}
|
|
229
268
|
export function evictModel(pool, model, options) {
|
|
230
269
|
return evictWhereWithin(pool, (key) => modelOf(key) === model, options);
|
|
231
270
|
}
|
|
@@ -318,12 +357,12 @@ export function loadClockGuard(model, sdk, { now = () => Date.now(), monoMs = mo
|
|
|
318
357
|
};
|
|
319
358
|
return { race };
|
|
320
359
|
}
|
|
321
|
-
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker() } = {}) {
|
|
322
|
-
const load = async (model, opts) => {
|
|
360
|
+
export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = createLoadTracker(), beforeLoad, chooseContext, hold } = {}) {
|
|
361
|
+
const load = async (key, model, opts) => {
|
|
323
362
|
const sdk = await loadSdk();
|
|
324
363
|
const modelSrc = await resolveModelSrc(sdk, model);
|
|
325
364
|
const resolved = resolveLoadOptions(opts, deviceConfig);
|
|
326
|
-
const
|
|
365
|
+
const tier = defaultContextSize(modelSrc, customContextLength(model));
|
|
327
366
|
const projectionModelSrc = resolved.projectionModel
|
|
328
367
|
? await resolveModelSrc(sdk, resolved.projectionModel)
|
|
329
368
|
: undefined;
|
|
@@ -337,11 +376,10 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
337
376
|
const openclCache = deviceConfig.device !== 'cpu' && deviceConfig.openclCacheDir
|
|
338
377
|
? deviceConfig.openclCacheDir()
|
|
339
378
|
: undefined;
|
|
340
|
-
const
|
|
379
|
+
const config = definedFields({
|
|
341
380
|
verbosity: llmVerbosity(),
|
|
342
381
|
tools: true,
|
|
343
382
|
stop_sequences: RUNAWAY_STOP_SEQUENCES,
|
|
344
|
-
ctx_size: ctxSize,
|
|
345
383
|
'cache-type-k': kvCache.k,
|
|
346
384
|
'cache-type-v': kvCache.v,
|
|
347
385
|
projectionModelSrc,
|
|
@@ -350,56 +388,80 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
350
388
|
'main-gpu': mainGpu,
|
|
351
389
|
openclCacheDir: openclCache || undefined
|
|
352
390
|
});
|
|
391
|
+
const pending = { key, model, modelSrc, modelType: 'llamacpp-completion' };
|
|
392
|
+
const ctxSize = resolved.ctxSize ||
|
|
393
|
+
(chooseContext ? await chooseContext({ ...pending, modelConfig: config }, tier) : tier);
|
|
394
|
+
const modelConfig = { ...config, ctx_size: ctxSize };
|
|
353
395
|
const { projectionModelSrc: projection, ...loggedConfig } = modelConfig;
|
|
354
396
|
hlog('llm load config', { model, projection: Boolean(projection), ...loggedConfig });
|
|
397
|
+
const admission = await beforeLoad?.({ ...pending, modelConfig });
|
|
355
398
|
const loadStarted = Date.now();
|
|
356
|
-
const modelId = await loadClockGuard(model, sdk)
|
|
399
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
400
|
+
.race(loads.track(sdk.loadModel({
|
|
357
401
|
modelSrc,
|
|
358
402
|
modelType: 'llamacpp-completion',
|
|
359
403
|
modelConfig,
|
|
360
404
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
361
|
-
})))
|
|
405
|
+
})))
|
|
406
|
+
.finally(() => admission?.release());
|
|
362
407
|
hlog('llm loaded', { model, loadMs: Date.now() - loadStarted });
|
|
363
|
-
return { model, modelId, ctxSize };
|
|
408
|
+
return { model, modelId, ctxSize, bytes: admission?.bytes };
|
|
364
409
|
};
|
|
365
410
|
return new ModelPuddle({
|
|
366
|
-
open: (key, model = key, opts = {}) => reportedHandle(notify, model, () => load(model, opts)),
|
|
411
|
+
open: (key, model = key, opts = {}) => reportedHandle({ notify, model, addon: 'llm', hold, load: () => load(key, model, opts) }),
|
|
367
412
|
gc: unloadOnGc(loadSdk, notify),
|
|
368
413
|
...NO_IDLE_GC
|
|
369
414
|
});
|
|
370
415
|
}
|
|
371
|
-
export function createEmbedPool(loadSdk, notify, { faults, batchSize = DEFAULT_EMBED_BATCH_SIZE, openclCacheDir, loads = createLoadTracker() }) {
|
|
372
|
-
const load = async (model) => {
|
|
416
|
+
export function createEmbedPool(loadSdk, notify, { faults, batchSize = DEFAULT_EMBED_BATCH_SIZE, openclCacheDir, loads = createLoadTracker(), beforeLoad, hold }) {
|
|
417
|
+
const load = async (key, model) => {
|
|
373
418
|
const sdk = await loadSdk();
|
|
374
419
|
const modelSrc = await resolveModelSrc(sdk, model, 'embeddings');
|
|
375
420
|
const openclCache = faults.device !== 'cpu' && openclCacheDir ? openclCacheDir() : undefined;
|
|
376
|
-
const
|
|
421
|
+
const modelConfig = {
|
|
422
|
+
batchSize,
|
|
423
|
+
gpuLayers: faults.gpuLayers(),
|
|
424
|
+
device: faults.device,
|
|
425
|
+
openclCacheDir: openclCache || undefined
|
|
426
|
+
};
|
|
427
|
+
const admission = await beforeLoad?.({
|
|
428
|
+
key,
|
|
429
|
+
model,
|
|
430
|
+
modelSrc,
|
|
431
|
+
modelType: 'llamacpp-embedding',
|
|
432
|
+
modelConfig
|
|
433
|
+
});
|
|
434
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
435
|
+
.race(loads.track(sdk.loadModel({
|
|
377
436
|
modelSrc,
|
|
378
437
|
modelType: 'llamacpp-embedding',
|
|
379
|
-
modelConfig
|
|
380
|
-
batchSize,
|
|
381
|
-
gpuLayers: faults.gpuLayers(),
|
|
382
|
-
device: faults.device,
|
|
383
|
-
openclCacheDir: openclCache || undefined
|
|
384
|
-
},
|
|
438
|
+
modelConfig,
|
|
385
439
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
386
|
-
})))
|
|
387
|
-
|
|
440
|
+
})))
|
|
441
|
+
.finally(() => admission?.release());
|
|
442
|
+
return { model, modelId, bytes: admission?.bytes };
|
|
388
443
|
};
|
|
389
444
|
return new ModelPuddle({
|
|
390
|
-
open: (key, model = key) => reportedHandle(notify, model, () => load(model)),
|
|
445
|
+
open: (key, model = key) => reportedHandle({ notify, model, addon: 'embeddings', hold, load: () => load(key, model) }),
|
|
391
446
|
gc: unloadOnGc(loadSdk, notify),
|
|
392
447
|
...NO_IDLE_GC
|
|
393
448
|
});
|
|
394
449
|
}
|
|
395
|
-
export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads = createLoadTracker() }) {
|
|
450
|
+
export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig, modelConfigFor, openclCacheDir, loads = createLoadTracker(), beforeLoad, hold }) {
|
|
396
451
|
const wantsGpu = modelConfig?.['useGPU'] === true;
|
|
397
452
|
let gpuRefused = false;
|
|
398
|
-
const load = async (model) => {
|
|
453
|
+
const load = async (key, model) => {
|
|
399
454
|
const sdk = await loadSdk();
|
|
400
455
|
const modelSrc = await resolveModelSrc(sdk, model, addon);
|
|
401
456
|
const tuned = modelConfigFor?.(modelSrc);
|
|
402
457
|
const loadConfig = tuned ? { ...modelConfig, ...tuned } : modelConfig;
|
|
458
|
+
const admission = await beforeLoad?.({
|
|
459
|
+
key,
|
|
460
|
+
model,
|
|
461
|
+
modelSrc,
|
|
462
|
+
modelType,
|
|
463
|
+
modelConfig: loadConfig
|
|
464
|
+
});
|
|
403
465
|
const attempt = () => {
|
|
404
466
|
const config = gpuRefused ? { ...loadConfig, useGPU: false } : loadConfig;
|
|
405
467
|
const openclCache = gpuRefused ? undefined : openclCacheDir?.();
|
|
@@ -410,17 +472,19 @@ export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig
|
|
|
410
472
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
411
473
|
}));
|
|
412
474
|
};
|
|
413
|
-
const modelId = await loadClockGuard(model, sdk)
|
|
475
|
+
const modelId = await loadClockGuard(model, sdk)
|
|
476
|
+
.race(attempt().catch((err) => {
|
|
414
477
|
if (!wantsGpu || gpuRefused || loads.draining)
|
|
415
478
|
throw err;
|
|
416
479
|
hlog('voice gpu load refused, retrying on cpu', { model, modelType, error: String(err) });
|
|
417
480
|
gpuRefused = true;
|
|
418
481
|
return attempt();
|
|
419
|
-
}))
|
|
420
|
-
|
|
482
|
+
}))
|
|
483
|
+
.finally(() => admission?.release());
|
|
484
|
+
return { model, modelId, bytes: admission?.bytes };
|
|
421
485
|
};
|
|
422
486
|
return new ModelPuddle({
|
|
423
|
-
open: (key, model = key) => reportedHandle(notify, model, () => load(model)),
|
|
487
|
+
open: (key, model = key) => reportedHandle({ notify, model, addon, hold, load: () => load(key, model) }),
|
|
424
488
|
gc: unloadOnGc(loadSdk, notify),
|
|
425
489
|
...NO_IDLE_GC
|
|
426
490
|
});
|
|
@@ -445,7 +509,8 @@ function unloadOnGc(loadSdk, notify) {
|
|
|
445
509
|
try {
|
|
446
510
|
await sdk.unloadModel({ modelId });
|
|
447
511
|
}
|
|
448
|
-
catch {
|
|
512
|
+
catch (err) {
|
|
513
|
+
hlog('unload failed', { model, err: errorMessage(err) });
|
|
449
514
|
continue;
|
|
450
515
|
}
|
|
451
516
|
notify({ status: 'unloaded', model, catalogChanged: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EmbedDevice } from './embed-config.ts';
|
|
2
2
|
export declare function isSdkGpuDeviceLostError(err: unknown): boolean;
|
|
3
3
|
export declare function isSdkModelWedgedError(err: unknown): boolean;
|
|
4
|
+
export declare function isGrammarOverrunError(err: unknown): boolean;
|
|
4
5
|
export declare function isContextOverflowError(err: unknown): boolean;
|
|
5
6
|
export type GpuFaultDecision = 'reload' | 'fallback';
|
|
6
7
|
interface GpuFaultEscalation {
|
|
@@ -16,6 +16,12 @@ export function isSdkModelWedgedError(err) {
|
|
|
16
16
|
}
|
|
17
17
|
return /failed to (decode next token|eval chunk)/i.test(message);
|
|
18
18
|
}
|
|
19
|
+
export function isGrammarOverrunError(err) {
|
|
20
|
+
if (typeof err !== 'object' || err === null)
|
|
21
|
+
return false;
|
|
22
|
+
const message = 'message' in err ? err.message : undefined;
|
|
23
|
+
return typeof message === 'string' && /empty grammar stack/i.test(message);
|
|
24
|
+
}
|
|
19
25
|
export function isContextOverflowError(err) {
|
|
20
26
|
if (typeof err !== 'object' || err === null)
|
|
21
27
|
return false;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Box } from './inherit.ts';
|
|
2
|
+
import type { SlideSize } from './template.ts';
|
|
3
|
+
export type Role = 'slot' | 'blank' | 'body' | 'picture' | 'other';
|
|
4
|
+
export interface Size {
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Placed {
|
|
9
|
+
readonly box: Box;
|
|
10
|
+
readonly role: Role;
|
|
11
|
+
}
|
|
12
|
+
export interface Beside<T> {
|
|
13
|
+
readonly shape: T;
|
|
14
|
+
readonly box: Box;
|
|
15
|
+
}
|
|
16
|
+
export interface Area<T> {
|
|
17
|
+
readonly box: Box;
|
|
18
|
+
readonly cleared: readonly T[];
|
|
19
|
+
readonly beside: Beside<T> | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function framed(slide: SlideSize): Box;
|
|
22
|
+
export declare function fitted(size: Size, area: Box): Box;
|
|
23
|
+
export declare function pictureArea<T extends Placed>(slide: SlideSize, shapes: readonly T[]): Area<T>;
|