@telora/daemon 0.18.61 → 0.18.67

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 (52) hide show
  1. package/build-info.json +2 -2
  2. package/dist/config.d.ts +16 -3
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +69 -13
  5. package/dist/config.js.map +1 -1
  6. package/dist/discovery-poll.d.ts.map +1 -1
  7. package/dist/discovery-poll.js +14 -2
  8. package/dist/discovery-poll.js.map +1 -1
  9. package/dist/focus-executor.d.ts +26 -3
  10. package/dist/focus-executor.d.ts.map +1 -1
  11. package/dist/focus-executor.js +79 -0
  12. package/dist/focus-executor.js.map +1 -1
  13. package/dist/git-fetch.d.ts +123 -0
  14. package/dist/git-fetch.d.ts.map +1 -0
  15. package/dist/git-fetch.js +164 -0
  16. package/dist/git-fetch.js.map +1 -0
  17. package/dist/git-state-detector.d.ts.map +1 -1
  18. package/dist/git-state-detector.js +63 -26
  19. package/dist/git-state-detector.js.map +1 -1
  20. package/dist/host-control/capability.d.ts +31 -0
  21. package/dist/host-control/capability.d.ts.map +1 -0
  22. package/dist/host-control/capability.js +50 -0
  23. package/dist/host-control/capability.js.map +1 -0
  24. package/dist/host-control/catalog.d.ts +48 -0
  25. package/dist/host-control/catalog.d.ts.map +1 -0
  26. package/dist/host-control/catalog.js +149 -0
  27. package/dist/host-control/catalog.js.map +1 -0
  28. package/dist/host-control/client.d.ts +76 -0
  29. package/dist/host-control/client.d.ts.map +1 -0
  30. package/dist/host-control/client.js +203 -0
  31. package/dist/host-control/client.js.map +1 -0
  32. package/dist/host-control/index.d.ts +18 -0
  33. package/dist/host-control/index.d.ts.map +1 -0
  34. package/dist/host-control/index.js +14 -0
  35. package/dist/host-control/index.js.map +1 -0
  36. package/dist/host-control/load.d.ts +136 -0
  37. package/dist/host-control/load.d.ts.map +1 -0
  38. package/dist/host-control/load.js +241 -0
  39. package/dist/host-control/load.js.map +1 -0
  40. package/dist/host-control/sizing.d.ts +207 -0
  41. package/dist/host-control/sizing.d.ts.map +1 -0
  42. package/dist/host-control/sizing.js +464 -0
  43. package/dist/host-control/sizing.js.map +1 -0
  44. package/dist/host-control/types.d.ts +174 -0
  45. package/dist/host-control/types.d.ts.map +1 -0
  46. package/dist/host-control/types.js +16 -0
  47. package/dist/host-control/types.js.map +1 -0
  48. package/dist/prd-controller.d.ts +10 -12
  49. package/dist/prd-controller.d.ts.map +1 -1
  50. package/dist/prd-controller.js +24 -54
  51. package/dist/prd-controller.js.map +1 -1
  52. package/package.json +3 -3
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Hardware-aware context sizing for the host-control plane (Local model host
3
+ * control, D3).
4
+ *
5
+ * The problem this closes: `max_seq_length` (the load-time context window) was a
6
+ * MANUAL, hardware-blind input. Set it too SMALL and codex's turn-1 prompt does
7
+ * not fit -- the load silently fails turn-1 with an instant `response.failed`
8
+ * (live: a 4096-ctx load failed; a manual reload to >=16k fixed it). Set it too
9
+ * LARGE and the KV-cache (which scales with context) OOMs a near-full GPU (live:
10
+ * a 12GB card sat ~93% used with a model resident). The safe value is bounded
11
+ * BELOW by codex's ~8k turn-1 floor and ABOVE by remaining VRAM headroom.
12
+ *
13
+ * This module adds two things:
14
+ * 1. defensive client reads over the host's hardware + kv-cache endpoints
15
+ * (`getHardware`, `getKvCacheEstimate`) -- the live JSON shapes are NOT
16
+ * pinned, so the normalizers tolerate reasonable field-name variants and
17
+ * are flagged for runbook reconciliation;
18
+ * 2. a PURE `computeSafeMaxSeqLength` (no HTTP) -- the heart: pick the largest
19
+ * context that clears the codex floor without exceeding VRAM headroom,
20
+ * reaching for optional KV-cache quantization when it lets a context fit.
21
+ *
22
+ * The hardware/kv-cache endpoints live under the host ROOT (NOT `/v1`), so we
23
+ * derive the root with `hostRootFromBaseUrl` (strips a trailing `/v1`). All
24
+ * errors are address-free (TEL-5): the load path treats any read failure as
25
+ * "no sizing data" and falls back to the fixed floor default, never breaking a
26
+ * swap that previously worked.
27
+ */
28
+ import { hostGet, hostRootFromBaseUrl } from './client.js';
29
+ import { DEFAULT_MAX_SEQ_LENGTH } from './load.js';
30
+ /**
31
+ * The control-surface path (under the host ROOT, not `/v1`) for GPU hardware
32
+ * info: GPU name + total/used VRAM. Reconcile field names per the runbook.
33
+ */
34
+ export const HOST_CONTROL_HARDWARE_PATH = '/api/system/hardware';
35
+ /**
36
+ * The control-surface path (under the host ROOT, not `/v1`) for a KV-cache fit
37
+ * prediction at a candidate context. Reconcile field names per the runbook.
38
+ */
39
+ export const HOST_CONTROL_KV_CACHE_PATH = '/api/models/kv-cache-estimate';
40
+ /**
41
+ * Codex's turn-1 context floor. A load BELOW this silently fails the very first
42
+ * codex turn (instant `response.failed`) -- so we treat it as a HARD lower
43
+ * bound and never return a `maxSeqLength` under it, even when VRAM headroom is
44
+ * tight (we signal quant-required/marginal instead of silently going lower).
45
+ *
46
+ * The live datapoint: a 4096-ctx load failed turn-1; a reload to >=16384
47
+ * worked. The exact floor is ~8k; 8192 is the documented conservative pin.
48
+ */
49
+ export const CODEX_TURN1_FLOOR = 8192;
50
+ /**
51
+ * VRAM safety margin (MB) held back from raw headroom before sizing the KV
52
+ * cache. A near-full GPU (live: 12GB card ~93% used with a model resident)
53
+ * leaves little slack; this guards against the estimate being slightly optimistic,
54
+ * driver/allocator overhead, and fragmentation. Conservative on purpose --
55
+ * an OOM costs a whole swap, a slightly-smaller context costs nothing.
56
+ */
57
+ export const VRAM_SAFETY_MARGIN_MB = 512;
58
+ /**
59
+ * Per-token KV-cache cost heuristic (MB per 1 token of context) used as the
60
+ * FALLBACK growth model when the host's kv-cache-estimate endpoint is not
61
+ * consulted (the pure path takes only static inputs). This is intentionally a
62
+ * coarse upper-ish estimate for a ~7-8B class model at fp16 KV; the actual
63
+ * footprint is model-architecture-specific. The host's `getKvCacheEstimate`
64
+ * endpoint, when reachable, gives a far better per-context number and SHOULD be
65
+ * preferred by the orchestrator; this constant only bounds the pure computation
66
+ * when no live estimate is supplied.
67
+ *
68
+ * Derivation sketch (documented so the runbook can refine it): KV bytes ~=
69
+ * 2 (K and V) * layers * 2 (kv heads grouping varies) * head_dim * bytes/elem.
70
+ * For a 7B-class model at fp16 this lands on the order of ~0.12-0.20 MB/token;
71
+ * we pin a conservative 0.16 MB/token. Quantized KV (int8/fp8) roughly halves
72
+ * this -- see `KV_QUANT_FOOTPRINT_FACTOR`.
73
+ */
74
+ export const KV_CACHE_MB_PER_TOKEN = 0.16;
75
+ /**
76
+ * Footprint multiplier when KV-cache quantization is requested. int8/fp8 KV
77
+ * roughly halves the per-token cost vs fp16. Conservative 0.5 -- a real host
78
+ * may do better; the kv-cache-estimate endpoint, when consulted, supersedes
79
+ * this heuristic.
80
+ */
81
+ export const KV_QUANT_FOOTPRINT_FACTOR = 0.5;
82
+ /**
83
+ * The KV-cache dtype token sent as `cache_type_kv` on the load wire when the
84
+ * sizer elects to quantize to fit a context. The exact accepted token is
85
+ * host-specific; `q8_0` is the common llama.cpp-family int8 KV token. Flag for
86
+ * the runbook to confirm against the live host's accepted values.
87
+ */
88
+ export const KV_QUANT_CACHE_TYPE = 'q8_0';
89
+ /**
90
+ * Hard ceiling on the context we will ever request, independent of headroom,
91
+ * when no native-context bound is known. A sane upper clamp so a wildly large
92
+ * estimate cannot produce an absurd request. Native context (when known) clamps
93
+ * tighter than this.
94
+ */
95
+ export const MAX_SEQ_LENGTH_CEILING = 131072;
96
+ // ── Defensive normalizers (live shapes NOT pinned -- runbook reconciles) ──────
97
+ /** Read the first finite, non-negative number-valued key present; null if none. */
98
+ function firstNumberOrNull(obj, keys) {
99
+ for (const key of keys) {
100
+ const v = obj[key];
101
+ if (typeof v === 'number' && Number.isFinite(v) && v >= 0)
102
+ return v;
103
+ // Some hosts stringify numbers; accept a clean numeric string defensively.
104
+ if (typeof v === 'string') {
105
+ const n = Number(v.trim());
106
+ if (Number.isFinite(n) && n >= 0)
107
+ return n;
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ /** Read the first non-blank string-valued key present, trimmed; null if none. */
113
+ function firstStringOrNull(obj, keys) {
114
+ for (const key of keys) {
115
+ const v = obj[key];
116
+ if (typeof v === 'string') {
117
+ const trimmed = v.trim();
118
+ if (trimmed)
119
+ return trimmed;
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+ /** Candidate keys for the GPU product name on a hardware payload. */
125
+ const HW_GPU_NAME_KEYS = ['gpu_name', 'gpuName', 'name', 'gpu', 'device_name', 'product_name'];
126
+ /**
127
+ * Candidate keys for TOTAL VRAM in MB. We tolerate both MB-named and bytes-named
128
+ * fields; a bytes field is converted in `normalizeHardware`. The MB keys win
129
+ * when present (no conversion ambiguity).
130
+ */
131
+ const HW_VRAM_TOTAL_MB_KEYS = ['vram_total_mb', 'vramTotalMb', 'memory_total_mb', 'total_memory_mb'];
132
+ const HW_VRAM_TOTAL_BYTES_KEYS = ['vram_total_bytes', 'vram_total', 'memory_total', 'total_memory_bytes'];
133
+ /** Candidate keys for USED VRAM in MB / bytes (same MB-preferred convention). */
134
+ const HW_VRAM_USED_MB_KEYS = ['vram_used_mb', 'vramUsedMb', 'memory_used_mb', 'used_memory_mb'];
135
+ const HW_VRAM_USED_BYTES_KEYS = ['vram_used_bytes', 'vram_used', 'memory_used', 'used_memory_bytes'];
136
+ /** Bytes -> MB (binary MiB), rounded down so we never overstate available memory. */
137
+ function bytesToMb(bytes) {
138
+ return Math.floor(bytes / (1024 * 1024));
139
+ }
140
+ /**
141
+ * Resolve a VRAM quantity in MB from a payload, preferring an MB-named field and
142
+ * falling back to a bytes-named field (converted). Some hosts nest VRAM under a
143
+ * `gpu`/`gpus[0]`/`memory` sub-object; we also probe a single nested object.
144
+ * Returns null when nothing usable is present.
145
+ */
146
+ function vramMb(obj, mbKeys, bytesKeys) {
147
+ const mb = firstNumberOrNull(obj, mbKeys);
148
+ if (mb !== null)
149
+ return Math.floor(mb);
150
+ const bytes = firstNumberOrNull(obj, bytesKeys);
151
+ if (bytes !== null)
152
+ return bytesToMb(bytes);
153
+ return null;
154
+ }
155
+ /**
156
+ * Probe for a nested hardware sub-object so a payload like
157
+ * `{ gpu: { name, vram_total_mb, ... } }` or `{ gpus: [ {...} ] }` still yields
158
+ * fields. Returns the merged top-level + nested view (top-level wins).
159
+ */
160
+ function flattenHardwarePayload(obj) {
161
+ const nestedKeys = ['gpu', 'memory', 'device'];
162
+ let merged = { ...obj };
163
+ for (const key of nestedKeys) {
164
+ const v = obj[key];
165
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
166
+ merged = { ...v, ...merged };
167
+ }
168
+ }
169
+ // A `gpus: [ {...} ]` array -- take the first entry as the active device.
170
+ const gpus = obj['gpus'];
171
+ if (Array.isArray(gpus) && gpus.length > 0 && gpus[0] && typeof gpus[0] === 'object') {
172
+ merged = { ...gpus[0], ...merged };
173
+ }
174
+ return merged;
175
+ }
176
+ /**
177
+ * Normalize a `/api/system/hardware` payload into a typed `HostHardware`.
178
+ * DEFENSIVE: the live shape is NOT pinned. Tolerates MB- or bytes-named VRAM
179
+ * fields, a flat or a `{ gpu: {...} }` / `{ gpus: [...] }` nesting, and missing
180
+ * fields (-> null). Pure + exported for unit testing.
181
+ *
182
+ * Runbook flag: confirm the live `/api/system/hardware` field names + units
183
+ * (MB vs bytes) and tighten `HW_*_KEYS` once observed.
184
+ */
185
+ export function normalizeHardware(payload) {
186
+ if (!payload || typeof payload !== 'object') {
187
+ return { gpuName: null, vramTotalMb: null, vramUsedMb: null };
188
+ }
189
+ const obj = flattenHardwarePayload(payload);
190
+ return {
191
+ gpuName: firstStringOrNull(obj, HW_GPU_NAME_KEYS),
192
+ vramTotalMb: vramMb(obj, HW_VRAM_TOTAL_MB_KEYS, HW_VRAM_TOTAL_BYTES_KEYS),
193
+ vramUsedMb: vramMb(obj, HW_VRAM_USED_MB_KEYS, HW_VRAM_USED_BYTES_KEYS),
194
+ };
195
+ }
196
+ /** Candidate keys for the predicted KV-cache footprint (MB) on an estimate payload. */
197
+ const KV_FOOTPRINT_MB_KEYS = ['kv_cache_mb', 'kvCacheMb', 'footprint_mb', 'estimate_mb', 'mb'];
198
+ const KV_FOOTPRINT_BYTES_KEYS = ['kv_cache_bytes', 'footprint_bytes', 'bytes'];
199
+ /** Candidate keys for a boolean "does it fit" verdict on an estimate payload. */
200
+ const KV_FITS_KEYS = ['fits', 'ok', 'will_fit'];
201
+ /**
202
+ * Normalize a `/api/models/kv-cache-estimate` payload into a typed
203
+ * `KvCacheEstimate`. DEFENSIVE: footprint under MB- or bytes-named fields, an
204
+ * optional boolean fit verdict, both null/undefined when absent. Pure + exported.
205
+ *
206
+ * Runbook flag: confirm the live estimate field names + whether the endpoint
207
+ * echoes the candidate context; tighten `KV_*_KEYS` once observed.
208
+ */
209
+ export function normalizeKvCacheEstimate(payload) {
210
+ if (!payload || typeof payload !== 'object') {
211
+ return { footprintMb: null, fits: null };
212
+ }
213
+ const obj = payload;
214
+ let footprintMb = firstNumberOrNull(obj, KV_FOOTPRINT_MB_KEYS);
215
+ if (footprintMb === null) {
216
+ const bytes = firstNumberOrNull(obj, KV_FOOTPRINT_BYTES_KEYS);
217
+ if (bytes !== null)
218
+ footprintMb = bytesToMb(bytes);
219
+ }
220
+ let fits = null;
221
+ for (const key of KV_FITS_KEYS) {
222
+ const v = obj[key];
223
+ if (typeof v === 'boolean') {
224
+ fits = v;
225
+ break;
226
+ }
227
+ }
228
+ return { footprintMb, fits };
229
+ }
230
+ // ── Client reads ──────────────────────────────────────────────────────────────
231
+ /**
232
+ * Read the host's GPU + VRAM info (`GET <root>/api/system/hardware`), normalized
233
+ * to a typed `HostHardware`. The hardware endpoint lives under the host ROOT,
234
+ * NOT `/v1`, so we derive the root from the stored baseUrl. Throws
235
+ * `HostControlError` (address-free) on any transport failure -- the caller
236
+ * fail-safes to the floor default.
237
+ */
238
+ export async function getHardware(baseUrl, apiKey) {
239
+ const root = hostRootFromBaseUrl(baseUrl);
240
+ const payload = await hostGet(`${root}${HOST_CONTROL_HARDWARE_PATH}`, { apiKey });
241
+ return normalizeHardware(payload);
242
+ }
243
+ /**
244
+ * Read a KV-cache fit prediction for a candidate context
245
+ * (`GET <root>/api/models/kv-cache-estimate`). The candidate context (and an
246
+ * optional model path + kv quant) are passed as query params; the live host's
247
+ * exact param names are NOT pinned, so we send the common-sense set and the
248
+ * normalizer is defensive about the response. Throws `HostControlError` on any
249
+ * transport failure -- the caller treats it as "no estimate" and uses the pure
250
+ * heuristic instead.
251
+ *
252
+ * Runbook flag: confirm the live query-param names (`max_seq_length`,
253
+ * `model_path`, `cache_type_kv`) the endpoint expects.
254
+ */
255
+ export async function getKvCacheEstimate(baseUrl, params, apiKey) {
256
+ const root = hostRootFromBaseUrl(baseUrl);
257
+ const qs = new URLSearchParams();
258
+ // snake_case ONLY at the wire boundary (query string).
259
+ qs.set('max_seq_length', String(params.maxSeqLength));
260
+ if (params.modelPath)
261
+ qs.set('model_path', params.modelPath);
262
+ if (params.cacheTypeKv)
263
+ qs.set('cache_type_kv', params.cacheTypeKv);
264
+ const payload = await hostGet(`${root}${HOST_CONTROL_KV_CACHE_PATH}?${qs.toString()}`, { apiKey });
265
+ return normalizeKvCacheEstimate(payload);
266
+ }
267
+ // ── The pure heart: computeSafeMaxSeqLength ──────────────────────────────────
268
+ /**
269
+ * Round a context length down to a power-of-two-ish, clean step. Hosts size KV
270
+ * buffers in chunks; a clean step avoids requesting an awkward non-aligned
271
+ * context. We step in 1024-token units (a context is always a multiple of 1024)
272
+ * and never round a value below the floor up-and-over -- callers clamp the floor
273
+ * separately. Returns at least 0.
274
+ */
275
+ function roundDownToStep(value, step = 1024) {
276
+ if (value <= 0)
277
+ return 0;
278
+ return Math.floor(value / step) * step;
279
+ }
280
+ /**
281
+ * The per-token KV-cache cost for the chosen quant mode (fp16 vs quantized).
282
+ */
283
+ function perTokenCost(kvQuant) {
284
+ return kvQuant ? KV_CACHE_MB_PER_TOKEN * KV_QUANT_FOOTPRINT_FACTOR : KV_CACHE_MB_PER_TOKEN;
285
+ }
286
+ /**
287
+ * The largest context (rounded to the step) whose KV cache fits within
288
+ * `availableMb` at the given per-token cost, clamped to `nativeCap`. May return
289
+ * a value BELOW the floor -- the caller decides what to do with that.
290
+ */
291
+ function largestFittingContext(availableMb, kvQuant, nativeCap) {
292
+ if (availableMb <= 0)
293
+ return 0;
294
+ const rawTokens = availableMb / perTokenCost(kvQuant);
295
+ const stepped = roundDownToStep(rawTokens);
296
+ return Math.min(stepped, nativeCap);
297
+ }
298
+ /**
299
+ * PURE: compute the safe `max_seq_length` (and whether to quantize the KV cache)
300
+ * for a load, given VRAM totals/usage, the resident model size, and the model's
301
+ * native context. NO HTTP -- the orchestrator gathers the inputs and (optionally)
302
+ * validates the result against the live kv-cache endpoint.
303
+ *
304
+ * The arithmetic, with the live constraints in mind:
305
+ *
306
+ * - headroom = vramTotal - vramUsed - VRAM_SAFETY_MARGIN_MB. This is the VRAM
307
+ * available for the KV cache AFTER the model weights are resident. (Live: a
308
+ * 12GB card sat ~93% used with a model loaded -- headroom is small, so the
309
+ * KV cache, which scales with context, is the binding constraint.) When
310
+ * `vramUsed` already accounts for the weights, `modelSizeMb` is informational;
311
+ * when it does not, we subtract `modelSizeMb` too (see below).
312
+ *
313
+ * - The KV cache grows ~linearly with context (`KV_CACHE_MB_PER_TOKEN`). We pick
314
+ * the LARGEST context whose KV cache fits headroom, clamped to the model's
315
+ * native context (`nativeContext`) and the global ceiling.
316
+ *
317
+ * - HARD LOWER BOUND (codex floor): a context below `CODEX_TURN1_FLOOR` silently
318
+ * fails turn-1, so we NEVER return below it. If headroom can't fit the floor
319
+ * at fp16 KV, we try quantization (`cacheTypeKv`), which ~halves the footprint.
320
+ * If even quantized KV can't fit the floor, we return the floor ANYWAY with
321
+ * `quantRequired`/`marginal` set -- going below is not an option; better to
322
+ * attempt the floor (and let the load fail loudly / the operator add VRAM)
323
+ * than to silently load a too-small context that fails turn-1 with no signal.
324
+ *
325
+ * - When inputs are missing/degenerate (no VRAM data, zero/negative headroom),
326
+ * we degrade SAFELY to the floor default -- never throw, never go below floor.
327
+ */
328
+ export function computeSafeMaxSeqLength(input) {
329
+ const floor = input.codexFloor ?? CODEX_TURN1_FLOOR;
330
+ // Native context bounds everything; when unknown, fall back to the global
331
+ // ceiling so headroom (not an unknown native cap) is the binding constraint.
332
+ const nativeCap = typeof input.nativeContext === 'number' && input.nativeContext >= floor
333
+ ? input.nativeContext
334
+ : MAX_SEQ_LENGTH_CEILING;
335
+ // Degenerate VRAM inputs -> we cannot size against headroom. Return the floor
336
+ // (clamped to native) and signal marginal so the orchestrator logs it. NEVER
337
+ // go below the floor; NEVER throw.
338
+ const vramTotal = input.vramTotalMb;
339
+ const vramUsed = input.vramUsedMb;
340
+ if (vramTotal === null ||
341
+ vramTotal === undefined ||
342
+ vramUsed === null ||
343
+ vramUsed === undefined ||
344
+ !Number.isFinite(vramTotal) ||
345
+ !Number.isFinite(vramUsed)) {
346
+ return { maxSeqLength: Math.min(floor, nativeCap === floor ? floor : Math.max(floor, nativeCap)), marginal: true };
347
+ }
348
+ // Headroom = total - used - safety margin. If the host's `vramUsed` does NOT
349
+ // already include the candidate model's weights (i.e. nothing resident yet),
350
+ // also subtract the model size so we size the KV cache against memory left
351
+ // AFTER the weights load. We can't know which the host reports, so we take the
352
+ // conservative interpretation: subtract modelSize only when it would not already
353
+ // be reflected -- i.e. when used is small relative to the model size, treat the
354
+ // model as not-yet-resident and reserve its weights. This keeps us safe on a
355
+ // cold GPU (subtract weights) and on a warm GPU (used already high -> no double
356
+ // subtract because we cap headroom at >= 0).
357
+ const modelSize = typeof input.modelSizeMb === 'number' && input.modelSizeMb > 0 ? input.modelSizeMb : 0;
358
+ const modelNotYetResident = modelSize > 0 && vramUsed < modelSize;
359
+ const reservedForWeights = modelNotYetResident ? modelSize : 0;
360
+ const headroom = vramTotal - vramUsed - VRAM_SAFETY_MARGIN_MB - reservedForWeights;
361
+ const requestedQuant = input.kvQuant === true;
362
+ // First try fp16 KV (no quant) unless the caller pre-requested quant.
363
+ const fp16Fit = largestFittingContext(headroom, false, nativeCap);
364
+ if (!requestedQuant && fp16Fit >= floor) {
365
+ // Comfortable: the largest fp16 context that fits clears the floor.
366
+ return { maxSeqLength: fp16Fit };
367
+ }
368
+ // fp16 can't clear the floor (or quant was requested) -> try quantized KV,
369
+ // which ~halves the per-token cost and so fits a larger context.
370
+ const quantFit = largestFittingContext(headroom, true, nativeCap);
371
+ if (quantFit >= floor) {
372
+ // Quantization buys us the floor (or more). Take the larger of the two paths
373
+ // that clears the floor; if fp16 already cleared it (requestedQuant case),
374
+ // honor the requested quant but never drop below what fp16 would have given.
375
+ const best = Math.max(quantFit, requestedQuant && fp16Fit >= floor ? fp16Fit : 0);
376
+ return { maxSeqLength: Math.min(best, nativeCap), cacheTypeKv: KV_QUANT_CACHE_TYPE };
377
+ }
378
+ // Even quantized KV can't fit the floor in the available headroom. Do NOT go
379
+ // below the floor (that silently fails turn-1). Return the floor clamped to
380
+ // native, flag quant-required + marginal so the load path logs the risk; the
381
+ // load may fail loudly / the operator must free VRAM. Better a loud floor
382
+ // attempt than a silent sub-floor failure.
383
+ return {
384
+ maxSeqLength: Math.min(floor, nativeCap),
385
+ cacheTypeKv: KV_QUANT_CACHE_TYPE,
386
+ quantRequired: true,
387
+ marginal: true,
388
+ };
389
+ }
390
+ /**
391
+ * Resolve the safe `EnsureLoadedOptions` sizing bits (`maxSeqLength` + optional
392
+ * `cacheTypeKv`) for a load by reading the host's hardware, running the PURE
393
+ * `computeSafeMaxSeqLength`, and OPTIONALLY validating the result against the
394
+ * live kv-cache-estimate endpoint. This is the seam the focus-executor calls in
395
+ * place of D2's fixed `DEFAULT_MAX_SEQ_LENGTH`.
396
+ *
397
+ * FAIL-SAFE: if the hardware endpoint is missing / unreachable / returns no VRAM
398
+ * totals, we DO NOT compute against headroom -- we return the fixed
399
+ * `DEFAULT_MAX_SEQ_LENGTH` (>= the codex floor) exactly as D2 did, and report the
400
+ * fallback so the caller logs it. A sizing failure must NEVER break a swap that
401
+ * previously worked with the fixed default.
402
+ *
403
+ * The kv-cache-estimate validation is best-effort: if it predicts the computed
404
+ * context will NOT fit (and we are not already quantizing), we re-run the sizer
405
+ * forcing quantization; if the endpoint is unreachable we keep the pure result.
406
+ * The estimate is never allowed to push the context BELOW the floor.
407
+ *
408
+ * Returns `{ maxSeqLength, cacheTypeKv?, source, marginal?, quantRequired? }`.
409
+ * `source` is `'computed'` when hardware drove the value, `'fallback'` when we
410
+ * fell back to the fixed default -- the caller logs which path it took.
411
+ */
412
+ export async function resolveSafeLoadOptions(baseUrl, modelPath, hints = {}, apiKey) {
413
+ let hardware;
414
+ try {
415
+ hardware = await getHardware(baseUrl, apiKey);
416
+ }
417
+ catch {
418
+ // Hardware endpoint missing / unreachable -> fail-safe to the fixed default.
419
+ // Address-free: the rich error was already categorized inside the client; we
420
+ // do not surface or rethrow it -- a sizing read failure is non-fatal.
421
+ return { maxSeqLength: DEFAULT_MAX_SEQ_LENGTH, source: 'fallback' };
422
+ }
423
+ // No usable VRAM totals -> we cannot size against headroom. Fail-safe to the
424
+ // fixed default (which clears the floor), rather than guessing.
425
+ if (hardware.vramTotalMb === null) {
426
+ return { maxSeqLength: DEFAULT_MAX_SEQ_LENGTH, source: 'fallback' };
427
+ }
428
+ const computed = computeSafeMaxSeqLength({
429
+ vramTotalMb: hardware.vramTotalMb,
430
+ vramUsedMb: hardware.vramUsedMb,
431
+ modelSizeMb: hints.modelSizeMb ?? null,
432
+ nativeContext: hints.nativeContext ?? null,
433
+ });
434
+ // Best-effort validation against the live kv-cache estimate. If the host says
435
+ // the computed context will NOT fit and we are not already quantizing, re-run
436
+ // the sizer forcing quantization. A failed estimate read is ignored (keep the
437
+ // pure result). The estimate can never push us below the floor.
438
+ let result = computed;
439
+ if (!computed.cacheTypeKv) {
440
+ try {
441
+ const estimate = await getKvCacheEstimate(baseUrl, { maxSeqLength: computed.maxSeqLength, modelPath: modelPath ?? undefined }, apiKey);
442
+ if (estimate.fits === false) {
443
+ result = computeSafeMaxSeqLength({
444
+ vramTotalMb: hardware.vramTotalMb,
445
+ vramUsedMb: hardware.vramUsedMb,
446
+ modelSizeMb: hints.modelSizeMb ?? null,
447
+ nativeContext: hints.nativeContext ?? null,
448
+ kvQuant: true,
449
+ });
450
+ }
451
+ }
452
+ catch {
453
+ // Estimate endpoint missing / unreachable -> keep the pure computed result.
454
+ }
455
+ }
456
+ return {
457
+ maxSeqLength: result.maxSeqLength,
458
+ ...(result.cacheTypeKv !== undefined ? { cacheTypeKv: result.cacheTypeKv } : {}),
459
+ source: 'computed',
460
+ ...(result.marginal !== undefined ? { marginal: result.marginal } : {}),
461
+ ...(result.quantRequired !== undefined ? { quantRequired: result.quantRequired } : {}),
462
+ };
463
+ }
464
+ //# sourceMappingURL=sizing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sizing.js","sourceRoot":"","sources":["../../src/host-control/sizing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Shared types for the host-control plane (Local model host control, D1).
3
+ *
4
+ * The host-control plane sits ABOVE the OpenAI-compatible `/v1/models` floor: a
5
+ * controllable model host (e.g. Unsloth Studio) exposes its FULL downloaded
6
+ * catalog -- including models that are downloaded-but-not-loaded -- under a
7
+ * control surface (`/api/models/local`). Detection is per-endpoint and
8
+ * fail-safe: a plain OpenAI-compatible endpoint that lacks the control surface
9
+ * degrades cleanly to the load-then-discover (`/v1/models`) behavior.
10
+ *
11
+ * Delivery 1 covers detection + catalog read. Later deliveries (`load.ts`,
12
+ * `sizing.ts`) add load/unload + sizing on top of these types; keep this module
13
+ * additive-friendly.
14
+ */
15
+ /**
16
+ * One loadable model from a controllable host's catalog. `id` is the served
17
+ * model identifier (the value the picklist offers); `modelPath` is the host's
18
+ * own load path (`model_path`) captured so a later delivery can resolve
19
+ * id -> path to load the model. Optional richer fields (size, family) are added
20
+ * by later deliveries -- keep this interface open to extension.
21
+ */
22
+ export interface LoadableModel {
23
+ /** Served model id -- the value offered in the add-model picklist. */
24
+ id: string;
25
+ /**
26
+ * The host's load path for this model (`model_path` on the wire). Null when
27
+ * the catalog entry did not carry one (a degraded/floor entry, or a host that
28
+ * omits the field). Delivery 2 consumes id -> modelPath to load.
29
+ */
30
+ modelPath: string | null;
31
+ }
32
+ /**
33
+ * The result of reading an endpoint's loadable-model catalog. `controllable`
34
+ * records whether the host-control surface answered (true) or we fell back to
35
+ * the `/v1/models` floor (false). `ids` is the flat string[] for the picklist;
36
+ * `models` carries the richer entries for downstream load resolution. On the
37
+ * floor path `models` mirrors `ids` with `modelPath: null`.
38
+ */
39
+ export interface LoadableCatalog {
40
+ /** True when the controllable host catalog answered; false on the floor path. */
41
+ controllable: boolean;
42
+ /** De-duplicated served model ids -- the picklist payload. */
43
+ ids: string[];
44
+ /** Richer per-model entries (id + modelPath) for downstream load resolution. */
45
+ models: LoadableModel[];
46
+ }
47
+ /**
48
+ * The outcome of a cheap, fail-safe capability probe against an endpoint's host
49
+ * root. `controllable: true` means the control surface answered a well-formed
50
+ * catalog; anything else (error / non-2xx / parse failure / timeout) yields
51
+ * `false` so the caller falls back to the `/v1/models` floor.
52
+ */
53
+ export interface HostCapability {
54
+ controllable: boolean;
55
+ }
56
+ /**
57
+ * Normalized `/v1/status` snapshot (Local model host control, D2). The host's
58
+ * live status shape is NOT pinned, so the normalizer is defensive; this is the
59
+ * typed result it yields. `loadedModelId` is the currently-resident served id
60
+ * (null when nothing is loaded); `ready` is whether that model is up + serving.
61
+ */
62
+ export interface HostStatus {
63
+ /** Currently-loaded served model id, or null when nothing is resident. */
64
+ loadedModelId: string | null;
65
+ /** True when the loaded model is up and ready to serve. */
66
+ ready: boolean;
67
+ }
68
+ /**
69
+ * Parameters for a `POST /v1/load` (Local model host control, D2). camelCase app
70
+ * types here; the load client maps them to the host's snake_case wire field
71
+ * names (`model_path`, `max_seq_length`, `cache_type_kv`, `gpu_ids`) at the
72
+ * boundary. `maxSeqLength` is REQUIRED + injected so Delivery 3 can pass a
73
+ * computed safe value rather than the fixed `DEFAULT_MAX_SEQ_LENGTH` floor.
74
+ */
75
+ export interface LoadModelParams {
76
+ /** The host's load path for the target model (`model_path` on the wire). */
77
+ modelPath: string;
78
+ /** Context window to load with (`max_seq_length` on the wire). */
79
+ maxSeqLength: number;
80
+ /** Optional KV cache dtype (`cache_type_kv` on the wire). */
81
+ cacheTypeKv?: string;
82
+ /** Optional GPU id selection (`gpu_ids` on the wire). */
83
+ gpuIds?: number[];
84
+ }
85
+ /**
86
+ * The action a single-GPU swap must take to make a target model resident, given
87
+ * the current resident model (Local model host control, D2). One model fits the
88
+ * GPU at a time, so a different resident must be evicted before the load.
89
+ */
90
+ export type SwapAction = 'noop' | 'load' | 'evict_then_load';
91
+ /** The pure swap decision -- the action `ensureLoaded` will perform. */
92
+ export interface SwapDecision {
93
+ action: SwapAction;
94
+ }
95
+ /**
96
+ * Normalized `/api/system/hardware` snapshot (Local model host control, D3). The
97
+ * host's live shape is NOT pinned, so the normalizer is defensive; this is the
98
+ * typed result it yields. VRAM is carried in MB (the normalizer converts a
99
+ * bytes-named source). Any field the host omits is null -- the sizer degrades
100
+ * safely to the floor default when VRAM totals are unavailable.
101
+ */
102
+ export interface HostHardware {
103
+ /** GPU product name (e.g. "NVIDIA GeForce RTX 3060"), or null when absent. */
104
+ gpuName: string | null;
105
+ /** Total VRAM in MB, or null when the host did not report it. */
106
+ vramTotalMb: number | null;
107
+ /** Currently-used VRAM in MB, or null when the host did not report it. */
108
+ vramUsedMb: number | null;
109
+ }
110
+ /**
111
+ * Query params for a `GET /api/models/kv-cache-estimate` (Local model host
112
+ * control, D3). camelCase here; the client maps them to the host's snake_case
113
+ * query-string field names (`max_seq_length`, `model_path`, `cache_type_kv`) at
114
+ * the boundary. The candidate context is required; model path + kv quant are
115
+ * optional refiners.
116
+ */
117
+ export interface KvCacheEstimateParams {
118
+ /** Candidate context to estimate the KV-cache footprint for. */
119
+ maxSeqLength: number;
120
+ /** Optional model path so the host can size against the specific model. */
121
+ modelPath?: string;
122
+ /** Optional KV-cache dtype (e.g. quantized) to estimate the smaller footprint. */
123
+ cacheTypeKv?: string;
124
+ }
125
+ /**
126
+ * Normalized `/api/models/kv-cache-estimate` result (Local model host control,
127
+ * D3). DEFENSIVE: the live shape is NOT pinned. `footprintMb` is the predicted
128
+ * KV-cache memory (MB) for the candidate context (null when not reported);
129
+ * `fits` is an optional host verdict on whether it fits available VRAM.
130
+ */
131
+ export interface KvCacheEstimate {
132
+ /** Predicted KV-cache footprint in MB for the candidate context, or null. */
133
+ footprintMb: number | null;
134
+ /** Optional host verdict on whether the candidate context fits, or null. */
135
+ fits: boolean | null;
136
+ }
137
+ /**
138
+ * Input to the PURE `computeSafeMaxSeqLength` (Local model host control, D3). All
139
+ * static -- gathered by the orchestrator from the hardware endpoint + catalog so
140
+ * the core sizing arithmetic is unit-testable with NO HTTP. Unknown numeric
141
+ * inputs (null/undefined) degrade safely to the floor default.
142
+ */
143
+ export interface SafeMaxSeqInput {
144
+ /** Total VRAM in MB (null/undefined -> degrade to floor). */
145
+ vramTotalMb: number | null | undefined;
146
+ /** Currently-used VRAM in MB (null/undefined -> degrade to floor). */
147
+ vramUsedMb: number | null | undefined;
148
+ /** The candidate model's on-disk/weights size in MB, when known. */
149
+ modelSizeMb?: number | null;
150
+ /** The model's native context window (clamps the result), when known. */
151
+ nativeContext?: number | null;
152
+ /** Pre-request KV quantization (forces the quantized footprint path). */
153
+ kvQuant?: boolean;
154
+ /** Override the codex turn-1 floor (defaults to `CODEX_TURN1_FLOOR`). */
155
+ codexFloor?: number;
156
+ }
157
+ /**
158
+ * Output of `computeSafeMaxSeqLength` (Local model host control, D3). The chosen
159
+ * context (never below the codex floor) and, when quantization was elected to
160
+ * fit a context, the `cacheTypeKv` to pass on the load. `quantRequired` +
161
+ * `marginal` are advisory signals for the load path to log -- they do NOT change
162
+ * the returned `maxSeqLength`, which is always at least the floor.
163
+ */
164
+ export interface SafeMaxSeqResult {
165
+ /** The safe context to load with (always >= the codex floor, <= native cap). */
166
+ maxSeqLength: number;
167
+ /** KV-cache dtype to quantize with, set only when quantization was elected. */
168
+ cacheTypeKv?: string;
169
+ /** True when even quantized KV could not fit the floor -- a tight, risky load. */
170
+ quantRequired?: boolean;
171
+ /** True when the result rests at the floor under tight/unknown headroom. */
172
+ marginal?: boolean;
173
+ }
174
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/host-control/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,gFAAgF;IAChF,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,2DAA2D;IAC3D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,iBAAiB,CAAC;AAE7D,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,iEAAiE;IACjE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB"}