@qvac/harness 0.8.6 → 0.8.8

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 (61) hide show
  1. package/CHANGELOG.md +1298 -507
  2. package/dist-lib/index.d.ts +11 -0
  3. package/dist-lib/index.js +102 -25
  4. package/dist-lib/lib/capabilities/completion.d.ts +5 -4
  5. package/dist-lib/lib/capabilities/completion.js +132 -64
  6. package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
  7. package/dist-lib/lib/capabilities/tool-selection.js +2 -4
  8. package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
  9. package/dist-lib/lib/capabilities/tool-turns.js +0 -6
  10. package/dist-lib/lib/connect.js +3 -0
  11. package/dist-lib/lib/contract-types.d.ts +2 -0
  12. package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
  13. package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
  14. package/dist-lib/lib/models/context-choice.d.ts +15 -0
  15. package/dist-lib/lib/models/context-choice.js +55 -0
  16. package/dist-lib/lib/models/model-pool.d.ts +3 -1
  17. package/dist-lib/lib/models/model-pool.js +8 -11
  18. package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
  19. package/dist-lib/lib/models/sdk-faults.js +6 -0
  20. package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
  21. package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
  22. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
  23. package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
  24. package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
  25. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
  26. package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
  27. package/dist-lib/lib/serve.js +6 -1
  28. package/dist-lib/lib/spawn.js +4 -10
  29. package/dist-lib/lib/tools/exec.d.ts +4 -1
  30. package/dist-lib/lib/tools/exec.js +55 -11
  31. package/dist-lib/lib/tools/file-glob.js +1 -0
  32. package/dist-lib/lib/tools/grep.js +1 -0
  33. package/dist-lib/lib/tools/office/ops.js +1 -1
  34. package/dist-lib/lib/tools/office.js +29 -6
  35. package/dist-lib/lib/tools/read-files.js +1 -0
  36. package/dist-lib/lib/tools/skill.js +6 -4
  37. package/dist-lib/lib/tools/skills/catalog.d.ts +2 -0
  38. package/dist-lib/lib/tools/skills/catalog.js +4 -0
  39. package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
  40. package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
  41. package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
  42. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
  43. package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
  44. package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +2 -0
  45. package/dist-lib/lib/tools/skills/frontmatter-parser.js +4 -0
  46. package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
  47. package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
  48. package/dist-lib/lib/tools/skills/mention-name.d.ts +6 -0
  49. package/dist-lib/lib/tools/skills/mention-name.js +6 -0
  50. package/dist-lib/lib/tools/skills/mention.d.ts +2 -3
  51. package/dist-lib/lib/tools/skills/mention.js +5 -6
  52. package/dist-lib/lib/tools/weather-lookup.js +1 -0
  53. package/dist-lib/lib/tools/web-fetch.js +1 -0
  54. package/dist-lib/lib/tools/web-search.js +1 -0
  55. package/dist-lib/schema/harness.js +4 -2
  56. package/dist-lib/spec/hrpc/messages.js +18 -8
  57. package/dist-lib/spec/hyperschema/index.js +18 -8
  58. package/dist-lib/spec/hyperschema/schema.json +11 -1
  59. package/dist-lib/spec/hyperschema/types.d.ts +3 -1
  60. package/package.json +5 -7
  61. package/worker/harness-sidecar.mjs +2 -1
@@ -99,7 +99,9 @@ export interface CompletionContext {
99
99
  model: string;
100
100
  agent?: Agent;
101
101
  messages: Message[];
102
+ typedText?: string;
102
103
  cacheKey?: string;
104
+ session?: string;
103
105
  thinkingDefault?: string;
104
106
  toolConfigs?: Record<string, Record<string, unknown>>;
105
107
  generationParams?: LlmGenerationParams;
@@ -189,6 +191,7 @@ export declare class Harness extends ReadyResource {
189
191
  private _sizeLoads;
190
192
  private _capBytes;
191
193
  private readonly _reserved;
194
+ private readonly _contexts;
192
195
  private readonly _admission;
193
196
  private readonly _byHand;
194
197
  constructor({ tools, sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats }?: HarnessOptions);
@@ -290,14 +293,22 @@ export declare class Harness extends ReadyResource {
290
293
  private _curatedFit;
291
294
  private _admitByHand;
292
295
  private _replacedBy;
296
+ private _replacedEntries;
297
+ private _alone;
293
298
  private _sizeByHand;
294
299
  private _loadByHand;
295
300
  private _admit;
296
301
  private _makeRoomForGenerator;
297
302
  private _yieldGenerators;
298
303
  private _beforeLoad;
304
+ private _reserveByHand;
299
305
  private _reserve;
300
306
  private _sizeLoad;
307
+ private _chooseContext;
308
+ private _capRoom;
309
+ private _contextProfile;
310
+ private _trainedContext;
311
+ private _ctxSizeOf;
301
312
  private _makeRoom;
302
313
  private _capVictims;
303
314
  private _victimRank;
package/dist-lib/index.js CHANGED
@@ -28,6 +28,8 @@ import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js'
28
28
  import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictIdleWithin, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledEntries, settledModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
29
29
  import { memoryCapBytes, nextVictim, VictimRank } from './lib/models/memory-cap.js';
30
30
  import { defaultContextSize } from './lib/models/context.js';
31
+ import { ContextChoice } from './lib/models/context-choice.js';
32
+ import { parseGgufHeader, readGgufHeaderBytes } from './lib/models/gguf-header.js';
31
33
  import { resolveMainGpu } from './lib/models/gpu-selection.js';
32
34
  import { createIdleReaper } from './lib/models/idle-unload.js';
33
35
  import { openclCacheDir } from './lib/models/opencl-cache.js';
@@ -163,8 +165,9 @@ export class Harness extends ReadyResource {
163
165
  _sizeLoads = false;
164
166
  _capBytes = null;
165
167
  _reserved = new Map();
168
+ _contexts = new ContextChoice(() => this._contextProfile());
166
169
  _admission = new ScopeLock();
167
- _byHand = new Set();
170
+ _byHand = new Map();
168
171
  constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
169
172
  super();
170
173
  this._sdk = sdkLoader ?? createSdkLoader();
@@ -682,7 +685,7 @@ export class Harness extends ReadyResource {
682
685
  const key = modelKey(model, loadOpts);
683
686
  const pool = this._models;
684
687
  if (!isResident(pool, key)) {
685
- await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
688
+ await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
686
689
  }
687
690
  try {
688
691
  const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
@@ -721,6 +724,7 @@ export class Harness extends ReadyResource {
721
724
  deviceConfig: this._deviceConfig(),
722
725
  loads: this._loads,
723
726
  beforeLoad: (load) => this._beforeLoad(load),
727
+ chooseContext: (load, tier) => this._chooseContext(load, tier),
724
728
  hold: this._hold
725
729
  });
726
730
  }
@@ -1346,7 +1350,9 @@ export class Harness extends ReadyResource {
1346
1350
  }
1347
1351
  async _admitByHand(model, target) {
1348
1352
  if (this._capBytes === null) {
1349
- return target.pool !== this._models || this._fits(model, target.ctxSize);
1353
+ return (target.pool !== this._models ||
1354
+ this._alone(model, target) ||
1355
+ this._fits(model, target.ctxSize));
1350
1356
  }
1351
1357
  return this._admit(async () => {
1352
1358
  if (isResident(target.pool, target.key) || this._byHand.has(target.key)) {
@@ -1354,18 +1360,14 @@ export class Harness extends ReadyResource {
1354
1360
  }
1355
1361
  const sized = await this._sizeByHand(model, target);
1356
1362
  const capBytes = this._capBytes;
1357
- const settled = this._pools().flatMap((pool) => settledEntries(pool));
1358
- const replacedBy = this._replacedBy(model);
1359
- const replaced = target.pool === this._models
1360
- ? settledEntries(this._models).filter(({ key, refs }) => refs === 0 && replacedBy(key))
1361
- : [];
1363
+ const replaced = this._replacedEntries(model, target);
1362
1364
  const used = this._usedBytes() - replaced.reduce((sum, { bytes }) => sum + (bytes ?? 0), 0);
1363
- const alone = this._reserved.size === 0 && settled.length === replaced.length;
1365
+ const alone = this._alone(model, target);
1364
1366
  const fits = capBytes === null || alone || sized.bytes === undefined || used + sized.bytes <= capBytes;
1365
- if (sized.tooLarge || !fits) {
1367
+ if ((sized.tooLarge && !alone) || !fits) {
1366
1368
  return false;
1367
1369
  }
1368
- this._byHand.add(target.key);
1370
+ this._byHand.set(target.key, sized.ctxSize);
1369
1371
  if (sized.bytes !== undefined) {
1370
1372
  this._reserved.set(target.key, sized.bytes);
1371
1373
  }
@@ -1375,19 +1377,35 @@ export class Harness extends ReadyResource {
1375
1377
  _replacedBy(model) {
1376
1378
  return (key) => modelOf(key) === model && !this._replying.includes(key);
1377
1379
  }
1378
- _sizeByHand(model, target) {
1380
+ _replacedEntries(model, target) {
1381
+ const replacedBy = this._replacedBy(model);
1382
+ return target.pool === this._models
1383
+ ? settledEntries(this._models).filter(({ key, refs }) => refs === 0 && replacedBy(key))
1384
+ : [];
1385
+ }
1386
+ _alone(model, target) {
1387
+ const settled = this._pools().flatMap((pool) => settledEntries(pool));
1388
+ const loading = this._pools().some((pool) => loadingModels(pool).length > 0);
1389
+ return (!loading &&
1390
+ this._reserved.size === 0 &&
1391
+ settled.length === this._replacedEntries(model, target).length);
1392
+ }
1393
+ async _sizeByHand(model, target) {
1379
1394
  const addon = modelAddonOf(model);
1380
1395
  const modelSrc = curatedModelAsset(model, addon) ?? customModelSrc(model, addon) ?? model;
1381
- const contextTokens = target.ctxSize || defaultContextSize(modelSrc, customContextLength(model));
1382
- return this._sizeLoad({
1396
+ const ctxSize = target.pool === this._models
1397
+ ? target.ctxSize || defaultContextSize(modelSrc, customContextLength(model))
1398
+ : undefined;
1399
+ const sized = await this._sizeLoad({
1383
1400
  key: target.key,
1384
1401
  model,
1385
1402
  modelSrc,
1386
1403
  modelType: target.modelType,
1387
- modelConfig: target.pool === this._models
1388
- ? { ctx_size: contextTokens, projectionModelSrc: this._projectionFor(model) }
1389
- : undefined
1404
+ modelConfig: ctxSize === undefined
1405
+ ? undefined
1406
+ : { ctx_size: ctxSize, projectionModelSrc: this._projectionFor(model) }
1390
1407
  });
1408
+ return { ...sized, ctxSize };
1391
1409
  }
1392
1410
  async _loadByHand(target, signal) {
1393
1411
  const loading = target.open().load();
@@ -1429,11 +1447,22 @@ export class Harness extends ReadyResource {
1429
1447
  async _beforeLoad(load) {
1430
1448
  if (!this._sizeLoads)
1431
1449
  return undefined;
1432
- const bytes = this._byHand.delete(load.key)
1433
- ? this._reserved.get(load.key)
1450
+ const bytes = this._byHand.has(load.key)
1451
+ ? await this._admit(() => this._reserveByHand(load))
1434
1452
  : await this._admit(() => this._reserve(load));
1435
1453
  return { bytes, release: () => this._reserved.delete(load.key) };
1436
1454
  }
1455
+ async _reserveByHand(load) {
1456
+ const admittedAt = this._byHand.get(load.key);
1457
+ this._byHand.delete(load.key);
1458
+ if (load.modelConfig?.['ctx_size'] !== admittedAt) {
1459
+ const { bytes } = await this._sizeLoad(load);
1460
+ if (bytes !== undefined) {
1461
+ this._reserved.set(load.key, bytes);
1462
+ }
1463
+ }
1464
+ return this._reserved.get(load.key);
1465
+ }
1437
1466
  async _reserve(load) {
1438
1467
  const sized = await this._sizeLoad(load);
1439
1468
  if (this._capBytes !== null) {
@@ -1461,17 +1490,60 @@ export class Harness extends ReadyResource {
1461
1490
  const bytes = fit?.estimate?.upperBoundBytes ?? fit?.floorBytes ?? downloaded;
1462
1491
  return {
1463
1492
  bytes: bytes === undefined ? undefined : bytes + projection,
1464
- tooLarge: fit?.verdict === 'likely-too-large'
1493
+ tooLarge: fit?.verdict === 'likely-too-large',
1494
+ confirmed: fit?.verdict === 'likely-fits' &&
1495
+ (fit.evidence === 'calibration' || fit.evidence === 'native-fit')
1465
1496
  };
1466
1497
  }
1467
1498
  catch (err) {
1468
1499
  hlog('fit estimate failed', { model, err: errorMessage(err) });
1469
1500
  return {
1470
1501
  bytes: downloaded === undefined ? undefined : downloaded + projection,
1471
- tooLarge: false
1502
+ tooLarge: false,
1503
+ confirmed: false
1472
1504
  };
1473
1505
  }
1474
1506
  }
1507
+ async _chooseContext(load, tier) {
1508
+ if (isTranslationModel(load.model)) {
1509
+ return tier;
1510
+ }
1511
+ const ctxSize = await this._contexts.choose(load.key, tier, {
1512
+ trained: () => this._trainedContext(load),
1513
+ fits: async (rung) => {
1514
+ const sized = await this._sizeLoad({
1515
+ ...load,
1516
+ modelConfig: { ...load.modelConfig, ctx_size: rung }
1517
+ });
1518
+ const room = this._capRoom(load.key);
1519
+ return sized.confirmed && (room === null || (sized.bytes ?? Infinity) <= room);
1520
+ }
1521
+ });
1522
+ hlog('context chosen', { model: load.model, tier, ctxSize });
1523
+ return ctxSize;
1524
+ }
1525
+ _capRoom(key) {
1526
+ const cap = this._capBytes;
1527
+ return cap === null ? null : cap - (this._usedBytes() - (this._reserved.get(key) ?? 0));
1528
+ }
1529
+ async _contextProfile() {
1530
+ const { capabilityProfile, memoryBudget } = await this.capability();
1531
+ return memoryBudget ? capabilityProfile : undefined;
1532
+ }
1533
+ async _trainedContext({ model, modelSrc }) {
1534
+ const custom = customContextLength(model);
1535
+ if (custom || typeof modelSrc === 'string') {
1536
+ return custom;
1537
+ }
1538
+ const sdk = await this._sdk.load();
1539
+ const info = await sdk.getModelInfo({ name: modelSrc.name }).catch(() => null);
1540
+ const file = info?.cacheFiles[0]?.path;
1541
+ const bytes = file ? await readGgufHeaderBytes(file) : null;
1542
+ return (bytes && parseGgufHeader(bytes)?.contextLength) || undefined;
1543
+ }
1544
+ _ctxSizeOf(key, loadOpts) {
1545
+ return loadOpts.ctxSize || this._contexts.chosen(key);
1546
+ }
1475
1547
  async _makeRoom(need, room) {
1476
1548
  const capBytes = this._capBytes;
1477
1549
  if (capBytes === null) {
@@ -1563,7 +1635,7 @@ export class Harness extends ReadyResource {
1563
1635
  pool: this._models,
1564
1636
  key,
1565
1637
  modelType: 'llamacpp-completion',
1566
- ctxSize: loadOpts.ctxSize,
1638
+ ctxSize: this._ctxSizeOf(key, loadOpts),
1567
1639
  open: () => this._models.open(key, model, loadOpts)
1568
1640
  };
1569
1641
  }
@@ -1625,7 +1697,7 @@ export class Harness extends ReadyResource {
1625
1697
  completion(context) {
1626
1698
  return this._busyStream(() => this._completion(context));
1627
1699
  }
1628
- async *_completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
1700
+ async *_completion({ model, agent, messages, typedText, cacheKey, session, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
1629
1701
  if (!this.opened)
1630
1702
  await this.ready();
1631
1703
  if (this.suspended)
@@ -1637,12 +1709,15 @@ export class Harness extends ReadyResource {
1637
1709
  const resident = isResident(pool, key);
1638
1710
  hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
1639
1711
  if (!resident) {
1640
- await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
1712
+ await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
1641
1713
  await this._yieldGenerators();
1642
1714
  }
1643
1715
  const acquireRound = async () => {
1644
1716
  if (!isResident(pool, key)) {
1645
- await this._makeRoomForCompletion(model, { ctxSize: loadOpts.ctxSize, signal });
1717
+ await this._makeRoomForCompletion(model, {
1718
+ ctxSize: this._ctxSizeOf(key, loadOpts),
1719
+ signal
1720
+ });
1646
1721
  await this._yieldGenerators();
1647
1722
  }
1648
1723
  try {
@@ -1690,11 +1765,13 @@ export class Harness extends ReadyResource {
1690
1765
  agent,
1691
1766
  ctxSize,
1692
1767
  cacheKey,
1768
+ session,
1693
1769
  thinkingDefault,
1694
1770
  generationParams,
1695
1771
  responseFormat,
1696
1772
  captureInferenceCalls,
1697
1773
  messages: turns,
1774
+ typedText,
1698
1775
  tools: this._tools,
1699
1776
  skillCatalog: await this._completionSkills(agent?.id ?? null, model),
1700
1777
  toolConfigs,
@@ -125,6 +125,7 @@ export interface ToolExecuteContext {
125
125
  skills?: readonly string[] | undefined;
126
126
  skillsLoadedThisTurn?: readonly string[] | undefined;
127
127
  ctxSize?: number | undefined;
128
+ session?: string | undefined;
128
129
  image?: ImageSettings | undefined;
129
130
  settings?: Record<string, unknown> | undefined;
130
131
  turnImages?: {
@@ -184,6 +185,7 @@ export interface ToolDefinition {
184
185
  toolset: Toolset | null;
185
186
  validate?: (args: Record<string, unknown>, ctx?: ToolExecuteContext) => string | null;
186
187
  effect: (args: Record<string, unknown>, ctx?: ToolExecuteContext) => ToolEffect;
188
+ stableRead?: true;
187
189
  resource?: (args: Record<string, unknown>, ctx?: ToolExecuteContext) => ResourceDescriptor | null;
188
190
  compiled?: true;
189
191
  execute: (args: Record<string, unknown>, ctx?: ToolExecuteContext) => Promise<unknown>;
@@ -296,9 +298,6 @@ export interface CompletionSdk {
296
298
  cancel?(params: {
297
299
  requestId: string;
298
300
  }): Promise<void>;
299
- deleteCache?(params: {
300
- kvCacheKey: string;
301
- }): Promise<unknown>;
302
301
  }
303
302
  export interface RoundModelLease {
304
303
  modelId: string;
@@ -311,10 +310,12 @@ interface CompletionArgs {
311
310
  agent?: Agent;
312
311
  ctxSize?: number;
313
312
  cacheKey?: string;
313
+ session?: string;
314
314
  thinkingDefault?: string;
315
315
  generationParams?: LlmGenerationParams;
316
316
  responseFormat?: ResponseFormat;
317
317
  messages: Message[];
318
+ typedText?: string;
318
319
  tools?: ToolDefinition[];
319
320
  skillCatalog?: readonly CompletionSkillEntry[];
320
321
  approve?: ApproveFn;
@@ -335,5 +336,5 @@ export declare function toSdkTool(tool: ToolDefinition): SdkTool;
335
336
  export type CompletionSkillEntry = SkillCatalogEntry & {
336
337
  readonly status?: SkillState;
337
338
  };
338
- export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
339
+ export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, session, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, typedText, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
339
340
  export {};