@qvac/harness 0.1.2 → 0.2.1
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.
- package/dist/harness.bundle +827 -6748
- package/dist/prebuilds-manifest.json +0 -7
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.js +21 -4
- package/dist-lib/lib/audio-transcode-bare.js +20 -20
- package/dist-lib/lib/capabilities/completion.d.ts +6 -0
- package/dist-lib/lib/capabilities/completion.js +48 -15
- package/dist-lib/lib/capabilities/download.d.ts +2 -0
- package/dist-lib/lib/capabilities/download.js +12 -3
- package/dist-lib/lib/capabilities/kv-cache.d.ts +1 -0
- package/dist-lib/lib/capabilities/kv-cache.js +7 -1
- package/dist-lib/lib/capabilities/tool-prompt-log.d.ts +23 -0
- package/dist-lib/lib/capabilities/tool-prompt-log.js +62 -0
- package/dist-lib/lib/capabilities/tool-selection.d.ts +2 -1
- package/dist-lib/lib/capabilities/tool-selection.js +2 -2
- package/dist-lib/lib/connect.d.ts +2 -0
- package/dist-lib/lib/connect.js +7 -1
- package/dist-lib/lib/contract-types.d.ts +1 -0
- package/dist-lib/lib/models/model-pool.d.ts +1 -1
- package/dist-lib/lib/sdk-loader.d.ts +1 -1
- package/dist-lib/lib/sdk-loader.js +1 -1
- package/dist-lib/lib/serve.js +2 -0
- package/dist-lib/lib/tools/exec.js +60 -8
- package/dist-lib/lib/tools/generate-image.d.ts +2 -1
- package/dist-lib/lib/tools/generate-image.js +2 -1
- package/dist-lib/lib/tools/skill.js +16 -0
- package/dist-lib/lib/wire.d.ts +1 -0
- package/dist-lib/lib/wire.js +6 -2
- package/dist-lib/schema/harness.js +12 -2
- package/dist-lib/spec/bind.js +3 -0
- package/dist-lib/spec/calls.js +1 -0
- package/dist-lib/spec/hrpc/hrpc.json +13 -0
- package/dist-lib/spec/hrpc/index.js +15 -3
- package/dist-lib/spec/hrpc/messages.js +37 -7
- package/dist-lib/spec/hyperschema/index.js +37 -7
- package/dist-lib/spec/hyperschema/schema.json +24 -1
- package/dist-lib/spec/hyperschema/types.d.ts +8 -2
- package/package.json +20 -12
package/dist-lib/index.d.ts
CHANGED
|
@@ -224,6 +224,7 @@ export declare class Harness extends ReadyResource {
|
|
|
224
224
|
list(options?: ListModelsOptions): Promise<ModelInfo[]>;
|
|
225
225
|
deleteModel(name: string): Promise<DeleteModelSummary>;
|
|
226
226
|
clearKvCache(): Promise<KvCacheDeletionResult>;
|
|
227
|
+
deleteKvCache(kvCacheKey: string): Promise<KvCacheDeletionResult>;
|
|
227
228
|
infraModels(profile: string): Promise<Array<{
|
|
228
229
|
name: string;
|
|
229
230
|
addon: string;
|
package/dist-lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { closePythonWorker } from "./lib/tools/exec/adapters/python/worker/clien
|
|
|
8
8
|
import { completion } from "./lib/capabilities/completion.js";
|
|
9
9
|
import { download } from "./lib/capabilities/download.js";
|
|
10
10
|
import { createEmbedLane } from "./lib/capabilities/embed.js";
|
|
11
|
-
import { deleteAllKvCaches } from "./lib/capabilities/kv-cache.js";
|
|
11
|
+
import { deleteAllKvCaches, deleteKvCacheKey } from "./lib/capabilities/kv-cache.js";
|
|
12
12
|
import { pcmForTranscription } from "./lib/capabilities/audio-resample.js";
|
|
13
13
|
import { buildWavBuffer } from "./lib/wav-encode.js";
|
|
14
14
|
import { createVoiceLane, packSamples, speakOnce, speakStreamOnce, transcribeOnce, transcribeStreaming, SUPERTONIC_SAMPLE_RATE } from "./lib/capabilities/voice.js";
|
|
@@ -137,6 +137,7 @@ export class Harness extends ReadyResource {
|
|
|
137
137
|
device: embedConfig.device,
|
|
138
138
|
gpuLayers: embedConfig.gpuLayers
|
|
139
139
|
});
|
|
140
|
+
const imageTransfers = new Map();
|
|
140
141
|
const image = typeof imageModel === 'string' ? { model: imageModel } : imageModel;
|
|
141
142
|
this._imageRuntime = image
|
|
142
143
|
? createDiffusionRuntime({
|
|
@@ -145,10 +146,22 @@ export class Harness extends ReadyResource {
|
|
|
145
146
|
loadSdk: () => this._loadDiffusionSdk(),
|
|
146
147
|
onProgress: (model, progress) => {
|
|
147
148
|
const state = progressState(model, progress);
|
|
148
|
-
if (state.status === '
|
|
149
|
-
|
|
149
|
+
if (state.status === 'downloading' &&
|
|
150
|
+
typeof progress.percentage === 'number' &&
|
|
151
|
+
progress.percentage < 100) {
|
|
152
|
+
imageTransfers.set(model, state);
|
|
150
153
|
}
|
|
151
|
-
this.emit('model', state)
|
|
154
|
+
this.emit('model', state.status === 'downloading' && !imageTransfers.has(model)
|
|
155
|
+
? { ...state, status: 'loading' }
|
|
156
|
+
: state);
|
|
157
|
+
},
|
|
158
|
+
onLoaded: (model) => {
|
|
159
|
+
const transfer = imageTransfers.get(model);
|
|
160
|
+
if (!transfer)
|
|
161
|
+
return;
|
|
162
|
+
imageTransfers.delete(model);
|
|
163
|
+
invalidateModelStatusCache(this._modelStatus);
|
|
164
|
+
this.emit('model', { ...transfer, status: 'downloaded' });
|
|
152
165
|
},
|
|
153
166
|
onBeforeLoad: async (signal) => {
|
|
154
167
|
if (this._models.destroyed)
|
|
@@ -852,6 +865,10 @@ export class Harness extends ReadyResource {
|
|
|
852
865
|
await this.ready();
|
|
853
866
|
return deleteAllKvCaches(() => this._sdk.load());
|
|
854
867
|
}
|
|
868
|
+
async deleteKvCache(kvCacheKey) {
|
|
869
|
+
await this.ready();
|
|
870
|
+
return deleteKvCacheKey(() => this._sdk.load(), kvCacheKey);
|
|
871
|
+
}
|
|
855
872
|
async infraModels(profile) {
|
|
856
873
|
return infraModelsFor(profile);
|
|
857
874
|
}
|
|
@@ -32,11 +32,11 @@ function bufferReadIO(input) {
|
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
function makeOutFrame(capacity) {
|
|
35
|
+
function makeOutFrame(capacity, rate) {
|
|
36
36
|
const out = new ffmpeg.Frame();
|
|
37
37
|
out.format = ffmpeg.constants.sampleFormats.S16;
|
|
38
38
|
out.channelLayout = ffmpeg.constants.channelLayouts.MONO;
|
|
39
|
-
out.sampleRate =
|
|
39
|
+
out.sampleRate = rate;
|
|
40
40
|
out.nbSamples = capacity;
|
|
41
41
|
out.alloc();
|
|
42
42
|
return out;
|
|
@@ -47,23 +47,23 @@ function readChunk(samples, outFrame, produced) {
|
|
|
47
47
|
samples.read(outFrame);
|
|
48
48
|
return Buffer.from(samples.data.subarray(0, produced * 2));
|
|
49
49
|
}
|
|
50
|
-
function createResampler(frame) {
|
|
51
|
-
return new ffmpeg.Resampler(frame.sampleRate, frame.channelLayout, frame.format,
|
|
50
|
+
function createResampler(frame, rate) {
|
|
51
|
+
return new ffmpeg.Resampler(frame.sampleRate, frame.channelLayout, frame.format, rate, ffmpeg.constants.channelLayouts.MONO, ffmpeg.constants.sampleFormats.S16);
|
|
52
52
|
}
|
|
53
|
-
function convertFrame(resampler, frame, samples) {
|
|
54
|
-
const capacity = Math.ceil((frame.nbSamples *
|
|
55
|
-
const outFrame = makeOutFrame(capacity);
|
|
53
|
+
function convertFrame(resampler, frame, samples, rate) {
|
|
54
|
+
const capacity = Math.ceil((frame.nbSamples * rate) / frame.sampleRate) + 1024;
|
|
55
|
+
const outFrame = makeOutFrame(capacity, rate);
|
|
56
56
|
const chunk = readChunk(samples, outFrame, resampler.convert(frame, outFrame));
|
|
57
57
|
outFrame.destroy();
|
|
58
58
|
return chunk;
|
|
59
59
|
}
|
|
60
|
-
function flushResampler(resampler, samples) {
|
|
60
|
+
function flushResampler(resampler, samples, rate) {
|
|
61
61
|
if (!resampler)
|
|
62
62
|
return [];
|
|
63
63
|
const chunks = [];
|
|
64
64
|
let flushed = 0;
|
|
65
65
|
do {
|
|
66
|
-
const outFrame = makeOutFrame(
|
|
66
|
+
const outFrame = makeOutFrame(rate, rate);
|
|
67
67
|
flushed = resampler.flush(outFrame);
|
|
68
68
|
const chunk = readChunk(samples, outFrame, flushed);
|
|
69
69
|
if (chunk)
|
|
@@ -72,11 +72,11 @@ function flushResampler(resampler, samples) {
|
|
|
72
72
|
} while (flushed > 0);
|
|
73
73
|
return chunks;
|
|
74
74
|
}
|
|
75
|
-
function drainDecodedFrames(decoder, frame, samples, resampler, chunks) {
|
|
75
|
+
function drainDecodedFrames(decoder, frame, samples, resampler, chunks, rate) {
|
|
76
76
|
let current = resampler;
|
|
77
77
|
while (decoder.receiveFrame(frame)) {
|
|
78
|
-
current ??= createResampler(frame);
|
|
79
|
-
const chunk = convertFrame(current, frame, samples);
|
|
78
|
+
current ??= createResampler(frame, rate);
|
|
79
|
+
const chunk = convertFrame(current, frame, samples, rate);
|
|
80
80
|
if (chunk)
|
|
81
81
|
chunks.push(chunk);
|
|
82
82
|
}
|
|
@@ -86,7 +86,7 @@ const YIELD_EVERY_PACKETS = 32;
|
|
|
86
86
|
function yieldToLoop() {
|
|
87
87
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
88
88
|
}
|
|
89
|
-
async function decodeToPcmChunks(inputFormat, stream, decoder) {
|
|
89
|
+
async function decodeToPcmChunks(inputFormat, stream, decoder, rate) {
|
|
90
90
|
const chunks = [];
|
|
91
91
|
const packet = new ffmpeg.Packet();
|
|
92
92
|
const flush = new ffmpeg.Packet();
|
|
@@ -97,15 +97,15 @@ async function decodeToPcmChunks(inputFormat, stream, decoder) {
|
|
|
97
97
|
try {
|
|
98
98
|
while (inputFormat.readFrame(packet)) {
|
|
99
99
|
if (packet.streamIndex === stream.index && decoder.sendPacket(packet)) {
|
|
100
|
-
resampler = drainDecodedFrames(decoder, frame, samples, resampler, chunks);
|
|
100
|
+
resampler = drainDecodedFrames(decoder, frame, samples, resampler, chunks, rate);
|
|
101
101
|
}
|
|
102
102
|
packet.unref();
|
|
103
103
|
if (++packets % YIELD_EVERY_PACKETS === 0)
|
|
104
104
|
await yieldToLoop();
|
|
105
105
|
}
|
|
106
106
|
decoder.sendPacket(flush);
|
|
107
|
-
resampler = drainDecodedFrames(decoder, frame, samples, resampler, chunks);
|
|
108
|
-
chunks.push(...flushResampler(resampler, samples));
|
|
107
|
+
resampler = drainDecodedFrames(decoder, frame, samples, resampler, chunks, rate);
|
|
108
|
+
chunks.push(...flushResampler(resampler, samples, rate));
|
|
109
109
|
}
|
|
110
110
|
finally {
|
|
111
111
|
resampler?.destroy();
|
|
@@ -115,7 +115,7 @@ async function decodeToPcmChunks(inputFormat, stream, decoder) {
|
|
|
115
115
|
}
|
|
116
116
|
return chunks;
|
|
117
117
|
}
|
|
118
|
-
async function decodeInputToPcm(input) {
|
|
118
|
+
async function decodeInputToPcm(input, rate) {
|
|
119
119
|
let inputFormat = null;
|
|
120
120
|
let decoder = null;
|
|
121
121
|
try {
|
|
@@ -123,7 +123,7 @@ async function decodeInputToPcm(input) {
|
|
|
123
123
|
const stream = inputFormat.getBestStream(ffmpeg.constants.mediaTypes.AUDIO);
|
|
124
124
|
decoder = stream.decoder();
|
|
125
125
|
decoder.open();
|
|
126
|
-
const chunks = await decodeToPcmChunks(inputFormat, stream, decoder);
|
|
126
|
+
const chunks = await decodeToPcmChunks(inputFormat, stream, decoder, rate);
|
|
127
127
|
return chunks.length === 0 ? null : Buffer.concat(chunks);
|
|
128
128
|
}
|
|
129
129
|
catch (e) {
|
|
@@ -137,9 +137,9 @@ async function decodeInputToPcm(input) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
export async function transcodeToWav16kMono(input) {
|
|
140
|
-
const pcm = await decodeInputToPcm(input);
|
|
140
|
+
const pcm = await decodeInputToPcm(input, TARGET_SAMPLE_RATE);
|
|
141
141
|
return pcm ? buildWavBuffer(pcm) : null;
|
|
142
142
|
}
|
|
143
143
|
export async function transcodeToPcm16kMono(input) {
|
|
144
|
-
return decodeInputToPcm(input);
|
|
144
|
+
return decodeInputToPcm(input, TARGET_SAMPLE_RATE);
|
|
145
145
|
}
|
|
@@ -159,10 +159,15 @@ export interface ToolParameters {
|
|
|
159
159
|
properties: Record<string, ToolParameterSchema>;
|
|
160
160
|
required?: string[];
|
|
161
161
|
}
|
|
162
|
+
export interface ToolPromptDefinition {
|
|
163
|
+
description?: string;
|
|
164
|
+
parameters?: ToolParameters;
|
|
165
|
+
}
|
|
162
166
|
export interface ToolDefinition {
|
|
163
167
|
name: string;
|
|
164
168
|
description: string;
|
|
165
169
|
parameters: ToolParameters;
|
|
170
|
+
prompt?: ToolPromptDefinition;
|
|
166
171
|
autoApprove?: boolean;
|
|
167
172
|
skillGated?: boolean;
|
|
168
173
|
rootsRequired?: boolean;
|
|
@@ -279,6 +284,7 @@ interface CompletionArgs {
|
|
|
279
284
|
fsRoots?: readonly DriveRoot[];
|
|
280
285
|
fsWriteRoots?: readonly DriveRoot[];
|
|
281
286
|
}
|
|
287
|
+
export declare const CONTEXT_OVERFLOW_MESSAGE = "This message is too large for the model context window.";
|
|
282
288
|
export declare function trustedSourcesFrom(history: readonly Message[]): TrustedSource[];
|
|
283
289
|
export declare function toSdkTool(tool: ToolDefinition): SdkTool;
|
|
284
290
|
export type CompletionSkillEntry = SkillCatalogEntry & {
|
|
@@ -26,6 +26,7 @@ import { isAvailableState } from "../tools/skills/skill-state-constants.js";
|
|
|
26
26
|
import { resolveImplicitSkill, resolveSkillMention, skillPreloadMessage } from "../tools/skills/mention.js";
|
|
27
27
|
import { permittedTools } from "../tools/toolsets.js";
|
|
28
28
|
import { promptPrefixHash } from "./prompt-prefix.js";
|
|
29
|
+
import { logToolPrompt } from "./tool-prompt-log.js";
|
|
29
30
|
import { DIRECT_CALL_ID, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn, withoutSkillLoadTurns } from "./tool-turns.js";
|
|
30
31
|
import { projectVisionHistory, visionHistoryCacheKey } from "./vision-history.js";
|
|
31
32
|
export function imageSettingsFrom(agent) {
|
|
@@ -49,7 +50,7 @@ function runWallClockBudgetMs() {
|
|
|
49
50
|
const ms = Number(process.env.QVAC_RUN_BUDGET_MS);
|
|
50
51
|
return ms > 0 ? ms : RUN_WALL_CLOCK_BUDGET_MS;
|
|
51
52
|
}
|
|
52
|
-
const CONTEXT_OVERFLOW_MESSAGE = 'This message is too large for the model context window.';
|
|
53
|
+
export const CONTEXT_OVERFLOW_MESSAGE = 'This message is too large for the model context window.';
|
|
53
54
|
const MAX_MUTE_ROUNDS = 1;
|
|
54
55
|
const NARRATED_INTENT = /\b(I['’]ll|I will|Now I|Let me(?! know)|Next,? I)\b[^.!?]*[.!?]?\s*$/i;
|
|
55
56
|
const MAX_WEB_SEARCHES_PER_TURN = 3;
|
|
@@ -72,25 +73,25 @@ function originOfToolTurn(text) {
|
|
|
72
73
|
return text.startsWith(`[Tool: ${SKILL_TOOL_NAME} (`) ? 'skill' : 'tool-result';
|
|
73
74
|
}
|
|
74
75
|
const noop = () => { };
|
|
75
|
-
function intentParameters(tool) {
|
|
76
|
-
if (tool.autoApprove === true || INTENT_PARAM in
|
|
77
|
-
return
|
|
76
|
+
function intentParameters(tool, params) {
|
|
77
|
+
if (tool.autoApprove === true || INTENT_PARAM in params.properties) {
|
|
78
|
+
return params;
|
|
78
79
|
}
|
|
79
80
|
return {
|
|
80
|
-
...
|
|
81
|
+
...params,
|
|
81
82
|
properties: {
|
|
82
83
|
[INTENT_PARAM]: { type: 'string', description: INTENT_PARAM_DESCRIPTION },
|
|
83
|
-
...
|
|
84
|
+
...params.properties
|
|
84
85
|
},
|
|
85
|
-
required: [INTENT_PARAM, ...(
|
|
86
|
+
required: [INTENT_PARAM, ...(params.required ?? [])]
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
export function toSdkTool(tool) {
|
|
89
90
|
return {
|
|
90
91
|
type: 'function',
|
|
91
92
|
name: tool.name,
|
|
92
|
-
description: tool.description,
|
|
93
|
-
parameters: intentParameters(tool)
|
|
93
|
+
description: tool.prompt?.description ?? tool.description,
|
|
94
|
+
parameters: intentParameters(tool, tool.prompt?.parameters ?? tool.parameters)
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
function sanitizeTurn(turn) {
|
|
@@ -597,18 +598,21 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
597
598
|
};
|
|
598
599
|
const activeSkills = inContextSkills.filter((skill) => skillIsActive(skill.name, activation));
|
|
599
600
|
const { grantedNames, grantedOwners, grantedOps } = collectToolGrants(activeSkills);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
601
|
+
function selectEnabled() {
|
|
602
|
+
if (responseFormat)
|
|
603
|
+
return [];
|
|
604
|
+
const loaded = activeSkills.filter((skill) => loadedSkills.has(skill.name));
|
|
605
|
+
return enabledTools(tools, {
|
|
603
606
|
toolsSelected,
|
|
604
607
|
allowlist: agent?.enabledTools ?? [],
|
|
605
608
|
grantedNames,
|
|
606
609
|
grantedOwners,
|
|
610
|
+
loadedGrantedNames: collectToolGrants(loaded).grantedNames,
|
|
607
611
|
hasRoots: fsRoots.length > 0,
|
|
608
612
|
hasWriteRoots: writableRoots.length > 0,
|
|
609
613
|
hasActiveSkills: activeSkills.length > 0
|
|
610
614
|
});
|
|
611
|
-
|
|
615
|
+
}
|
|
612
616
|
const reasoningEnabled = agent?.reasoning === 'enabled' ? true : agent?.reasoning === 'disabled' ? false : undefined;
|
|
613
617
|
const deviceReasoning = thinkingDefault === 'enabled' ? true : thinkingDefault === 'disabled' ? false : undefined;
|
|
614
618
|
const resolvedGenerationParams = resolveLlmGenerationParams(model, reasoningEnabled, deviceReasoning);
|
|
@@ -672,14 +676,35 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
672
676
|
loadedThisTurn.add(preload.name);
|
|
673
677
|
skillIcon = skillEmoji(preload.name) ?? skillIcon;
|
|
674
678
|
}
|
|
679
|
+
let enabled = selectEnabled();
|
|
680
|
+
let sdkTools = enabled.length ? enabled.map((tool) => toSdkTool(tool)) : undefined;
|
|
675
681
|
const system = systemPromptParts({
|
|
676
682
|
bio: agent?.bio,
|
|
677
683
|
toolNames: enabled.map((tool) => tool.name),
|
|
678
684
|
skillsSection: skillsPromptSection(activeSkills)
|
|
679
685
|
});
|
|
680
686
|
const systemContent = system.text;
|
|
681
|
-
|
|
682
|
-
|
|
687
|
+
let promptPrefix = promptPrefixHash({ system: systemContent, tools: sdkTools });
|
|
688
|
+
let promptParts = promptUsageParts({ system, toolSchemas: sdkTools });
|
|
689
|
+
function logSelection() {
|
|
690
|
+
logToolPrompt({
|
|
691
|
+
promptPrefix,
|
|
692
|
+
modelId,
|
|
693
|
+
cacheKey,
|
|
694
|
+
agentId: agent?.id,
|
|
695
|
+
messageCount: messages.length,
|
|
696
|
+
responseFormat: responseFormat?.type,
|
|
697
|
+
tools: sdkTools,
|
|
698
|
+
definitions: enabled,
|
|
699
|
+
activeSkills: activeSkills.map((skill) => skill.name),
|
|
700
|
+
skillsSelected,
|
|
701
|
+
enabledSkills: agent?.enabledSkills ?? [],
|
|
702
|
+
mentionedSkill: mention?.name ?? null,
|
|
703
|
+
loadedSkills,
|
|
704
|
+
toolOps: grantedOps
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
logSelection();
|
|
683
708
|
let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...turns] : turns);
|
|
684
709
|
let roundCacheKey = stubDropped ? undefined : visionHistoryCacheKey(cacheKey, history);
|
|
685
710
|
const runUsage = () => usageBreakdown(promptParts, stats.cacheTokens, model);
|
|
@@ -771,6 +796,14 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
771
796
|
outcome = 'time-capped';
|
|
772
797
|
break;
|
|
773
798
|
}
|
|
799
|
+
const reselected = selectEnabled();
|
|
800
|
+
if (reselected.map((tool) => tool.name).join() !== enabled.map((tool) => tool.name).join()) {
|
|
801
|
+
enabled = reselected;
|
|
802
|
+
sdkTools = enabled.length ? enabled.map((tool) => toSdkTool(tool)) : undefined;
|
|
803
|
+
promptPrefix = promptPrefixHash({ system: systemContent, tools: sdkTools });
|
|
804
|
+
promptParts = promptUsageParts({ system, toolSchemas: sdkTools });
|
|
805
|
+
logSelection();
|
|
806
|
+
}
|
|
774
807
|
hlog('round start', {
|
|
775
808
|
round,
|
|
776
809
|
turns: history.length,
|
|
@@ -5,10 +5,12 @@ import { type ModelAddon, type ModelRegistrySdk } from '../models/model-source.t
|
|
|
5
5
|
export type DownloadProgress = Omit<ModelProgressUpdate, 'type'>;
|
|
6
6
|
export type DownloadProgressEvent = {
|
|
7
7
|
type: 'progress';
|
|
8
|
+
transferred?: boolean;
|
|
8
9
|
} & DownloadProgress;
|
|
9
10
|
export type DownloadCompleteEvent = {
|
|
10
11
|
type: 'complete';
|
|
11
12
|
assetId: string;
|
|
13
|
+
downloaded?: boolean;
|
|
12
14
|
};
|
|
13
15
|
export type DownloadErrorEvent = {
|
|
14
16
|
type: 'error';
|
|
@@ -5,6 +5,7 @@ const noop = () => { };
|
|
|
5
5
|
export function download(sdkOrLoader, args, onSettled) {
|
|
6
6
|
const { model, addon = 'llm', seed, signal, projection, companions = [], assetSizes } = args;
|
|
7
7
|
const loadSdk = typeof sdkOrLoader === 'function' ? sdkOrLoader : () => Promise.resolve(sdkOrLoader);
|
|
8
|
+
let transferred = false;
|
|
8
9
|
let settled = false;
|
|
9
10
|
let opDone = false;
|
|
10
11
|
let cancelSent = false;
|
|
@@ -30,7 +31,7 @@ export function download(sdkOrLoader, args, onSettled) {
|
|
|
30
31
|
};
|
|
31
32
|
const onProgress = (p) => {
|
|
32
33
|
if (!settled)
|
|
33
|
-
stream.push({ ...p, type: 'progress' });
|
|
34
|
+
stream.push({ ...p, type: 'progress', transferred });
|
|
34
35
|
};
|
|
35
36
|
const start = async () => {
|
|
36
37
|
if (signal?.aborted)
|
|
@@ -56,14 +57,22 @@ export function download(sdkOrLoader, args, onSettled) {
|
|
|
56
57
|
totalBytes: aggregate.totalBytes
|
|
57
58
|
})
|
|
58
59
|
: onProgress;
|
|
59
|
-
const op = sdkRef.downloadAsset({
|
|
60
|
+
const op = sdkRef.downloadAsset({
|
|
61
|
+
assetSrc: srcs[i],
|
|
62
|
+
seed,
|
|
63
|
+
onProgress: (update) => {
|
|
64
|
+
if (update.percentage < 100)
|
|
65
|
+
transferred = true;
|
|
66
|
+
progress(update);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
60
69
|
requestId = op.requestId;
|
|
61
70
|
const assetId = await op;
|
|
62
71
|
if (i === 0)
|
|
63
72
|
mainAssetId = assetId;
|
|
64
73
|
}
|
|
65
74
|
opDone = true;
|
|
66
|
-
finish({ type: 'complete', assetId: mainAssetId });
|
|
75
|
+
finish({ type: 'complete', assetId: mainAssetId, downloaded: transferred });
|
|
67
76
|
}
|
|
68
77
|
catch (err) {
|
|
69
78
|
opDone = true;
|
|
@@ -9,5 +9,6 @@ export type KvCacheDeletionResult = {
|
|
|
9
9
|
};
|
|
10
10
|
type KvCacheSdk = Pick<typeof import('@qvac/sdk'), 'deleteCache'>;
|
|
11
11
|
export declare function deleteAllKvCaches(loadSdk: () => Promise<KvCacheSdk>): Promise<KvCacheDeletionResult>;
|
|
12
|
+
export declare function deleteKvCacheKey(loadSdk: () => Promise<KvCacheSdk>, kvCacheKey: string): Promise<KvCacheDeletionResult>;
|
|
12
13
|
export declare function unsupportedKvCacheDeletion(error?: string): KvCacheDeletionResult;
|
|
13
14
|
export {};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export async function deleteAllKvCaches(loadSdk) {
|
|
2
|
+
return deleteCacheWith(loadSdk, { all: true });
|
|
3
|
+
}
|
|
4
|
+
export async function deleteKvCacheKey(loadSdk, kvCacheKey) {
|
|
5
|
+
return deleteCacheWith(loadSdk, { kvCacheKey });
|
|
6
|
+
}
|
|
7
|
+
async function deleteCacheWith(loadSdk, params) {
|
|
2
8
|
try {
|
|
3
9
|
const sdk = await loadSdk();
|
|
4
|
-
const result = await sdk.deleteCache(
|
|
10
|
+
const result = await sdk.deleteCache(params);
|
|
5
11
|
return result.success
|
|
6
12
|
? { status: 'success' }
|
|
7
13
|
: { status: 'failure', error: 'SDK cache deletion did not succeed' };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Tool as SdkTool } from '@qvac/sdk';
|
|
2
|
+
export interface ToolOwnership {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly skillGated?: boolean | undefined;
|
|
5
|
+
readonly owningSkill?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface ToolPromptLogOptions {
|
|
8
|
+
readonly promptPrefix: string;
|
|
9
|
+
readonly modelId: string;
|
|
10
|
+
readonly cacheKey: string | undefined;
|
|
11
|
+
readonly agentId: string | undefined;
|
|
12
|
+
readonly messageCount: number;
|
|
13
|
+
readonly responseFormat: string | undefined;
|
|
14
|
+
readonly tools: ReadonlyArray<SdkTool> | undefined;
|
|
15
|
+
readonly definitions: ReadonlyArray<ToolOwnership>;
|
|
16
|
+
readonly activeSkills: ReadonlyArray<string>;
|
|
17
|
+
readonly skillsSelected: boolean;
|
|
18
|
+
readonly enabledSkills: ReadonlyArray<string>;
|
|
19
|
+
readonly mentionedSkill: string | null;
|
|
20
|
+
readonly loadedSkills: ReadonlySet<string>;
|
|
21
|
+
readonly toolOps: ReadonlyMap<string, ReadonlyArray<unknown>>;
|
|
22
|
+
}
|
|
23
|
+
export declare function logToolPrompt({ promptPrefix, modelId, cacheKey, agentId, messageCount, responseFormat, tools, definitions, activeSkills, skillsSelected, enabledSkills, mentionedSkill, loadedSkills, toolOps }: ToolPromptLogOptions): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { harnessLogEnabled, hlog } from "../log.js";
|
|
2
|
+
export function logToolPrompt({ promptPrefix, modelId, cacheKey, agentId, messageCount, responseFormat, tools = [], definitions, activeSkills, skillsSelected, enabledSkills, mentionedSkill, loadedSkills, toolOps }) {
|
|
3
|
+
if (!harnessLogEnabled())
|
|
4
|
+
return;
|
|
5
|
+
hlog('sdk tool definitions', {
|
|
6
|
+
promptPrefix,
|
|
7
|
+
modelId,
|
|
8
|
+
cacheKey: cacheKey ?? null,
|
|
9
|
+
agentId: agentId ?? null,
|
|
10
|
+
messageCount,
|
|
11
|
+
responseFormat: responseFormat ?? 'text',
|
|
12
|
+
schemaChars: tools.length ? JSON.stringify(tools).length : 0,
|
|
13
|
+
tools: rankedTools(tools, definitions),
|
|
14
|
+
skillGatedChars: skillGatedChars(tools, definitions),
|
|
15
|
+
activeSkills,
|
|
16
|
+
skillActivation: {
|
|
17
|
+
scope: skillsSelected ? 'selected' : 'all',
|
|
18
|
+
enabled: enabledSkills,
|
|
19
|
+
mentioned: mentionedSkill,
|
|
20
|
+
loaded: [...loadedSkills]
|
|
21
|
+
},
|
|
22
|
+
localCliValidators: [...toolOps].map(([bin, validators]) => ({
|
|
23
|
+
bin,
|
|
24
|
+
count: validators.length
|
|
25
|
+
}))
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function ownerOf(definitions, name) {
|
|
29
|
+
return definitions.find((definition) => definition.name === name);
|
|
30
|
+
}
|
|
31
|
+
function rankedTools(tools, definitions) {
|
|
32
|
+
return tools
|
|
33
|
+
.map((tool) => {
|
|
34
|
+
const owner = ownerOf(definitions, tool.name);
|
|
35
|
+
return {
|
|
36
|
+
name: tool.name,
|
|
37
|
+
schemaChars: JSON.stringify(tool).length,
|
|
38
|
+
descriptionChars: descriptionChars(tool),
|
|
39
|
+
parameters: Object.keys(tool.parameters.properties),
|
|
40
|
+
required: tool.parameters.required ?? [],
|
|
41
|
+
skillGated: owner?.skillGated === true,
|
|
42
|
+
owningSkill: owner?.owningSkill ?? null
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
.sort((a, b) => b.schemaChars - a.schemaChars);
|
|
46
|
+
}
|
|
47
|
+
function skillGatedChars(tools, definitions) {
|
|
48
|
+
let chars = 0;
|
|
49
|
+
for (const tool of tools) {
|
|
50
|
+
if (ownerOf(definitions, tool.name)?.skillGated !== true)
|
|
51
|
+
continue;
|
|
52
|
+
chars += JSON.stringify(tool).length;
|
|
53
|
+
}
|
|
54
|
+
return chars;
|
|
55
|
+
}
|
|
56
|
+
function descriptionChars(tool) {
|
|
57
|
+
let chars = tool.description.length;
|
|
58
|
+
for (const parameter of Object.values(tool.parameters.properties)) {
|
|
59
|
+
chars += parameter.description?.length ?? 0;
|
|
60
|
+
}
|
|
61
|
+
return chars;
|
|
62
|
+
}
|
|
@@ -35,9 +35,10 @@ export interface ToolGateInputs {
|
|
|
35
35
|
allowlist: ReadonlyArray<string>;
|
|
36
36
|
grantedNames: ReadonlySet<string>;
|
|
37
37
|
grantedOwners: ReadonlyMap<string, ReadonlySet<string>>;
|
|
38
|
+
loadedGrantedNames: ReadonlySet<string>;
|
|
38
39
|
hasRoots: boolean;
|
|
39
40
|
hasWriteRoots: boolean;
|
|
40
41
|
hasActiveSkills: boolean;
|
|
41
42
|
}
|
|
42
43
|
export declare function isSelected(scope: 'all' | 'selected' | null | undefined, list?: ReadonlyArray<string> | null): boolean;
|
|
43
|
-
export declare function enabledTools<T extends SelectableTool>(tools: ReadonlyArray<T>, { toolsSelected, allowlist, grantedNames, grantedOwners, hasRoots, hasWriteRoots, hasActiveSkills }: ToolGateInputs): T[];
|
|
44
|
+
export declare function enabledTools<T extends SelectableTool>(tools: ReadonlyArray<T>, { toolsSelected, allowlist, grantedNames, grantedOwners, loadedGrantedNames, hasRoots, hasWriteRoots, hasActiveSkills }: ToolGateInputs): T[];
|
|
@@ -35,7 +35,7 @@ export function skillIsActive(name, { skillsSelected, enabledSkills, mention, lo
|
|
|
35
35
|
export function isSelected(scope, list) {
|
|
36
36
|
return (scope ?? (list?.length ? 'selected' : 'all')) === 'selected';
|
|
37
37
|
}
|
|
38
|
-
export function enabledTools(tools, { toolsSelected, allowlist, grantedNames, grantedOwners, hasRoots, hasWriteRoots, hasActiveSkills }) {
|
|
38
|
+
export function enabledTools(tools, { toolsSelected, allowlist, grantedNames, grantedOwners, loadedGrantedNames, hasRoots, hasWriteRoots, hasActiveSkills }) {
|
|
39
39
|
return tools.filter((tool) => {
|
|
40
40
|
if (tool.name === SKILL_TOOL_NAME)
|
|
41
41
|
return hasActiveSkills;
|
|
@@ -52,7 +52,7 @@ export function enabledTools(tools, { toolsSelected, allowlist, grantedNames, gr
|
|
|
52
52
|
if (tool.writeRootsRequired)
|
|
53
53
|
return hasWriteRoots;
|
|
54
54
|
if (tool.skillGated)
|
|
55
|
-
return
|
|
55
|
+
return loadedGrantedNames.has(tool.name);
|
|
56
56
|
if (!toolsSelected)
|
|
57
57
|
return true;
|
|
58
58
|
return allowlist.includes(tool.name) || grantedNames.has(tool.name);
|
|
@@ -65,6 +65,8 @@ export declare class RemoteHarness extends EventEmitter implements HarnessContra
|
|
|
65
65
|
list(options?: ListModelsOptions): Promise<ModelInfo[]>;
|
|
66
66
|
deleteModel(name: string): Promise<DeleteModelSummary>;
|
|
67
67
|
clearKvCache(): Promise<KvCacheDeletionResult>;
|
|
68
|
+
deleteKvCache(kvCacheKey: string): Promise<KvCacheDeletionResult>;
|
|
69
|
+
private _deleteKvCaches;
|
|
68
70
|
infraModels(profile: string): Promise<Array<{
|
|
69
71
|
name: string;
|
|
70
72
|
addon: string;
|
package/dist-lib/lib/connect.js
CHANGED
|
@@ -524,12 +524,18 @@ export class RemoteHarness extends EventEmitter {
|
|
|
524
524
|
return { name: response.name, removed: response.removed, freedBytes: response.freedBytes };
|
|
525
525
|
}
|
|
526
526
|
async clearKvCache() {
|
|
527
|
+
return this._deleteKvCaches((client) => client.clearKvCache({}));
|
|
528
|
+
}
|
|
529
|
+
async deleteKvCache(kvCacheKey) {
|
|
530
|
+
return this._deleteKvCaches((client) => client.deleteKvCache({ kvCacheKey }));
|
|
531
|
+
}
|
|
532
|
+
async _deleteKvCaches(call) {
|
|
527
533
|
try {
|
|
528
534
|
const { client } = await this._ensure();
|
|
529
535
|
const info = await client.describe({});
|
|
530
536
|
if (info.supportsKvCacheDeletion !== true)
|
|
531
537
|
return unsupportedKvCacheDeletion();
|
|
532
|
-
const response = await client
|
|
538
|
+
const response = await call(client);
|
|
533
539
|
if (response.status === 'success')
|
|
534
540
|
return { status: 'success' };
|
|
535
541
|
if (response.status === 'failure') {
|
|
@@ -191,6 +191,7 @@ export interface HarnessContract {
|
|
|
191
191
|
warmModel?(context: WarmModelContext): Promise<void>;
|
|
192
192
|
deleteModel?(name: string): Promise<DeleteModelSummary>;
|
|
193
193
|
clearKvCache?(): Promise<KvCacheDeletionResult>;
|
|
194
|
+
deleteKvCache?(kvCacheKey: string): Promise<KvCacheDeletionResult>;
|
|
194
195
|
infraModels?(profile: string): Promise<ReadonlyArray<{
|
|
195
196
|
name: string;
|
|
196
197
|
addon: string;
|
|
@@ -5,7 +5,7 @@ import { type ModelAddon } from './model-source.ts';
|
|
|
5
5
|
import type { GpuFaultState } from './sdk-faults.ts';
|
|
6
6
|
type SdkModule = typeof import('@qvac/sdk');
|
|
7
7
|
export interface ModelState {
|
|
8
|
-
status: 'loading' | 'downloading' | 'ready' | 'unloading' | 'unloaded';
|
|
8
|
+
status: 'loading' | 'downloading' | 'downloaded' | 'ready' | 'unloading' | 'unloaded';
|
|
9
9
|
model: string;
|
|
10
10
|
[key: string]: unknown;
|
|
11
11
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { retryMemo } from "./helpers/retry-memo.js";
|
|
2
2
|
import { hlog } from "./log.js";
|
|
3
3
|
const defaultImports = {
|
|
4
|
-
sdk: () => import('
|
|
4
|
+
sdk: () => import('#sdk'),
|
|
5
5
|
plugins: () => import('@qvac/sdk/plugins'),
|
|
6
6
|
llm: () => import('@qvac/sdk/llamacpp-completion/plugin'),
|
|
7
7
|
embeddings: () => import('@qvac/sdk/llamacpp-embedding/plugin'),
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -16,6 +16,7 @@ import { packVectors, toDownloadWire, toWire } from "./wire.js";
|
|
|
16
16
|
const MODEL_STATUSES = new Set([
|
|
17
17
|
'loading',
|
|
18
18
|
'downloading',
|
|
19
|
+
'downloaded',
|
|
19
20
|
'ready',
|
|
20
21
|
'error',
|
|
21
22
|
'unloading',
|
|
@@ -133,6 +134,7 @@ export function serveHarness(stream, harness) {
|
|
|
133
134
|
return (await harness.deleteModel?.(name)) ?? { name, removed: 0, freedBytes: 0 };
|
|
134
135
|
},
|
|
135
136
|
clearKvCache: async () => (await harness.clearKvCache?.()) ?? unsupportedKvCacheDeletion(),
|
|
137
|
+
deleteKvCache: async ({ kvCacheKey }) => (await harness.deleteKvCache?.(kvCacheKey)) ?? unsupportedKvCacheDeletion(),
|
|
136
138
|
infraModels: async ({ profile }) => ({
|
|
137
139
|
models: (await harness.infraModels?.(profile ?? '')) ?? []
|
|
138
140
|
}),
|