@qvac/harness 0.3.3 → 0.5.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 +700 -126
- package/README.md +156 -0
- package/dist/harness.bundle +71 -103
- package/dist/prebuilds-manifest.json +0 -8
- package/dist-lib/index.d.ts +42 -40
- package/dist-lib/index.js +307 -117
- 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 +194 -135
- 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 +69 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +439 -0
- 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 +15 -37
- package/dist-lib/lib/connect.js +161 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +26 -20
- 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 +13 -0
- package/dist-lib/lib/models/custom-model-cache.js +70 -0
- package/dist-lib/lib/models/gguf-header.d.ts +31 -0
- package/dist-lib/lib/models/gguf-header.js +230 -0
- 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 +19 -21
- package/dist-lib/lib/models/model-catalog.js +66 -33
- package/dist-lib/lib/models/model-delete.d.ts +4 -1
- package/dist-lib/lib/models/model-delete.js +30 -15
- package/dist-lib/lib/models/model-handle.js +2 -1
- package/dist-lib/lib/models/model-list.d.ts +14 -3
- package/dist-lib/lib/models/model-list.js +33 -6
- package/dist-lib/lib/models/model-pool.d.ts +44 -10
- package/dist-lib/lib/models/model-pool.js +47 -42
- package/dist-lib/lib/models/model-source.js +11 -6
- 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 +128 -68
- 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 +55 -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 +6 -0
- package/dist-lib/spec/calls.js +2 -0
- package/dist-lib/spec/hrpc/hrpc.json +26 -0
- package/dist-lib/spec/hrpc/index.js +27 -3
- package/dist-lib/spec/hrpc/messages.js +207 -8
- package/dist-lib/spec/hyperschema/index.js +207 -8
- package/dist-lib/spec/hyperschema/schema.json +165 -1
- package/dist-lib/spec/hyperschema/types.d.ts +42 -1
- package/package.json +13 -14
- package/worker/harness-sidecar.mjs +16 -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
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import defaultFetch from '#fetch';
|
|
3
|
+
import { errorMessage } from '../guards.js';
|
|
4
|
+
import { defaultContextSize } from '../models/context.js';
|
|
5
|
+
import { isRemoteSource, localSourcePath, parseGgufHeader, readGgufHeaderBytes } from '../models/gguf-header.js';
|
|
6
|
+
import { shardSet } from '../models/shards.js';
|
|
7
|
+
const HUGGING_FACE = 'https://huggingface.co';
|
|
8
|
+
const HUGGING_FACE_HOSTS = new Set(['huggingface.co', 'www.huggingface.co', 'hf.co']);
|
|
9
|
+
const NON_MODEL_NAMESPACES = new Set(['api', 'datasets', 'spaces', 'docs', 'models']);
|
|
10
|
+
const SHARD = /^(.+)-(\d{5})-of-(\d{5})\.gguf$/i;
|
|
11
|
+
const PIPELINE_TASKS = {
|
|
12
|
+
'automatic-speech-recognition': 'speech-to-text',
|
|
13
|
+
'text-to-speech': 'text-to-speech',
|
|
14
|
+
'text-to-image': 'image-generation',
|
|
15
|
+
'feature-extraction': 'embeddings',
|
|
16
|
+
'sentence-similarity': 'embeddings',
|
|
17
|
+
'text-generation': 'llm',
|
|
18
|
+
'image-text-to-text': 'llm'
|
|
19
|
+
};
|
|
20
|
+
const ARCHITECTURE_TASKS = [
|
|
21
|
+
[/^whisper/i, 'speech-to-text'],
|
|
22
|
+
[/^parakeet/i, 'speech-to-text'],
|
|
23
|
+
[/^flux/i, 'image-generation'],
|
|
24
|
+
[/^supertonic/i, 'text-to-speech'],
|
|
25
|
+
[/bert/i, 'embeddings']
|
|
26
|
+
];
|
|
27
|
+
const NOT_LLM_REASON = 'Fit is estimated only for text-generation models.';
|
|
28
|
+
const NO_CHECKSUM_REASON = 'Fit is estimated only for files the SDK catalog knows by checksum, and this source publishes none.';
|
|
29
|
+
const NO_ESTIMATOR_REASON = 'This harness has no fit estimator.';
|
|
30
|
+
export async function previewModelSource(context, deps = {}) {
|
|
31
|
+
const fetch = deps.fetch ?? defaultFetch;
|
|
32
|
+
const src = context.src.trim();
|
|
33
|
+
if (!src) {
|
|
34
|
+
return refuse('invalid-source', 'Paste a Hugging Face link, a direct link to a .gguf file, or a local path.');
|
|
35
|
+
}
|
|
36
|
+
const weight = await inspectModelFile(src, fetch, deps);
|
|
37
|
+
if ('answer' in weight) {
|
|
38
|
+
return weight.answer;
|
|
39
|
+
}
|
|
40
|
+
const problem = modelProblem(weight.header);
|
|
41
|
+
if (problem) {
|
|
42
|
+
return refuse(problem[0], problem[1]);
|
|
43
|
+
}
|
|
44
|
+
const taskType = taskTypeFor({
|
|
45
|
+
pipelineTag: weight.file.pipelineTag,
|
|
46
|
+
tags: [...(weight.file.tags ?? []), ...(weight.header.tags ?? [])],
|
|
47
|
+
architecture: weight.header.architecture
|
|
48
|
+
});
|
|
49
|
+
let projectionBytes = 0;
|
|
50
|
+
let projectionSrc;
|
|
51
|
+
const mmprojSrc = context.mmprojSrc?.trim();
|
|
52
|
+
if (mmprojSrc) {
|
|
53
|
+
const projection = await inspectModelFile(mmprojSrc, fetch, deps);
|
|
54
|
+
if ('answer' in projection) {
|
|
55
|
+
return projection.answer.ok
|
|
56
|
+
? refuse('invalid-projection', 'Link the projection (mmproj) file itself, not its repository.')
|
|
57
|
+
: refuse('invalid-projection', `The projection was refused: ${projection.answer.reason ?? ''}`.trim());
|
|
58
|
+
}
|
|
59
|
+
if (!isProjection(projection.header)) {
|
|
60
|
+
return refuse('invalid-projection', 'The projection is not an mmproj file.');
|
|
61
|
+
}
|
|
62
|
+
projectionBytes = projection.file.sizeBytes;
|
|
63
|
+
projectionSrc = projection.file.src;
|
|
64
|
+
}
|
|
65
|
+
const { fit, fitReasons } = await assess(weight, taskType, deps);
|
|
66
|
+
return defined({
|
|
67
|
+
ok: true,
|
|
68
|
+
src: weight.file.src,
|
|
69
|
+
sizeBytes: weight.file.sizeBytes + projectionBytes || undefined,
|
|
70
|
+
contextLength: weight.header.contextLength,
|
|
71
|
+
architecture: weight.header.architecture,
|
|
72
|
+
sizeLabel: weight.header.sizeLabel,
|
|
73
|
+
taskType,
|
|
74
|
+
license: weight.file.license ?? weight.header.license,
|
|
75
|
+
fit,
|
|
76
|
+
fitReasons,
|
|
77
|
+
mmprojSrc: projectionSrc
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function readModelSourcePreview(response) {
|
|
81
|
+
return defined({
|
|
82
|
+
ok: response.ok === true,
|
|
83
|
+
refusal: (response.refusal || undefined),
|
|
84
|
+
reason: response.reason || undefined,
|
|
85
|
+
src: response.src || undefined,
|
|
86
|
+
files: response.files?.length
|
|
87
|
+
? response.files.map(({ src, label, sizeBytes }) => ({
|
|
88
|
+
src,
|
|
89
|
+
label,
|
|
90
|
+
sizeBytes: sizeBytes ?? 0
|
|
91
|
+
}))
|
|
92
|
+
: undefined,
|
|
93
|
+
sizeBytes: response.sizeBytes || undefined,
|
|
94
|
+
contextLength: response.contextLength || undefined,
|
|
95
|
+
architecture: response.architecture || undefined,
|
|
96
|
+
sizeLabel: response.sizeLabel || undefined,
|
|
97
|
+
taskType: response.taskType || undefined,
|
|
98
|
+
license: response.license || undefined,
|
|
99
|
+
fit: (response.fit || undefined),
|
|
100
|
+
fitReasons: response.fitReasons?.length ? [...response.fitReasons] : undefined,
|
|
101
|
+
mmprojSrc: response.mmprojSrc || undefined
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function parseHuggingFaceRef(src) {
|
|
105
|
+
let url;
|
|
106
|
+
try {
|
|
107
|
+
url = new URL(src);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (!HUGGING_FACE_HOSTS.has(url.hostname)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const segments = pathSegments(url.pathname);
|
|
116
|
+
if (!segments) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const [org, name, kind, revision, ...rest] = segments;
|
|
120
|
+
if (!org || !name || NON_MODEL_NAMESPACES.has(org)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const repo = `${org}/${name}`;
|
|
124
|
+
if (!kind) {
|
|
125
|
+
const shown = url.searchParams.get('show_file_info');
|
|
126
|
+
return shown ? { repo, revision: 'main', file: shown } : { repo, revision: 'main' };
|
|
127
|
+
}
|
|
128
|
+
if ((kind === 'blob' || kind === 'resolve') && revision && rest.length > 0) {
|
|
129
|
+
return { repo, revision, file: rest.join('/') };
|
|
130
|
+
}
|
|
131
|
+
if (kind === 'tree' && revision) {
|
|
132
|
+
return { repo, revision };
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
async function inspectModelFile(src, fetch, deps) {
|
|
137
|
+
const inspection = await inspect(src, fetch, deps);
|
|
138
|
+
if (inspection.kind === 'answer') {
|
|
139
|
+
return { answer: inspection.preview };
|
|
140
|
+
}
|
|
141
|
+
const bytes = await readGgufHeaderBytes(inspection.file.src, { fetch });
|
|
142
|
+
if (!bytes) {
|
|
143
|
+
return {
|
|
144
|
+
answer: refuse('unreachable', 'Could not read the start of the file to check it is a model.')
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const header = parseGgufHeader(bytes);
|
|
148
|
+
if (!header) {
|
|
149
|
+
return {
|
|
150
|
+
answer: refuse('not-gguf', 'This is not a GGUF file. Only GGUF models run on this device.')
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { file: inspection.file, header };
|
|
154
|
+
}
|
|
155
|
+
function inspect(src, fetch, deps) {
|
|
156
|
+
const ref = parseHuggingFaceRef(src);
|
|
157
|
+
if (ref) {
|
|
158
|
+
return inspectHuggingFace(ref, fetch);
|
|
159
|
+
}
|
|
160
|
+
if (isRemoteSource(src)) {
|
|
161
|
+
return wellFormedUrl(src)
|
|
162
|
+
? inspectUrl(src, fetch)
|
|
163
|
+
: Promise.resolve(answer(refuse('invalid-source', 'This link is not a valid URL.')));
|
|
164
|
+
}
|
|
165
|
+
const file = localSourcePath(src);
|
|
166
|
+
if (file === null) {
|
|
167
|
+
return Promise.resolve(answer(refuse('invalid-source', 'Only Hugging Face links, http(s) links and local paths can be added.')));
|
|
168
|
+
}
|
|
169
|
+
return inspectLocal(src, file, deps);
|
|
170
|
+
}
|
|
171
|
+
async function inspectHuggingFace(ref, fetch) {
|
|
172
|
+
let response;
|
|
173
|
+
try {
|
|
174
|
+
response = await fetch(modelInfoUrl(ref));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return answer(refuse('unreachable', 'Could not reach Hugging Face.'));
|
|
178
|
+
}
|
|
179
|
+
if (response.status === 401 || response.status === 403) {
|
|
180
|
+
return answer(refuse('private', `${ref.repo} is private or does not exist on Hugging Face.`));
|
|
181
|
+
}
|
|
182
|
+
if (response.status === 404) {
|
|
183
|
+
return answer(refuse('not-found', `${ref.repo} does not exist on Hugging Face.`));
|
|
184
|
+
}
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
return answer(refuse('unreachable', `Hugging Face answered HTTP ${response.status}.`));
|
|
187
|
+
}
|
|
188
|
+
const info = (await response.json());
|
|
189
|
+
if (info.private) {
|
|
190
|
+
return answer(refuse('private', `${ref.repo} is private on Hugging Face.`));
|
|
191
|
+
}
|
|
192
|
+
if (info.gated) {
|
|
193
|
+
return answer(refuse('gated', `${ref.repo} is gated: its files need an accepted license and a Hugging Face sign-in, which custom models do not support.`));
|
|
194
|
+
}
|
|
195
|
+
const pipelineTag = info.pipeline_tag;
|
|
196
|
+
const license = licenseOf(info);
|
|
197
|
+
const siblings = info.siblings ?? [];
|
|
198
|
+
const candidates = modelFiles(ref, siblings);
|
|
199
|
+
const wanted = ref.file;
|
|
200
|
+
if (wanted) {
|
|
201
|
+
const sibling = siblings.find((entry) => entry.rfilename === wanted);
|
|
202
|
+
if (!sibling) {
|
|
203
|
+
return answer(refuse('not-found', `${ref.repo} has no file ${wanted}.`));
|
|
204
|
+
}
|
|
205
|
+
if (!/\.gguf$/i.test(wanted)) {
|
|
206
|
+
return answer(refuse('not-gguf', `${wanted} is not a GGUF file. Only GGUF models run on this device.`));
|
|
207
|
+
}
|
|
208
|
+
const file = candidates.find((candidate) => candidate.members.includes(wanted)) ?? fileOf(ref, sibling);
|
|
209
|
+
return { kind: 'file', file: { ...file, license, pipelineTag, tags: info.tags } };
|
|
210
|
+
}
|
|
211
|
+
if (candidates.length === 0) {
|
|
212
|
+
return answer(refuse('no-model-files', `${ref.repo} has no GGUF model file. Only GGUF models run on this device.`));
|
|
213
|
+
}
|
|
214
|
+
if (candidates.length === 1) {
|
|
215
|
+
return { kind: 'file', file: { ...candidates[0], license, pipelineTag, tags: info.tags } };
|
|
216
|
+
}
|
|
217
|
+
return answer(defined({
|
|
218
|
+
ok: true,
|
|
219
|
+
files: candidates.map(({ src, label, sizeBytes }) => ({ src, label, sizeBytes })),
|
|
220
|
+
license,
|
|
221
|
+
taskType: taskTypeFor({ pipelineTag, tags: info.tags })
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
async function inspectUrl(src, fetch) {
|
|
225
|
+
let response;
|
|
226
|
+
try {
|
|
227
|
+
response = await fetch(src, { method: 'HEAD', redirect: 'follow' });
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return answer(refuse('unreachable', `Could not reach ${new URL(src).host}.`));
|
|
231
|
+
}
|
|
232
|
+
const { status } = response;
|
|
233
|
+
if (status === 401 || status === 403) {
|
|
234
|
+
return answer(refuse('private', `The server refused access (HTTP ${status}). Only public links can be added.`));
|
|
235
|
+
}
|
|
236
|
+
if (status === 404 || status === 410) {
|
|
237
|
+
return answer(refuse('not-found', `Nothing is at this link (HTTP ${status}).`));
|
|
238
|
+
}
|
|
239
|
+
if (status === 405 || status === 501) {
|
|
240
|
+
return { kind: 'file', file: { src, label: basename(src), sizeBytes: 0 } };
|
|
241
|
+
}
|
|
242
|
+
if (!response.ok) {
|
|
243
|
+
return answer(refuse('unreachable', `The server answered HTTP ${status}.`));
|
|
244
|
+
}
|
|
245
|
+
if ((response.headers.get('content-type') ?? '').includes('text/html')) {
|
|
246
|
+
return answer(refuse('web-page', 'This link opens a web page, not a model file. Link to the .gguf file itself.'));
|
|
247
|
+
}
|
|
248
|
+
const set = shardSet(src);
|
|
249
|
+
if (set && set.shards.length > 1) {
|
|
250
|
+
return inspectShards(set, fetch);
|
|
251
|
+
}
|
|
252
|
+
return { kind: 'file', file: { src, label: basename(src), sizeBytes: sizeOfResponse(response) } };
|
|
253
|
+
}
|
|
254
|
+
async function inspectShards(set, fetch) {
|
|
255
|
+
let responses;
|
|
256
|
+
try {
|
|
257
|
+
responses = await Promise.all(set.shards.map((shard) => fetch(shard.url, { method: 'HEAD', redirect: 'follow' })));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return answer(refuse('unreachable', `Could not reach every part of ${set.base}.`));
|
|
261
|
+
}
|
|
262
|
+
const missing = responses.findIndex((response) => !response.ok);
|
|
263
|
+
if (missing !== -1) {
|
|
264
|
+
const { status } = responses[missing];
|
|
265
|
+
return answer(refuse(status === 404 || status === 410 ? 'not-found' : 'unreachable', `Part ${missing + 1} of ${set.shards.length}, ${set.shards[missing].filename}, answered HTTP ${status}.`));
|
|
266
|
+
}
|
|
267
|
+
const sizes = responses.map(sizeOfResponse);
|
|
268
|
+
return {
|
|
269
|
+
kind: 'file',
|
|
270
|
+
file: {
|
|
271
|
+
src: set.shards[0].url,
|
|
272
|
+
label: `${set.base}${set.extension} (${set.shards.length} parts)`,
|
|
273
|
+
sizeBytes: sizes.includes(0) ? 0 : sizes.reduce((sum, size) => sum + size, 0)
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function sizeOfResponse(response) {
|
|
278
|
+
return (positive(response.headers.get('x-linked-size')) ??
|
|
279
|
+
positive(response.headers.get('content-length')) ??
|
|
280
|
+
0);
|
|
281
|
+
}
|
|
282
|
+
async function inspectLocal(src, file, deps) {
|
|
283
|
+
const stat = await (deps.stat ?? statFile)(file);
|
|
284
|
+
if (!stat) {
|
|
285
|
+
return answer(refuse('not-found', `There is no file at ${file}.`));
|
|
286
|
+
}
|
|
287
|
+
return { kind: 'file', file: { src, label: basename(file), sizeBytes: stat.size } };
|
|
288
|
+
}
|
|
289
|
+
async function statFile(file) {
|
|
290
|
+
try {
|
|
291
|
+
const stat = await fs.promises.stat(file);
|
|
292
|
+
return stat.isFile() ? { size: stat.size } : null;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function modelFiles(ref, siblings) {
|
|
299
|
+
const singles = [];
|
|
300
|
+
const shardSets = new Map();
|
|
301
|
+
for (const sibling of siblings) {
|
|
302
|
+
const name = sibling.rfilename;
|
|
303
|
+
if (!/\.gguf$/i.test(name) || /mmproj/i.test(name)) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const shard = SHARD.exec(name);
|
|
307
|
+
if (!shard) {
|
|
308
|
+
singles.push({ ...fileOf(ref, sibling), members: [name] });
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const key = `${shard[1]}|${shard[3]}`;
|
|
312
|
+
shardSets.set(key, [...(shardSets.get(key) ?? []), sibling]);
|
|
313
|
+
}
|
|
314
|
+
const sets = [...shardSets.values()].map((parts) => {
|
|
315
|
+
const sorted = [...parts].sort((a, b) => a.rfilename.localeCompare(b.rfilename));
|
|
316
|
+
const first = sorted[0];
|
|
317
|
+
return {
|
|
318
|
+
src: resolveUrl(ref, first.rfilename),
|
|
319
|
+
label: `${basename(first.rfilename.replace(/-\d{5}-of-\d{5}\.gguf$/i, '.gguf'))} (${sorted.length} parts)`,
|
|
320
|
+
sizeBytes: sorted.reduce((total, part) => total + sizeOf(part), 0),
|
|
321
|
+
members: sorted.map((part) => part.rfilename)
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
return [...singles, ...sets];
|
|
325
|
+
}
|
|
326
|
+
function fileOf(ref, sibling) {
|
|
327
|
+
return defined({
|
|
328
|
+
src: resolveUrl(ref, sibling.rfilename),
|
|
329
|
+
label: basename(sibling.rfilename),
|
|
330
|
+
sizeBytes: sizeOf(sibling),
|
|
331
|
+
sha256: sibling.lfs?.sha256
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function modelProblem(header) {
|
|
335
|
+
if (isProjection(header)) {
|
|
336
|
+
return [
|
|
337
|
+
'not-a-model',
|
|
338
|
+
'This is a vision projection (mmproj), not a model. Add the model file and pass this as its projection.'
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
if (header.type === 'adapter') {
|
|
342
|
+
return ['not-a-model', 'This is a LoRA adapter, not a model.'];
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function isProjection(header) {
|
|
347
|
+
return header.architecture === 'clip' || header.type === 'mmproj';
|
|
348
|
+
}
|
|
349
|
+
function taskTypeFor({ pipelineTag, tags = [], architecture }) {
|
|
350
|
+
const tagged = [pipelineTag, ...tags].find((tag) => tag !== undefined && Object.hasOwn(PIPELINE_TASKS, tag));
|
|
351
|
+
if (tagged) {
|
|
352
|
+
return PIPELINE_TASKS[tagged];
|
|
353
|
+
}
|
|
354
|
+
return ARCHITECTURE_TASKS.find(([pattern]) => pattern.test(architecture ?? ''))?.[1] ?? 'llm';
|
|
355
|
+
}
|
|
356
|
+
async function assess({ file, header }, taskType, deps) {
|
|
357
|
+
if (taskType !== 'llm') {
|
|
358
|
+
return { fit: 'unknown', fitReasons: [NOT_LLM_REASON] };
|
|
359
|
+
}
|
|
360
|
+
if (!file.sha256) {
|
|
361
|
+
return { fit: 'unknown', fitReasons: [NO_CHECKSUM_REASON] };
|
|
362
|
+
}
|
|
363
|
+
if (!deps.assessFit) {
|
|
364
|
+
return { fit: 'unknown', fitReasons: [NO_ESTIMATOR_REASON] };
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
return await deps.assessFit({
|
|
368
|
+
sha256Checksum: file.sha256,
|
|
369
|
+
name: header.name ?? file.label,
|
|
370
|
+
contextTokens: defaultContextSize(file.src, header.contextLength)
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
return {
|
|
375
|
+
fit: 'unknown',
|
|
376
|
+
fitReasons: [`The fit estimate failed: ${errorMessage(err)}`]
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function modelInfoUrl(ref) {
|
|
381
|
+
const revision = ref.revision === 'main' ? '' : `/revision/${encodeURIComponent(ref.revision)}`;
|
|
382
|
+
return `${HUGGING_FACE}/api/models/${encodePath(ref.repo)}${revision}?blobs=true`;
|
|
383
|
+
}
|
|
384
|
+
function resolveUrl(ref, file) {
|
|
385
|
+
return `${HUGGING_FACE}/${encodePath(ref.repo)}/resolve/${encodeURIComponent(ref.revision)}/${encodePath(file)}`;
|
|
386
|
+
}
|
|
387
|
+
function encodePath(path) {
|
|
388
|
+
return path.split('/').map(encodeURIComponent).join('/');
|
|
389
|
+
}
|
|
390
|
+
function pathSegments(pathname) {
|
|
391
|
+
try {
|
|
392
|
+
return pathname.split('/').filter(Boolean).map(decodeURIComponent);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function wellFormedUrl(src) {
|
|
399
|
+
try {
|
|
400
|
+
return pathSegments(new URL(src).pathname) !== null;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function licenseOf(info) {
|
|
407
|
+
const license = info.cardData?.license;
|
|
408
|
+
if (Array.isArray(license)) {
|
|
409
|
+
return license.length ? license.join(', ') : undefined;
|
|
410
|
+
}
|
|
411
|
+
return license || undefined;
|
|
412
|
+
}
|
|
413
|
+
function sizeOf(sibling) {
|
|
414
|
+
return sibling.lfs?.size ?? sibling.size ?? 0;
|
|
415
|
+
}
|
|
416
|
+
function basename(src) {
|
|
417
|
+
const path = src.split(/[?#]/)[0];
|
|
418
|
+
const name = path.slice(path.lastIndexOf('/') + 1);
|
|
419
|
+
return (pathSegments(name) ?? [name])[0] || src;
|
|
420
|
+
}
|
|
421
|
+
function positive(raw) {
|
|
422
|
+
const value = Number(raw);
|
|
423
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
424
|
+
}
|
|
425
|
+
function refuse(refusal, reason) {
|
|
426
|
+
return { ok: false, refusal, reason };
|
|
427
|
+
}
|
|
428
|
+
function answer(preview) {
|
|
429
|
+
return { kind: 'answer', preview };
|
|
430
|
+
}
|
|
431
|
+
function defined(value) {
|
|
432
|
+
const copy = { ...value };
|
|
433
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
434
|
+
if (entry === undefined) {
|
|
435
|
+
Reflect.deleteProperty(copy, key);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return copy;
|
|
439
|
+
}
|
|
@@ -31,9 +31,9 @@ function verbalize(text, lang) {
|
|
|
31
31
|
let spoken = text.replace(/#(?=\d)/g, ' number ');
|
|
32
32
|
for (const [symbol, code] of CURRENCY_CODES) {
|
|
33
33
|
spoken = spoken.replace(amountAfter(symbol), (_match, amount) => {
|
|
34
|
-
return ` ${amount} ${currencyName(code, lang, amount === '1')} `;
|
|
34
|
+
return ` ${amount} ${currencyName(code, lang, { singular: amount === '1' })} `;
|
|
35
35
|
});
|
|
36
|
-
spoken = spoken.split(symbol).join(` ${currencyName(code, lang
|
|
36
|
+
spoken = spoken.split(symbol).join(` ${currencyName(code, lang)} `);
|
|
37
37
|
}
|
|
38
38
|
for (const [symbol, spokenForm] of SPEECH_LEXICON[lang]) {
|
|
39
39
|
spoken = spoken.split(symbol).join(spokenForm);
|
|
@@ -46,11 +46,12 @@ function amountAfter(symbol) {
|
|
|
46
46
|
function escapeRegExp(text) {
|
|
47
47
|
return text.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
|
48
48
|
}
|
|
49
|
-
function currencyName(code, lang, singular) {
|
|
49
|
+
function currencyName(code, lang, { singular = false } = {}) {
|
|
50
50
|
const key = `${lang}:${code}:${singular}`;
|
|
51
51
|
const hit = CURRENCY_NAMES.get(key);
|
|
52
|
-
if (hit !== undefined)
|
|
52
|
+
if (hit !== undefined) {
|
|
53
53
|
return hit;
|
|
54
|
+
}
|
|
54
55
|
const parts = new Intl.NumberFormat(lang, {
|
|
55
56
|
style: 'currency',
|
|
56
57
|
currency: code,
|
|
@@ -13,12 +13,10 @@ export function collectToolGrants(skills) {
|
|
|
13
13
|
const owners = grants.grantedOwners.get(name) ?? new Set();
|
|
14
14
|
owners.add(skill.name);
|
|
15
15
|
grants.grantedOwners.set(name, owners);
|
|
16
|
-
if (scope) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
grants.grantedOps.set(scope.toLowerCase(), list);
|
|
21
|
-
}
|
|
16
|
+
if (scope && skill.cliValidator && skill.cliValidator.bin === scope.toLowerCase()) {
|
|
17
|
+
const list = grants.grantedOps.get(scope.toLowerCase()) ?? [];
|
|
18
|
+
list.push(skill.cliValidator);
|
|
19
|
+
grants.grantedOps.set(scope.toLowerCase(), list);
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
}
|
|
@@ -49,12 +47,15 @@ export function enabledTools(tools, { toolsSelected, allowlist, grantedNames, gr
|
|
|
49
47
|
}
|
|
50
48
|
if (tool.rootsRequired)
|
|
51
49
|
return hasRoots;
|
|
52
|
-
if (tool.writeRootsRequired)
|
|
50
|
+
if (tool.writeRootsRequired) {
|
|
53
51
|
return hasWriteRoots;
|
|
54
|
-
|
|
52
|
+
}
|
|
53
|
+
if (tool.skillGated) {
|
|
55
54
|
return loadedGrantedNames.has(tool.name);
|
|
56
|
-
|
|
55
|
+
}
|
|
56
|
+
if (!toolsSelected) {
|
|
57
57
|
return true;
|
|
58
|
+
}
|
|
58
59
|
return allowlist.includes(tool.name) || grantedNames.has(tool.name);
|
|
59
60
|
});
|
|
60
61
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../guards.js';
|
|
1
2
|
import { SKILL_TOOL_NAME } from './tool-names.js';
|
|
2
3
|
export const DIRECT_CALL_ID = 'call-direct';
|
|
3
4
|
export const TOOL_CONTINUATION_NUDGE = "[Tool results above. Continue the user's request: call the next tool, or answer.]";
|
|
@@ -13,37 +14,43 @@ export function skillLoadedTurn(name) {
|
|
|
13
14
|
function refFrom(value) {
|
|
14
15
|
if (!value || typeof value !== 'object')
|
|
15
16
|
return null;
|
|
16
|
-
|
|
17
|
-
if (typeof record.attachmentId !== 'string')
|
|
17
|
+
if (!('attachmentId' in value) || typeof value.attachmentId !== 'string') {
|
|
18
18
|
return null;
|
|
19
|
-
|
|
20
|
-
const
|
|
19
|
+
}
|
|
20
|
+
const ref = { attachmentId: value.attachmentId };
|
|
21
|
+
const name = 'fileName' in value && typeof value.fileName === 'string' ? value.fileName : '';
|
|
21
22
|
const dot = name.lastIndexOf('.');
|
|
22
|
-
if (dot > 0)
|
|
23
|
+
if (dot > 0) {
|
|
23
24
|
ref.type = name.slice(dot + 1).toLowerCase();
|
|
25
|
+
}
|
|
24
26
|
return ref;
|
|
25
27
|
}
|
|
26
28
|
export function attachmentTurn(name, result) {
|
|
27
29
|
const direct = refFrom(result);
|
|
28
|
-
if (direct)
|
|
30
|
+
if (direct) {
|
|
29
31
|
return toolResultTurn(name, DIRECT_CALL_ID, direct);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
}
|
|
33
|
+
const listed = isRecord(result) ? result.attachments : undefined;
|
|
34
|
+
if (!Array.isArray(listed)) {
|
|
32
35
|
return null;
|
|
36
|
+
}
|
|
33
37
|
const refs = [];
|
|
34
38
|
for (const entry of listed) {
|
|
35
39
|
const ref = refFrom(entry);
|
|
36
|
-
if (ref)
|
|
40
|
+
if (ref) {
|
|
37
41
|
refs.push(ref);
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
|
-
if (!refs.length)
|
|
44
|
+
if (!refs.length) {
|
|
40
45
|
return null;
|
|
46
|
+
}
|
|
41
47
|
return toolResultTurn(name, DIRECT_CALL_ID, { attachments: refs });
|
|
42
48
|
}
|
|
43
49
|
export function loadedSkillsFrom(messages) {
|
|
44
50
|
const loaded = new Set();
|
|
45
|
-
for (const { name } of skillLoadPayloads(messages))
|
|
51
|
+
for (const { name } of skillLoadPayloads(messages)) {
|
|
46
52
|
loaded.add(name);
|
|
53
|
+
}
|
|
47
54
|
return loaded;
|
|
48
55
|
}
|
|
49
56
|
export function loadedSkillFilesFrom(messages) {
|
|
@@ -66,21 +73,27 @@ export function withoutSkillLoadTurns(messages, name) {
|
|
|
66
73
|
function* skillLoadPayloads(messages) {
|
|
67
74
|
for (const turn of messages) {
|
|
68
75
|
const payload = skillLoadPayload(turn);
|
|
69
|
-
if (payload)
|
|
76
|
+
if (payload) {
|
|
70
77
|
yield payload;
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
function skillLoadPayload(turn) {
|
|
74
82
|
if (turn.role !== 'tool')
|
|
75
83
|
return null;
|
|
76
84
|
const text = String(turn.content ?? '');
|
|
77
|
-
if (!text.startsWith(`[Tool: ${SKILL_TOOL_NAME} (`))
|
|
85
|
+
if (!text.startsWith(`[Tool: ${SKILL_TOOL_NAME} (`)) {
|
|
78
86
|
return null;
|
|
87
|
+
}
|
|
79
88
|
try {
|
|
80
89
|
const payload = JSON.parse(text.slice(text.indexOf('\n') + 1));
|
|
81
|
-
if (typeof payload
|
|
90
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
82
91
|
return null;
|
|
83
|
-
|
|
92
|
+
}
|
|
93
|
+
if (!('name' in payload) || typeof payload.name !== 'string') {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return 'file' in payload && typeof payload.file === 'string'
|
|
84
97
|
? { name: payload.name, file: payload.file }
|
|
85
98
|
: { name: payload.name };
|
|
86
99
|
}
|
|
@@ -95,7 +108,8 @@ function historyPayload(output) {
|
|
|
95
108
|
if (!HOST_ONLY_KEYS.some((key) => key in output))
|
|
96
109
|
return output;
|
|
97
110
|
const rest = { ...output };
|
|
98
|
-
for (const key of HOST_ONLY_KEYS)
|
|
111
|
+
for (const key of HOST_ONLY_KEYS) {
|
|
99
112
|
delete rest[key];
|
|
113
|
+
}
|
|
100
114
|
return rest;
|
|
101
115
|
}
|
|
@@ -27,8 +27,9 @@ export function usageBreakdown(chars, totalTokens, modelName) {
|
|
|
27
27
|
: 0
|
|
28
28
|
};
|
|
29
29
|
const prompt = rows.systemPrompt + rows.agentInstructions + rows.activeSkills + rows.tools;
|
|
30
|
-
if (prompt > totalTokens)
|
|
30
|
+
if (prompt > totalTokens) {
|
|
31
31
|
return scaledToWindow(rows, totalTokens);
|
|
32
|
+
}
|
|
32
33
|
return { ...rows, conversation: totalTokens - prompt, totalTokens };
|
|
33
34
|
}
|
|
34
35
|
function scaledToWindow(rows, totalTokens) {
|
|
@@ -71,7 +71,11 @@ export interface SpeakStreamContext {
|
|
|
71
71
|
export type VoiceLane = <T>(job: () => Promise<T>) => Promise<T>;
|
|
72
72
|
export declare function createVoiceLane(): VoiceLane;
|
|
73
73
|
export declare function transcribeOnce(sdk: VoiceSdk, modelId: string, audio: Buffer): Promise<string>;
|
|
74
|
-
export
|
|
74
|
+
export interface TranscribeStreamingOptions {
|
|
75
|
+
modelId: string;
|
|
76
|
+
signal?: BareAbortSignal | undefined;
|
|
77
|
+
}
|
|
78
|
+
export declare function transcribeStreaming(sdk: VoiceSdk, pcm: Uint8Array, { modelId, signal }: TranscribeStreamingOptions): Promise<string>;
|
|
75
79
|
export declare function speakStreamOnce(sdk: VoiceSdk, modelId: string, text: AsyncIterable<string>): AsyncGenerator<SpeakResult>;
|
|
76
80
|
export declare function speakable(text: string): string;
|
|
77
81
|
export declare function speakOnce(sdk: VoiceSdk, modelId: string, text: string): Promise<number[]>;
|