@qvac/harness 0.4.0 → 0.6.0
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 +740 -141
- package/README.md +75 -7
- package/dist/harness.bundle +72 -50
- package/dist-lib/index.d.ts +49 -39
- package/dist-lib/index.js +448 -161
- package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
- package/dist-lib/lib/audio-transcode-bare.js +28 -18
- package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
- package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
- package/dist-lib/lib/capabilities/audio-resample.js +30 -18
- package/dist-lib/lib/capabilities/completion.d.ts +20 -19
- package/dist-lib/lib/capabilities/completion.js +193 -132
- package/dist-lib/lib/capabilities/download.d.ts +6 -6
- package/dist-lib/lib/capabilities/download.js +15 -8
- package/dist-lib/lib/capabilities/embed.js +2 -1
- package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
- package/dist-lib/lib/capabilities/enhance.js +7 -4
- package/dist-lib/lib/capabilities/invoke.js +7 -4
- package/dist-lib/lib/capabilities/kv-cache.js +2 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +9 -7
- package/dist-lib/lib/capabilities/preview-model-source.js +80 -28
- package/dist-lib/lib/capabilities/speech-text.js +5 -4
- package/dist-lib/lib/capabilities/tool-selection.js +10 -9
- package/dist-lib/lib/capabilities/tool-turns.js +30 -16
- package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +22 -12
- package/dist-lib/lib/connect.d.ts +13 -32
- package/dist-lib/lib/connect.js +170 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +35 -15
- package/dist-lib/lib/diagram-repair.d.ts +4 -4
- package/dist-lib/lib/diagram-repair.js +6 -3
- package/dist-lib/lib/docx/index.d.ts +3 -2
- package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
- package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
- package/dist-lib/lib/hardware/capability-probe.js +20 -10
- package/dist-lib/lib/hardware/gpu-classify.js +6 -3
- package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
- package/dist-lib/lib/hardware/gpu-probe.js +4 -2
- package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
- package/dist-lib/lib/hardware/parsers.js +8 -4
- package/dist-lib/lib/helpers/buffer-burst.js +4 -2
- package/dist-lib/lib/helpers/control-tokens.js +4 -2
- package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
- package/dist-lib/lib/helpers/join-guard.js +4 -2
- package/dist-lib/lib/helpers/load-stall.js +4 -2
- package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
- package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
- package/dist-lib/lib/helpers/race-abort.js +2 -1
- package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
- package/dist-lib/lib/helpers/race-timeout.js +14 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
- package/dist-lib/lib/helpers/run-outcome.js +36 -20
- package/dist-lib/lib/helpers/spawn-path.js +6 -3
- package/dist-lib/lib/helpers/text-coalescer.js +6 -3
- package/dist-lib/lib/helpers/thinking.js +38 -45
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
- package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
- package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
- package/dist-lib/lib/helpers/tool-intent.js +4 -2
- package/dist-lib/lib/helpers/tool-label.js +18 -9
- package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
- package/dist-lib/lib/hooks/pipeline.js +6 -3
- package/dist-lib/lib/hooks/run.js +12 -8
- package/dist-lib/lib/model-download-progress.js +10 -8
- package/dist-lib/lib/models/context.d.ts +1 -1
- package/dist-lib/lib/models/context.js +5 -1
- package/dist-lib/lib/models/custom-model-cache.d.ts +10 -1
- package/dist-lib/lib/models/custom-model-cache.js +43 -15
- package/dist-lib/lib/models/gguf-header.d.ts +3 -2
- package/dist-lib/lib/models/gguf-header.js +44 -22
- package/dist-lib/lib/models/idle-unload.d.ts +14 -0
- package/dist-lib/lib/models/idle-unload.js +51 -0
- package/dist-lib/lib/models/kv-cache.d.ts +3 -2
- package/dist-lib/lib/models/kv-cache.js +4 -3
- package/dist-lib/lib/models/llm-presets.d.ts +2 -2
- package/dist-lib/lib/models/llm-presets.js +4 -2
- package/dist-lib/lib/models/model-catalog-constants.js +3 -2
- package/dist-lib/lib/models/model-catalog.d.ts +15 -20
- package/dist-lib/lib/models/model-catalog.js +66 -31
- package/dist-lib/lib/models/model-delete.js +18 -8
- package/dist-lib/lib/models/model-handle.js +2 -1
- package/dist-lib/lib/models/model-list.d.ts +8 -3
- package/dist-lib/lib/models/model-list.js +11 -6
- package/dist-lib/lib/models/model-pool.d.ts +48 -10
- package/dist-lib/lib/models/model-pool.js +106 -45
- package/dist-lib/lib/models/model-source.js +10 -5
- package/dist-lib/lib/models/music-catalog.d.ts +2 -2
- package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
- package/dist-lib/lib/models/nmt-catalog.js +10 -5
- package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
- package/dist-lib/lib/models/opencl-cache.js +5 -3
- package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
- package/dist-lib/lib/models/sdk-faults.js +9 -6
- package/dist-lib/lib/models/shards.d.ts +11 -0
- package/dist-lib/lib/models/shards.js +15 -0
- package/dist-lib/lib/models/tool-dialect.js +10 -5
- package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
- package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
- package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
- package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
- package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
- package/dist-lib/lib/office/index.d.ts +3 -2
- package/dist-lib/lib/office/python.d.ts +3 -2
- package/dist-lib/lib/office/python.js +2 -1
- package/dist-lib/lib/prompt.d.ts +5 -4
- package/dist-lib/lib/prompt.js +8 -4
- package/dist-lib/lib/queue.js +10 -5
- package/dist-lib/lib/resolution-detector.js +21 -11
- package/dist-lib/lib/rpc-session.js +2 -1
- package/dist-lib/lib/sdk-loader.js +2 -1
- package/dist-lib/lib/sdk-storage.js +4 -2
- package/dist-lib/lib/serve.js +122 -66
- package/dist-lib/lib/spawn.js +7 -4
- package/dist-lib/lib/tmp-path.d.ts +7 -6
- package/dist-lib/lib/tool-call-display.d.ts +2 -2
- package/dist-lib/lib/tools/create-documents.js +18 -10
- package/dist-lib/lib/tools/edit-documents.js +23 -13
- package/dist-lib/lib/tools/edit-image.d.ts +2 -2
- package/dist-lib/lib/tools/edit-image.js +60 -36
- package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
- package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
- package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
- package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
- package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
- package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
- package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
- package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
- package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
- package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
- package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
- package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
- package/dist-lib/lib/tools/exec/runner.js +5 -3
- package/dist-lib/lib/tools/exec/scope.js +52 -28
- package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
- package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
- package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
- package/dist-lib/lib/tools/exec/video-tools.js +2 -2
- package/dist-lib/lib/tools/exec.d.ts +6 -8
- package/dist-lib/lib/tools/exec.js +113 -54
- package/dist-lib/lib/tools/file-glob.d.ts +4 -5
- package/dist-lib/lib/tools/file-glob.js +16 -11
- package/dist-lib/lib/tools/fs/glob.js +13 -18
- package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
- package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
- package/dist-lib/lib/tools/fs/remote.js +13 -7
- package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
- package/dist-lib/lib/tools/fs/scope.js +20 -12
- package/dist-lib/lib/tools/fs/walk.js +6 -3
- package/dist-lib/lib/tools/fs/write.d.ts +3 -2
- package/dist-lib/lib/tools/generate-image.d.ts +11 -9
- package/dist-lib/lib/tools/generate-image.js +73 -37
- package/dist-lib/lib/tools/generate-music.d.ts +22 -11
- package/dist-lib/lib/tools/generate-music.js +79 -48
- package/dist-lib/lib/tools/grep.js +66 -41
- package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
- package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
- package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
- package/dist-lib/lib/tools/http-request.d.ts +1 -1
- package/dist-lib/lib/tools/http-request.js +33 -18
- package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
- package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
- package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
- package/dist-lib/lib/tools/mcp-call.js +37 -16
- package/dist-lib/lib/tools/operations/builders.js +10 -6
- package/dist-lib/lib/tools/operations/compile.js +20 -10
- package/dist-lib/lib/tools/operations/decoders.js +12 -6
- package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
- package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
- package/dist-lib/lib/tools/outbound-guard.js +14 -8
- package/dist-lib/lib/tools/private-ip.js +8 -7
- package/dist-lib/lib/tools/rag-search.js +14 -7
- package/dist-lib/lib/tools/read-files.js +23 -20
- package/dist-lib/lib/tools/skill.js +19 -10
- package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
- package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
- package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bin-check.js +10 -3
- package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
- package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
- package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
- package/dist-lib/lib/tools/skills/catalog.js +33 -28
- package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
- package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
- package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
- package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
- package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
- package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
- package/dist-lib/lib/tools/skills/credential-check.js +24 -12
- package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
- package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
- package/dist-lib/lib/tools/skills/credential-health.js +5 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
- package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
- package/dist-lib/lib/tools/skills/mention.js +7 -6
- package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
- package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
- package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
- package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
- package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
- package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
- package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
- package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
- package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
- package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
- package/dist-lib/lib/tools/skills/skill-state.js +48 -29
- package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
- package/dist-lib/lib/tools/toolsets.js +2 -1
- package/dist-lib/lib/tools/translate.d.ts +4 -5
- package/dist-lib/lib/tools/translate.js +31 -14
- package/dist-lib/lib/tools/url-guard.js +2 -1
- package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
- package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
- package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
- package/dist-lib/lib/tools/weather-lookup.js +18 -12
- package/dist-lib/lib/tools/web-fetch.js +47 -24
- package/dist-lib/lib/tools/web-search.js +8 -3
- package/dist-lib/lib/validating-lookup.d.ts +4 -4
- package/dist-lib/lib/wav-encode.d.ts +1 -1
- package/dist-lib/lib/wav-encode.js +11 -10
- package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
- package/dist-lib/lib/web-fetch/extract.js +4 -2
- package/dist-lib/lib/web-fetch/images.js +8 -4
- package/dist-lib/lib/web-fetch/video.d.ts +2 -2
- package/dist-lib/lib/web-fetch/video.js +8 -4
- package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
- package/dist-lib/lib/web-search/engines/aol.js +1 -1
- package/dist-lib/lib/web-search/engines/bing.js +1 -1
- package/dist-lib/lib/web-search/engines/brave.js +4 -1
- package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
- package/dist-lib/lib/web-search/engines/google.js +7 -3
- package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
- package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
- package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
- package/dist-lib/lib/web-search/fetch-html.js +13 -7
- package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
- package/dist-lib/lib/web-search/orchestrator.js +3 -3
- package/dist-lib/lib/wire.d.ts +3 -2
- package/dist-lib/lib/wire.js +23 -15
- package/dist-lib/schema/baseline.js +10 -5
- package/dist-lib/schema/build.js +2 -1
- package/dist-lib/schema/harness.d.ts +15 -2
- package/dist-lib/schema/harness.js +39 -2
- package/dist-lib/schema/release-tags.d.ts +3 -2
- package/dist-lib/schema/release-tags.js +6 -3
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +105 -8
- package/dist-lib/spec/hyperschema/index.js +105 -8
- package/dist-lib/spec/hyperschema/schema.json +77 -1
- package/dist-lib/spec/hyperschema/types.d.ts +25 -1
- package/package.json +14 -14
- package/worker/harness-sidecar.mjs +15 -22
- package/worker/stow-entry.mjs +7 -4
- package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
- /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
|
@@ -28,12 +28,14 @@ export function musicPercentTick(onPercent) {
|
|
|
28
28
|
let last = -1;
|
|
29
29
|
return ({ stage, step, total }) => {
|
|
30
30
|
const weight = STAGE_WEIGHT[stage];
|
|
31
|
-
if (!weight)
|
|
31
|
+
if (!weight) {
|
|
32
32
|
return;
|
|
33
|
+
}
|
|
33
34
|
const frac = total > 0 ? Math.min(1, Math.max(0, step / total)) : 0;
|
|
34
35
|
const percentage = Math.min(100, Math.round(weight.base + frac * weight.span));
|
|
35
|
-
if (percentage <= last)
|
|
36
|
+
if (percentage <= last) {
|
|
36
37
|
return;
|
|
38
|
+
}
|
|
37
39
|
last = percentage;
|
|
38
40
|
onPercent(percentage);
|
|
39
41
|
};
|
|
@@ -83,16 +85,18 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
|
|
|
83
85
|
loadSdk,
|
|
84
86
|
gpuRequested: useGpu,
|
|
85
87
|
defaultSteps,
|
|
86
|
-
ensureLoaded: async (sdk, signal, modelChoice, steps) => {
|
|
88
|
+
ensureLoaded: async (sdk, { signal, modelChoice, inferenceSteps: steps } = {}) => {
|
|
87
89
|
const stages = stagesFor(modelChoice, base);
|
|
88
90
|
const effectiveSteps = steps ?? baseSteps;
|
|
89
91
|
const fingerprint = fingerprintOf(stages, effectiveSteps);
|
|
90
92
|
await lock.lock();
|
|
91
93
|
try {
|
|
92
|
-
if (loading && residentFingerprint !== fingerprint)
|
|
94
|
+
if (loading && residentFingerprint !== fingerprint) {
|
|
93
95
|
await doUnload();
|
|
94
|
-
|
|
96
|
+
}
|
|
97
|
+
if (!loading && onBeforeLoad) {
|
|
95
98
|
await onBeforeLoad(signal);
|
|
99
|
+
}
|
|
96
100
|
residentFingerprint = fingerprint;
|
|
97
101
|
loading ??= (async () => {
|
|
98
102
|
onProgress?.(musicProgressName(stages.dit), {});
|
|
@@ -108,7 +112,7 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
|
|
|
108
112
|
ditModelSrc,
|
|
109
113
|
vaeModelSrc,
|
|
110
114
|
useGPU: useGpu,
|
|
111
|
-
|
|
115
|
+
inferenceSteps: effectiveSteps || undefined
|
|
112
116
|
};
|
|
113
117
|
const modelId = await loads.track(sdk.loadModel({
|
|
114
118
|
modelType: 'audiogen',
|
|
@@ -136,41 +140,48 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
|
|
|
136
140
|
};
|
|
137
141
|
}
|
|
138
142
|
export const BUSY_ERROR = 'a music generation is already running; try again when it finishes';
|
|
139
|
-
export async function runAudioGen(sdk, modelId, params, signal, onTick) {
|
|
140
|
-
if (signal?.aborted)
|
|
143
|
+
export async function runAudioGen(sdk, { modelId, params, signal, onTick }) {
|
|
144
|
+
if (signal?.aborted) {
|
|
141
145
|
return { error: 'music generation canceled' };
|
|
146
|
+
}
|
|
142
147
|
let run;
|
|
143
148
|
try {
|
|
144
149
|
run = sdk.audioGen({ modelId, ...params });
|
|
145
150
|
}
|
|
146
151
|
catch (err) {
|
|
147
|
-
if (signal?.aborted)
|
|
152
|
+
if (signal?.aborted) {
|
|
148
153
|
return { error: 'music generation canceled' };
|
|
154
|
+
}
|
|
149
155
|
throw err;
|
|
150
156
|
}
|
|
151
157
|
const onAbort = () => {
|
|
152
|
-
void sdk.cancel?.({ requestId: run.requestId }).catch(
|
|
158
|
+
void sdk.cancel?.({ requestId: run.requestId }).catch(noop);
|
|
153
159
|
};
|
|
154
160
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
155
161
|
let settled = false;
|
|
156
162
|
try {
|
|
157
163
|
const drained = (async () => {
|
|
158
|
-
for await (const tick of run.progressStream)
|
|
159
|
-
if (!settled)
|
|
164
|
+
for await (const tick of run.progressStream) {
|
|
165
|
+
if (!settled) {
|
|
160
166
|
onTick?.(tick);
|
|
161
|
-
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
})().catch(noop);
|
|
162
170
|
const audio = await run.audio;
|
|
163
171
|
await drained;
|
|
164
|
-
if (signal?.aborted)
|
|
172
|
+
if (signal?.aborted) {
|
|
165
173
|
return { error: 'music generation canceled' };
|
|
166
|
-
|
|
174
|
+
}
|
|
175
|
+
if (!audio?.pcm?.length) {
|
|
167
176
|
return { error: 'the audiogen model produced no audio' };
|
|
168
|
-
|
|
177
|
+
}
|
|
178
|
+
const stats = await run.stats.catch(noop);
|
|
169
179
|
return stats ? { ...audio, stats } : audio;
|
|
170
180
|
}
|
|
171
181
|
catch (err) {
|
|
172
|
-
if (signal?.aborted)
|
|
182
|
+
if (signal?.aborted) {
|
|
173
183
|
return { error: 'music generation canceled' };
|
|
184
|
+
}
|
|
174
185
|
throw err;
|
|
175
186
|
}
|
|
176
187
|
finally {
|
|
@@ -180,8 +191,9 @@ export async function runAudioGen(sdk, modelId, params, signal, onTick) {
|
|
|
180
191
|
}
|
|
181
192
|
export function audioDurationSeconds(audio) {
|
|
182
193
|
const bytesPerSample = audio.bitsPerSample / 8;
|
|
183
|
-
if (bytesPerSample <= 0 || audio.channels <= 0 || audio.sampleRate <= 0)
|
|
194
|
+
if (bytesPerSample <= 0 || audio.channels <= 0 || audio.sampleRate <= 0) {
|
|
184
195
|
return 0;
|
|
196
|
+
}
|
|
185
197
|
const samplesPerChannel = audio.pcm.byteLength / bytesPerSample / audio.channels;
|
|
186
198
|
return Math.round(samplesPerChannel / audio.sampleRate);
|
|
187
199
|
}
|
|
@@ -197,8 +209,9 @@ export function presentStats(stats) {
|
|
|
197
209
|
'backendId'
|
|
198
210
|
]) {
|
|
199
211
|
const value = stats[key];
|
|
200
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
212
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
201
213
|
kept[key] = value;
|
|
214
|
+
}
|
|
202
215
|
}
|
|
203
216
|
return Object.keys(kept).length > 0 ? kept : undefined;
|
|
204
217
|
}
|
|
@@ -240,8 +253,9 @@ export function pcmToWav(audio) {
|
|
|
240
253
|
const header = new ArrayBuffer(44);
|
|
241
254
|
const view = new DataView(header);
|
|
242
255
|
const ascii = (offset, value) => {
|
|
243
|
-
for (let i = 0; i < value.length; i++)
|
|
256
|
+
for (let i = 0; i < value.length; i++) {
|
|
244
257
|
view.setUint8(offset + i, value.charCodeAt(i));
|
|
258
|
+
}
|
|
245
259
|
};
|
|
246
260
|
ascii(0, 'RIFF');
|
|
247
261
|
view.setUint32(4, 36 + pcm.byteLength, true);
|
|
@@ -263,8 +277,9 @@ export function pcmToWav(audio) {
|
|
|
263
277
|
}
|
|
264
278
|
function parseArgs(args, defaults, maxDuration) {
|
|
265
279
|
const caption = typeof args.prompt === 'string' ? args.prompt.trim() : '';
|
|
266
|
-
if (!caption)
|
|
280
|
+
if (!caption) {
|
|
267
281
|
return { error: 'prompt must be a non-empty string' };
|
|
282
|
+
}
|
|
268
283
|
if (caption.length > MAX_CAPTION_CHARS) {
|
|
269
284
|
return { error: `prompt exceeds ${MAX_CAPTION_CHARS} characters` };
|
|
270
285
|
}
|
|
@@ -285,16 +300,18 @@ function parseArgs(args, defaults, maxDuration) {
|
|
|
285
300
|
}
|
|
286
301
|
duration = Math.round(n);
|
|
287
302
|
}
|
|
288
|
-
if (duration !== undefined)
|
|
303
|
+
if (duration !== undefined) {
|
|
289
304
|
duration = Math.min(duration, maxDuration);
|
|
305
|
+
}
|
|
290
306
|
let seed;
|
|
291
307
|
if (args.seed !== undefined && args.seed !== null) {
|
|
292
308
|
seed = Number(args.seed);
|
|
293
309
|
if (!Number.isInteger(seed) || seed < -1 || seed > MAX_SEED) {
|
|
294
310
|
return { error: `seed must be an integer between -1 (random) and ${MAX_SEED}` };
|
|
295
311
|
}
|
|
296
|
-
if (seed === -1)
|
|
312
|
+
if (seed === -1) {
|
|
297
313
|
seed = undefined;
|
|
314
|
+
}
|
|
298
315
|
}
|
|
299
316
|
let vocalLanguage;
|
|
300
317
|
if (typeof args.vocalLanguage === 'string' && args.vocalLanguage.trim()) {
|
|
@@ -362,8 +379,9 @@ export function normalizeClamps(clamps) {
|
|
|
362
379
|
}
|
|
363
380
|
export function resolveMusicSettings(ctx) {
|
|
364
381
|
const settings = ctx?.settings;
|
|
365
|
-
if (!settings)
|
|
382
|
+
if (!settings) {
|
|
366
383
|
return {};
|
|
384
|
+
}
|
|
367
385
|
const out = {};
|
|
368
386
|
if (typeof settings.modelChoice === 'string' && settings.modelChoice) {
|
|
369
387
|
out.modelChoice = settings.modelChoice;
|
|
@@ -438,12 +456,15 @@ export function createGenerateMusicTool(runtime, clamps) {
|
|
|
438
456
|
execute: async (args, ctx) => {
|
|
439
457
|
const music = resolveMusicSettings(ctx);
|
|
440
458
|
const parsed = parseArgs(args, { duration: settingsDuration(music.duration, cap.maxDuration) }, cap.maxDuration);
|
|
441
|
-
if ('error' in parsed)
|
|
459
|
+
if ('error' in parsed) {
|
|
442
460
|
return parsed;
|
|
443
|
-
|
|
461
|
+
}
|
|
462
|
+
if (!ctx?.attach) {
|
|
444
463
|
return { error: 'this device cannot persist generated audio' };
|
|
445
|
-
|
|
464
|
+
}
|
|
465
|
+
if (!runtime.acquire()) {
|
|
446
466
|
return { error: BUSY_ERROR, code: 'busy' };
|
|
467
|
+
}
|
|
447
468
|
const report = ctx.progress;
|
|
448
469
|
const onTick = report && musicPercentTick((percentage) => report({ percentage }));
|
|
449
470
|
const capMax = stepsCap ?? MAX_STEPS;
|
|
@@ -453,25 +474,35 @@ export function createGenerateMusicTool(runtime, clamps) {
|
|
|
453
474
|
stepsCap;
|
|
454
475
|
try {
|
|
455
476
|
const sdk = await runtime.loadSdk();
|
|
456
|
-
const { modelId } = await runtime.ensureLoaded(sdk,
|
|
477
|
+
const { modelId } = await runtime.ensureLoaded(sdk, {
|
|
478
|
+
signal: ctx.signal,
|
|
479
|
+
modelChoice: music.modelChoice,
|
|
480
|
+
inferenceSteps: steps
|
|
481
|
+
});
|
|
457
482
|
const hasVocals = parsed.lyrics !== undefined &&
|
|
458
483
|
parsed.lyrics.trim().toLowerCase() !== INSTRUMENTAL.toLowerCase();
|
|
459
484
|
const instrumental = !hasVocals;
|
|
460
485
|
const vocalLanguage = hasVocals
|
|
461
486
|
? (parsed.vocalLanguage ?? DEFAULT_VOCAL_LANGUAGE)
|
|
462
487
|
: undefined;
|
|
463
|
-
const result = await runAudioGen(sdk,
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
488
|
+
const result = await runAudioGen(sdk, {
|
|
489
|
+
modelId,
|
|
490
|
+
params: {
|
|
491
|
+
caption: parsed.caption,
|
|
492
|
+
lyrics: hasVocals ? parsed.lyrics : INSTRUMENTAL,
|
|
493
|
+
duration: parsed.duration,
|
|
494
|
+
seed: parsed.seed,
|
|
495
|
+
vocalLanguage: vocalLanguage || undefined,
|
|
496
|
+
bpm: parsed.bpm,
|
|
497
|
+
keyscale: parsed.keyscale || undefined,
|
|
498
|
+
timesignature: parsed.timesignature || undefined
|
|
499
|
+
},
|
|
500
|
+
signal: ctx.signal,
|
|
501
|
+
onTick
|
|
502
|
+
});
|
|
503
|
+
if ('error' in result) {
|
|
474
504
|
return result;
|
|
505
|
+
}
|
|
475
506
|
const wav = pcmToWav(result);
|
|
476
507
|
const record = await ctx.attach({
|
|
477
508
|
fileName: musicFileName(parsed.title, parsed.caption),
|
|
@@ -497,20 +528,19 @@ export function createGenerateMusicTool(runtime, clamps) {
|
|
|
497
528
|
sampleRate: result.sampleRate,
|
|
498
529
|
channels: result.channels,
|
|
499
530
|
instrumental,
|
|
500
|
-
|
|
501
|
-
?
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
: {}),
|
|
531
|
+
note: instrumental && captionImpliesVocals(parsed.caption)
|
|
532
|
+
? 'This track is instrumental: no lyrics were provided, so the model did not sing. ' +
|
|
533
|
+
'The prompt described vocals; to produce a sung version, call generate_music again ' +
|
|
534
|
+
'with lyrics ([verse]/[chorus] tags). Otherwise describe this track as instrumental, not vocal.'
|
|
535
|
+
: undefined,
|
|
507
536
|
hostNotice: notice,
|
|
508
|
-
|
|
537
|
+
data: stats ? { stats } : undefined
|
|
509
538
|
};
|
|
510
539
|
}
|
|
511
540
|
catch (err) {
|
|
512
|
-
if (err instanceof AbortedError)
|
|
541
|
+
if (err instanceof AbortedError) {
|
|
513
542
|
return { error: 'music generation canceled' };
|
|
543
|
+
}
|
|
514
544
|
throw err;
|
|
515
545
|
}
|
|
516
546
|
finally {
|
|
@@ -520,3 +550,4 @@ export function createGenerateMusicTool(runtime, clamps) {
|
|
|
520
550
|
}
|
|
521
551
|
};
|
|
522
552
|
}
|
|
553
|
+
function noop() { }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorMessage } from '../guards.js';
|
|
1
2
|
import { hlog } from '../log.js';
|
|
2
3
|
import { searchTargets } from './file-glob.js';
|
|
3
4
|
import { readLines } from './fs/read.js';
|
|
@@ -8,6 +9,38 @@ export const DEFAULT_GREP_MATCHES = 200;
|
|
|
8
9
|
export const MAX_GREP_MATCHES = 1000;
|
|
9
10
|
const MAX_LINE_CHARS = 500;
|
|
10
11
|
const BINARY_PROBE_CHARS = 1024;
|
|
12
|
+
async function scanFile(drive, key, { path, tests, room }) {
|
|
13
|
+
const hits = [];
|
|
14
|
+
let probed = 0;
|
|
15
|
+
let binary = false;
|
|
16
|
+
let index = 0;
|
|
17
|
+
try {
|
|
18
|
+
for await (const line of readLines(drive, key)) {
|
|
19
|
+
index++;
|
|
20
|
+
if (probed < BINARY_PROBE_CHARS && line.includes('\0')) {
|
|
21
|
+
binary = true;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (probed < BINARY_PROBE_CHARS) {
|
|
25
|
+
probed += line.length + 1;
|
|
26
|
+
}
|
|
27
|
+
if (!tests.some((test) => test(line))) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
hits.push({ file: path, line: index, text: line.slice(0, MAX_LINE_CHARS) });
|
|
31
|
+
if (hits.length >= room) {
|
|
32
|
+
return { hits, binary, end: 'capped' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (isDriveReadTruncated(error)) {
|
|
38
|
+
return { hits, binary, end: 'cut' };
|
|
39
|
+
}
|
|
40
|
+
return { hits, binary, end: 'unreadable', error };
|
|
41
|
+
}
|
|
42
|
+
return { hits, binary, end: 'complete' };
|
|
43
|
+
}
|
|
11
44
|
function stringArray(raw) {
|
|
12
45
|
if (!Array.isArray(raw) || raw.length === 0)
|
|
13
46
|
return null;
|
|
@@ -23,7 +56,7 @@ function compileQueries(queries, regex, caseSensitive) {
|
|
|
23
56
|
});
|
|
24
57
|
}
|
|
25
58
|
catch (err) {
|
|
26
|
-
return { error: `invalid regex: ${err
|
|
59
|
+
return { error: `invalid regex: ${errorMessage(err)}` };
|
|
27
60
|
}
|
|
28
61
|
}
|
|
29
62
|
return queries.map((q) => {
|
|
@@ -70,11 +103,13 @@ export function createGrepTool({ realpath = defaultRealpath, deniedRoots } = {})
|
|
|
70
103
|
rootsRequired: true,
|
|
71
104
|
validate(rawArgs) {
|
|
72
105
|
const queries = stringArray(rawArgs.queries);
|
|
73
|
-
if (!queries)
|
|
106
|
+
if (!queries) {
|
|
74
107
|
return 'queries must be a non-empty array of strings';
|
|
108
|
+
}
|
|
75
109
|
const compiled = compileQueries(queries, rawArgs.regex === true, rawArgs.caseSensitive === true);
|
|
76
|
-
if ('error' in compiled)
|
|
110
|
+
if ('error' in compiled) {
|
|
77
111
|
return compiled.error;
|
|
112
|
+
}
|
|
78
113
|
const maxMatches = rawArgs.maxMatches;
|
|
79
114
|
if (maxMatches !== undefined && maxMatches !== null) {
|
|
80
115
|
const n = Number(maxMatches);
|
|
@@ -91,16 +126,19 @@ export function createGrepTool({ realpath = defaultRealpath, deniedRoots } = {})
|
|
|
91
126
|
async execute(rawArgs, ctx) {
|
|
92
127
|
const roots = ctx?.fsRoots ?? [];
|
|
93
128
|
const queries = stringArray(rawArgs.queries);
|
|
94
|
-
if (!queries)
|
|
129
|
+
if (!queries) {
|
|
95
130
|
return { error: 'queries must be a non-empty array of strings' };
|
|
131
|
+
}
|
|
96
132
|
const compiled = compileQueries(queries, rawArgs.regex === true, rawArgs.caseSensitive === true);
|
|
97
|
-
if ('error' in compiled)
|
|
133
|
+
if ('error' in compiled) {
|
|
98
134
|
return compiled;
|
|
135
|
+
}
|
|
99
136
|
const maxMatches = rawArgs.maxMatches ? Number(rawArgs.maxMatches) : DEFAULT_GREP_MATCHES;
|
|
100
137
|
const deny = await canonicalDenyRoots(deniedRoots, realpath);
|
|
101
|
-
const scoped = await searchTargets(rawArgs.dir, roots, realpath, deny);
|
|
102
|
-
if ('error' in scoped)
|
|
138
|
+
const scoped = await searchTargets(rawArgs.dir, roots, { realpath, deny });
|
|
139
|
+
if ('error' in scoped) {
|
|
103
140
|
return scoped;
|
|
141
|
+
}
|
|
104
142
|
const matches = [];
|
|
105
143
|
let truncated = false;
|
|
106
144
|
let skippedLinks = 0;
|
|
@@ -111,47 +149,34 @@ export function createGrepTool({ realpath = defaultRealpath, deniedRoots } = {})
|
|
|
111
149
|
for (const file of walk.files) {
|
|
112
150
|
if (isDeniedPath(file.path, deny))
|
|
113
151
|
continue;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (line.includes('\0')) {
|
|
123
|
-
binary = true;
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
probed += line.length + 1;
|
|
127
|
-
}
|
|
128
|
-
if (!compiled.some((test) => test(line)))
|
|
129
|
-
continue;
|
|
130
|
-
hits.push({ file: file.path, line: index, text: line.slice(0, MAX_LINE_CHARS) });
|
|
131
|
-
if (matches.length + hits.length >= maxMatches) {
|
|
132
|
-
matches.push(...hits);
|
|
133
|
-
return { matches, truncated: true, ...(skippedLinks ? { skippedLinks } : {}) };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
152
|
+
const scan = await scanFile(target.root.drive, file.key, {
|
|
153
|
+
path: file.path,
|
|
154
|
+
tests: compiled,
|
|
155
|
+
room: maxMatches - matches.length
|
|
156
|
+
});
|
|
157
|
+
if (scan.end === 'capped') {
|
|
158
|
+
matches.push(...scan.hits);
|
|
159
|
+
return { matches, truncated: true, skippedLinks: skippedLinks || undefined };
|
|
136
160
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
hlog('warn: grep skipped a file', { file: file.path, err: err.message });
|
|
161
|
+
if (scan.end === 'unreadable') {
|
|
162
|
+
hlog('warn: grep skipped a file', {
|
|
163
|
+
file: file.path,
|
|
164
|
+
err: errorMessage(scan.error)
|
|
165
|
+
});
|
|
145
166
|
continue;
|
|
146
167
|
}
|
|
147
|
-
if (
|
|
148
|
-
|
|
168
|
+
if (scan.end === 'cut') {
|
|
169
|
+
truncated = true;
|
|
170
|
+
}
|
|
171
|
+
if (!scan.binary) {
|
|
172
|
+
matches.push(...scan.hits);
|
|
173
|
+
}
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
176
|
return {
|
|
152
177
|
matches,
|
|
153
|
-
|
|
154
|
-
|
|
178
|
+
truncated: truncated || undefined,
|
|
179
|
+
skippedLinks: skippedLinks || undefined
|
|
155
180
|
};
|
|
156
181
|
}
|
|
157
182
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import { isRecord } from '../../guards.js';
|
|
1
3
|
const BASE64URL = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
2
4
|
const PRINTABLE_RATIO = 0.9;
|
|
3
5
|
export function encodeDeclaredFields(body, encode) {
|
|
4
6
|
if (!encode)
|
|
5
7
|
return body;
|
|
6
8
|
const parsed = asObject(body);
|
|
7
|
-
if (!parsed)
|
|
9
|
+
if (!parsed) {
|
|
8
10
|
return body;
|
|
11
|
+
}
|
|
9
12
|
let next = parsed;
|
|
10
13
|
for (const [path, encoding] of Object.entries(encode)) {
|
|
11
14
|
if (encoding !== 'base64' && encoding !== 'base64url')
|
|
@@ -23,34 +26,39 @@ function asObject(body) {
|
|
|
23
26
|
return null;
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
if (
|
|
29
|
+
if (!isRecord(body) || Array.isArray(body)) {
|
|
27
30
|
return null;
|
|
31
|
+
}
|
|
28
32
|
return body;
|
|
29
33
|
}
|
|
30
34
|
function setAt(body, path, encoding) {
|
|
31
35
|
const [head, ...rest] = path;
|
|
32
|
-
if (head === undefined)
|
|
36
|
+
if (head === undefined) {
|
|
33
37
|
return body;
|
|
38
|
+
}
|
|
34
39
|
const current = body[head];
|
|
35
40
|
if (rest.length) {
|
|
36
|
-
if (
|
|
41
|
+
if (!isRecord(current)) {
|
|
37
42
|
return body;
|
|
43
|
+
}
|
|
38
44
|
return { ...body, [head]: setAt(current, rest, encoding) };
|
|
39
45
|
}
|
|
40
|
-
if (typeof current !== 'string')
|
|
46
|
+
if (typeof current !== 'string') {
|
|
41
47
|
return body;
|
|
48
|
+
}
|
|
42
49
|
return { ...body, [head]: encodeValue(current, encoding) };
|
|
43
50
|
}
|
|
44
51
|
function encodeValue(value, encoding) {
|
|
45
52
|
if (isAlreadyEncoded(value))
|
|
46
53
|
return value;
|
|
47
|
-
return
|
|
54
|
+
return b4a.toString(b4a.from(value, 'utf8'), encoding);
|
|
48
55
|
}
|
|
49
56
|
function isAlreadyEncoded(value) {
|
|
50
|
-
if (!BASE64URL.test(value) && !/^[A-Za-z0-9+/]+={0,2}$/.test(value))
|
|
57
|
+
if (!BASE64URL.test(value) && !/^[A-Za-z0-9+/]+={0,2}$/.test(value)) {
|
|
51
58
|
return false;
|
|
59
|
+
}
|
|
52
60
|
try {
|
|
53
|
-
const decoded =
|
|
61
|
+
const decoded = b4a.toString(b4a.from(value, 'base64'), 'utf8');
|
|
54
62
|
return decoded.length > 0 && isMostlyPrintable(decoded);
|
|
55
63
|
}
|
|
56
64
|
catch {
|
|
@@ -61,8 +69,9 @@ function isMostlyPrintable(text) {
|
|
|
61
69
|
const chars = [...text];
|
|
62
70
|
const printable = chars.filter((char) => {
|
|
63
71
|
const code = char.codePointAt(0) ?? 0;
|
|
64
|
-
if (code === 0xfffd)
|
|
72
|
+
if (code === 0xfffd) {
|
|
65
73
|
return false;
|
|
74
|
+
}
|
|
66
75
|
return code === 9 || code === 10 || code === 13 || (code >= 32 && code !== 127);
|
|
67
76
|
}).length;
|
|
68
77
|
return printable / chars.length >= PRINTABLE_RATIO;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../../guards.js';
|
|
1
2
|
const BLOCKED_HEADERS = new Set(['authorization', 'cookie', 'proxy-authorization']);
|
|
2
3
|
export const MAX_RESPONSE_CHARS = 8192;
|
|
3
4
|
export const TIMEOUT_MS = 10_000;
|
|
@@ -5,8 +6,9 @@ export function isRedirectStatus(status) {
|
|
|
5
6
|
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
6
7
|
}
|
|
7
8
|
export function parseObjectArg(v, name) {
|
|
8
|
-
if (v === null || v === undefined)
|
|
9
|
+
if (v === null || v === undefined) {
|
|
9
10
|
return { ok: true, value: undefined };
|
|
11
|
+
}
|
|
10
12
|
let value = v;
|
|
11
13
|
if (typeof v === 'string') {
|
|
12
14
|
try {
|
|
@@ -16,20 +18,23 @@ export function parseObjectArg(v, name) {
|
|
|
16
18
|
return { ok: false, error: `${name} must be an object` };
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
|
-
if (
|
|
21
|
+
if (!isPlainRecord(value)) {
|
|
20
22
|
return { ok: false, error: `${name} must be an object` };
|
|
21
23
|
}
|
|
22
|
-
return { ok: true, value
|
|
24
|
+
return { ok: true, value };
|
|
23
25
|
}
|
|
24
26
|
const BODY_JSON_ERROR = 'body is not valid JSON — resend it as one complete JSON object';
|
|
25
27
|
export function parseJsonBody(body, contentType) {
|
|
26
|
-
if (typeof body !== 'string')
|
|
28
|
+
if (typeof body !== 'string') {
|
|
27
29
|
return { ok: true, value: body };
|
|
28
|
-
|
|
30
|
+
}
|
|
31
|
+
if (contentType !== undefined && !/json/i.test(contentType)) {
|
|
29
32
|
return { ok: true, value: body };
|
|
33
|
+
}
|
|
30
34
|
const text = body.trim();
|
|
31
|
-
if (text[0] !== '{' && text[0] !== '[')
|
|
35
|
+
if (text[0] !== '{' && text[0] !== '[') {
|
|
32
36
|
return { ok: true, value: body };
|
|
37
|
+
}
|
|
33
38
|
let parsed;
|
|
34
39
|
try {
|
|
35
40
|
parsed = JSON.parse(text);
|
|
@@ -40,15 +45,16 @@ export function parseJsonBody(body, contentType) {
|
|
|
40
45
|
return { ok: true, value: isPlainRecord(parsed) ? parsed : body };
|
|
41
46
|
}
|
|
42
47
|
function isPlainRecord(value) {
|
|
43
|
-
return
|
|
48
|
+
return isRecord(value) && !Array.isArray(value);
|
|
44
49
|
}
|
|
45
50
|
export function applyQuery(url, query) {
|
|
46
51
|
for (const [key, value] of Object.entries(query)) {
|
|
47
52
|
if (value === null || value === undefined)
|
|
48
53
|
continue;
|
|
49
54
|
if (Array.isArray(value)) {
|
|
50
|
-
for (const item of value)
|
|
55
|
+
for (const item of value) {
|
|
51
56
|
url.searchParams.append(key, String(item));
|
|
57
|
+
}
|
|
52
58
|
}
|
|
53
59
|
else {
|
|
54
60
|
url.searchParams.append(key, String(value));
|
|
@@ -59,24 +65,27 @@ export function contentTypeOf(headers) {
|
|
|
59
65
|
if (typeof headers !== 'object' || headers === null)
|
|
60
66
|
return undefined;
|
|
61
67
|
for (const [key, value] of Object.entries(headers)) {
|
|
62
|
-
if (key.toLowerCase() === 'content-type')
|
|
68
|
+
if (key.toLowerCase() === 'content-type') {
|
|
63
69
|
return String(value);
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
return undefined;
|
|
66
73
|
}
|
|
67
74
|
export function buildHeaders(headerArgs) {
|
|
68
75
|
const headers = {};
|
|
69
76
|
for (const [key, value] of Object.entries(headerArgs)) {
|
|
70
|
-
if (!BLOCKED_HEADERS.has(key.toLowerCase()))
|
|
77
|
+
if (!BLOCKED_HEADERS.has(key.toLowerCase())) {
|
|
71
78
|
headers[key] = String(value);
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
return headers;
|
|
74
82
|
}
|
|
75
83
|
export function encodeBody(body, headers) {
|
|
76
84
|
if (body === null || body === undefined)
|
|
77
85
|
return undefined;
|
|
78
|
-
if (typeof body !== 'object')
|
|
86
|
+
if (typeof body !== 'object') {
|
|
79
87
|
return String(body);
|
|
88
|
+
}
|
|
80
89
|
if (!Object.keys(headers).some((h) => h.toLowerCase() === 'content-type')) {
|
|
81
90
|
headers['Content-Type'] = 'application/json';
|
|
82
91
|
}
|