@qvac/harness 0.8.8 → 0.8.10
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/CHANGELOG.md +80 -1
- package/dist-lib/index.d.ts +1 -1
- package/dist-lib/index.js +51 -50
- package/dist-lib/lib/audio-transcode-bare.js +2 -2
- package/dist-lib/lib/capabilities/completion.d.ts +1 -2
- package/dist-lib/lib/capabilities/completion.js +29 -25
- package/dist-lib/lib/capabilities/tool-prompt-log.js +3 -3
- package/dist-lib/lib/capabilities/voice.js +7 -6
- package/dist-lib/lib/connect.js +18 -17
- package/dist-lib/lib/errors.d.ts +266 -0
- package/dist-lib/lib/errors.js +189 -0
- package/dist-lib/lib/helpers/load-stall.d.ts +1 -1
- package/dist-lib/lib/helpers/load-stall.js +2 -2
- package/dist-lib/lib/helpers/race-abort.d.ts +2 -1
- package/dist-lib/lib/helpers/race-abort.js +3 -4
- package/dist-lib/lib/hooks/built-in/arg-repair.js +2 -2
- package/dist-lib/lib/hooks/pipeline.js +4 -4
- package/dist-lib/lib/hooks/run.js +2 -2
- package/dist-lib/lib/log.d.ts +26 -2
- package/dist-lib/lib/log.js +66 -16
- package/dist-lib/lib/models/model-pool.js +11 -6
- package/dist-lib/lib/models/model-source.js +3 -2
- package/dist-lib/lib/no-redirect-fetch-bare.js +2 -1
- package/dist-lib/lib/no-redirect-fetch-impl.js +2 -1
- package/dist-lib/lib/office/extract.js +4 -3
- package/dist-lib/lib/office/index.js +2 -1
- package/dist-lib/lib/resolution-detector.js +3 -3
- package/dist-lib/lib/rpc-session.js +2 -1
- package/dist-lib/lib/sdk-loader.js +2 -2
- package/dist-lib/lib/serve.js +33 -29
- package/dist-lib/lib/spawn.d.ts +2 -4
- package/dist-lib/lib/spawn.js +14 -7
- package/dist-lib/lib/tools/edit-image.js +2 -1
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +8 -8
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +7 -6
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +8 -8
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +11 -7
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +5 -5
- package/dist-lib/lib/tools/exec/adapters/python/worker/active.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +9 -8
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -0
- package/dist-lib/lib/tools/exec/adapters/python/worker/serve.js +4 -4
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn.d.ts +2 -1
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/note-name.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/safe-path.js +5 -4
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +3 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-fs.js +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +3 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +6 -5
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +13 -12
- package/dist-lib/lib/tools/exec/runner.js +2 -1
- package/dist-lib/lib/tools/files/edit-file.d.ts +3 -0
- package/dist-lib/lib/tools/files/edit-file.js +73 -0
- package/dist-lib/lib/tools/files/glob.d.ts +3 -0
- package/dist-lib/lib/tools/files/glob.js +63 -0
- package/dist-lib/lib/tools/files/grep.d.ts +3 -0
- package/dist-lib/lib/tools/files/grep.js +114 -0
- package/dist-lib/lib/tools/files/index.d.ts +3 -0
- package/dist-lib/lib/tools/files/index.js +23 -0
- package/dist-lib/lib/tools/files/list-dir.d.ts +5 -0
- package/dist-lib/lib/tools/files/list-dir.js +130 -0
- package/dist-lib/lib/tools/files/make-dir.d.ts +3 -0
- package/dist-lib/lib/tools/files/make-dir.js +47 -0
- package/dist-lib/lib/tools/files/read-file.d.ts +3 -0
- package/dist-lib/lib/tools/files/read-file.js +103 -0
- package/dist-lib/lib/tools/files/transfer.d.ts +4 -0
- package/dist-lib/lib/tools/files/transfer.js +82 -0
- package/dist-lib/lib/tools/files/trash-file.d.ts +4 -0
- package/dist-lib/lib/tools/files/trash-file.js +61 -0
- package/dist-lib/lib/tools/files/write-file.d.ts +3 -0
- package/dist-lib/lib/tools/files/write-file.js +55 -0
- package/dist-lib/lib/tools/fs/document-text.d.ts +2 -0
- package/dist-lib/lib/tools/fs/document-text.js +38 -0
- package/dist-lib/lib/tools/fs/edit-match.d.ts +11 -0
- package/dist-lib/lib/tools/fs/edit-match.js +85 -0
- package/dist-lib/lib/tools/fs/remote.d.ts +2 -1
- package/dist-lib/lib/tools/fs/remote.js +4 -4
- package/dist-lib/lib/tools/fs/scope.d.ts +1 -1
- package/dist-lib/lib/tools/fs/scope.js +3 -2
- package/dist-lib/lib/tools/fs/targets.d.ts +10 -0
- package/dist-lib/lib/tools/fs/targets.js +32 -0
- package/dist-lib/lib/tools/fs/tool-path.d.ts +24 -0
- package/dist-lib/lib/tools/fs/tool-path.js +58 -0
- package/dist-lib/lib/tools/fs/walk.js +3 -3
- package/dist-lib/lib/tools/generate-image.d.ts +2 -2
- package/dist-lib/lib/tools/generate-image.js +5 -6
- package/dist-lib/lib/tools/office.js +2 -1
- package/dist-lib/lib/tools/skill.js +2 -1
- package/dist-lib/lib/tools/skills/bundled-dir.js +4 -4
- package/dist-lib/lib/tools/skills/catalog.js +2 -2
- package/dist-lib/lib/tools/skills/credential-health.js +3 -3
- package/dist-lib/lib/tools/skills/mention.js +7 -2
- package/dist-lib/lib/tools/skills/overlay-crud.js +3 -2
- package/dist-lib/lib/tools/translate.d.ts +2 -2
- package/dist-lib/lib/tools/translate.js +5 -6
- package/dist-lib/lib/tools/weather-lookup/geocode.js +5 -4
- package/dist-lib/lib/web-search/engines/bing.js +2 -1
- package/dist-lib/lib/web-search/engines/google.js +2 -1
- package/dist-lib/lib/web-search/fetch-html.d.ts +5 -5
- package/dist-lib/lib/web-search/fetch-html.js +12 -15
- package/package.json +67 -25
- package/worker/harness-sidecar.mjs +12 -13
- package/dist-lib/lib/tools/create-documents.d.ts +0 -3
- package/dist-lib/lib/tools/create-documents.js +0 -95
- package/dist-lib/lib/tools/edit-documents.d.ts +0 -3
- package/dist-lib/lib/tools/edit-documents.js +0 -132
- package/dist-lib/lib/tools/file-glob.d.ts +0 -14
- package/dist-lib/lib/tools/file-glob.js +0 -118
- package/dist-lib/lib/tools/grep.d.ts +0 -5
- package/dist-lib/lib/tools/grep.js +0 -184
- package/dist-lib/lib/tools/read-files.d.ts +0 -5
- package/dist-lib/lib/tools/read-files.js +0 -157
package/dist-lib/lib/log.js
CHANGED
|
@@ -1,31 +1,81 @@
|
|
|
1
|
+
import QvacLogger, {} from '@qvac/logging';
|
|
2
|
+
import { ENV_LOG_LEVEL, LEVEL_PRIORITIES } from '@qvac/logging/constants';
|
|
1
3
|
import process from 'process';
|
|
2
|
-
|
|
4
|
+
const HOST_SCOPE = 'harness-client';
|
|
5
|
+
let sink = null;
|
|
6
|
+
let host = null;
|
|
7
|
+
const logger = new QvacLogger({
|
|
8
|
+
level: 'debug',
|
|
9
|
+
error: writer('error'),
|
|
10
|
+
warn: writer('warn'),
|
|
11
|
+
info: writer('info'),
|
|
12
|
+
debug: writer('debug')
|
|
13
|
+
});
|
|
14
|
+
const fromEnv = envLevel();
|
|
15
|
+
let configured = logger.getLevel();
|
|
16
|
+
if (process.env.QVAC_HARNESS_LOG !== '1') {
|
|
17
|
+
logger.setLevel('off');
|
|
18
|
+
}
|
|
19
|
+
export const log = logger;
|
|
3
20
|
export function setHarnessLog(on) {
|
|
4
|
-
|
|
21
|
+
logger.setLevel(on ? configured : 'off');
|
|
5
22
|
}
|
|
6
|
-
export function
|
|
7
|
-
|
|
23
|
+
export function setHarnessLogLevel(level) {
|
|
24
|
+
logger.setLevel(level);
|
|
25
|
+
}
|
|
26
|
+
export function followHostLevel(level) {
|
|
27
|
+
configured = fromEnv ?? (isLogLevel(level) ? level : 'debug');
|
|
28
|
+
logger.setLevel(configured);
|
|
29
|
+
}
|
|
30
|
+
export function logToHost(next) {
|
|
31
|
+
host = next;
|
|
32
|
+
if (next) {
|
|
33
|
+
followHostLevel(next.getLevel?.());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function harnessLogLevel() {
|
|
37
|
+
return logger.getLevel();
|
|
38
|
+
}
|
|
39
|
+
export function isLogLevel(value) {
|
|
40
|
+
return typeof value === 'string' && Object.hasOwn(LEVEL_PRIORITIES, value);
|
|
41
|
+
}
|
|
42
|
+
export function logEnabled(level) {
|
|
43
|
+
const current = logger.getLevel();
|
|
44
|
+
return current !== 'off' && LEVEL_PRIORITIES[level] <= LEVEL_PRIORITIES[current];
|
|
8
45
|
}
|
|
9
|
-
let sink = null;
|
|
10
46
|
export function setHarnessLogSink(next) {
|
|
11
47
|
sink = next;
|
|
12
48
|
}
|
|
13
49
|
export function hlog(message, data) {
|
|
14
|
-
|
|
15
|
-
return;
|
|
16
|
-
const tail = data === undefined ? '' : ` ${safeJson(data)}`;
|
|
17
|
-
const line = `${message}${tail}`;
|
|
18
|
-
if (sink) {
|
|
19
|
-
sink(line);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
process.stdout.write(`${line}\n`);
|
|
50
|
+
log.info(message, data);
|
|
23
51
|
}
|
|
24
52
|
export function hdiag(message, data) {
|
|
25
53
|
if (!sink)
|
|
26
54
|
return;
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
sink(format(message, data), 'info');
|
|
56
|
+
}
|
|
57
|
+
function writer(level) {
|
|
58
|
+
return (message, data) => {
|
|
59
|
+
const line = format(message, data);
|
|
60
|
+
if (sink) {
|
|
61
|
+
sink(line, level);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (host) {
|
|
65
|
+
const write = host[level] ?? host.info;
|
|
66
|
+
write.call(host, HOST_SCOPE, line);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(`${line}\n`);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function envLevel() {
|
|
73
|
+
const value = process.env[ENV_LOG_LEVEL] || process.env[`EXPO_PUBLIC_${ENV_LOG_LEVEL}`];
|
|
74
|
+
const level = value?.toLowerCase();
|
|
75
|
+
return isLogLevel(level) ? level : null;
|
|
76
|
+
}
|
|
77
|
+
function format(message, data) {
|
|
78
|
+
return data === undefined ? message : `${message} ${safeJson(data)}`;
|
|
29
79
|
}
|
|
30
80
|
function safeJson(data) {
|
|
31
81
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from 'process';
|
|
2
|
+
import { HarnessError } from '../errors.js';
|
|
2
3
|
import { transferProgress } from '../model-download-progress.js';
|
|
3
4
|
import ResourcePuddle from 'resource-puddle';
|
|
4
5
|
import { ModelHandle } from './model-handle.js';
|
|
@@ -6,7 +7,7 @@ import { defaultContextSize } from './context.js';
|
|
|
6
7
|
import { customContextLength } from './model-catalog.js';
|
|
7
8
|
import { adrenoSafeKvCache } from './kv-cache.js';
|
|
8
9
|
import { DEFAULT_EMBED_BATCH_SIZE } from './embed-config.js';
|
|
9
|
-
import {
|
|
10
|
+
import { log } from '../log.js';
|
|
10
11
|
import { RUNAWAY_STOP_SEQUENCES } from '../helpers/control-tokens.js';
|
|
11
12
|
import { raceTimeout } from '../helpers/race-timeout.js';
|
|
12
13
|
import { resolveModelSrc } from './model-source.js';
|
|
@@ -330,7 +331,7 @@ export function loadClockGuard(model, sdk, { now = () => Date.now(), monoMs = mo
|
|
|
330
331
|
const mono = monoMs();
|
|
331
332
|
if (wall - lastWall < mono - lastMono - jumpMs) {
|
|
332
333
|
abandoned = true;
|
|
333
|
-
fail(new
|
|
334
|
+
fail(new HarnessError('MODEL_LOAD_CLOCK_SKEW', [model]));
|
|
334
335
|
return;
|
|
335
336
|
}
|
|
336
337
|
lastWall = wall;
|
|
@@ -393,7 +394,7 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
393
394
|
(chooseContext ? await chooseContext({ ...pending, modelConfig: config }, tier) : tier);
|
|
394
395
|
const modelConfig = { ...config, ctx_size: ctxSize };
|
|
395
396
|
const { projectionModelSrc: projection, ...loggedConfig } = modelConfig;
|
|
396
|
-
|
|
397
|
+
log.debug('llm load config', { model, projection: Boolean(projection), ...loggedConfig });
|
|
397
398
|
const admission = await beforeLoad?.({ ...pending, modelConfig });
|
|
398
399
|
const loadStarted = Date.now();
|
|
399
400
|
const modelId = await loadClockGuard(model, sdk)
|
|
@@ -404,7 +405,7 @@ export function createModelPool(loadSdk, notify, { deviceConfig = {}, loads = cr
|
|
|
404
405
|
onProgress: (progress) => notify(progressState(model, progress))
|
|
405
406
|
})))
|
|
406
407
|
.finally(() => admission?.release());
|
|
407
|
-
|
|
408
|
+
log.info('llm loaded', { model, loadMs: Date.now() - loadStarted });
|
|
408
409
|
return { model, modelId, ctxSize, bytes: admission?.bytes };
|
|
409
410
|
};
|
|
410
411
|
return new ModelPuddle({
|
|
@@ -476,7 +477,11 @@ export function createVoicePool(loadSdk, notify, { modelType, addon, modelConfig
|
|
|
476
477
|
.race(attempt().catch((err) => {
|
|
477
478
|
if (!wantsGpu || gpuRefused || loads.draining)
|
|
478
479
|
throw err;
|
|
479
|
-
|
|
480
|
+
log.warn('voice gpu load refused, retrying on cpu', {
|
|
481
|
+
model,
|
|
482
|
+
modelType,
|
|
483
|
+
error: String(err)
|
|
484
|
+
});
|
|
480
485
|
gpuRefused = true;
|
|
481
486
|
return attempt();
|
|
482
487
|
}))
|
|
@@ -510,7 +515,7 @@ function unloadOnGc(loadSdk, notify) {
|
|
|
510
515
|
await sdk.unloadModel({ modelId });
|
|
511
516
|
}
|
|
512
517
|
catch (err) {
|
|
513
|
-
|
|
518
|
+
log.warn('unload failed', { model, err: errorMessage(err) });
|
|
514
519
|
continue;
|
|
515
520
|
}
|
|
516
521
|
notify({ status: 'unloaded', model, catalogChanged: true });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HarnessError } from '../errors.js';
|
|
1
2
|
import { audioGenModelByName, curatedModelAsset, customModelSrc } from './model-catalog.js';
|
|
2
3
|
export async function resolveModelSrc(sdk, model, addon = 'llm') {
|
|
3
4
|
if (/^(\/|~|\.|file:|https?:|registry:|pear:|hyper:)/.test(model)) {
|
|
@@ -10,7 +11,7 @@ export async function resolveModelSrc(sdk, model, addon = 'llm') {
|
|
|
10
11
|
if (addon === 'audiogen') {
|
|
11
12
|
const audio = audioGenModelByName(model);
|
|
12
13
|
if (!audio) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new HarnessError('AUDIOGEN_MODEL_NOT_FOUND', [model]);
|
|
14
15
|
}
|
|
15
16
|
return audio.src;
|
|
16
17
|
}
|
|
@@ -20,7 +21,7 @@ export async function resolveModelSrc(sdk, model, addon = 'llm') {
|
|
|
20
21
|
}
|
|
21
22
|
const [entry] = await sdk.modelRegistrySearch({ filter: model, addon });
|
|
22
23
|
if (!entry) {
|
|
23
|
-
throw new
|
|
24
|
+
throw new HarnessError('MODEL_NOT_IN_REGISTRY', [model, addon]);
|
|
24
25
|
}
|
|
25
26
|
return { ...entry, src: `registry://${entry.registrySource}/${entry.registryPath}` };
|
|
26
27
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http, { Agent as HttpAgent } from 'bare-http1';
|
|
2
2
|
import https, { Agent as HttpsAgent } from 'bare-https';
|
|
3
3
|
import * as bareDns from 'bare-dns';
|
|
4
|
+
import { HarnessError } from './errors.js';
|
|
4
5
|
import { isPrivateOrBlocked } from './tools/private-ip.js';
|
|
5
6
|
import { makeValidatingLookup } from './validating-lookup.js';
|
|
6
7
|
import { runNoRedirectFetch } from './no-redirect-fetch-impl.js';
|
|
@@ -55,5 +56,5 @@ function readMethod(method) {
|
|
|
55
56
|
method === 'OPTIONS') {
|
|
56
57
|
return method;
|
|
57
58
|
}
|
|
58
|
-
throw new
|
|
59
|
+
throw new HarnessError('UNSUPPORTED_HTTP_METHOD', [method]);
|
|
59
60
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import b4a from 'b4a';
|
|
2
|
+
import { HarnessError } from './errors.js';
|
|
2
3
|
import { DEFAULT_USER_AGENT } from './user-agent.js';
|
|
3
4
|
const UA_SNIFFING_HOSTS = new Set(['wttr.in']);
|
|
4
5
|
export function runNoRedirectFetch(request, url, init = {}) {
|
|
@@ -55,7 +56,7 @@ export function runNoRedirectFetch(request, url, init = {}) {
|
|
|
55
56
|
req.destroy();
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
const err = new
|
|
59
|
+
const err = new HarnessError('RESPONSE_TOO_LARGE', [init.maxBytes]);
|
|
59
60
|
req.destroy(err);
|
|
60
61
|
fail(err);
|
|
61
62
|
return;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { HarnessError } from '../errors.js';
|
|
1
2
|
import { errorMessage } from '../guards.js';
|
|
2
3
|
const MAX_OFFICE_BYTES = 20 * 1024 * 1024;
|
|
3
4
|
export function extractWith(bytes, { fileName, signal, extract }) {
|
|
4
5
|
if (bytes.byteLength > MAX_OFFICE_BYTES) {
|
|
5
|
-
throw new
|
|
6
|
+
throw new HarnessError('OFFICE_TOO_LARGE', [bytes.byteLength, MAX_OFFICE_BYTES]);
|
|
6
7
|
}
|
|
7
8
|
if (signal?.aborted) {
|
|
8
|
-
throw new
|
|
9
|
+
throw new HarnessError('OFFICE_EXTRACTION_ABORTED', [fileName]);
|
|
9
10
|
}
|
|
10
11
|
try {
|
|
11
12
|
return extract(bytes);
|
|
12
13
|
}
|
|
13
14
|
catch (err) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new HarnessError('OFFICE_EXTRACTION_FAILED', [fileName, errorMessage(err)]);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
export function cellText(value) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import { HarnessError } from '../errors.js';
|
|
2
3
|
import { extractDocxText } from '../docx/index.js';
|
|
3
4
|
import { pptx, xlsx } from './engine/index.js';
|
|
4
5
|
import { cellText, extractWith } from './extract.js';
|
|
@@ -89,5 +90,5 @@ export async function extractOfficeText(bytes, fileName, options = {}) {
|
|
|
89
90
|
if (extract) {
|
|
90
91
|
return extractWith(bytes, { fileName, signal: options.signal, extract });
|
|
91
92
|
}
|
|
92
|
-
throw new
|
|
93
|
+
throw new HarnessError('OFFICE_UNSUPPORTED_DOCUMENT', [fileName]);
|
|
93
94
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { errorMessage, isRecord } from './guards.js';
|
|
2
|
-
import {
|
|
2
|
+
import { log } from './log.js';
|
|
3
3
|
function readValue(value, key) {
|
|
4
4
|
if (value === null)
|
|
5
5
|
return undefined;
|
|
@@ -80,7 +80,7 @@ export function installResolutionDetector(meta) {
|
|
|
80
80
|
const wrapped = (specifier, parentURL, opts = {}) => {
|
|
81
81
|
const parent = String(parentURL);
|
|
82
82
|
if (isReferrerLost(opts.referrer, parent, cacheRecord)) {
|
|
83
|
-
|
|
83
|
+
log.warn('diag referrer LOST', {
|
|
84
84
|
specifier,
|
|
85
85
|
parent,
|
|
86
86
|
modules: Object.keys(cacheRecord).length,
|
|
@@ -92,7 +92,7 @@ export function installResolutionDetector(meta) {
|
|
|
92
92
|
}
|
|
93
93
|
catch (err) {
|
|
94
94
|
if (isReferrerLost(opts.referrer, parent, cacheRecord)) {
|
|
95
|
-
|
|
95
|
+
log.error('diag resolve FAILED after a lost referrer', resolutionFailure(specifier, parent, err));
|
|
96
96
|
}
|
|
97
97
|
throw err;
|
|
98
98
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { HarnessError } from './errors.js';
|
|
1
2
|
export function settleOnClose(hrpc) {
|
|
2
3
|
const session = hrpc;
|
|
3
4
|
let reason = null;
|
|
4
5
|
const dead = (err) => {
|
|
5
|
-
reason ??= err ?? new
|
|
6
|
+
reason ??= err ?? new HarnessError('RPC_STREAM_DESTROYED');
|
|
6
7
|
};
|
|
7
8
|
const call = session._call.bind(session);
|
|
8
9
|
const callSync = session._callSync.bind(session);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { errorMessage } from './guards.js';
|
|
2
2
|
import { retryMemo } from './helpers/retry-memo.js';
|
|
3
|
-
import {
|
|
3
|
+
import { log } from './log.js';
|
|
4
4
|
const defaultImports = {
|
|
5
5
|
sdk: () => import('#sdk'),
|
|
6
6
|
plugins: () => import('@qvac/sdk/plugins'),
|
|
@@ -14,7 +14,7 @@ function register(registry, name, plugin) {
|
|
|
14
14
|
registry.registerPlugin(plugin);
|
|
15
15
|
}
|
|
16
16
|
catch (err) {
|
|
17
|
-
|
|
17
|
+
log.warn('plugin registration skipped', {
|
|
18
18
|
plugin: name,
|
|
19
19
|
err: errorMessage(err)
|
|
20
20
|
});
|
package/dist-lib/lib/serve.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AbortController } from 'bare-abort-controller';
|
|
2
2
|
import b4a from 'b4a';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import { HarnessError } from './errors.js';
|
|
4
5
|
import { settleOnClose } from './rpc-session.js';
|
|
5
6
|
import { pipeline, Readable } from 'streamx';
|
|
6
7
|
import { bindApi } from '../spec/bind.js';
|
|
@@ -9,7 +10,7 @@ import { isCapability } from './capabilities/invoke.js';
|
|
|
9
10
|
import { CUSTOM_TASK_TYPES } from './models/model-catalog.js';
|
|
10
11
|
import { unsupportedKvCacheDeletion } from './capabilities/kv-cache.js';
|
|
11
12
|
import { errorMessage, isRecord } from './guards.js';
|
|
12
|
-
import {
|
|
13
|
+
import { log } from './log.js';
|
|
13
14
|
import { pushQueue } from './queue.js';
|
|
14
15
|
import { TextCoalescer } from './helpers/text-coalescer.js';
|
|
15
16
|
import { prepareOpenclCache } from './models/opencl-cache.js';
|
|
@@ -57,7 +58,7 @@ export function serveHarness(stream, harness) {
|
|
|
57
58
|
},
|
|
58
59
|
memoryUsage: async () => {
|
|
59
60
|
if (!harness.memoryUsage) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['memoryUsage']);
|
|
61
62
|
}
|
|
62
63
|
return harness.memoryUsage();
|
|
63
64
|
},
|
|
@@ -109,7 +110,7 @@ export function serveHarness(stream, harness) {
|
|
|
109
110
|
},
|
|
110
111
|
previewModelSource: async ({ src, mmprojSrc }) => {
|
|
111
112
|
if (!harness.previewModelSource) {
|
|
112
|
-
throw new
|
|
113
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['previewModelSource']);
|
|
113
114
|
}
|
|
114
115
|
return harness.previewModelSource({ src: src ?? '', mmprojSrc: mmprojSrc ?? undefined });
|
|
115
116
|
},
|
|
@@ -127,13 +128,14 @@ export function serveHarness(stream, harness) {
|
|
|
127
128
|
skills: []
|
|
128
129
|
},
|
|
129
130
|
embed: async ({ model, texts }) => {
|
|
130
|
-
if (!harness.embed)
|
|
131
|
-
throw new
|
|
131
|
+
if (!harness.embed) {
|
|
132
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['embed']);
|
|
133
|
+
}
|
|
132
134
|
return packVectors(await harness.embed({ model, texts: texts ?? [] }));
|
|
133
135
|
},
|
|
134
136
|
transcribe: async ({ model, audio }) => {
|
|
135
137
|
if (!harness.transcribe) {
|
|
136
|
-
throw new
|
|
138
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['transcribe']);
|
|
137
139
|
}
|
|
138
140
|
return {
|
|
139
141
|
text: await harness.transcribe({ model: model ?? '', audio: audio ?? b4a.alloc(0) })
|
|
@@ -141,7 +143,7 @@ export function serveHarness(stream, harness) {
|
|
|
141
143
|
},
|
|
142
144
|
detectLanguage: async ({ text, topK }) => {
|
|
143
145
|
if (!harness.detectLanguage) {
|
|
144
|
-
throw new
|
|
146
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['detectLanguage']);
|
|
145
147
|
}
|
|
146
148
|
const languages = await harness.detectLanguage({
|
|
147
149
|
text: text ?? '',
|
|
@@ -150,8 +152,9 @@ export function serveHarness(stream, harness) {
|
|
|
150
152
|
return { languages };
|
|
151
153
|
},
|
|
152
154
|
speak: async ({ model, text }) => {
|
|
153
|
-
if (!harness.speak)
|
|
154
|
-
throw new
|
|
155
|
+
if (!harness.speak) {
|
|
156
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['speak']);
|
|
157
|
+
}
|
|
155
158
|
return harness.speak({ model: model ?? '', text: text ?? '' });
|
|
156
159
|
},
|
|
157
160
|
speakStream: speakStream(harness),
|
|
@@ -195,26 +198,26 @@ export function serveHarness(stream, harness) {
|
|
|
195
198
|
}),
|
|
196
199
|
capability: async () => {
|
|
197
200
|
if (!harness.capability) {
|
|
198
|
-
throw new
|
|
201
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['capability']);
|
|
199
202
|
}
|
|
200
203
|
return harness.capability();
|
|
201
204
|
},
|
|
202
205
|
warmModel: async ({ model, agent }) => {
|
|
203
206
|
if (!harness.warmModel) {
|
|
204
|
-
throw new
|
|
207
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['warmModel']);
|
|
205
208
|
}
|
|
206
209
|
await harness.warmModel({ model, agent: agent ?? {} });
|
|
207
210
|
return { ok: true };
|
|
208
211
|
},
|
|
209
212
|
loadModel: async ({ model, agent }) => {
|
|
210
213
|
if (!harness.loadModel) {
|
|
211
|
-
throw new
|
|
214
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['loadModel']);
|
|
212
215
|
}
|
|
213
216
|
return harness.loadModel({ model, agent: agent ?? {} });
|
|
214
217
|
},
|
|
215
218
|
unloadModel: async ({ model }) => {
|
|
216
219
|
if (!harness.unloadModel) {
|
|
217
|
-
throw new
|
|
220
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['unloadModel']);
|
|
218
221
|
}
|
|
219
222
|
return harness.unloadModel({ model });
|
|
220
223
|
},
|
|
@@ -255,7 +258,7 @@ function toWireSkill(skill) {
|
|
|
255
258
|
};
|
|
256
259
|
}
|
|
257
260
|
function unsupportedEmbeddingDeviceControl() {
|
|
258
|
-
return new
|
|
261
|
+
return new HarnessError('CAPABILITY_UNSUPPORTED', ['embedding device control']);
|
|
259
262
|
}
|
|
260
263
|
function modelEvents(harness) {
|
|
261
264
|
let last = '';
|
|
@@ -328,7 +331,7 @@ function modelEvents(harness) {
|
|
|
328
331
|
function speakStream(harness) {
|
|
329
332
|
return async function* (duplex) {
|
|
330
333
|
if (!harness.speakStream) {
|
|
331
|
-
throw new
|
|
334
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['speakStream']);
|
|
332
335
|
}
|
|
333
336
|
const text = pushQueue();
|
|
334
337
|
const end = () => text.end();
|
|
@@ -349,11 +352,12 @@ function runFrame(harness, frame, context) {
|
|
|
349
352
|
if (frame.type !== 'invoke') {
|
|
350
353
|
return harness.completion(context);
|
|
351
354
|
}
|
|
352
|
-
if (!harness.invoke)
|
|
353
|
-
throw new
|
|
355
|
+
if (!harness.invoke) {
|
|
356
|
+
throw new HarnessError('CAPABILITY_UNSUPPORTED', ['invoke']);
|
|
357
|
+
}
|
|
354
358
|
const capability = frame.capability ?? '';
|
|
355
359
|
if (!isCapability(capability)) {
|
|
356
|
-
throw new
|
|
360
|
+
throw new HarnessError('UNKNOWN_CAPABILITY', [capability]);
|
|
357
361
|
}
|
|
358
362
|
return harness.invoke({
|
|
359
363
|
capability,
|
|
@@ -386,15 +390,15 @@ function completion(harness) {
|
|
|
386
390
|
pending.clear();
|
|
387
391
|
for (const { reject, staged } of pendingAttach.values()) {
|
|
388
392
|
void fs.promises.unlink(staged).catch(noop);
|
|
389
|
-
reject(new
|
|
393
|
+
reject(new HarnessError('CLIENT_GONE'));
|
|
390
394
|
}
|
|
391
395
|
pendingAttach.clear();
|
|
392
396
|
for (const { reject } of pendingFetch.values()) {
|
|
393
|
-
reject(new
|
|
397
|
+
reject(new HarnessError('CLIENT_GONE'));
|
|
394
398
|
}
|
|
395
399
|
pendingFetch.clear();
|
|
396
400
|
for (const { reject } of pendingRag.values()) {
|
|
397
|
-
reject(new
|
|
401
|
+
reject(new HarnessError('CLIENT_GONE'));
|
|
398
402
|
}
|
|
399
403
|
pendingRag.clear();
|
|
400
404
|
for (const { resolve } of pendingQuestion.values()) {
|
|
@@ -405,7 +409,7 @@ function completion(harness) {
|
|
|
405
409
|
if (path) {
|
|
406
410
|
void fs.promises.unlink(path).catch(noop);
|
|
407
411
|
}
|
|
408
|
-
reject(new
|
|
412
|
+
reject(new HarnessError('CLIENT_GONE'));
|
|
409
413
|
}
|
|
410
414
|
pendingDrive.clear();
|
|
411
415
|
events.end();
|
|
@@ -498,7 +502,7 @@ function completion(harness) {
|
|
|
498
502
|
return;
|
|
499
503
|
}
|
|
500
504
|
if (frame.message) {
|
|
501
|
-
|
|
505
|
+
log.warn('question failed', { callId: frame.callId, message: frame.message });
|
|
502
506
|
}
|
|
503
507
|
waiter.resolve(frame.answer || null);
|
|
504
508
|
return;
|
|
@@ -551,7 +555,7 @@ function completion(harness) {
|
|
|
551
555
|
: undefined,
|
|
552
556
|
driveOp: frame.grantedDrives?.length
|
|
553
557
|
? (request) => controller.signal.aborted
|
|
554
|
-
? Promise.reject(new
|
|
558
|
+
? Promise.reject(new HarnessError('CLIENT_GONE'))
|
|
555
559
|
: new Promise((resolve, reject) => {
|
|
556
560
|
const callId = String(calls++);
|
|
557
561
|
pendingDrive.set(callId, {
|
|
@@ -582,7 +586,7 @@ function completion(harness) {
|
|
|
582
586
|
const staged = await stageFile('attach', input.bytes);
|
|
583
587
|
if (controller.signal.aborted) {
|
|
584
588
|
void fs.promises.unlink(staged).catch(noop);
|
|
585
|
-
throw new
|
|
589
|
+
throw new HarnessError('CLIENT_GONE');
|
|
586
590
|
}
|
|
587
591
|
return new Promise((resolve, reject) => {
|
|
588
592
|
const callId = String(calls++);
|
|
@@ -598,7 +602,7 @@ function completion(harness) {
|
|
|
598
602
|
});
|
|
599
603
|
},
|
|
600
604
|
fetchAttachment: ({ attachmentId, meta } = {}) => controller.signal.aborted
|
|
601
|
-
? Promise.reject(new
|
|
605
|
+
? Promise.reject(new HarnessError('CLIENT_GONE'))
|
|
602
606
|
: new Promise((resolve, reject) => {
|
|
603
607
|
const callId = String(calls++);
|
|
604
608
|
pendingFetch.set(callId, { resolve, reject });
|
|
@@ -610,7 +614,7 @@ function completion(harness) {
|
|
|
610
614
|
});
|
|
611
615
|
}),
|
|
612
616
|
ragSearch: ({ query, limit }) => controller.signal.aborted
|
|
613
|
-
? Promise.reject(new
|
|
617
|
+
? Promise.reject(new HarnessError('CLIENT_GONE'))
|
|
614
618
|
: new Promise((resolve, reject) => {
|
|
615
619
|
const callId = String(calls++);
|
|
616
620
|
pendingRag.set(callId, { resolve, reject });
|
|
@@ -660,7 +664,7 @@ function completion(harness) {
|
|
|
660
664
|
events.push({ type: 'error', message: errorMessage(err) });
|
|
661
665
|
}
|
|
662
666
|
events.push({ type: 'done' });
|
|
663
|
-
|
|
667
|
+
log.debug('wire end', { produced, frames });
|
|
664
668
|
events.end();
|
|
665
669
|
})();
|
|
666
670
|
});
|
|
@@ -668,7 +672,7 @@ function completion(harness) {
|
|
|
668
672
|
yield event;
|
|
669
673
|
await tick();
|
|
670
674
|
}
|
|
671
|
-
|
|
675
|
+
log.info('wire handed off');
|
|
672
676
|
};
|
|
673
677
|
}
|
|
674
678
|
function downloadStream(harness, req) {
|
package/dist-lib/lib/spawn.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SdkStorage } from './storage.ts';
|
|
|
2
2
|
import { type StowIpc, type StowRunner } from 'bare-supervisor/stow';
|
|
3
3
|
import type { ChildSpec } from 'bare-supervisor';
|
|
4
4
|
import { type RemoteHarness } from './connect.ts';
|
|
5
|
+
import { type HostLogger } from './log.ts';
|
|
5
6
|
import { type EmbedDevice } from './models/embed-config.ts';
|
|
6
7
|
import type { ImageClamps } from './models/model-catalog.ts';
|
|
7
8
|
import type { ImageModelSet } from './tools/generate-image.ts';
|
|
@@ -10,10 +11,7 @@ export type MusicModelArg = string | (MusicModelStages & {
|
|
|
10
11
|
inferenceSteps?: number;
|
|
11
12
|
useGpu?: boolean;
|
|
12
13
|
});
|
|
13
|
-
export
|
|
14
|
-
info(...args: unknown[]): void;
|
|
15
|
-
warn(...args: unknown[]): void;
|
|
16
|
-
}
|
|
14
|
+
export type Logger = HostLogger;
|
|
17
15
|
export interface HarnessLlmConfig {
|
|
18
16
|
llmDevice?: 'cpu' | 'gpu';
|
|
19
17
|
llmGpuLayers?: number;
|
package/dist-lib/lib/spawn.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { HarnessError } from './errors.js';
|
|
1
2
|
import stowChild, {} from 'bare-supervisor/stow';
|
|
2
3
|
import { connectHarness } from './connect.js';
|
|
3
|
-
import {
|
|
4
|
+
import { isLogLevel, logToHost } from './log.js';
|
|
4
5
|
import { createEmbedConfig } from './models/embed-config.js';
|
|
5
6
|
export function harnessChild(entry, opts) {
|
|
6
7
|
if (opts.logger) {
|
|
7
|
-
|
|
8
|
+
logToHost(opts.logger);
|
|
8
9
|
}
|
|
9
10
|
const { runner, skillsDir, gpuStats, wheelsDir, sdkStorage, embedDevice, embedGpuLayers, embedBatchSize, imageModel, imageClamps, musicModel, musicClamps, llm, onIpc, logger = null, ...spec } = opts;
|
|
10
11
|
const embedConfig = createEmbedConfig({
|
|
@@ -21,7 +22,7 @@ export function harnessChild(entry, opts) {
|
|
|
21
22
|
JSON.stringify(embedConfig),
|
|
22
23
|
imageModel ? JSON.stringify(imageModel) : '',
|
|
23
24
|
imageClamps ? JSON.stringify(imageClamps) : '',
|
|
24
|
-
logger
|
|
25
|
+
logSlot(logger),
|
|
25
26
|
llm ? JSON.stringify(llm) : '',
|
|
26
27
|
musicModel ? JSON.stringify(musicModel) : '',
|
|
27
28
|
musicClamps ? JSON.stringify(musicClamps) : '',
|
|
@@ -38,7 +39,7 @@ export function harnessChild(entry, opts) {
|
|
|
38
39
|
export function spawnHarness(opts) {
|
|
39
40
|
const logger = opts.logger ?? null;
|
|
40
41
|
if (logger) {
|
|
41
|
-
|
|
42
|
+
logToHost(logger);
|
|
42
43
|
}
|
|
43
44
|
const embedConfig = createEmbedConfig({
|
|
44
45
|
device: opts.embedDevice,
|
|
@@ -53,18 +54,24 @@ export function spawnHarness(opts) {
|
|
|
53
54
|
JSON.stringify(embedConfig),
|
|
54
55
|
opts.imageModel ? JSON.stringify(opts.imageModel) : '',
|
|
55
56
|
opts.imageClamps ? JSON.stringify(opts.imageClamps) : '',
|
|
56
|
-
logger
|
|
57
|
+
logSlot(logger),
|
|
57
58
|
opts.llm ? JSON.stringify(opts.llm) : '',
|
|
58
59
|
opts.musicModel ? JSON.stringify(opts.musicModel) : '',
|
|
59
60
|
opts.musicClamps ? JSON.stringify(opts.musicClamps) : '',
|
|
60
61
|
opts.wheelsDir ?? '',
|
|
61
62
|
opts.gpuStats === undefined ? '' : JSON.stringify(opts.gpuStats)
|
|
62
63
|
], logger);
|
|
63
|
-
const died = exit.then((code) =>
|
|
64
|
+
const died = exit.then((code) => new HarnessError('HARNESS_EXITED_BEFORE_READY', [code]));
|
|
64
65
|
const failure = await Promise.race([ipc.ready.then(() => null), died]);
|
|
65
66
|
if (failure) {
|
|
66
|
-
throw
|
|
67
|
+
throw failure;
|
|
67
68
|
}
|
|
68
69
|
return ipc;
|
|
69
70
|
});
|
|
70
71
|
}
|
|
72
|
+
function logSlot(logger) {
|
|
73
|
+
if (!logger)
|
|
74
|
+
return '';
|
|
75
|
+
const level = logger.getLevel?.();
|
|
76
|
+
return isLogLevel(level) ? level : '1';
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import b4a from 'b4a';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { isHarnessError } from '../errors.js';
|
|
4
5
|
import { AbortedError } from '../helpers/race-abort.js';
|
|
5
6
|
import { BUSY_ERROR, DIM_STEP, effectiveSteps, generationPercentTick, IMAGE_LOAD_STALL_ERROR, normalizeClamps, parseCommonArgs, runDiffusion } from './generate-image.js';
|
|
6
7
|
const DEFAULT_STRENGTH = 0.7;
|
|
@@ -278,7 +279,7 @@ export function createEditImageTool(runtime, { loadMedia = defaultLoadMedia, res
|
|
|
278
279
|
if (err instanceof AbortedError) {
|
|
279
280
|
return { error: 'image generation canceled' };
|
|
280
281
|
}
|
|
281
|
-
if (err
|
|
282
|
+
if (isHarnessError(err, 'IMAGE_LOAD_STALLED')) {
|
|
282
283
|
return { error: IMAGE_LOAD_STALL_ERROR };
|
|
283
284
|
}
|
|
284
285
|
throw err;
|