@qvac/harness 0.8.5 → 0.8.7

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 (88) hide show
  1. package/CHANGELOG.md +1278 -472
  2. package/README.md +49 -0
  3. package/dist-lib/index.d.ts +42 -2
  4. package/dist-lib/index.js +447 -58
  5. package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
  6. package/dist-lib/lib/audio-transcode-bare.js +22 -25
  7. package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
  8. package/dist-lib/lib/audio-transcode-node.js +1 -3
  9. package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
  10. package/dist-lib/lib/capabilities/audio-resample.js +7 -8
  11. package/dist-lib/lib/capabilities/completion.d.ts +6 -3
  12. package/dist-lib/lib/capabilities/completion.js +96 -49
  13. package/dist-lib/lib/capabilities/download.d.ts +3 -1
  14. package/dist-lib/lib/capabilities/download.js +1 -1
  15. package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
  16. package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
  17. package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
  18. package/dist-lib/lib/capabilities/tool-selection.js +2 -4
  19. package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
  20. package/dist-lib/lib/capabilities/tool-turns.js +0 -6
  21. package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
  22. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  23. package/dist-lib/lib/capabilities/voice.js +94 -26
  24. package/dist-lib/lib/connect.d.ts +4 -1
  25. package/dist-lib/lib/connect.js +16 -1
  26. package/dist-lib/lib/contract-types.d.ts +12 -0
  27. package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
  28. package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
  29. package/dist-lib/lib/models/context-choice.d.ts +15 -0
  30. package/dist-lib/lib/models/context-choice.js +55 -0
  31. package/dist-lib/lib/models/download-gate.d.ts +20 -0
  32. package/dist-lib/lib/models/download-gate.js +73 -0
  33. package/dist-lib/lib/models/memory-cap.d.ts +13 -0
  34. package/dist-lib/lib/models/memory-cap.js +21 -0
  35. package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
  36. package/dist-lib/lib/models/model-catalog-constants.js +9 -1
  37. package/dist-lib/lib/models/model-catalog.d.ts +6 -2
  38. package/dist-lib/lib/models/model-catalog.js +35 -3
  39. package/dist-lib/lib/models/model-handle.d.ts +8 -1
  40. package/dist-lib/lib/models/model-handle.js +20 -2
  41. package/dist-lib/lib/models/model-list.d.ts +8 -1
  42. package/dist-lib/lib/models/model-list.js +23 -0
  43. package/dist-lib/lib/models/model-pool.d.ts +35 -5
  44. package/dist-lib/lib/models/model-pool.js +97 -32
  45. package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
  46. package/dist-lib/lib/models/sdk-faults.js +6 -0
  47. package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
  48. package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
  49. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
  50. package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
  51. package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
  52. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
  53. package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
  54. package/dist-lib/lib/serve.js +23 -3
  55. package/dist-lib/lib/spawn.js +4 -10
  56. package/dist-lib/lib/tools/exec.d.ts +4 -1
  57. package/dist-lib/lib/tools/exec.js +55 -11
  58. package/dist-lib/lib/tools/generate-image.d.ts +4 -2
  59. package/dist-lib/lib/tools/generate-image.js +5 -2
  60. package/dist-lib/lib/tools/generate-music.d.ts +4 -2
  61. package/dist-lib/lib/tools/generate-music.js +5 -2
  62. package/dist-lib/lib/tools/office/ops.js +1 -1
  63. package/dist-lib/lib/tools/office.js +29 -6
  64. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  65. package/dist-lib/lib/tools/skills/catalog.js +2 -0
  66. package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
  67. package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
  68. package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
  69. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
  70. package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
  71. package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
  72. package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
  73. package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
  74. package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
  75. package/dist-lib/lib/tools/skills/mention.js +0 -3
  76. package/dist-lib/lib/tools/translate.d.ts +3 -1
  77. package/dist-lib/lib/tools/translate.js +6 -1
  78. package/dist-lib/schema/harness.js +43 -4
  79. package/dist-lib/spec/bind.js +9 -0
  80. package/dist-lib/spec/calls.js +3 -0
  81. package/dist-lib/spec/hrpc/hrpc.json +39 -0
  82. package/dist-lib/spec/hrpc/index.js +39 -3
  83. package/dist-lib/spec/hrpc/messages.js +101 -13
  84. package/dist-lib/spec/hyperschema/index.js +101 -13
  85. package/dist-lib/spec/hyperschema/schema.json +62 -1
  86. package/dist-lib/spec/hyperschema/types.d.ts +20 -2
  87. package/package.json +8 -10
  88. package/worker/harness-sidecar.mjs +2 -1
@@ -2,6 +2,7 @@ import b4a from 'b4a';
2
2
  import { errorMessage } from '../guards.js';
3
3
  import { AbortedError } from '../helpers/race-abort.js';
4
4
  import { hlog } from '../log.js';
5
+ import { buildWavBuffer } from '../wav-encode.js';
5
6
  import { normalizeForSpeech } from './speech-text.js';
6
7
  export const SUPERTONIC_SAMPLE_RATE = 44100;
7
8
  export function createVoiceLane() {
@@ -14,31 +15,19 @@ export function createVoiceLane() {
14
15
  }
15
16
  const TRANSCRIBE_SAMPLE_RATE = 16000;
16
17
  const TRANSCRIBE_CHUNK_BYTES = TRANSCRIBE_SAMPLE_RATE * 2;
18
+ const TRANSCRIBE_STREAM_MIN_BYTES = TRANSCRIBE_CHUNK_BYTES * 60;
19
+ const TRANSCRIBE_SEGMENT_SECONDS = 300;
17
20
  const PARAKEET_STREAMING = { emitPartials: false, chunkMs: 5000 };
18
21
  const TRANSCRIBE_WITH_METADATA = false;
19
22
  const TRANSCRIBE_PACE_MS = 40;
20
23
  function pace(ms) {
21
24
  return new Promise((resolve) => setTimeout(resolve, ms));
22
25
  }
23
- const TRANSCRIBE_DRAIN_IDLE_MS = 3000;
24
- const TRANSCRIBE_DRAIN_MAX_MS = 60_000;
25
- async function writePaced(session, data, signal) {
26
- for (let offset = 0; offset < data.length; offset += TRANSCRIBE_CHUNK_BYTES) {
26
+ async function writePaced(session, pcm, signal) {
27
+ for await (const second of pcm) {
27
28
  if (signal?.aborted)
28
29
  return;
29
- session.write(data.subarray(offset, offset + TRANSCRIBE_CHUNK_BYTES));
30
- await pace(TRANSCRIBE_PACE_MS);
31
- }
32
- }
33
- async function drainUntilIdle(lastPieceAt, streamClosed, signal) {
34
- const deadline = Date.now() + TRANSCRIBE_DRAIN_MAX_MS;
35
- for (;;) {
36
- if (signal?.aborted || streamClosed() || Date.now() >= deadline)
37
- return;
38
- const last = lastPieceAt();
39
- if (last > 0 && Date.now() - last >= TRANSCRIBE_DRAIN_IDLE_MS) {
40
- return;
41
- }
30
+ session.write(second);
42
31
  await pace(TRANSCRIBE_PACE_MS);
43
32
  }
44
33
  }
@@ -59,7 +48,93 @@ function transcribePiece(piece) {
59
48
  }
60
49
  return null;
61
50
  }
62
- export async function transcribeStreaming(sdk, pcm, { modelId, signal }) {
51
+ async function* inSeconds(pcm) {
52
+ let pending = [];
53
+ let size = 0;
54
+ for await (const chunk of pcm) {
55
+ pending.push(chunk);
56
+ size += chunk.length;
57
+ if (size < TRANSCRIBE_CHUNK_BYTES) {
58
+ continue;
59
+ }
60
+ const joined = pending.length === 1 ? chunk : b4a.concat(pending);
61
+ let offset = 0;
62
+ for (; joined.length - offset >= TRANSCRIBE_CHUNK_BYTES; offset += TRANSCRIBE_CHUNK_BYTES) {
63
+ yield joined.subarray(offset, offset + TRANSCRIBE_CHUNK_BYTES);
64
+ }
65
+ size = joined.length - offset;
66
+ pending = size > 0 ? [joined.subarray(offset)] : [];
67
+ }
68
+ if (size > 0) {
69
+ yield b4a.concat(pending);
70
+ }
71
+ }
72
+ async function readHead(seconds, signal) {
73
+ const head = { seconds: [], bytes: 0 };
74
+ try {
75
+ while (head.bytes < TRANSCRIBE_STREAM_MIN_BYTES && !signal?.aborted) {
76
+ const next = await seconds.next();
77
+ if (next.done) {
78
+ break;
79
+ }
80
+ head.seconds.push(next.value);
81
+ head.bytes += next.value.length;
82
+ }
83
+ }
84
+ catch (err) {
85
+ hlog('audio decode/resample failed', { error: errorMessage(err) });
86
+ return null;
87
+ }
88
+ return head;
89
+ }
90
+ async function* resume(head, rest) {
91
+ yield* head.seconds;
92
+ yield* { [Symbol.asyncIterator]: () => rest };
93
+ }
94
+ export async function transcribeAudio(sdk, pcm, { modelId, signal, audio }) {
95
+ const seconds = inSeconds(pcm);
96
+ const head = await readHead(seconds, signal);
97
+ if (signal?.aborted) {
98
+ await seconds.return(undefined);
99
+ throw new AbortedError();
100
+ }
101
+ if (!head || head.bytes === 0) {
102
+ return transcribeOnce(sdk, modelId, audio);
103
+ }
104
+ if (head.bytes < TRANSCRIBE_STREAM_MIN_BYTES) {
105
+ return transcribeOnce(sdk, modelId, buildWavBuffer(b4a.concat(head.seconds)));
106
+ }
107
+ try {
108
+ return await transcribeStreaming(sdk, resume(head, seconds), { modelId, signal });
109
+ }
110
+ finally {
111
+ await seconds.return(undefined);
112
+ }
113
+ }
114
+ async function* segment(first, rest) {
115
+ yield first;
116
+ for (let n = 1; n < TRANSCRIBE_SEGMENT_SECONDS; n++) {
117
+ const next = await rest.next();
118
+ if (next.done) {
119
+ return;
120
+ }
121
+ yield next.value;
122
+ }
123
+ }
124
+ export async function transcribeStreaming(sdk, pcm, opts) {
125
+ const seconds = pcm[Symbol.asyncIterator]();
126
+ const parts = [];
127
+ try {
128
+ for (let first = await seconds.next(); !first.done; first = await seconds.next()) {
129
+ parts.push(await transcribeSession(sdk, segment(first.value, seconds), opts));
130
+ }
131
+ }
132
+ finally {
133
+ await seconds.return?.();
134
+ }
135
+ return parts.filter(Boolean).join(' ');
136
+ }
137
+ async function transcribeSession(sdk, pcm, { modelId, signal }) {
63
138
  const session = await sdk.transcribeStream({
64
139
  modelId,
65
140
  metadata: TRANSCRIBE_WITH_METADATA,
@@ -67,27 +142,20 @@ export async function transcribeStreaming(sdk, pcm, { modelId, signal }) {
67
142
  });
68
143
  const onAbort = () => session.destroy();
69
144
  signal?.addEventListener('abort', onAbort, { once: true });
70
- let lastPieceAt = 0;
71
- let streamClosed = false;
72
145
  const collect = (async () => {
73
146
  let out = '';
74
147
  for await (const piece of session) {
75
- lastPieceAt = Date.now();
76
148
  const seg = transcribePiece(piece);
77
149
  if (!seg) {
78
150
  continue;
79
151
  }
80
152
  out += !out || seg.append ? seg.text : ` ${seg.text}`;
81
153
  }
82
- streamClosed = true;
83
154
  return out.replace(/\s+/g, ' ').trim();
84
155
  })();
85
- collect.catch(() => {
86
- streamClosed = true;
87
- });
156
+ collect.catch(noop);
88
157
  try {
89
158
  await writePaced(session, pcm, signal);
90
- await drainUntilIdle(() => lastPieceAt, () => streamClosed, signal);
91
159
  if (!signal?.aborted) {
92
160
  session.end();
93
161
  }
@@ -3,7 +3,7 @@ import { type Transport } from './rpc-session.ts';
3
3
  import { type KvCacheDeletionResult } from './capabilities/kv-cache.ts';
4
4
  import type { CustomModelEntry } from './models/model-catalog.ts';
5
5
  import { type ModelSourcePreview, type PreviewModelSourceContext } from './capabilities/preview-model-source.ts';
6
- import type { CapabilityInfo, CompletionContext, CredentialEntry, DetectedLanguage, DetectLanguageContext, DownloadContext, EmbedContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, InfraModel, InvokeContext, ListModelsOptions, ModelInfo, ResolvedSkillCatalogEntry, SkillsOptions, SkillWriteResult, SpeakContext, SpeakResult, SpeakStreamContext, ToolCatalogEntry, TranscribeContext, LoadModelContext, LoadModelResult, UnloadModelContext, UnloadModelResult, WarmModelContext } from './contract-types.ts';
6
+ import type { CapabilityInfo, CompletionContext, CredentialEntry, DetectedLanguage, DetectLanguageContext, DownloadContext, EmbedContext, HarnessContract, HarnessDiagnostics, HarnessEvent, HarnessInfo, InfraModel, InvokeContext, ListModelsOptions, ModelInfo, ResolvedSkillCatalogEntry, SkillsOptions, SkillWriteResult, SpeakContext, SpeakResult, SpeakStreamContext, ToolCatalogEntry, TranscribeContext, LoadModelContext, LoadModelResult, MemoryUsage, UnloadModelContext, UnloadModelResult, WarmModelContext, DownloadsAllowedOptions } from './contract-types.ts';
7
7
  import { type EmbedDevice } from './models/embed-config.ts';
8
8
  import type { DeleteModelSummary } from './models/model-delete.ts';
9
9
  import type { CopyBaseResult, DeleteOverlayResult, ManagedSkill, SkillFileContent } from './tools/skills/overlay-crud.ts';
@@ -36,6 +36,9 @@ export declare class RemoteHarness extends EventEmitter implements HarnessContra
36
36
  setCredentials(entries: ReadonlyArray<CredentialEntry>): Promise<void>;
37
37
  setSkillSettings(entries: ReadonlyArray<SkillSettingEntry>): Promise<void>;
38
38
  setModelIdleTimeout(ms: number): Promise<void>;
39
+ setModelMemoryCap(percent: number): Promise<void>;
40
+ memoryUsage(): Promise<MemoryUsage>;
41
+ setDownloadsAllowed({ allowed }: DownloadsAllowedOptions): Promise<void>;
39
42
  listManagedSkills(): Promise<ManagedSkill[]>;
40
43
  readSkillFile(name: string): Promise<SkillFileContent | null>;
41
44
  writeSkillFile(name: string, content: string): Promise<SkillWriteResult>;
@@ -102,6 +102,7 @@ export class RemoteHarness extends EventEmitter {
102
102
  model: context.model,
103
103
  agent: knobs(context.agent),
104
104
  cacheKey: context.cacheKey,
105
+ session: context.session,
105
106
  thinkingDefault: context.thinkingDefault,
106
107
  generationParams: context.generationParams,
107
108
  responseFormat: context.responseFormat,
@@ -111,6 +112,7 @@ export class RemoteHarness extends EventEmitter {
111
112
  content,
112
113
  attachments: attachments?.map(({ path: file }) => ({ path: file }))
113
114
  })),
115
+ payload: { typedText: context.typedText },
114
116
  toolConfigs: context.toolConfigs,
115
117
  grantedDirs: context.grantedDirs,
116
118
  grantedDrives: context.grantedDrives,
@@ -422,6 +424,18 @@ export class RemoteHarness extends EventEmitter {
422
424
  const { client } = await this._ensure();
423
425
  await client.setModelIdleTimeout({ ms });
424
426
  }
427
+ async setModelMemoryCap(percent) {
428
+ const { client } = await this._ensure();
429
+ await client.setModelMemoryCap({ percent });
430
+ }
431
+ async memoryUsage() {
432
+ const { client } = await this._ensure();
433
+ return await client.memoryUsage({});
434
+ }
435
+ async setDownloadsAllowed({ allowed }) {
436
+ const { client } = await this._ensure();
437
+ await client.setDownloadsAllowed({ allowed });
438
+ }
425
439
  async listManagedSkills() {
426
440
  const { client } = await this._ensure();
427
441
  const response = await client.listManagedSkills({});
@@ -566,10 +580,11 @@ export class RemoteHarness extends EventEmitter {
566
580
  cachedOnly: options.cachedOnly,
567
581
  fullRegistry: options.fullRegistry
568
582
  });
569
- return (response.models ?? []).map(({ actualSize, label, ...row }) => ({
583
+ return (response.models ?? []).map(({ actualSize, label, memoryBytes, ...row }) => ({
570
584
  ...row,
571
585
  actualSize: actualSize || undefined,
572
586
  label: label || undefined,
587
+ memoryBytes: memoryBytes || undefined,
573
588
  taskType: addonTaskType(row.type)
574
589
  }));
575
590
  }
@@ -46,7 +46,9 @@ export interface CompletionContext {
46
46
  model: string;
47
47
  agent: AgentKnobs;
48
48
  messages: ChatMessage[];
49
+ typedText?: string;
49
50
  cacheKey?: string;
51
+ session?: string;
50
52
  thinkingDefault?: string;
51
53
  toolConfigs?: Record<string, Record<string, unknown>>;
52
54
  generationParams?: LlmGenerationParams;
@@ -95,6 +97,9 @@ export interface HarnessEvent {
95
97
  message?: string;
96
98
  [key: string]: unknown;
97
99
  }
100
+ export interface DownloadsAllowedOptions {
101
+ allowed: boolean;
102
+ }
98
103
  export interface ModelState {
99
104
  status: string;
100
105
  model?: string;
@@ -107,6 +112,7 @@ export interface ModelState {
107
112
  model: string;
108
113
  refs: number;
109
114
  }>;
115
+ addon?: string | null;
110
116
  }
111
117
  export interface WarmModelContext {
112
118
  model: string;
@@ -118,6 +124,9 @@ export interface LoadModelResult {
118
124
  loaded: boolean;
119
125
  reason?: 'memory';
120
126
  }
127
+ export interface MemoryUsage {
128
+ readonly usedBytes: number;
129
+ }
121
130
  export interface UnloadModelContext {
122
131
  model: string;
123
132
  }
@@ -223,6 +232,9 @@ export interface HarnessContract {
223
232
  agentId?: string | null;
224
233
  }>): void | Promise<void>;
225
234
  setModelIdleTimeout?(ms: number): void | Promise<void>;
235
+ setModelMemoryCap?(percent: number): void | Promise<void>;
236
+ memoryUsage?(): Promise<MemoryUsage>;
237
+ setDownloadsAllowed?(options: DownloadsAllowedOptions): void | Promise<void>;
226
238
  listManagedSkills?(): Promise<ManagedSkill[]>;
227
239
  readSkillFile?(name: string): Promise<SkillFileContent | null>;
228
240
  writeSkillFile?(name: string, content: string): Promise<{
@@ -9,6 +9,7 @@ export declare function clipRawCall(raw: string): string;
9
9
  export interface KnownTool {
10
10
  readonly name: string;
11
11
  }
12
+ export declare function hasMarkupCall(text: string): boolean;
12
13
  export declare function recoverMarkupToolCalls(text: string, knownTools: ReadonlyArray<KnownTool>): RecoveredToolCall[];
13
14
  export declare function stripToolCallMarkup(text: string): string;
14
15
  export declare function stripToolResponseBlocks(text: string): string;
@@ -46,6 +46,9 @@ export const MAX_RAW_CALL_CHARS = 4096;
46
46
  export function clipRawCall(raw) {
47
47
  return raw.length > MAX_RAW_CALL_CHARS ? raw.slice(0, MAX_RAW_CALL_CHARS) : raw;
48
48
  }
49
+ export function hasMarkupCall(text) {
50
+ return !markupBlocks(text, FUNCTION_OPEN, FUNCTION_CLOSE).next().done;
51
+ }
49
52
  export function recoverMarkupToolCalls(text, knownTools) {
50
53
  const known = new Set(knownTools.map((tool) => tool.name));
51
54
  const calls = [];
@@ -0,0 +1,15 @@
1
+ import type { CapabilityProfile } from './model-catalog-constants.ts';
2
+ export interface ContextAsk {
3
+ trained(): Promise<number | undefined>;
4
+ fits(ctxSize: number): Promise<boolean>;
5
+ }
6
+ export declare class ContextChoice {
7
+ private readonly _profile;
8
+ private readonly _pending;
9
+ private readonly _chosen;
10
+ constructor(profile: () => Promise<CapabilityProfile | undefined>);
11
+ static ceiling(profile: CapabilityProfile | undefined): number | undefined;
12
+ chosen(key: string): number | undefined;
13
+ choose(key: string, tier: number, ask: ContextAsk): Promise<number>;
14
+ private _climb;
15
+ }
@@ -0,0 +1,55 @@
1
+ const RUNGS = [262_144, 131_072, 65_536];
2
+ const CEILINGS = {
3
+ mid: 65_536,
4
+ high: 131_072,
5
+ xhigh: 262_144
6
+ };
7
+ export class ContextChoice {
8
+ _profile;
9
+ _pending = new Map();
10
+ _chosen = new Map();
11
+ constructor(profile) {
12
+ this._profile = profile;
13
+ }
14
+ static ceiling(profile) {
15
+ return profile && CEILINGS[profile];
16
+ }
17
+ chosen(key) {
18
+ return this._chosen.get(key);
19
+ }
20
+ choose(key, tier, ask) {
21
+ const pending = this._pending.get(key);
22
+ if (pending) {
23
+ return pending;
24
+ }
25
+ const choice = this._climb(tier, ask).then((ctxSize) => {
26
+ if (ctxSize === undefined) {
27
+ this._pending.delete(key);
28
+ return tier;
29
+ }
30
+ this._chosen.set(key, ctxSize);
31
+ return ctxSize;
32
+ });
33
+ this._pending.set(key, choice);
34
+ return choice;
35
+ }
36
+ async _climb(tier, ask) {
37
+ const ceiling = ContextChoice.ceiling(await this._profile());
38
+ if (!ceiling || ceiling <= tier) {
39
+ return tier;
40
+ }
41
+ const trained = await ask.trained();
42
+ if (!trained) {
43
+ return undefined;
44
+ }
45
+ for (const rung of RUNGS) {
46
+ if (rung > ceiling || rung > trained || rung <= tier) {
47
+ continue;
48
+ }
49
+ if (await ask.fits(rung)) {
50
+ return rung;
51
+ }
52
+ }
53
+ return tier;
54
+ }
55
+ }
@@ -0,0 +1,20 @@
1
+ import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
+ import type { ModelAddon } from './model-source.ts';
3
+ export interface HeldModel {
4
+ readonly model: string;
5
+ readonly addon: ModelAddon;
6
+ }
7
+ export interface DownloadGateOptions {
8
+ isCached(model: string, addon: ModelAddon): Promise<boolean>;
9
+ onHeld(held: HeldModel): void;
10
+ onDropped(held: HeldModel): void;
11
+ }
12
+ export type HoldFn = (model: string, addon: ModelAddon, signal?: BareAbortSignal) => Promise<void>;
13
+ export interface DownloadGate {
14
+ readonly allowed: boolean;
15
+ setAllowed(allowed: boolean): void;
16
+ downloaded(): void;
17
+ hold: HoldFn;
18
+ abandon(): void;
19
+ }
20
+ export declare function createDownloadGate({ isCached, onHeld, onDropped }: DownloadGateOptions): DownloadGate;
@@ -0,0 +1,73 @@
1
+ import { AbortedError, raceAbort } from '../helpers/race-abort.js';
2
+ function deferred() {
3
+ const next = Promise.withResolvers();
4
+ next.promise.catch(noop);
5
+ return next;
6
+ }
7
+ export function createDownloadGate({ isCached, onHeld, onDropped }) {
8
+ let allowed = true;
9
+ let woke = deferred();
10
+ let dropped = deferred();
11
+ const wake = () => {
12
+ const woken = woke;
13
+ woke = deferred();
14
+ woken.resolve();
15
+ };
16
+ const wait = async (held, signal, onWait) => {
17
+ const drop = dropped;
18
+ const cancelled = () => signal?.aborted === true || drop !== dropped;
19
+ for (;;) {
20
+ const change = Promise.race([raceAbort(woke.promise, signal), drop.promise]);
21
+ change.catch(noop);
22
+ const ready = allowed || (await isCached(held.model, held.addon).catch(() => false));
23
+ if (cancelled()) {
24
+ throw new AbortedError();
25
+ }
26
+ if (ready) {
27
+ return;
28
+ }
29
+ onWait();
30
+ await change;
31
+ }
32
+ };
33
+ return {
34
+ get allowed() {
35
+ return allowed;
36
+ },
37
+ setAllowed(next) {
38
+ allowed = next;
39
+ if (allowed) {
40
+ wake();
41
+ }
42
+ },
43
+ downloaded: wake,
44
+ async hold(model, addon, signal) {
45
+ if (allowed)
46
+ return;
47
+ if (signal?.aborted)
48
+ throw new AbortedError();
49
+ const held = { model, addon };
50
+ let shown = false;
51
+ try {
52
+ await wait(held, signal, () => {
53
+ if (!shown) {
54
+ shown = true;
55
+ onHeld(held);
56
+ }
57
+ });
58
+ }
59
+ catch (err) {
60
+ if (shown) {
61
+ onDropped(held);
62
+ }
63
+ throw err;
64
+ }
65
+ },
66
+ abandon() {
67
+ const abandoned = dropped;
68
+ dropped = deferred();
69
+ abandoned.reject(new AbortedError());
70
+ }
71
+ };
72
+ }
73
+ function noop() { }
@@ -0,0 +1,13 @@
1
+ export declare const VictimRank: {
2
+ readonly generator: 0;
3
+ readonly companion: 1;
4
+ readonly requested: 2;
5
+ readonly pinned: 3;
6
+ readonly replying: 4;
7
+ };
8
+ export interface CapVictim {
9
+ readonly rank: number;
10
+ readonly lastUsed: number;
11
+ }
12
+ export declare function nextVictim<T extends CapVictim>(victims: ReadonlyArray<T>): T | undefined;
13
+ export declare function memoryCapBytes(percent: number, availableBytes: number): number;
@@ -0,0 +1,21 @@
1
+ export const VictimRank = {
2
+ generator: 0,
3
+ companion: 1,
4
+ requested: 2,
5
+ pinned: 3,
6
+ replying: 4
7
+ };
8
+ export function nextVictim(victims) {
9
+ let best;
10
+ for (const victim of victims) {
11
+ if (best === undefined ||
12
+ victim.rank < best.rank ||
13
+ (victim.rank === best.rank && victim.lastUsed < best.lastUsed)) {
14
+ best = victim;
15
+ }
16
+ }
17
+ return best;
18
+ }
19
+ export function memoryCapBytes(percent, availableBytes) {
20
+ return Math.round((Math.min(percent, 100) / 100) * availableBytes);
21
+ }
@@ -6,6 +6,8 @@ export declare const QWEN_35_9B = "Qwen3.5-9B-Q4_K_M";
6
6
  export declare const QWEN_36_27B = "Qwen3.6-27B-UD-Q4_K_XL";
7
7
  export declare const QWEN_36_35B_A3B = "Qwen3.6-35B-A3B-UD-Q4_K_M";
8
8
  export declare const QWEN_38_27B = "Qwen3.8-27B-UD-Q4_K_XL";
9
+ export declare const BONSAI_2_27B_PQ2 = "Ternary-Bonsai-2-27B-PQ2_0";
10
+ export declare const BONSAI_2_27B_PTQ1 = "Ternary-Bonsai-2-27B-PTQ1_0";
9
11
  export declare const MEDPSY_1_7B = "medpsy-1.7b-q4_k_m-imat";
10
12
  export declare const MEDPSY_4B = "medpsy-4b-q4_k_m-imat";
11
13
  export declare function isCapabilityProfile(value: string): value is CapabilityProfile;
@@ -19,7 +21,7 @@ export interface HostCapability {
19
21
  export declare function isMobilePlatform(platform: string): boolean;
20
22
  export declare function runnableModelName(requested: string, host: HostCapability | null): string;
21
23
  export declare function capabilityProfileToModelName(profile: CapabilityProfile): string;
22
- export declare const AGENT_MODEL_NAMES: readonly ["Qwen3.5-2B-Q4_K_M", "Qwen3.5-4B-Q4_K_M", "Qwen3.5-9B-Q4_K_M", "Qwen3.6-27B-UD-Q4_K_XL", "Qwen3.6-35B-A3B-UD-Q4_K_M", "Qwen3.8-27B-UD-Q4_K_XL", "medpsy-1.7b-q4_k_m-imat", "medpsy-4b-q4_k_m-imat"];
24
+ export declare const AGENT_MODEL_NAMES: readonly ["Qwen3.5-2B-Q4_K_M", "Qwen3.5-4B-Q4_K_M", "Qwen3.5-9B-Q4_K_M", "Qwen3.6-27B-UD-Q4_K_XL", "Qwen3.6-35B-A3B-UD-Q4_K_M", "Qwen3.8-27B-UD-Q4_K_XL", "Ternary-Bonsai-2-27B-PQ2_0", "Ternary-Bonsai-2-27B-PTQ1_0", "medpsy-1.7b-q4_k_m-imat", "medpsy-4b-q4_k_m-imat"];
23
25
  export declare function modelDisplayName(modelName: string): string;
24
26
  export declare function capabilityProfileModelLabel(profile: CapabilityProfile): string;
25
27
  export declare function isMultimodalLlm(modelName: string): boolean;
@@ -11,6 +11,8 @@ export const QWEN_35_9B = 'Qwen3.5-9B-Q4_K_M';
11
11
  export const QWEN_36_27B = 'Qwen3.6-27B-UD-Q4_K_XL';
12
12
  export const QWEN_36_35B_A3B = 'Qwen3.6-35B-A3B-UD-Q4_K_M';
13
13
  export const QWEN_38_27B = 'Qwen3.8-27B-UD-Q4_K_XL';
14
+ export const BONSAI_2_27B_PQ2 = 'Ternary-Bonsai-2-27B-PQ2_0';
15
+ export const BONSAI_2_27B_PTQ1 = 'Ternary-Bonsai-2-27B-PTQ1_0';
14
16
  export const MEDPSY_1_7B = 'medpsy-1.7b-q4_k_m-imat';
15
17
  export const MEDPSY_4B = 'medpsy-4b-q4_k_m-imat';
16
18
  export function isCapabilityProfile(value) {
@@ -26,6 +28,8 @@ const MODEL_NAME_TO_PROFILE = new Map([
26
28
  [QWEN_36_27B, 'xhigh'],
27
29
  [QWEN_36_35B_A3B, 'xhigh'],
28
30
  [QWEN_38_27B, 'xhigh'],
31
+ [BONSAI_2_27B_PQ2, 'high'],
32
+ [BONSAI_2_27B_PTQ1, 'high'],
29
33
  [MEDPSY_1_7B, 'low'],
30
34
  [MEDPSY_4B, 'mid']
31
35
  ]);
@@ -70,6 +74,8 @@ export const AGENT_MODEL_NAMES = [
70
74
  QWEN_36_27B,
71
75
  QWEN_36_35B_A3B,
72
76
  QWEN_38_27B,
77
+ BONSAI_2_27B_PQ2,
78
+ BONSAI_2_27B_PTQ1,
73
79
  MEDPSY_1_7B,
74
80
  MEDPSY_4B
75
81
  ];
@@ -87,7 +93,9 @@ const MULTIMODAL_LLMS = new Set([
87
93
  QWEN_35_9B,
88
94
  QWEN_36_27B,
89
95
  QWEN_36_35B_A3B,
90
- QWEN_38_27B
96
+ QWEN_38_27B,
97
+ BONSAI_2_27B_PQ2,
98
+ BONSAI_2_27B_PTQ1
91
99
  ]);
92
100
  export function isMultimodalLlm(modelName) {
93
101
  return MULTIMODAL_LLMS.has(modelName);
@@ -6,8 +6,8 @@ import type { ModelAddon } from './model-source.ts';
6
6
  export interface SdkModelAsset extends ModelRegistryEntry {
7
7
  readonly src: string;
8
8
  }
9
- import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName, type CapabilityProfile, type HostCapability, type ImageModelChoice, type ImageChoiceEntry } from './model-catalog-constants.ts';
10
- export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName };
9
+ import { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, BONSAI_2_27B_PQ2, BONSAI_2_27B_PTQ1, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName, type CapabilityProfile, type HostCapability, type ImageModelChoice, type ImageChoiceEntry } from './model-catalog-constants.ts';
10
+ export { CAPABILITY_PROFILE_ORDER, QWEN_35_2B, QWEN_35_4B, QWEN_35_9B, QWEN_36_27B, QWEN_36_35B_A3B, QWEN_38_27B, BONSAI_2_27B_PQ2, BONSAI_2_27B_PTQ1, MEDPSY_1_7B, MEDPSY_4B, isCapabilityProfile, meetsMinCapabilityProfile, modelNameToCapabilityProfile, capabilityProfileToModelName, isMultimodalLlm, AGENT_MODEL_NAMES, modelDisplayName, capabilityProfileModelLabel, DEFAULT_IMAGE_MODEL_CHOICE, IMAGE_CHOICE_CATALOG, isMobilePlatform, runnableModelName };
11
11
  export type { CapabilityProfile, HostCapability, ImageModelChoice, ImageChoiceEntry };
12
12
  export interface LlmCatalogEntry {
13
13
  readonly name: string;
@@ -15,6 +15,7 @@ export interface LlmCatalogEntry {
15
15
  readonly capabilityProfile: CapabilityProfile;
16
16
  readonly projection?: SdkModelAsset;
17
17
  readonly textOnly?: true;
18
+ readonly toolDialect?: ToolDialect;
18
19
  }
19
20
  export declare const LLM_CATALOG: ReadonlyArray<LlmCatalogEntry>;
20
21
  export declare function defaultProjectionMap(): Record<string, string>;
@@ -52,6 +53,7 @@ export interface DiffusionDownloadMetadata {
52
53
  }
53
54
  export declare function diffusionDownloadName(model: string): string | null;
54
55
  export declare function diffusionDownloadMetadata(model: string, addon: ModelAddon | undefined): DiffusionDownloadMetadata | undefined;
56
+ export declare function assetBytes(src: string): number | undefined;
55
57
  import { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom, type MusicModelChoice } from './music-catalog.ts';
56
58
  export { DEFAULT_MUSIC_MODEL_CHOICE, isMusicModelChoice, musicModelChoiceFrom, type MusicModelChoice };
57
59
  export interface AudioGenCatalogEntry {
@@ -108,6 +110,7 @@ export declare function curatedListables(): CuratedListable[];
108
110
  export declare function curatedModelAsset(name: string, addon: ModelRegistryEntry['addon']): SdkModelAsset | undefined;
109
111
  export declare function modelAddonOf(model: string): ModelRegistryEntry['addon'];
110
112
  export declare function residentSrc(model: string, addon: ModelRegistryEntry['addon']): string;
113
+ export declare function catalogModelName(model: string, addon: ModelRegistryEntry['addon']): string;
111
114
  export declare const CUSTOM_TASK_TYPES: readonly ['llm'];
112
115
  export type CustomTaskType = (typeof CUSTOM_TASK_TYPES)[number];
113
116
  export interface CustomModelEntry {
@@ -123,6 +126,7 @@ export declare function setCustomModels(entries: ReadonlyArray<CustomModelEntry>
123
126
  export declare function customModels(): ReadonlyArray<CustomModelEntry>;
124
127
  export declare function customContextLength(name: string): number | undefined;
125
128
  export declare function customModelSrc(name: string, addon: ModelRegistryEntry['addon']): string | undefined;
129
+ export declare function toolDialectFor(name: string): ToolDialect | undefined;
126
130
  export declare function customToolDialect(name: string): ToolDialect | undefined;
127
131
  export declare function customProjectionMap(): Record<string, string>;
128
132
  export declare function companionRegistryPaths(): Map<string, string[]>;