@qvac/harness 0.2.5 → 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.
@@ -272,14 +272,6 @@
272
272
  "win32-arm64",
273
273
  "win32-x64"
274
274
  ],
275
- "bare-tty/node_modules/bare-signals": [
276
- "darwin-arm64",
277
- "darwin-x64",
278
- "linux-arm64",
279
- "linux-x64",
280
- "win32-arm64",
281
- "win32-x64"
282
- ],
283
275
  "bare-tty": [
284
276
  "darwin-arm64",
285
277
  "darwin-x64",
@@ -82,6 +82,7 @@ export interface HarnessOptions {
82
82
  transcriptionDevice?: 'cpu' | 'gpu';
83
83
  credentialStore?: CredentialStoreFn;
84
84
  platform?: string;
85
+ completionModelCap?: number;
85
86
  }
86
87
  export interface CompletionContext {
87
88
  model: string;
@@ -133,6 +134,7 @@ export declare class Harness extends ReadyResource {
133
134
  private readonly _kvCacheV?;
134
135
  private readonly _transcriptionDevice?;
135
136
  private readonly _gpuStats?;
137
+ private readonly _completionModelCap;
136
138
  private _allSkills;
137
139
  private _credentialStore?;
138
140
  private _credentials;
@@ -145,7 +147,7 @@ export declare class Harness extends ReadyResource {
145
147
  private _capability;
146
148
  private _capabilityInflight;
147
149
  private readonly _modelFaults;
148
- constructor({ tools, sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, credentialStore, platform, gpuStats }?: 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);
149
151
  _open(): Promise<void>;
150
152
  get suspended(): boolean;
151
153
  get skillNames(): string[];
@@ -238,6 +240,7 @@ export declare class Harness extends ReadyResource {
238
240
  signal?: BareAbortSignal;
239
241
  }): Promise<void>;
240
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;
241
244
  private _evictWedgedModel;
242
245
  private _evictAfterLeases;
243
246
  }
package/dist-lib/index.js CHANGED
@@ -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, evictAllWithin, evictIdle, evictModelWithin, isResident, leaseCount, loadedModels, modelKey, releaseModel, resolveLoadOptions, progressState } from "./lib/models/model-pool.js";
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',
@@ -104,6 +107,7 @@ export class Harness extends ReadyResource {
104
107
  _kvCacheV;
105
108
  _transcriptionDevice;
106
109
  _gpuStats;
110
+ _completionModelCap;
107
111
  _allSkills;
108
112
  _credentialStore;
109
113
  _credentials = new Map();
@@ -116,7 +120,7 @@ export class Harness extends ReadyResource {
116
120
  _capability = null;
117
121
  _capabilityInflight = null;
118
122
  _modelFaults = new Map();
119
- constructor({ tools = [], sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, credentialStore, platform, gpuStats } = {}) {
123
+ constructor({ tools = [], sdkLoader, skillsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
120
124
  super();
121
125
  this._sdk = sdkLoader ?? createSdkLoader();
122
126
  this._llmDevice = llmDevice;
@@ -126,6 +130,7 @@ export class Harness extends ReadyResource {
126
130
  this._kvCacheK = kvCacheK;
127
131
  this._kvCacheV = kvCacheV;
128
132
  this._transcriptionDevice = transcriptionDevice;
133
+ this._completionModelCap = completionModelCap ?? DEFAULT_COMPLETION_MODEL_CAP;
129
134
  const embedConfig = createEmbedConfig({
130
135
  device: embedDevice,
131
136
  gpuLayers: embedGpuLayers,
@@ -168,7 +173,7 @@ export class Harness extends ReadyResource {
168
173
  onBeforeLoad: async (signal) => {
169
174
  if (this._models.destroyed)
170
175
  return;
171
- const evicted = await evictAllWithin(this._models, DIFFUSION_EVICT_WAIT_MS, undefined, signal);
176
+ const evicted = await awaitModelCapacity(this._models, 1, DIFFUSION_EVICT_WAIT_MS, undefined, signal);
172
177
  if (signal?.aborted)
173
178
  throw new AbortedError();
174
179
  if (!evicted) {
@@ -187,7 +192,7 @@ export class Harness extends ReadyResource {
187
192
  onBeforeLoad: async (signal) => {
188
193
  if (this._models.destroyed)
189
194
  return;
190
- const evicted = await evictAllWithin(this._models, MUSIC_EVICT_WAIT_MS, undefined, signal);
195
+ const evicted = await awaitModelCapacity(this._models, 1, MUSIC_EVICT_WAIT_MS, undefined, signal);
191
196
  if (signal?.aborted)
192
197
  throw new AbortedError();
193
198
  if (!evicted) {
@@ -927,7 +932,8 @@ export class Harness extends ReadyResource {
927
932
  const pool = this._models;
928
933
  hlog('warm model', { model, resident: isResident(pool, key) });
929
934
  if (!isResident(pool, key)) {
930
- await evictIdle(pool);
935
+ if (!(await this._makeRoomForCompletion(signal, WARM_ROOM_WAIT_MS)))
936
+ return;
931
937
  await this._imageRuntime?.unloadIdle().catch(noop);
932
938
  }
933
939
  try {
@@ -953,13 +959,13 @@ export class Harness extends ReadyResource {
953
959
  const resident = isResident(pool, key);
954
960
  hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
955
961
  if (!resident) {
956
- await evictIdle(pool);
962
+ await this._makeRoomForCompletion(signal);
957
963
  await this._imageRuntime?.unloadIdle().catch(noop);
958
964
  await this._musicRuntime?.unloadIdle().catch(noop);
959
965
  }
960
966
  const acquireRound = async () => {
961
967
  if (!isResident(pool, key)) {
962
- await evictIdle(pool);
968
+ await this._makeRoomForCompletion(signal);
963
969
  await this._imageRuntime?.unloadIdle().catch(noop);
964
970
  await this._musicRuntime?.unloadIdle().catch(noop);
965
971
  }
@@ -1052,6 +1058,16 @@ export class Harness extends ReadyResource {
1052
1058
  void this._musicRuntime?.unloadIdle().catch(noop);
1053
1059
  }
1054
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
+ }
1055
1071
  async _evictWedgedModel(model, key) {
1056
1072
  const faults = (this._modelFaults.get(model) ?? 0) + 1;
1057
1073
  this._modelFaults.set(model, faults);
@@ -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 evictAllWithin(pool: PoolLike, deadlineMs: number, tickMs?: number, signal?: {
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 evictAllWithin(pool, deadlineMs, tickMs = 250, signal) {
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 (loadedModels(pool).length > 0) {
118
+ while (!roomed()) {
118
119
  if (done || signal?.aborted)
119
120
  return false;
120
121
  await new Promise((resolve) => setTimeout(resolve, tickMs));
@@ -20,6 +20,7 @@ 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;
@@ -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, exportName?: string): void;
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, exportName = 'engineClient') {
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 === '@assistant/empty' ? `input?: ${req}` : `input: ${req}`;
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`;