@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.
Files changed (35) hide show
  1. package/.claude/skills/asana/SKILL.md +86 -0
  2. package/.claude/skills/gh-stack/SKILL.md +181 -0
  3. package/.claude/skills/gh-stack/references/commands.md +177 -0
  4. package/.claude/skills/gh-stack/references/stack-design.md +97 -0
  5. package/.claude/skills/gh-stack/references/troubleshooting.md +159 -0
  6. package/.claude/skills/js-house-style/SKILL.md +650 -0
  7. package/.claude/skills/kvcache/SKILL.md +171 -0
  8. package/.claude/skills/pr-create/SKILL.md +137 -0
  9. package/.claude/skills/pr-review/SKILL.md +148 -0
  10. package/CLAUDE.md +36 -0
  11. package/dist/harness.bundle +23 -14
  12. package/dist-lib/index.d.ts +6 -1
  13. package/dist-lib/index.js +27 -9
  14. package/dist-lib/lib/hardware/capability-probe.d.ts +1 -1
  15. package/dist-lib/lib/hardware/capability-probe.js +1 -1
  16. package/dist-lib/lib/hardware/gpu-probe-entry.d.ts +1 -0
  17. package/dist-lib/lib/hardware/gpu-probe-entry.js +2 -0
  18. package/dist-lib/lib/hardware/gpu-probe.d.ts +8 -0
  19. package/dist-lib/lib/hardware/gpu-probe.js +54 -0
  20. package/dist-lib/lib/hardware/gpu-stats.bare.d.ts +1 -1
  21. package/dist-lib/lib/hardware/gpu-stats.bare.js +1 -24
  22. package/dist-lib/lib/hardware/gpu-stats.native.cjs +27 -0
  23. package/dist-lib/lib/hardware/gpu-stats.native.d.cts +1 -0
  24. package/dist-lib/lib/models/model-pool.d.ts +1 -1
  25. package/dist-lib/lib/models/model-pool.js +3 -2
  26. package/dist-lib/lib/spawn.d.ts +3 -0
  27. package/dist-lib/lib/spawn.js +5 -3
  28. package/dist-lib/lib/tools/exec/adapters/python/loader.d.ts +9 -3
  29. package/dist-lib/lib/tools/exec/adapters/python/loader.js +9 -6
  30. package/dist-lib/schema/generate.d.ts +6 -2
  31. package/dist-lib/schema/generate.js +10 -5
  32. package/docs/kvcache-reference.md +655 -0
  33. package/package.json +1 -1
  34. package/worker/harness-sidecar.mjs +8 -3
  35. package/worker/stow-entry.mjs +3 -19
@@ -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 { readSystemStats } from '#hw-gpu-stats';
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, 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',
@@ -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 evictAllWithin(this._models, DIFFUSION_EVICT_WAIT_MS, undefined, signal);
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 evictAllWithin(this._models, MUSIC_EVICT_WAIT_MS, undefined, signal);
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: () => readSystemStats()
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 evictIdle(pool);
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 evictIdle(pool);
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 evictIdle(pool);
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,2 @@
1
+ import { runGpuProbeIfRequested } from "./gpu-probe.js";
2
+ runGpuProbeIfRequested();
@@ -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 declare function readSystemStats(): unknown;
1
+ export { readSystemStats } from './gpu-stats.native.cjs';
@@ -1,24 +1 @@
1
- import os from 'os';
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 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,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;
@@ -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
- export declare function pyodideLoadOptions(): {
8
- packageCacheDir: string;
9
- } | undefined;
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 undefined;
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
- return (await loadPyodide(options));
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, 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`;