@qvac/harness 0.8.9 → 0.8.11
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 +73 -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/cli-argv.js +25 -5
- package/dist-lib/lib/tools/skills/credential-health.js +3 -3
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { errorMessage } from '../../guards.js';
|
|
2
|
-
import {
|
|
2
|
+
import { log } from '../../log.js';
|
|
3
3
|
import { displayPath } from './scope.js';
|
|
4
|
-
export const WALK_SKIP_DIRS = new Set(['.git', 'node_modules']);
|
|
4
|
+
export const WALK_SKIP_DIRS = new Set(['.git', 'node_modules', '.trash']);
|
|
5
5
|
export const MAX_WALK_ENTRIES = 10_000;
|
|
6
6
|
export const MAX_WALK_MS = 30_000;
|
|
7
7
|
export async function walkFiles(root, folder = '/') {
|
|
@@ -29,7 +29,7 @@ export async function walkFiles(root, folder = '/') {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
catch (err) {
|
|
32
|
-
|
|
32
|
+
log.warn('warn: walk aborted mid-listing', {
|
|
33
33
|
root: displayPath(root, '/'),
|
|
34
34
|
folder,
|
|
35
35
|
visited,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
|
+
import { IMAGE_LOAD_STALL_ERROR } from '../errors.ts';
|
|
2
3
|
import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
3
4
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
5
|
import { type ImageClamps } from '../models/model-catalog.ts';
|
|
@@ -88,7 +89,7 @@ export interface DiffusionRuntime {
|
|
|
88
89
|
release(): void;
|
|
89
90
|
}
|
|
90
91
|
export declare function generationPercentTick(onPercent: (percentage: number) => void, encodePhases?: number): (tick: DiffusionTick) => void;
|
|
91
|
-
export
|
|
92
|
+
export { IMAGE_LOAD_STALL_ERROR };
|
|
92
93
|
export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
|
|
93
94
|
export declare const BUSY_ERROR = "an image generation is already running; try again when it finishes";
|
|
94
95
|
export interface ImageToolError {
|
|
@@ -113,4 +114,3 @@ export declare function settingsDimension(value: number | undefined, maxDim?: nu
|
|
|
113
114
|
export declare function settingsSteps(value: number | undefined, maxSteps?: number): number | undefined;
|
|
114
115
|
export declare function effectiveSteps(parsedSteps: number | undefined, settingsValue: number | undefined, stepsCap: number | undefined): number | undefined;
|
|
115
116
|
export declare function createGenerateImageTool(runtime: DiffusionRuntime, clamps?: ImageClamps): ToolDefinition;
|
|
116
|
-
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ScopeLock from 'scope-lock';
|
|
2
|
+
import { HarnessError, IMAGE_LOAD_STALL_ERROR, isHarnessError } from '../errors.js';
|
|
2
3
|
import { AbortedError, raceAbort } from '../helpers/race-abort.js';
|
|
3
4
|
import { DIFFUSION_CATALOG, imageModelFor } from '../models/model-catalog.js';
|
|
4
5
|
import { createLoadStall } from '../helpers/load-stall.js';
|
|
@@ -38,7 +39,7 @@ function diffusionProgressName(set) {
|
|
|
38
39
|
const entry = DIFFUSION_CATALOG.find((candidate) => candidate.model.src === set.model);
|
|
39
40
|
return entry?.name ?? set.model;
|
|
40
41
|
}
|
|
41
|
-
export
|
|
42
|
+
export { IMAGE_LOAD_STALL_ERROR };
|
|
42
43
|
export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
|
|
43
44
|
let loading = null;
|
|
44
45
|
let residentFingerprint = null;
|
|
@@ -102,7 +103,7 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
|
|
|
102
103
|
config.vaeModelSrc = await resolveModelSrc(sdk, set.vaeModel, 'diffusion');
|
|
103
104
|
}
|
|
104
105
|
const modelConfig = Object.keys(config).length ? config : undefined;
|
|
105
|
-
const stall = createLoadStall(stallMs,
|
|
106
|
+
const stall = createLoadStall(stallMs, () => new HarnessError('IMAGE_LOAD_STALLED'));
|
|
106
107
|
const pending = sdk.loadModel({
|
|
107
108
|
modelSrc,
|
|
108
109
|
modelType: 'diffusion',
|
|
@@ -118,9 +119,7 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
|
|
|
118
119
|
return { modelId, prediction: set.prediction };
|
|
119
120
|
}
|
|
120
121
|
catch (err) {
|
|
121
|
-
if (err
|
|
122
|
-
err.message === IMAGE_LOAD_STALL_ERROR &&
|
|
123
|
-
pending.requestId) {
|
|
122
|
+
if (isHarnessError(err, 'IMAGE_LOAD_STALLED') && pending.requestId) {
|
|
124
123
|
void sdk.cancel?.({ requestId: pending.requestId }).catch(noop);
|
|
125
124
|
}
|
|
126
125
|
throw err;
|
|
@@ -371,7 +370,7 @@ export function createGenerateImageTool(runtime, clamps) {
|
|
|
371
370
|
if (err instanceof AbortedError) {
|
|
372
371
|
return { error: 'image generation canceled' };
|
|
373
372
|
}
|
|
374
|
-
if (err
|
|
373
|
+
if (isHarnessError(err, 'IMAGE_LOAD_STALLED')) {
|
|
375
374
|
return { error: IMAGE_LOAD_STALL_ERROR };
|
|
376
375
|
}
|
|
377
376
|
throw err;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import b4a from 'b4a';
|
|
2
|
+
import { HarnessError } from '../errors.js';
|
|
2
3
|
import { errorMessage, isRecord } from '../guards.js';
|
|
3
4
|
import { DocsError, docx, pptx, units, xlsx } from '../office/engine/index.js';
|
|
4
5
|
import { stageInputs } from './exec/adapters/python/attachments.js';
|
|
@@ -356,7 +357,7 @@ async function attachOutput(job, ctx) {
|
|
|
356
357
|
function runOp(document, op, input) {
|
|
357
358
|
const method = Reflect.get(document, op.name);
|
|
358
359
|
if (typeof method !== 'function') {
|
|
359
|
-
throw new
|
|
360
|
+
throw new HarnessError('OFFICE_OP_UNAVAILABLE', [op.name]);
|
|
360
361
|
}
|
|
361
362
|
return Reflect.apply(method, document, opArguments(op.spec, input, units.inches));
|
|
362
363
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import b4a from 'b4a';
|
|
3
3
|
import Localdrive from 'localdrive';
|
|
4
|
+
import { HarnessError } from '../errors.js';
|
|
4
5
|
import { OBSIDIAN_VAULT_MODE_NOTE } from './exec/obsidian/guidance.js';
|
|
5
6
|
import { unquoteArg } from './model-args.js';
|
|
6
7
|
import { credentialRecord } from './skills/auth-resolver.js';
|
|
@@ -101,7 +102,7 @@ export function createSkillTool({ skillsDir, overlayDir = resolveSkillsOverlayDi
|
|
|
101
102
|
return { raw: b4a.toString(buf, 'utf8'), root: root ?? undefined };
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
|
-
throw new
|
|
105
|
+
throw new HarnessError('SKILL_FILE_NOT_FOUND', [name, file]);
|
|
105
106
|
}
|
|
106
107
|
return {
|
|
107
108
|
name: 'skill',
|
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import process from 'process';
|
|
5
|
+
import { HarnessError } from '../../errors.js';
|
|
5
6
|
import { userTmpPath } from '../../tmp-path.js';
|
|
6
7
|
import { hashBundledSkills } from './bundled-hash.js';
|
|
7
8
|
import { SKILLS as BUNDLED_SKILLS } from '@qvac/skills/bundled';
|
|
@@ -26,7 +27,7 @@ async function materialize() {
|
|
|
26
27
|
try {
|
|
27
28
|
await writeTree(staging);
|
|
28
29
|
if (!(await validTree(staging))) {
|
|
29
|
-
throw new
|
|
30
|
+
throw new HarnessError('BUNDLED_SKILLS_INVALID', [staging]);
|
|
30
31
|
}
|
|
31
32
|
if (await tryRename(staging, dir)) {
|
|
32
33
|
return dir;
|
|
@@ -58,12 +59,11 @@ async function materialize() {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
function tamperError(dir) {
|
|
61
|
-
return new
|
|
62
|
-
'another process is tampering with it; remove it and restart');
|
|
62
|
+
return new HarnessError('BUNDLED_SKILLS_TAMPERED', [dir]);
|
|
63
63
|
}
|
|
64
64
|
function occupiedError(dir, cause) {
|
|
65
65
|
const why = cause instanceof Error ? ` (${cause.message})` : '';
|
|
66
|
-
return new
|
|
66
|
+
return new HarnessError('BUNDLED_SKILLS_OCCUPIED', [dir, why]);
|
|
67
67
|
}
|
|
68
68
|
export function randomSuffix() {
|
|
69
69
|
return Math.random().toString(36).slice(2, 10);
|
|
@@ -10,7 +10,7 @@ import { parseToolGrant } from './tool-grants.js';
|
|
|
10
10
|
import { deriveSetup } from './setup-routes.js';
|
|
11
11
|
import { resolveSkillsOverlayDir } from './overlay-dir.js';
|
|
12
12
|
import { parseOperationsManifest } from '../operations/manifest.js';
|
|
13
|
-
import {
|
|
13
|
+
import { log } from '../../log.js';
|
|
14
14
|
export function skillSupportsPlatform(entry, platform) {
|
|
15
15
|
return supportsPlatform(entry, platform);
|
|
16
16
|
}
|
|
@@ -193,7 +193,7 @@ async function loadOperations(skillDir, name) {
|
|
|
193
193
|
}
|
|
194
194
|
const operations = parseOperationsManifest(raw);
|
|
195
195
|
if (!operations) {
|
|
196
|
-
|
|
196
|
+
log.warn('skill operations manifest rejected', { skill: name });
|
|
197
197
|
return [];
|
|
198
198
|
}
|
|
199
199
|
return operations;
|
|
@@ -19,7 +19,7 @@ export function walkCommands(argv, schema) {
|
|
|
19
19
|
}
|
|
20
20
|
export function argvToObject(argv, schema) {
|
|
21
21
|
const { node, pathSegs, index } = walkCommands(argv, schema);
|
|
22
|
-
const leaf = parseParams(argv.slice(index), node);
|
|
22
|
+
const leaf = parseParams(argv.slice(index), node, pathSegs.join(' '));
|
|
23
23
|
if (typeof leaf === 'string') {
|
|
24
24
|
return leaf;
|
|
25
25
|
}
|
|
@@ -52,7 +52,27 @@ function flagParts(tok) {
|
|
|
52
52
|
}
|
|
53
53
|
return { key: body.slice(0, ieq), inline: body.slice(ieq + 1) };
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function accepted(schema, command) {
|
|
56
|
+
const commands = [];
|
|
57
|
+
const params = [];
|
|
58
|
+
for (const [key, prop] of Object.entries(objectProps(schema))) {
|
|
59
|
+
if (isObjectSchema(prop)) {
|
|
60
|
+
commands.push(key);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
params.push(key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (commands.length) {
|
|
68
|
+
parts.push(`commands: ${commands.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
if (params.length) {
|
|
71
|
+
parts.push(`${command || 'it'} takes: ${params.join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
return parts.length ? `; ${parts.join('; ')}` : '';
|
|
74
|
+
}
|
|
75
|
+
function parseParams(tokens, schema, command) {
|
|
56
76
|
const props = objectProps(schema);
|
|
57
77
|
const propSchemas = new Map(Object.entries(props));
|
|
58
78
|
const boolKeys = new Set(Object.entries(props)
|
|
@@ -77,7 +97,7 @@ function parseParams(tokens, schema) {
|
|
|
77
97
|
continue;
|
|
78
98
|
}
|
|
79
99
|
if (!canAbsorb) {
|
|
80
|
-
return `unknown parameter "${key}"`;
|
|
100
|
+
return `unknown parameter "${key}"${accepted(schema, command)}`;
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
const flag = tok.startsWith('-') ? flagParts(tok) : null;
|
|
@@ -103,7 +123,7 @@ function parseParams(tokens, schema) {
|
|
|
103
123
|
continue;
|
|
104
124
|
}
|
|
105
125
|
if (flag && !canAbsorb) {
|
|
106
|
-
return `unknown flag "--${flag.key}"`;
|
|
126
|
+
return `unknown flag "--${flag.key}"${accepted(schema, command)}`;
|
|
107
127
|
}
|
|
108
128
|
if (boolKeys.has(tok)) {
|
|
109
129
|
out[tok] = true;
|
|
@@ -131,7 +151,7 @@ function parseParams(tokens, schema) {
|
|
|
131
151
|
i++;
|
|
132
152
|
continue;
|
|
133
153
|
}
|
|
134
|
-
return `unexpected argument "${tok}"`;
|
|
154
|
+
return `unexpected argument "${tok}"${accepted(schema, command)}`;
|
|
135
155
|
}
|
|
136
156
|
if (restName && restVals.length > 0) {
|
|
137
157
|
out[restName] = restVals;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { log } from '../../log.js';
|
|
2
2
|
import { scopeKey } from './scope-key.js';
|
|
3
3
|
const rejected = new Set();
|
|
4
4
|
const lastProbedAt = new Map();
|
|
@@ -16,12 +16,12 @@ export function markCredentialValid(key, agentId, skill) {
|
|
|
16
16
|
}
|
|
17
17
|
export function recordCredentialHealth(key, status, agentId) {
|
|
18
18
|
if (status === 401 || status === 403) {
|
|
19
|
-
|
|
19
|
+
log.warn('credential rejected by provider', { key, status });
|
|
20
20
|
markCredentialRejected(key, agentId);
|
|
21
21
|
}
|
|
22
22
|
else if (status < 400) {
|
|
23
23
|
if (isCredentialRejected(key, agentId)) {
|
|
24
|
-
|
|
24
|
+
log.info('credential recovered', { key });
|
|
25
25
|
}
|
|
26
26
|
markCredentialValid(key, agentId);
|
|
27
27
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { HarnessError } from '../../errors.js';
|
|
3
4
|
import { randomSuffix } from './bundled-dir.js';
|
|
4
5
|
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
5
6
|
function assertSafeName(name) {
|
|
6
7
|
if (!SAFE_NAME.test(name)) {
|
|
7
|
-
throw new
|
|
8
|
+
throw new HarnessError('INVALID_SKILL_NAME', [name]);
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
async function dirNames(root) {
|
|
@@ -66,7 +67,7 @@ export async function copyBaseForEdit(baseDir, overlayDir, name) {
|
|
|
66
67
|
}
|
|
67
68
|
const src = path.join(baseDir, name);
|
|
68
69
|
if (!(await exists(src))) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new HarnessError('SKILL_NOT_FOUND', [name]);
|
|
70
71
|
}
|
|
71
72
|
await copyDir(src, dest);
|
|
72
73
|
return { copied: true };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
|
+
import { TRANSLATION_LOAD_STALL_ERROR } from '../errors.ts';
|
|
2
3
|
import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
3
4
|
import type { ModelSrc } from '../models/context.ts';
|
|
4
5
|
import { type NmtPlan } from '../models/nmt-catalog.ts';
|
|
@@ -38,7 +39,7 @@ export type TranslationSdk = ModelRegistrySdk & {
|
|
|
38
39
|
};
|
|
39
40
|
};
|
|
40
41
|
export declare const TRANSLATION_LOAD_STALL_MS: number;
|
|
41
|
-
export
|
|
42
|
+
export { TRANSLATION_LOAD_STALL_ERROR };
|
|
42
43
|
export interface TranslationRuntimeOptions {
|
|
43
44
|
loadSdk: () => Promise<TranslationSdk>;
|
|
44
45
|
loads?: LoadTracker;
|
|
@@ -76,4 +77,3 @@ export interface TranslateToolOptions {
|
|
|
76
77
|
slm?: SlmTranslateFn;
|
|
77
78
|
}
|
|
78
79
|
export declare function createTranslateTool(runtime: TranslationRuntime, { slm }?: TranslateToolOptions): ToolDefinition;
|
|
79
|
-
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ScopeLock from 'scope-lock';
|
|
2
2
|
import { getLangName } from '@qvac/langdetect-text';
|
|
3
|
+
import { HarnessError, isHarnessError, TRANSLATION_LOAD_STALL_ERROR } from '../errors.js';
|
|
3
4
|
import { detectLanguage, UNDETERMINED } from '../capabilities/detect-language.js';
|
|
4
5
|
import { AbortedError, raceAbort } from '../helpers/race-abort.js';
|
|
5
6
|
import { ENGLISH, NMT_FROM_ENGLISH, NMT_TO_ENGLISH, languageLabel, SLM_LANGUAGE_CODES, sameLanguage, translationPlanFor } from '../models/nmt-catalog.js';
|
|
@@ -7,7 +8,7 @@ import { createLoadStall } from '../helpers/load-stall.js';
|
|
|
7
8
|
import { createLoadTracker } from '../models/model-pool.js';
|
|
8
9
|
import { resolveModelSrc } from '../models/model-source.js';
|
|
9
10
|
export const TRANSLATION_LOAD_STALL_MS = 15 * 60_000;
|
|
10
|
-
export
|
|
11
|
+
export { TRANSLATION_LOAD_STALL_ERROR };
|
|
11
12
|
function packName(plan) {
|
|
12
13
|
const pair = plan.model.match(/model\.([a-z]+)\.intgemm/)?.[1];
|
|
13
14
|
if (pair) {
|
|
@@ -38,7 +39,7 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
38
39
|
modelConfig.pivotModel = { modelSrc: await resolveModelSrc(sdk, plan.pivot, 'nmt') };
|
|
39
40
|
}
|
|
40
41
|
const name = packName(plan);
|
|
41
|
-
const stall = createLoadStall(stallMs,
|
|
42
|
+
const stall = createLoadStall(stallMs, () => new HarnessError('TRANSLATION_LOAD_STALLED'));
|
|
42
43
|
onProgress?.(name, {});
|
|
43
44
|
const pending = sdk.loadModel({
|
|
44
45
|
modelSrc,
|
|
@@ -54,9 +55,7 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
54
55
|
return { modelId };
|
|
55
56
|
}
|
|
56
57
|
catch (err) {
|
|
57
|
-
if (err
|
|
58
|
-
err.message === TRANSLATION_LOAD_STALL_ERROR &&
|
|
59
|
-
pending.requestId) {
|
|
58
|
+
if (isHarnessError(err, 'TRANSLATION_LOAD_STALLED') && pending.requestId) {
|
|
60
59
|
void sdk.cancel?.({ requestId: pending.requestId }).catch(noop);
|
|
61
60
|
}
|
|
62
61
|
throw err;
|
|
@@ -216,7 +215,7 @@ export function createTranslateTool(runtime, { slm } = {}) {
|
|
|
216
215
|
entered = true;
|
|
217
216
|
if (plan.route === 'slm') {
|
|
218
217
|
if (!slm) {
|
|
219
|
-
throw new
|
|
218
|
+
throw new HarnessError('SLM_TRANSLATION_UNAVAILABLE');
|
|
220
219
|
}
|
|
221
220
|
const text = await slm(plan.model, { text: parsed.text, from: plan.from, to: plan.to }, ctx?.signal);
|
|
222
221
|
return {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AbortController } from 'bare-abort-controller';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { HarnessError } from '../../errors.js';
|
|
3
4
|
import { errorMessage } from '../../guards.js';
|
|
4
|
-
import {
|
|
5
|
+
import { log } from '../../log.js';
|
|
5
6
|
import { resolveOutboundUrl } from '../url-guard.js';
|
|
6
7
|
import { checkQualifier, describePlace, fold } from './place.js';
|
|
7
8
|
export const GEOCODER_HOST = 'geocoding-api.open-meteo.com';
|
|
@@ -33,7 +34,7 @@ export async function geocode(city, lookup) {
|
|
|
33
34
|
if (lookup.signal.aborted) {
|
|
34
35
|
throw err;
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
log.warn('warn: weather geocoder failed, asking wttr.in by name', { err: errorMessage(err) });
|
|
37
38
|
return [];
|
|
38
39
|
}
|
|
39
40
|
finally {
|
|
@@ -46,7 +47,7 @@ async function search(city, { fetch, resolveHost }, signal) {
|
|
|
46
47
|
const url = new URL(`https://${GEOCODER_HOST}/v1/search?${query}`);
|
|
47
48
|
const check = await resolveOutboundUrl(url, resolveHost, signal);
|
|
48
49
|
if (check.blocked) {
|
|
49
|
-
throw new
|
|
50
|
+
throw new HarnessError('EGRESS_BLOCKED', [check.blocked]);
|
|
50
51
|
}
|
|
51
52
|
const response = await fetch(url, {
|
|
52
53
|
method: 'GET',
|
|
@@ -57,7 +58,7 @@ async function search(city, { fetch, resolveHost }, signal) {
|
|
|
57
58
|
maxBytes: MAX_BODY_BYTES
|
|
58
59
|
});
|
|
59
60
|
if (response.status !== 200) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new HarnessError('GEOCODER_HTTP_STATUS', [response.status]);
|
|
61
62
|
}
|
|
62
63
|
const { results = [] } = GeocodeSchema.parse(JSON.parse(await response.text()));
|
|
63
64
|
return results.map((result) => ({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HarnessError } from '../../errors.js';
|
|
1
2
|
import { fetchHtml } from '../fetch-html.js';
|
|
2
3
|
import { clean } from '../html-helpers.js';
|
|
3
4
|
import { urlMatchesHost } from '../url-helpers.js';
|
|
@@ -101,7 +102,7 @@ export async function searchBing(query, fetcher, signal) {
|
|
|
101
102
|
const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
|
|
102
103
|
const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA }, signal });
|
|
103
104
|
if (html.includes('captcha') || html.includes('challenge/verify') || html.includes('Turnstile')) {
|
|
104
|
-
throw new
|
|
105
|
+
throw new HarnessError('SEARCH_CAPTCHA', ['Bing']);
|
|
105
106
|
}
|
|
106
107
|
const primary = parseBAlgoBlocks(html);
|
|
107
108
|
if (primary.length > 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HarnessError } from '../../errors.js';
|
|
1
2
|
import { fetchHtml } from '../fetch-html.js';
|
|
2
3
|
import { clean } from '../html-helpers.js';
|
|
3
4
|
import { urlMatchesHost } from '../url-helpers.js';
|
|
@@ -86,7 +87,7 @@ export async function searchGoogle(query, fetcher, signal) {
|
|
|
86
87
|
signal
|
|
87
88
|
});
|
|
88
89
|
if (html.includes('detected unusual traffic') || html.includes('g-recaptcha')) {
|
|
89
|
-
throw new
|
|
90
|
+
throw new HarnessError('SEARCH_CAPTCHA', ['Google']);
|
|
90
91
|
}
|
|
91
92
|
const primary = parseDataVedAnchors(html);
|
|
92
93
|
if (primary.length > 0) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type AbortSignal as BareAbortSignal } from 'bare-abort-controller';
|
|
2
|
+
import { HarnessError } from '../errors.ts';
|
|
2
3
|
import type { Fetcher } from './types.ts';
|
|
3
4
|
export declare const ENGINE_TIMEOUT_MS = 8000;
|
|
4
5
|
export declare const MAX_HTML_CHARS = 2000000;
|
|
5
|
-
export declare class TimeoutError extends
|
|
6
|
+
export declare class TimeoutError extends HarnessError<'FETCH_TIMED_OUT'> {
|
|
6
7
|
constructor(ms: number);
|
|
7
8
|
}
|
|
8
|
-
export declare class AbortError extends
|
|
9
|
+
export declare class AbortError extends HarnessError<'FETCH_CANCELLED'> {
|
|
9
10
|
constructor();
|
|
10
11
|
}
|
|
11
12
|
interface FetchOptions {
|
|
@@ -18,9 +19,8 @@ interface FetchOptions {
|
|
|
18
19
|
readonly signal?: BareAbortSignal;
|
|
19
20
|
}
|
|
20
21
|
export declare function fetchHtml(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<string>;
|
|
21
|
-
export declare class JsonParseError extends
|
|
22
|
-
|
|
23
|
-
constructor(message: string, cause?: unknown);
|
|
22
|
+
export declare class JsonParseError extends HarnessError<'RESPONSE_NOT_JSON'> {
|
|
23
|
+
constructor(chars: number, cause: unknown);
|
|
24
24
|
}
|
|
25
25
|
export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<unknown>;
|
|
26
26
|
export {};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { AbortController } from 'bare-abort-controller';
|
|
2
|
+
import { errorMessage } from '../guards.js';
|
|
3
|
+
import { HarnessError } from '../errors.js';
|
|
2
4
|
import { BROWSER_UA } from './user-agents.js';
|
|
3
5
|
export const ENGINE_TIMEOUT_MS = 8000;
|
|
4
6
|
export const MAX_HTML_CHARS = 2_000_000;
|
|
5
|
-
export class TimeoutError extends
|
|
7
|
+
export class TimeoutError extends HarnessError {
|
|
6
8
|
constructor(ms) {
|
|
7
|
-
super(
|
|
8
|
-
this.name = 'TimeoutError';
|
|
9
|
+
super('FETCH_TIMED_OUT', [ms]);
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
|
-
export class AbortError extends
|
|
12
|
+
export class AbortError extends HarnessError {
|
|
12
13
|
constructor() {
|
|
13
|
-
super('
|
|
14
|
-
this.name = 'AbortError';
|
|
14
|
+
super('FETCH_CANCELLED');
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
function buildInit(options, signal) {
|
|
@@ -74,16 +74,13 @@ async function fetchWithTimeout(fetcher, url, options) {
|
|
|
74
74
|
export async function fetchHtml(fetcher, url, options) {
|
|
75
75
|
const { ok, status, text } = await fetchWithTimeout(fetcher, url, options);
|
|
76
76
|
if (!ok) {
|
|
77
|
-
throw new
|
|
77
|
+
throw new HarnessError('HTTP_STATUS', [status]);
|
|
78
78
|
}
|
|
79
79
|
return text;
|
|
80
80
|
}
|
|
81
|
-
export class JsonParseError extends
|
|
82
|
-
cause
|
|
83
|
-
|
|
84
|
-
super(message);
|
|
85
|
-
this.cause = cause;
|
|
86
|
-
this.name = 'JsonParseError';
|
|
81
|
+
export class JsonParseError extends HarnessError {
|
|
82
|
+
constructor(chars, cause) {
|
|
83
|
+
super('RESPONSE_NOT_JSON', [chars, errorMessage(cause)], { cause });
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
86
|
export async function fetchJson(fetcher, url, options) {
|
|
@@ -92,12 +89,12 @@ export async function fetchJson(fetcher, url, options) {
|
|
|
92
89
|
accept: options?.accept ?? 'application/json'
|
|
93
90
|
});
|
|
94
91
|
if (!ok) {
|
|
95
|
-
throw new
|
|
92
|
+
throw new HarnessError('HTTP_STATUS', [status]);
|
|
96
93
|
}
|
|
97
94
|
try {
|
|
98
95
|
return JSON.parse(text);
|
|
99
96
|
}
|
|
100
97
|
catch (err) {
|
|
101
|
-
throw new JsonParseError(
|
|
98
|
+
throw new JsonParseError(text.length, err);
|
|
102
99
|
}
|
|
103
100
|
}
|