@qvac/harness 0.2.4 → 0.2.6
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/.claude/skills/asana/SKILL.md +86 -0
- package/.claude/skills/gh-stack/SKILL.md +181 -0
- package/.claude/skills/gh-stack/references/commands.md +177 -0
- package/.claude/skills/gh-stack/references/stack-design.md +97 -0
- package/.claude/skills/gh-stack/references/troubleshooting.md +159 -0
- package/.claude/skills/js-house-style/SKILL.md +650 -0
- package/.claude/skills/kvcache/SKILL.md +171 -0
- package/.claude/skills/pr-create/SKILL.md +137 -0
- package/.claude/skills/pr-review/SKILL.md +148 -0
- package/CLAUDE.md +36 -0
- package/dist/harness.bundle +23 -14
- package/dist-lib/index.d.ts +6 -1
- package/dist-lib/index.js +27 -9
- package/dist-lib/lib/hardware/capability-probe.d.ts +1 -1
- package/dist-lib/lib/hardware/capability-probe.js +1 -1
- package/dist-lib/lib/hardware/gpu-probe-entry.d.ts +1 -0
- package/dist-lib/lib/hardware/gpu-probe-entry.js +2 -0
- package/dist-lib/lib/hardware/gpu-probe.d.ts +8 -0
- package/dist-lib/lib/hardware/gpu-probe.js +54 -0
- package/dist-lib/lib/hardware/gpu-stats.bare.d.ts +1 -1
- package/dist-lib/lib/hardware/gpu-stats.bare.js +1 -24
- package/dist-lib/lib/hardware/gpu-stats.native.cjs +27 -0
- package/dist-lib/lib/hardware/gpu-stats.native.d.cts +1 -0
- package/dist-lib/lib/models/model-pool.d.ts +1 -1
- package/dist-lib/lib/models/model-pool.js +3 -2
- package/dist-lib/lib/spawn.d.ts +3 -0
- package/dist-lib/lib/spawn.js +5 -3
- package/dist-lib/lib/tools/exec/adapters/python/loader.d.ts +9 -3
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +9 -6
- package/dist-lib/schema/generate.d.ts +6 -2
- package/dist-lib/schema/generate.js +10 -5
- package/docs/kvcache-reference.md +655 -0
- package/package.json +1 -1
- package/worker/harness-sidecar.mjs +8 -3
- package/worker/stow-entry.mjs +3 -19
package/dist-lib/index.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface MusicModelOptions extends MusicModelStages {
|
|
|
63
63
|
}
|
|
64
64
|
export interface HarnessOptions {
|
|
65
65
|
tools?: ToolDefinition[];
|
|
66
|
+
gpuStats?: unknown;
|
|
66
67
|
sdkLoader?: SdkLoader;
|
|
67
68
|
skillsDir?: string;
|
|
68
69
|
projectionModel?: string | Record<string, string>;
|
|
@@ -81,6 +82,7 @@ export interface HarnessOptions {
|
|
|
81
82
|
transcriptionDevice?: 'cpu' | 'gpu';
|
|
82
83
|
credentialStore?: CredentialStoreFn;
|
|
83
84
|
platform?: string;
|
|
85
|
+
completionModelCap?: number;
|
|
84
86
|
}
|
|
85
87
|
export interface CompletionContext {
|
|
86
88
|
model: string;
|
|
@@ -131,6 +133,8 @@ export declare class Harness extends ReadyResource {
|
|
|
131
133
|
private readonly _kvCacheK?;
|
|
132
134
|
private readonly _kvCacheV?;
|
|
133
135
|
private readonly _transcriptionDevice?;
|
|
136
|
+
private readonly _gpuStats?;
|
|
137
|
+
private readonly _completionModelCap;
|
|
134
138
|
private _allSkills;
|
|
135
139
|
private _credentialStore?;
|
|
136
140
|
private _credentials;
|
|
@@ -143,7 +147,7 @@ export declare class Harness extends ReadyResource {
|
|
|
143
147
|
private _capability;
|
|
144
148
|
private _capabilityInflight;
|
|
145
149
|
private readonly _modelFaults;
|
|
146
|
-
constructor({ tools, sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, credentialStore, platform }?: HarnessOptions);
|
|
150
|
+
constructor({ tools, sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats }?: HarnessOptions);
|
|
147
151
|
_open(): Promise<void>;
|
|
148
152
|
get suspended(): boolean;
|
|
149
153
|
get skillNames(): string[];
|
|
@@ -236,6 +240,7 @@ export declare class Harness extends ReadyResource {
|
|
|
236
240
|
signal?: BareAbortSignal;
|
|
237
241
|
}): Promise<void>;
|
|
238
242
|
completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }: CompletionContext): AsyncGenerator<CompletionEvent>;
|
|
243
|
+
private _makeRoomForCompletion;
|
|
239
244
|
private _evictWedgedModel;
|
|
240
245
|
private _evictAfterLeases;
|
|
241
246
|
}
|
package/dist-lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import { platform, totalmem } from 'os';
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import ReadyResource from 'ready-resource';
|
|
5
|
-
import {
|
|
5
|
+
import { probeGpuStats } from "./lib/hardware/gpu-probe.js";
|
|
6
6
|
import { probeCapabilityProfile } from "./lib/hardware/capability-probe.js";
|
|
7
7
|
import { closePythonWorker } from "./lib/tools/exec/adapters/python/worker/client.js";
|
|
8
8
|
import { completion } from "./lib/capabilities/completion.js";
|
|
@@ -17,7 +17,7 @@ import { AbortedError, raceAbort } from "./lib/helpers/race-abort.js";
|
|
|
17
17
|
import { audioGenDownloadMetadata, customProjectionMap, defaultProjectionMap, defaultTtsFor, defaultVoiceModel, diffusionDownloadMetadata, infraModelsFor, modelNameToCapabilityProfile, musicModelSet, setCustomModels } from "./lib/models/model-catalog.js";
|
|
18
18
|
import { createModelStatusCache, invalidateModelStatusCache, listModels } from "./lib/models/model-list.js";
|
|
19
19
|
import { deleteModelFiles, summarizeDelete } from "./lib/models/model-delete.js";
|
|
20
|
-
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool,
|
|
20
|
+
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictModelWithin, isResident, leaseCount, loadedModels, modelKey, releaseModel, resolveLoadOptions, progressState } from "./lib/models/model-pool.js";
|
|
21
21
|
import { resolveMainGpu } from "./lib/models/gpu-selection.js";
|
|
22
22
|
import { openclCacheDir } from "./lib/models/opencl-cache.js";
|
|
23
23
|
import { assertEmbedDevice, createEmbedConfig } from "./lib/models/embed-config.js";
|
|
@@ -68,6 +68,9 @@ const TRANSCRIPTION_MODEL_LABEL = 'transcription model';
|
|
|
68
68
|
const DIFFUSION_EVICT_WAIT_MS = 15_000;
|
|
69
69
|
const MUSIC_EVICT_WAIT_MS = 15_000;
|
|
70
70
|
const WEDGED_EVICT_WAIT_MS = 10_000;
|
|
71
|
+
const MODEL_ROOM_WAIT_MS = 300_000;
|
|
72
|
+
const WARM_ROOM_WAIT_MS = 2_000;
|
|
73
|
+
const DEFAULT_COMPLETION_MODEL_CAP = 1;
|
|
71
74
|
const WEDGED_EVICT_LEASED_MS = 120_000;
|
|
72
75
|
const TTS_MODEL_CONFIG = {
|
|
73
76
|
ttsEngine: 'supertonic',
|
|
@@ -103,6 +106,8 @@ export class Harness extends ReadyResource {
|
|
|
103
106
|
_kvCacheK;
|
|
104
107
|
_kvCacheV;
|
|
105
108
|
_transcriptionDevice;
|
|
109
|
+
_gpuStats;
|
|
110
|
+
_completionModelCap;
|
|
106
111
|
_allSkills;
|
|
107
112
|
_credentialStore;
|
|
108
113
|
_credentials = new Map();
|
|
@@ -115,15 +120,17 @@ export class Harness extends ReadyResource {
|
|
|
115
120
|
_capability = null;
|
|
116
121
|
_capabilityInflight = null;
|
|
117
122
|
_modelFaults = new Map();
|
|
118
|
-
constructor({ tools = [], sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, credentialStore, platform } = {}) {
|
|
123
|
+
constructor({ tools = [], sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
|
|
119
124
|
super();
|
|
120
125
|
this._sdk = sdkLoader ?? createSdkLoader();
|
|
121
126
|
this._llmDevice = llmDevice;
|
|
127
|
+
this._gpuStats = gpuStats;
|
|
122
128
|
this._llmGpuLayers = llmGpuLayers;
|
|
123
129
|
this._contextSize = contextSize;
|
|
124
130
|
this._kvCacheK = kvCacheK;
|
|
125
131
|
this._kvCacheV = kvCacheV;
|
|
126
132
|
this._transcriptionDevice = transcriptionDevice;
|
|
133
|
+
this._completionModelCap = completionModelCap ?? DEFAULT_COMPLETION_MODEL_CAP;
|
|
127
134
|
const embedConfig = createEmbedConfig({
|
|
128
135
|
device: embedDevice,
|
|
129
136
|
gpuLayers: embedGpuLayers,
|
|
@@ -166,7 +173,7 @@ export class Harness extends ReadyResource {
|
|
|
166
173
|
onBeforeLoad: async (signal) => {
|
|
167
174
|
if (this._models.destroyed)
|
|
168
175
|
return;
|
|
169
|
-
const evicted = await
|
|
176
|
+
const evicted = await awaitModelCapacity(this._models, 1, DIFFUSION_EVICT_WAIT_MS, undefined, signal);
|
|
170
177
|
if (signal?.aborted)
|
|
171
178
|
throw new AbortedError();
|
|
172
179
|
if (!evicted) {
|
|
@@ -185,7 +192,7 @@ export class Harness extends ReadyResource {
|
|
|
185
192
|
onBeforeLoad: async (signal) => {
|
|
186
193
|
if (this._models.destroyed)
|
|
187
194
|
return;
|
|
188
|
-
const evicted = await
|
|
195
|
+
const evicted = await awaitModelCapacity(this._models, 1, MUSIC_EVICT_WAIT_MS, undefined, signal);
|
|
189
196
|
if (signal?.aborted)
|
|
190
197
|
throw new AbortedError();
|
|
191
198
|
if (!evicted) {
|
|
@@ -898,7 +905,7 @@ export class Harness extends ReadyResource {
|
|
|
898
905
|
return null;
|
|
899
906
|
}
|
|
900
907
|
},
|
|
901
|
-
readGpuStats: () =>
|
|
908
|
+
readGpuStats: () => (this._gpuStats !== undefined ? this._gpuStats : probeGpuStats())
|
|
902
909
|
};
|
|
903
910
|
this._capabilityInflight = probeCapabilityProfile(reads)
|
|
904
911
|
.then((capability) => {
|
|
@@ -925,7 +932,8 @@ export class Harness extends ReadyResource {
|
|
|
925
932
|
const pool = this._models;
|
|
926
933
|
hlog('warm model', { model, resident: isResident(pool, key) });
|
|
927
934
|
if (!isResident(pool, key)) {
|
|
928
|
-
await
|
|
935
|
+
if (!(await this._makeRoomForCompletion(signal, WARM_ROOM_WAIT_MS)))
|
|
936
|
+
return;
|
|
929
937
|
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
930
938
|
}
|
|
931
939
|
try {
|
|
@@ -951,13 +959,13 @@ export class Harness extends ReadyResource {
|
|
|
951
959
|
const resident = isResident(pool, key);
|
|
952
960
|
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
953
961
|
if (!resident) {
|
|
954
|
-
await
|
|
962
|
+
await this._makeRoomForCompletion(signal);
|
|
955
963
|
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
956
964
|
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
957
965
|
}
|
|
958
966
|
const acquireRound = async () => {
|
|
959
967
|
if (!isResident(pool, key)) {
|
|
960
|
-
await
|
|
968
|
+
await this._makeRoomForCompletion(signal);
|
|
961
969
|
await this._imageRuntime?.unloadIdle().catch(noop);
|
|
962
970
|
await this._musicRuntime?.unloadIdle().catch(noop);
|
|
963
971
|
}
|
|
@@ -1050,6 +1058,16 @@ export class Harness extends ReadyResource {
|
|
|
1050
1058
|
void this._musicRuntime?.unloadIdle().catch(noop);
|
|
1051
1059
|
}
|
|
1052
1060
|
}
|
|
1061
|
+
async _makeRoomForCompletion(signal, deadlineMs = MODEL_ROOM_WAIT_MS) {
|
|
1062
|
+
const roomed = await awaitModelCapacity(this._models, this._completionModelCap, deadlineMs, 250, signal);
|
|
1063
|
+
if (!roomed) {
|
|
1064
|
+
hlog('model room wait gave up', {
|
|
1065
|
+
cap: this._completionModelCap,
|
|
1066
|
+
loaded: loadedModels(this._models).length
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
return roomed;
|
|
1070
|
+
}
|
|
1053
1071
|
async _evictWedgedModel(model, key) {
|
|
1054
1072
|
const faults = (this._modelFaults.get(model) ?? 0) + 1;
|
|
1055
1073
|
this._modelFaults.set(model, faults);
|
|
@@ -4,6 +4,6 @@ export interface PlatformReads {
|
|
|
4
4
|
platform(): string;
|
|
5
5
|
readFile(path: string): Promise<string | null>;
|
|
6
6
|
run(command: string): Promise<string | null>;
|
|
7
|
-
readGpuStats(): unknown
|
|
7
|
+
readGpuStats(): unknown | Promise<unknown>;
|
|
8
8
|
}
|
|
9
9
|
export declare function probeCapabilityProfile(reads: PlatformReads): Promise<CapabilityInfo>;
|
|
@@ -49,7 +49,7 @@ async function probeSysfs(reads) {
|
|
|
49
49
|
}
|
|
50
50
|
async function probeDesktop(reads) {
|
|
51
51
|
const ram = reads.totalmem();
|
|
52
|
-
const addon = probeWithAddon(reads.readGpuStats(), ram);
|
|
52
|
+
const addon = probeWithAddon(await reads.readGpuStats(), ram);
|
|
53
53
|
if (addon && addon.source === 'vram')
|
|
54
54
|
return addon;
|
|
55
55
|
const nvidia = await reads.run('nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface GpuProbeResult {
|
|
2
|
+
readonly ok: boolean;
|
|
3
|
+
readonly stats: unknown;
|
|
4
|
+
}
|
|
5
|
+
export declare function runGpuProbeIfRequested(): void;
|
|
6
|
+
export declare function parseGpuProbeOutput(output: string): unknown;
|
|
7
|
+
export declare function spawnGpuProbe(entry?: string): Promise<GpuProbeResult>;
|
|
8
|
+
export declare function probeGpuStats(): Promise<unknown>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { readSystemStats } from '#hw-gpu-stats';
|
|
4
|
+
const FLAG = '--gpu-probe';
|
|
5
|
+
const TIMEOUT_MS = 5000;
|
|
6
|
+
export function runGpuProbeIfRequested() {
|
|
7
|
+
if (!Bare.argv.includes(FLAG))
|
|
8
|
+
return;
|
|
9
|
+
console.log(JSON.stringify(readSystemStats()));
|
|
10
|
+
queueMicrotask(() => Bare.exit(0));
|
|
11
|
+
}
|
|
12
|
+
export function parseGpuProbeOutput(output) {
|
|
13
|
+
const line = output.trim().split('\n').at(-1) ?? '';
|
|
14
|
+
if (line.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(line);
|
|
18
|
+
return typeof parsed === 'object' ? parsed : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function spawnGpuProbe(entry = Bare.argv[1]) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const child = spawn(Bare.argv[0], [entry, FLAG], {
|
|
27
|
+
stdio: ['ignore', 'pipe', 'pipe', 'pipe']
|
|
28
|
+
});
|
|
29
|
+
let output = '';
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
child.kill();
|
|
32
|
+
resolve({ ok: false, stats: null });
|
|
33
|
+
}, TIMEOUT_MS);
|
|
34
|
+
child.stdout?.on('data', (chunk) => {
|
|
35
|
+
output += Buffer.from(chunk).toString();
|
|
36
|
+
});
|
|
37
|
+
child.stderr?.on('data', () => { });
|
|
38
|
+
child.on('error', () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve({ ok: false, stats: null });
|
|
41
|
+
});
|
|
42
|
+
child.on('exit', (code) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
resolve(code === 0 ? { ok: true, stats: parseGpuProbeOutput(output) } : { ok: false, stats: null });
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export async function probeGpuStats() {
|
|
49
|
+
const platform = os.platform();
|
|
50
|
+
if (typeof Bare === 'undefined' || (platform !== 'linux' && platform !== 'win32')) {
|
|
51
|
+
return readSystemStats();
|
|
52
|
+
}
|
|
53
|
+
return (await spawnGpuProbe()).stats;
|
|
54
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { readSystemStats } from './gpu-stats.native.cjs';
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import GPUInfo from 'bare-gpu-info';
|
|
3
|
-
export function readSystemStats() {
|
|
4
|
-
try {
|
|
5
|
-
const info = new GPUInfo();
|
|
6
|
-
try {
|
|
7
|
-
const devices = [];
|
|
8
|
-
for (const gpu of info) {
|
|
9
|
-
if (typeof gpu.memory !== 'number' || !gpu.name)
|
|
10
|
-
continue;
|
|
11
|
-
devices.push({ name: gpu.name, videoMemoryBytes: gpu.memory });
|
|
12
|
-
}
|
|
13
|
-
if (devices.length === 0)
|
|
14
|
-
return null;
|
|
15
|
-
return { memory: { totalBytes: os.totalmem() }, gpu: { devices } };
|
|
16
|
-
}
|
|
17
|
-
finally {
|
|
18
|
-
info.destroy();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
export { readSystemStats } from './gpu-stats.native.cjs';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const os = require('os')
|
|
2
|
+
|
|
3
|
+
// loaded on first read: the addon drags the graphics stack (and on Linux a
|
|
4
|
+
// second LLVM) into whichever process imports it
|
|
5
|
+
let GPUInfo
|
|
6
|
+
|
|
7
|
+
// Shaped for probeWithAddon (gpu-probe-addon.ts): the probe only wants each
|
|
8
|
+
// device's name and VRAM total, plus system RAM to catch integrated-GPU lies.
|
|
9
|
+
exports.readSystemStats = function readSystemStats() {
|
|
10
|
+
try {
|
|
11
|
+
GPUInfo ??= require('bare-gpu-info')
|
|
12
|
+
const info = new GPUInfo()
|
|
13
|
+
try {
|
|
14
|
+
const devices = []
|
|
15
|
+
for (const gpu of info) {
|
|
16
|
+
if (typeof gpu.memory !== 'number' || !gpu.name) continue
|
|
17
|
+
devices.push({ name: gpu.name, videoMemoryBytes: gpu.memory })
|
|
18
|
+
}
|
|
19
|
+
if (devices.length === 0) return null
|
|
20
|
+
return { memory: { totalBytes: os.totalmem() }, gpu: { devices } }
|
|
21
|
+
} finally {
|
|
22
|
+
info.destroy()
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function readSystemStats(): unknown
|
|
@@ -72,7 +72,7 @@ export declare function loadedModels(pool: PoolLike): LoadedModel[];
|
|
|
72
72
|
export declare function isResident(pool: PoolLike, key: string): boolean;
|
|
73
73
|
export declare function leaseCount(pool: PoolLike, key: string): number;
|
|
74
74
|
export declare function evictIdle(pool: PoolLike): Promise<void>;
|
|
75
|
-
export declare function
|
|
75
|
+
export declare function awaitModelCapacity(pool: PoolLike, cap: number, deadlineMs: number, tickMs?: number, signal?: {
|
|
76
76
|
aborted?: boolean;
|
|
77
77
|
}): Promise<boolean>;
|
|
78
78
|
export declare function evictModelWithin(pool: PoolLike, key: string, deadlineMs: number, tickMs?: number): Promise<boolean>;
|
|
@@ -103,7 +103,8 @@ export async function evictIdle(pool) {
|
|
|
103
103
|
await pool.gc();
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
export async function
|
|
106
|
+
export async function awaitModelCapacity(pool, cap, deadlineMs, tickMs = 250, signal) {
|
|
107
|
+
const roomed = () => loadedModels(pool).length < cap;
|
|
107
108
|
let done = false;
|
|
108
109
|
let timer = null;
|
|
109
110
|
const expired = new Promise((resolve) => {
|
|
@@ -114,7 +115,7 @@ export async function evictAllWithin(pool, deadlineMs, tickMs = 250, signal) {
|
|
|
114
115
|
});
|
|
115
116
|
const evicting = (async () => {
|
|
116
117
|
await evictIdle(pool);
|
|
117
|
-
while (
|
|
118
|
+
while (!roomed()) {
|
|
118
119
|
if (done || signal?.aborted)
|
|
119
120
|
return false;
|
|
120
121
|
await new Promise((resolve) => setTimeout(resolve, tickMs));
|
package/dist-lib/lib/spawn.d.ts
CHANGED
|
@@ -20,10 +20,12 @@ export interface HarnessLlmConfig {
|
|
|
20
20
|
contextSize?: number;
|
|
21
21
|
kvCacheK?: string;
|
|
22
22
|
kvCacheV?: string;
|
|
23
|
+
completionModelCap?: number;
|
|
23
24
|
}
|
|
24
25
|
export interface HarnessChildOptions {
|
|
25
26
|
runner: StowRunner;
|
|
26
27
|
skillsDir?: string;
|
|
28
|
+
gpuStats?: unknown;
|
|
27
29
|
wheelsDir?: string;
|
|
28
30
|
sdkStorage?: SdkStorage;
|
|
29
31
|
embedDevice?: EmbedDevice;
|
|
@@ -46,6 +48,7 @@ export interface HarnessChildOptions {
|
|
|
46
48
|
export declare function harnessChild(entry: string, opts: HarnessChildOptions): ChildSpec<RemoteHarness>;
|
|
47
49
|
export interface SpawnHarnessOptions {
|
|
48
50
|
entry: string;
|
|
51
|
+
gpuStats?: unknown;
|
|
49
52
|
skillsDir?: string;
|
|
50
53
|
wheelsDir?: string;
|
|
51
54
|
sdkStorage: SdkStorage;
|
package/dist-lib/lib/spawn.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createEmbedConfig } from "./models/embed-config.js";
|
|
|
5
5
|
export function harnessChild(entry, opts) {
|
|
6
6
|
if (opts.logger)
|
|
7
7
|
setHarnessLog(true);
|
|
8
|
-
const { runner, skillsDir, wheelsDir, sdkStorage, embedDevice, embedGpuLayers, embedBatchSize, imageModel, imageClamps, musicModel, musicClamps, llm, onIpc, logger = null, ...spec } = opts;
|
|
8
|
+
const { runner, skillsDir, gpuStats, wheelsDir, sdkStorage, embedDevice, embedGpuLayers, embedBatchSize, imageModel, imageClamps, musicModel, musicClamps, llm, onIpc, logger = null, ...spec } = opts;
|
|
9
9
|
const embedConfig = createEmbedConfig({
|
|
10
10
|
device: embedDevice,
|
|
11
11
|
gpuLayers: embedGpuLayers,
|
|
@@ -24,7 +24,8 @@ export function harnessChild(entry, opts) {
|
|
|
24
24
|
llm ? JSON.stringify(llm) : '',
|
|
25
25
|
musicModel ? JSON.stringify(musicModel) : '',
|
|
26
26
|
musicClamps ? JSON.stringify(musicClamps) : '',
|
|
27
|
-
wheelsDir ?? ''
|
|
27
|
+
wheelsDir ?? '',
|
|
28
|
+
gpuStats === undefined ? '' : JSON.stringify(gpuStats)
|
|
28
29
|
],
|
|
29
30
|
logger,
|
|
30
31
|
create: (ipc) => {
|
|
@@ -55,7 +56,8 @@ export function spawnHarness(opts) {
|
|
|
55
56
|
opts.llm ? JSON.stringify(opts.llm) : '',
|
|
56
57
|
opts.musicModel ? JSON.stringify(opts.musicModel) : '',
|
|
57
58
|
opts.musicClamps ? JSON.stringify(opts.musicClamps) : '',
|
|
58
|
-
opts.wheelsDir ?? ''
|
|
59
|
+
opts.wheelsDir ?? '',
|
|
60
|
+
opts.gpuStats === undefined ? '' : JSON.stringify(opts.gpuStats)
|
|
59
61
|
]);
|
|
60
62
|
sidecar.stdout.on('data', (data) => logger?.info('harness', data.toString().trimEnd()));
|
|
61
63
|
sidecar.stderr.on('data', (data) => logger?.warn('harness', data.toString().trimEnd()));
|
|
@@ -4,8 +4,14 @@ type NodeShapedProcess = typeof process & {
|
|
|
4
4
|
binding(name: string): Record<string, unknown>;
|
|
5
5
|
};
|
|
6
6
|
export declare function nodeShapedProcess(base: typeof process): NodeShapedProcess;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
declare const silentStdio: {
|
|
8
|
+
stdin: () => null;
|
|
9
|
+
stdout: (_msg: string) => void;
|
|
10
|
+
stderr: (_msg: string) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function pyodideLoadOptions(): PyodideLoadOptions;
|
|
13
|
+
type PyodideLoadOptions = typeof silentStdio & {
|
|
14
|
+
packageCacheDir?: string;
|
|
15
|
+
};
|
|
10
16
|
export declare const defaultLoad: LoadPyodideFn;
|
|
11
17
|
export {};
|
|
@@ -74,11 +74,16 @@ async function shapeBareGlobals() {
|
|
|
74
74
|
g.performance = { now: () => Date.now() - origin, timeOrigin: origin };
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
const silentStdio = {
|
|
78
|
+
stdin: () => null,
|
|
79
|
+
stdout: (_msg) => { },
|
|
80
|
+
stderr: (_msg) => { }
|
|
81
|
+
};
|
|
77
82
|
export function pyodideLoadOptions() {
|
|
78
83
|
const cacheDir = process.env.QVAC_PYODIDE_CACHE_DIR;
|
|
79
84
|
if (!cacheDir)
|
|
80
|
-
return
|
|
81
|
-
return { packageCacheDir: cacheDir };
|
|
85
|
+
return { ...silentStdio };
|
|
86
|
+
return { ...silentStdio, packageCacheDir: cacheDir };
|
|
82
87
|
}
|
|
83
88
|
export const defaultLoad = async () => {
|
|
84
89
|
if (typeof globalThis.Bare !== 'undefined')
|
|
@@ -86,9 +91,7 @@ export const defaultLoad = async () => {
|
|
|
86
91
|
const moduleName = 'pyodide';
|
|
87
92
|
const { loadPyodide } = (await import(__rewriteRelativeImportExtension(moduleName)));
|
|
88
93
|
const options = pyodideLoadOptions();
|
|
89
|
-
if (options)
|
|
94
|
+
if (options.packageCacheDir)
|
|
90
95
|
seedPackageCache(options.packageCacheDir);
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
return (await loadPyodide());
|
|
96
|
+
return (await loadPyodide(options));
|
|
94
97
|
};
|
|
@@ -13,8 +13,12 @@ interface HRPCHandler {
|
|
|
13
13
|
interface HRPCBuilderLike {
|
|
14
14
|
handlers: ReadonlyArray<HRPCHandler>;
|
|
15
15
|
}
|
|
16
|
+
interface CallSiteOptions {
|
|
17
|
+
readonly optionalRequests?: ReadonlySet<string>;
|
|
18
|
+
readonly exportName?: string;
|
|
19
|
+
}
|
|
16
20
|
export declare function writeBind(builder: HRPCBuilderLike, outFile: string): void;
|
|
17
|
-
export declare function writeClient(builder: HRPCBuilderLike, outFile: string,
|
|
18
|
-
export declare function writeCapabilities(builder: HRPCBuilderLike, outFile: string): void;
|
|
21
|
+
export declare function writeClient(builder: HRPCBuilderLike, outFile: string, options?: CallSiteOptions | string): void;
|
|
22
|
+
export declare function writeCapabilities(builder: HRPCBuilderLike, outFile: string, options?: CallSiteOptions): void;
|
|
19
23
|
export declare function writeConfigTypes(outFile: string): void;
|
|
20
24
|
export {};
|
|
@@ -13,6 +13,7 @@ function camel(name, prefix = '') {
|
|
|
13
13
|
return prefix + cased.join('');
|
|
14
14
|
}
|
|
15
15
|
const HEADER = '// This file is autogenerated by schema/build.js\n// Do not edit manually.\n\n';
|
|
16
|
+
const DEFAULT_OPTIONAL_REQUESTS = new Set(['@assistant/empty']);
|
|
16
17
|
export function writeBind(builder, outFile) {
|
|
17
18
|
let s = `${HEADER}import { pipeline, Readable } from "streamx";\n\nfunction noop() {}\n\nexport function bindApi(rpc, api) {\n`;
|
|
18
19
|
for (const handler of builder.handlers) {
|
|
@@ -56,24 +57,28 @@ export function writeBind(builder, outFile) {
|
|
|
56
57
|
s += '}\n';
|
|
57
58
|
fs.writeFileSync(outFile, s);
|
|
58
59
|
}
|
|
59
|
-
export function writeClient(builder, outFile,
|
|
60
|
+
export function writeClient(builder, outFile, options = {}) {
|
|
61
|
+
const callSite = typeof options === 'string' ? { exportName: options } : options;
|
|
62
|
+
const { optionalRequests = DEFAULT_OPTIONAL_REQUESTS, exportName = 'engineClient' } = callSite;
|
|
60
63
|
let s = `${HEADER}function noop() {}\n\nfunction armed(stream) {\n stream.on("error", noop);\n return stream;\n}\n\nexport function ${exportName}(rpc) {\n return {\n`;
|
|
61
64
|
for (const handler of builder.handlers) {
|
|
62
65
|
const method = camel(handler.name);
|
|
66
|
+
const input = optionalRequests.has(handler.request?.name ?? '') ? 'input ?? {}' : 'input';
|
|
63
67
|
if (handler.request?.stream && handler.response?.stream) {
|
|
64
68
|
s += ` ${method}: () => armed(rpc.${method}()),\n`;
|
|
65
69
|
}
|
|
66
70
|
else if (handler.response?.stream) {
|
|
67
|
-
s += ` ${method}: (input) => armed(rpc.${method}(input)),\n`;
|
|
71
|
+
s += ` ${method}: (input) => armed(rpc.${method}(${input})),\n`;
|
|
68
72
|
}
|
|
69
73
|
else {
|
|
70
|
-
s += ` ${method}: (input) => rpc.${method}(input),\n`;
|
|
74
|
+
s += ` ${method}: (input) => rpc.${method}(${input}),\n`;
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
s += ' };\n}\n';
|
|
74
78
|
fs.writeFileSync(outFile, s);
|
|
75
79
|
}
|
|
76
|
-
export function writeCapabilities(builder, outFile) {
|
|
80
|
+
export function writeCapabilities(builder, outFile, options = {}) {
|
|
81
|
+
const { optionalRequests = DEFAULT_OPTIONAL_REQUESTS } = options;
|
|
77
82
|
let s = HEADER;
|
|
78
83
|
s += "import type * as T from './hyperschema/types.d.ts'\n\n";
|
|
79
84
|
s +=
|
|
@@ -112,7 +117,7 @@ export function writeCapabilities(builder, outFile) {
|
|
|
112
117
|
const method = camel(handler.name);
|
|
113
118
|
const req = `T.${tsName(handler.request.name)}`;
|
|
114
119
|
const res = `T.${tsName(handler.response.name)}`;
|
|
115
|
-
const input = handler.request.name
|
|
120
|
+
const input = optionalRequests.has(handler.request.name) ? `input?: ${req}` : `input: ${req}`;
|
|
116
121
|
if (handler.request?.stream && handler.response?.stream) {
|
|
117
122
|
client += ` ${method}(): UploadStream<${req}, ${res}>\n`;
|
|
118
123
|
server += ` ${method}(stream: AsyncIterable<${req}> | HandlerFrames<${req}>): AsyncIterable<${res}>\n`;
|