@qvac/harness 0.4.0 → 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 +689 -141
- package/README.md +29 -7
- package/dist/harness.bundle +71 -50
- package/dist-lib/index.d.ts +36 -39
- package/dist-lib/index.js +279 -114
- 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 +11 -32
- package/dist-lib/lib/connect.js +152 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +22 -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 +13 -20
- package/dist-lib/lib/models/model-catalog.js +59 -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 +44 -10
- package/dist-lib/lib/models/model-pool.js +47 -42
- 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 +110 -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 +14 -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 +3 -0
- package/dist-lib/spec/calls.js +1 -0
- package/dist-lib/spec/hrpc/hrpc.json +13 -0
- package/dist-lib/spec/hrpc/index.js +15 -3
- package/dist-lib/spec/hrpc/messages.js +39 -8
- package/dist-lib/spec/hyperschema/index.js +39 -8
- package/dist-lib/spec/hyperschema/schema.json +30 -1
- package/dist-lib/spec/hyperschema/types.d.ts +9 -1
- package/package.json +13 -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
|
@@ -19,18 +19,36 @@ export function supportsPlatform(entry, platform) {
|
|
|
19
19
|
}
|
|
20
20
|
export function isModelCompatible(entry, activeProfile) {
|
|
21
21
|
const required = entry.minCapabilityProfile;
|
|
22
|
-
if (!required || activeProfile === undefined || !isCapabilityProfile(required))
|
|
22
|
+
if (!required || activeProfile === undefined || !isCapabilityProfile(required)) {
|
|
23
23
|
return true;
|
|
24
|
+
}
|
|
24
25
|
return meetsMinCapabilityProfile(activeProfile, required);
|
|
25
26
|
}
|
|
27
|
+
function slotHealth(slot, { records, isRejected }) {
|
|
28
|
+
let bad = false;
|
|
29
|
+
for (const key of slot) {
|
|
30
|
+
const record = records.get(key);
|
|
31
|
+
const expired = !!record && isCredentialExpired(record);
|
|
32
|
+
if (record && !isRejected(key) && !expired) {
|
|
33
|
+
return 'healthy';
|
|
34
|
+
}
|
|
35
|
+
if (isRejected(key) || expired) {
|
|
36
|
+
bad = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return bad ? 'bad' : 'missing';
|
|
40
|
+
}
|
|
26
41
|
export async function resolveSkillState(entry, inputs = {}) {
|
|
27
42
|
try {
|
|
28
|
-
if (!supportsPlatform(entry, inputs.platform))
|
|
43
|
+
if (!supportsPlatform(entry, inputs.platform)) {
|
|
29
44
|
return SKILL_STATE.unsupported;
|
|
30
|
-
|
|
45
|
+
}
|
|
46
|
+
if (isStub(entry)) {
|
|
31
47
|
return SKILL_STATE.disabled;
|
|
32
|
-
|
|
48
|
+
}
|
|
49
|
+
if (!isModelCompatible(entry, inputs.activeProfile)) {
|
|
33
50
|
return SKILL_STATE.incompatibleModel;
|
|
51
|
+
}
|
|
34
52
|
const bins = entry.requires?.bins ?? [];
|
|
35
53
|
if (entry.name === OBSIDIAN_SKILL_NAME) {
|
|
36
54
|
const vault = inputs.credentialStore
|
|
@@ -39,10 +57,12 @@ export async function resolveSkillState(entry, inputs = {}) {
|
|
|
39
57
|
skills: [entry.name]
|
|
40
58
|
}))
|
|
41
59
|
: null;
|
|
42
|
-
if (vault && isObsidianVaultPath(vault.token))
|
|
60
|
+
if (vault && isObsidianVaultPath(vault.token)) {
|
|
43
61
|
return SKILL_STATE.ready;
|
|
44
|
-
|
|
62
|
+
}
|
|
63
|
+
if (isSandboxedWorkbenchInstall()) {
|
|
45
64
|
return SKILL_STATE.needsSetup;
|
|
65
|
+
}
|
|
46
66
|
}
|
|
47
67
|
let missing = bins.length > 0 ? missingBins(bins, inputs.platform, inputs.binOnPath) : [];
|
|
48
68
|
if (entry.name === OBSIDIAN_SKILL_NAME) {
|
|
@@ -52,8 +72,9 @@ export async function resolveSkillState(entry, inputs = {}) {
|
|
|
52
72
|
obsidianInstallState(inputs.platform, inputs.obsidianProbe) === 'installed') {
|
|
53
73
|
missing = missing.filter((bin) => bin !== 'obsidian');
|
|
54
74
|
}
|
|
55
|
-
if (missing.length === 0 && cli === 'disabled')
|
|
75
|
+
if (missing.length === 0 && cli === 'disabled') {
|
|
56
76
|
return SKILL_STATE.needsSetup;
|
|
77
|
+
}
|
|
57
78
|
}
|
|
58
79
|
if (missing.length > 0) {
|
|
59
80
|
return SKILL_STATE.needsSetup;
|
|
@@ -67,18 +88,22 @@ export async function resolveSkillState(entry, inputs = {}) {
|
|
|
67
88
|
const settings = inputs.settingsStore
|
|
68
89
|
? await inputs.settingsStore(entry.name, inputs.agentId)
|
|
69
90
|
: {};
|
|
70
|
-
if (required.some((key) => !settings[key]))
|
|
91
|
+
if (required.some((key) => !settings[key])) {
|
|
71
92
|
return SKILL_STATE.needsSetup;
|
|
93
|
+
}
|
|
72
94
|
}
|
|
73
95
|
const slots = credentialSlots(entry);
|
|
74
|
-
if (slots.length === 0)
|
|
96
|
+
if (slots.length === 0) {
|
|
75
97
|
return SKILL_STATE.ready;
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
}
|
|
99
|
+
const { rejected } = inputs;
|
|
100
|
+
const isRejected = rejected
|
|
101
|
+
? (key) => rejected.has(key)
|
|
78
102
|
: (key) => isCredentialRejected(key, inputs.agentId, entry.name);
|
|
79
103
|
const store = inputs.credentialStore;
|
|
80
|
-
if (!store)
|
|
104
|
+
if (!store) {
|
|
81
105
|
return SKILL_STATE.needsSetup;
|
|
106
|
+
}
|
|
82
107
|
const records = new Map(await Promise.all([...new Set(slots.flat())].map(async (key) => [
|
|
83
108
|
key,
|
|
84
109
|
credentialRecord(await store(key, { agentId: inputs.agentId, skills: [entry.name] }))
|
|
@@ -86,29 +111,23 @@ export async function resolveSkillState(entry, inputs = {}) {
|
|
|
86
111
|
let disconnected = false;
|
|
87
112
|
let needsSetup = false;
|
|
88
113
|
for (const slot of slots) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for (const key of slot) {
|
|
92
|
-
const record = records.get(key);
|
|
93
|
-
const expired = !!record && isCredentialExpired(record);
|
|
94
|
-
if (record && !isRejected(key) && !expired) {
|
|
95
|
-
healthy = true;
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
if (isRejected(key) || expired)
|
|
99
|
-
bad = true;
|
|
100
|
-
}
|
|
101
|
-
if (healthy)
|
|
114
|
+
const health = slotHealth(slot, { records, isRejected });
|
|
115
|
+
if (health === 'healthy') {
|
|
102
116
|
continue;
|
|
103
|
-
|
|
117
|
+
}
|
|
118
|
+
if (health === 'bad') {
|
|
104
119
|
disconnected = true;
|
|
105
|
-
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
106
122
|
needsSetup = true;
|
|
123
|
+
}
|
|
107
124
|
}
|
|
108
|
-
if (disconnected)
|
|
125
|
+
if (disconnected) {
|
|
109
126
|
return SKILL_STATE.disconnected;
|
|
110
|
-
|
|
127
|
+
}
|
|
128
|
+
if (needsSetup) {
|
|
111
129
|
return SKILL_STATE.needsSetup;
|
|
130
|
+
}
|
|
112
131
|
return SKILL_STATE.ready;
|
|
113
132
|
}
|
|
114
133
|
catch {
|
|
@@ -2,13 +2,16 @@ const GRANT_RE = /^([\w-]+)\((.*)\)$/;
|
|
|
2
2
|
export function parseToolGrant(entry) {
|
|
3
3
|
const trimmed = entry.trim();
|
|
4
4
|
const match = GRANT_RE.exec(trimmed);
|
|
5
|
-
if (!match)
|
|
5
|
+
if (!match) {
|
|
6
6
|
return { name: trimmed, scope: null };
|
|
7
|
+
}
|
|
7
8
|
const name = match[1] ?? '';
|
|
8
9
|
const scope = (match[2] ?? '').trim();
|
|
9
|
-
if (!name)
|
|
10
|
+
if (!name) {
|
|
10
11
|
return { name: trimmed, scope: null };
|
|
11
|
-
|
|
12
|
+
}
|
|
13
|
+
if (!scope) {
|
|
12
14
|
return { name, scope: null };
|
|
15
|
+
}
|
|
13
16
|
return { name, scope };
|
|
14
17
|
}
|
|
@@ -11,8 +11,9 @@ export function asToolset(value) {
|
|
|
11
11
|
}
|
|
12
12
|
export const NETWORK_TOOLSETS = ['web-search', 'http-request', 'mcp-call'];
|
|
13
13
|
export function permittedTools(tools, disabled) {
|
|
14
|
-
if (!disabled?.length)
|
|
14
|
+
if (!disabled?.length) {
|
|
15
15
|
return [...tools];
|
|
16
|
+
}
|
|
16
17
|
const off = new Set(disabled);
|
|
17
18
|
return tools.filter((tool) => !tool.toolset || !off.has(tool.toolset));
|
|
18
19
|
}
|
|
@@ -65,12 +65,11 @@ export interface TranslateArgs {
|
|
|
65
65
|
to: string;
|
|
66
66
|
from?: string;
|
|
67
67
|
}
|
|
68
|
-
export
|
|
68
|
+
export interface TranslateError {
|
|
69
69
|
error: string;
|
|
70
|
-
}
|
|
71
|
-
export declare function
|
|
72
|
-
|
|
73
|
-
};
|
|
70
|
+
}
|
|
71
|
+
export declare function parseTranslateArgs(args: Record<string, unknown>): TranslateArgs | TranslateError;
|
|
72
|
+
export declare function sourceLanguage(text: string, from?: string): string | TranslateError;
|
|
74
73
|
export interface TranslateToolOptions {
|
|
75
74
|
slm?: SlmTranslateFn;
|
|
76
75
|
}
|
|
@@ -10,17 +10,20 @@ export const TRANSLATION_LOAD_STALL_MS = 15 * 60_000;
|
|
|
10
10
|
export const TRANSLATION_LOAD_STALL_ERROR = 'translation model download stalled';
|
|
11
11
|
function packName(plan) {
|
|
12
12
|
const pair = plan.model.match(/model\.([a-z]+)\.intgemm/)?.[1];
|
|
13
|
-
if (pair)
|
|
13
|
+
if (pair) {
|
|
14
14
|
return `Bergamot ${pair}`;
|
|
15
|
+
}
|
|
15
16
|
return plan.model.includes('/en-xx/') ? 'EuroNano en-xx' : 'EuroNano xx-en';
|
|
16
17
|
}
|
|
17
18
|
const fingerprintOf = (plan) => `${plan.model}|${plan.pivot ?? ''}`;
|
|
18
19
|
function bergamotConfig(plan) {
|
|
19
20
|
const config = { engine: 'Bergamot', from: plan.from, to: plan.to };
|
|
20
|
-
if (plan.srcVocab)
|
|
21
|
+
if (plan.srcVocab) {
|
|
21
22
|
config.srcVocabSrc = plan.srcVocab;
|
|
22
|
-
|
|
23
|
+
}
|
|
24
|
+
if (plan.dstVocab) {
|
|
23
25
|
config.dstVocabSrc = plan.dstVocab;
|
|
26
|
+
}
|
|
24
27
|
return config;
|
|
25
28
|
}
|
|
26
29
|
export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(), onProgress, stallMs = TRANSLATION_LOAD_STALL_MS }) {
|
|
@@ -54,7 +57,7 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
54
57
|
if (err instanceof Error &&
|
|
55
58
|
err.message === TRANSLATION_LOAD_STALL_ERROR &&
|
|
56
59
|
pending.requestId) {
|
|
57
|
-
void sdk.cancel?.({ requestId: pending.requestId }).catch(
|
|
60
|
+
void sdk.cancel?.({ requestId: pending.requestId }).catch(noop);
|
|
58
61
|
}
|
|
59
62
|
throw err;
|
|
60
63
|
}
|
|
@@ -94,8 +97,9 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
94
97
|
const fingerprint = fingerprintOf(plan);
|
|
95
98
|
await lock.lock();
|
|
96
99
|
try {
|
|
97
|
-
if (loading && residentFingerprint !== fingerprint)
|
|
100
|
+
if (loading && residentFingerprint !== fingerprint) {
|
|
98
101
|
await doUnload();
|
|
102
|
+
}
|
|
99
103
|
residentFingerprint = fingerprint;
|
|
100
104
|
loading ??= loadPack(sdk, plan).catch((err) => {
|
|
101
105
|
loading = null;
|
|
@@ -124,19 +128,22 @@ export function createTranslationRuntime({ loadSdk, loads = createLoadTracker(),
|
|
|
124
128
|
}
|
|
125
129
|
export function stripLanguageLabel(text, ...codes) {
|
|
126
130
|
const at = text.indexOf(':');
|
|
127
|
-
if (at === -1)
|
|
131
|
+
if (at === -1) {
|
|
128
132
|
return text;
|
|
133
|
+
}
|
|
129
134
|
const head = text.slice(0, at).trim();
|
|
130
135
|
const labels = codes.map((code) => languageLabel(code) ?? getLangName(code));
|
|
131
136
|
return labels.includes(head) ? text.slice(at + 1).trim() : text;
|
|
132
137
|
}
|
|
133
138
|
export function parseTranslateArgs(args) {
|
|
134
139
|
const text = typeof args.text === 'string' ? args.text.trim() : '';
|
|
135
|
-
if (!text)
|
|
140
|
+
if (!text) {
|
|
136
141
|
return { error: 'text must be a non-empty string' };
|
|
142
|
+
}
|
|
137
143
|
const to = typeof args.to === 'string' ? args.to.trim() : '';
|
|
138
|
-
if (!to)
|
|
144
|
+
if (!to) {
|
|
139
145
|
return { error: 'to must be a language code' };
|
|
146
|
+
}
|
|
140
147
|
const from = typeof args.from === 'string' ? args.from.trim() : undefined;
|
|
141
148
|
return { text, to, from };
|
|
142
149
|
}
|
|
@@ -144,8 +151,9 @@ export function sourceLanguage(text, from) {
|
|
|
144
151
|
if (from)
|
|
145
152
|
return from;
|
|
146
153
|
const [{ code }] = detectLanguage({ text });
|
|
147
|
-
if (code === UNDETERMINED)
|
|
154
|
+
if (code === UNDETERMINED) {
|
|
148
155
|
return { error: NEEDS_SOURCE };
|
|
156
|
+
}
|
|
149
157
|
return code;
|
|
150
158
|
}
|
|
151
159
|
const NEEDS_SOURCE = 'could not detect the source language; pass `from` explicitly';
|
|
@@ -180,17 +188,20 @@ export function createTranslateTool(runtime, { slm } = {}) {
|
|
|
180
188
|
resource: (args) => ({ kind: 'translation', label: String(args.to ?? '') }),
|
|
181
189
|
execute: async (args, ctx) => {
|
|
182
190
|
const parsed = parseTranslateArgs(args);
|
|
183
|
-
if ('error' in parsed)
|
|
191
|
+
if ('error' in parsed) {
|
|
184
192
|
return parsed;
|
|
193
|
+
}
|
|
185
194
|
const from = sourceLanguage(parsed.text, parsed.from);
|
|
186
|
-
if (typeof from === 'object')
|
|
195
|
+
if (typeof from === 'object') {
|
|
187
196
|
return from;
|
|
197
|
+
}
|
|
188
198
|
if (sameLanguage(from, parsed.to)) {
|
|
189
199
|
return { text: parsed.text, from, to: parsed.to, engine: 'none' };
|
|
190
200
|
}
|
|
191
201
|
const plan = translationPlanFor(from, parsed.to);
|
|
192
|
-
if ('error' in plan)
|
|
202
|
+
if ('error' in plan) {
|
|
193
203
|
return plan;
|
|
204
|
+
}
|
|
194
205
|
if (plan.route === 'slm' && !slm) {
|
|
195
206
|
return { error: `this device cannot translate ${from} to ${parsed.to}` };
|
|
196
207
|
}
|
|
@@ -199,6 +210,9 @@ export function createTranslateTool(runtime, { slm } = {}) {
|
|
|
199
210
|
await runtime.enter(ctx?.signal);
|
|
200
211
|
entered = true;
|
|
201
212
|
if (plan.route === 'slm') {
|
|
213
|
+
if (!slm) {
|
|
214
|
+
throw new Error('slm translation is unavailable');
|
|
215
|
+
}
|
|
202
216
|
const text = await slm(plan.model, { text: parsed.text, from: plan.from, to: plan.to }, ctx?.signal);
|
|
203
217
|
return {
|
|
204
218
|
text: stripLanguageLabel(text, plan.from, plan.to),
|
|
@@ -215,14 +229,17 @@ export function createTranslateTool(runtime, { slm } = {}) {
|
|
|
215
229
|
return { text, from, to: parsed.to, engine: 'nmt', pivoted: Boolean(plan.pivot) };
|
|
216
230
|
}
|
|
217
231
|
catch (err) {
|
|
218
|
-
if (err instanceof AbortedError)
|
|
232
|
+
if (err instanceof AbortedError) {
|
|
219
233
|
return { error: 'translation canceled' };
|
|
234
|
+
}
|
|
220
235
|
throw err;
|
|
221
236
|
}
|
|
222
237
|
finally {
|
|
223
|
-
if (entered)
|
|
238
|
+
if (entered) {
|
|
224
239
|
runtime.exit();
|
|
240
|
+
}
|
|
225
241
|
}
|
|
226
242
|
}
|
|
227
243
|
};
|
|
228
244
|
}
|
|
245
|
+
function noop() { }
|
|
@@ -24,8 +24,9 @@ export async function resolveOutboundUrl(url, resolveHost, signal) {
|
|
|
24
24
|
addresses = await raceAbort(resolveHost(host), signal);
|
|
25
25
|
}
|
|
26
26
|
catch (err) {
|
|
27
|
-
if (err instanceof AbortedError)
|
|
27
|
+
if (err instanceof AbortedError) {
|
|
28
28
|
throw err;
|
|
29
|
+
}
|
|
29
30
|
return { blocked: `could not resolve host: ${host}` };
|
|
30
31
|
}
|
|
31
32
|
if (addresses.length === 0) {
|
|
@@ -107,16 +107,18 @@ export function describePlace(place) {
|
|
|
107
107
|
}
|
|
108
108
|
export function qualifierOf(location) {
|
|
109
109
|
const comma = location.indexOf(',');
|
|
110
|
-
if (comma === -1)
|
|
110
|
+
if (comma === -1) {
|
|
111
111
|
return null;
|
|
112
|
+
}
|
|
112
113
|
const qualifier = fold(location.slice(comma + 1));
|
|
113
114
|
return qualifier.length > 0 ? qualifier : null;
|
|
114
115
|
}
|
|
115
116
|
function expand(qualifier) {
|
|
116
117
|
const code = qualifier.replace(/ /g, '');
|
|
117
118
|
const subdivision = US_STATES[code] ?? OTHER_SUBDIVISIONS[code];
|
|
118
|
-
if (subdivision !== undefined)
|
|
119
|
+
if (subdivision !== undefined) {
|
|
119
120
|
return subdivision;
|
|
121
|
+
}
|
|
120
122
|
for (const [alias, full] of QUALIFIER_ALIASES) {
|
|
121
123
|
if (qualifier === fold(alias))
|
|
122
124
|
return full;
|
|
@@ -143,10 +145,12 @@ export function checkQualifier(qualifier, place) {
|
|
|
143
145
|
continue;
|
|
144
146
|
if (initialsOf(candidate) === code)
|
|
145
147
|
return 'match';
|
|
146
|
-
if (opaque)
|
|
148
|
+
if (opaque) {
|
|
147
149
|
continue;
|
|
148
|
-
|
|
150
|
+
}
|
|
151
|
+
if (candidate.includes(wanted) || wanted.includes(candidate)) {
|
|
149
152
|
return 'match';
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
155
|
return opaque ? 'unverifiable' : 'contradiction';
|
|
152
156
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { type ResolvedPlace } from './place.ts';
|
|
2
3
|
interface Translated {
|
|
3
4
|
readonly value: string;
|
|
@@ -32,6 +33,11 @@ export interface WeatherJson {
|
|
|
32
33
|
readonly current_condition?: ReadonlyArray<CurrentCondition>;
|
|
33
34
|
readonly weather?: ReadonlyArray<DailyForecast>;
|
|
34
35
|
}
|
|
36
|
+
export declare const WeatherJsonSchema: z.ZodType<WeatherJson>;
|
|
35
37
|
export declare function placeOf(json: WeatherJson): ResolvedPlace | null;
|
|
36
|
-
export
|
|
38
|
+
export interface RenderReportOptions {
|
|
39
|
+
readonly asked: string;
|
|
40
|
+
readonly verified: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function renderReport(json: WeatherJson, place: ResolvedPlace, { asked, verified }: RenderReportOptions): string;
|
|
37
43
|
export {};
|
|
@@ -1,4 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { describePlace } from './place.js';
|
|
3
|
+
const TranslatedSchema = z.array(z.object({ value: z.string() })).optional();
|
|
4
|
+
export const WeatherJsonSchema = z.object({
|
|
5
|
+
nearest_area: z
|
|
6
|
+
.array(z.object({ areaName: TranslatedSchema, region: TranslatedSchema, country: TranslatedSchema }))
|
|
7
|
+
.optional(),
|
|
8
|
+
current_condition: z
|
|
9
|
+
.array(z.object({
|
|
10
|
+
temp_C: z.string().optional(),
|
|
11
|
+
temp_F: z.string().optional(),
|
|
12
|
+
FeelsLikeC: z.string().optional(),
|
|
13
|
+
humidity: z.string().optional(),
|
|
14
|
+
windspeedKmph: z.string().optional(),
|
|
15
|
+
weatherDesc: TranslatedSchema
|
|
16
|
+
}))
|
|
17
|
+
.optional(),
|
|
18
|
+
weather: z
|
|
19
|
+
.array(z.object({
|
|
20
|
+
date: z.string().optional(),
|
|
21
|
+
maxtempC: z.string().optional(),
|
|
22
|
+
mintempC: z.string().optional(),
|
|
23
|
+
maxtempF: z.string().optional(),
|
|
24
|
+
mintempF: z.string().optional(),
|
|
25
|
+
hourly: z
|
|
26
|
+
.array(z.object({ time: z.string().optional(), weatherDesc: TranslatedSchema }))
|
|
27
|
+
.optional()
|
|
28
|
+
}))
|
|
29
|
+
.optional()
|
|
30
|
+
});
|
|
2
31
|
const MAX_DAYS = 3;
|
|
3
32
|
const DAY_LABELS = ['today', 'tomorrow'];
|
|
4
33
|
const RANGE = '\u2013';
|
|
@@ -8,8 +37,9 @@ function firstValue(values) {
|
|
|
8
37
|
}
|
|
9
38
|
export function placeOf(json) {
|
|
10
39
|
const area = json.nearest_area?.[0];
|
|
11
|
-
if (area === undefined)
|
|
40
|
+
if (area === undefined) {
|
|
12
41
|
return null;
|
|
42
|
+
}
|
|
13
43
|
const place = {
|
|
14
44
|
area: firstValue(area.areaName),
|
|
15
45
|
region: firstValue(area.region),
|
|
@@ -17,33 +47,40 @@ export function placeOf(json) {
|
|
|
17
47
|
};
|
|
18
48
|
return place.area || place.region || place.country ? place : null;
|
|
19
49
|
}
|
|
20
|
-
export function renderReport(json, place, asked, verified) {
|
|
50
|
+
export function renderReport(json, place, { asked, verified }) {
|
|
21
51
|
const lines = [headline(place, asked, verified)];
|
|
22
52
|
const now = json.current_condition?.[0];
|
|
23
|
-
if (now !== undefined)
|
|
53
|
+
if (now !== undefined) {
|
|
24
54
|
lines.push(currentLine(now));
|
|
55
|
+
}
|
|
25
56
|
const days = json.weather?.slice(0, MAX_DAYS) ?? [];
|
|
26
|
-
for (const [index, day] of days.entries())
|
|
57
|
+
for (const [index, day] of days.entries()) {
|
|
27
58
|
lines.push(dailyLine(day, index));
|
|
59
|
+
}
|
|
28
60
|
return lines.join('\n');
|
|
29
61
|
}
|
|
30
62
|
function headline(place, asked, verified) {
|
|
31
63
|
const resolved = describePlace(place);
|
|
32
|
-
if (verified)
|
|
64
|
+
if (verified) {
|
|
33
65
|
return resolved;
|
|
66
|
+
}
|
|
34
67
|
return `${resolved} (not "${asked}" — this is where it resolved)`;
|
|
35
68
|
}
|
|
36
69
|
function currentLine(now) {
|
|
37
70
|
const parts = [`Now: ${now.temp_C ?? '?'}°C / ${now.temp_F ?? '?'}°F`];
|
|
38
71
|
const desc = firstValue(now.weatherDesc);
|
|
39
|
-
if (desc)
|
|
72
|
+
if (desc) {
|
|
40
73
|
parts.push(desc);
|
|
41
|
-
|
|
74
|
+
}
|
|
75
|
+
if (now.FeelsLikeC !== undefined) {
|
|
42
76
|
parts.push(`feels like ${now.FeelsLikeC}°C`);
|
|
43
|
-
|
|
77
|
+
}
|
|
78
|
+
if (now.humidity !== undefined) {
|
|
44
79
|
parts.push(`humidity ${now.humidity}%`);
|
|
45
|
-
|
|
80
|
+
}
|
|
81
|
+
if (now.windspeedKmph !== undefined) {
|
|
46
82
|
parts.push(`wind ${now.windspeedKmph} km/h`);
|
|
83
|
+
}
|
|
47
84
|
return parts.join(', ');
|
|
48
85
|
}
|
|
49
86
|
function dailyLine(day, index) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { noRedirectFetch } from '#no-redirect-fetch';
|
|
2
2
|
import { AbortController } from 'bare-abort-controller';
|
|
3
3
|
import { lookupHost } from '../dns-lookup.js';
|
|
4
|
-
import {
|
|
4
|
+
import { errorMessage } from '../guards.js';
|
|
5
|
+
import { isRedirectStatus } from './http-request/request.js';
|
|
5
6
|
import { resolveOutboundUrl } from './url-guard.js';
|
|
6
7
|
import { checkQualifier, cityMatches, describePlace, qualifierOf } from './weather-lookup/place.js';
|
|
7
|
-
import { placeOf, renderReport } from './weather-lookup/report.js';
|
|
8
|
+
import { placeOf, renderReport, WeatherJsonSchema } from './weather-lookup/report.js';
|
|
8
9
|
const WEATHER_HOST = 'wttr.in';
|
|
9
10
|
const LOOKUP_TIMEOUT_MS = 15_000;
|
|
10
11
|
const MAX_BODY_BYTES = 400_000;
|
|
@@ -36,18 +37,20 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
36
37
|
return { error: 'location must be a non-empty string' };
|
|
37
38
|
}
|
|
38
39
|
const location = raw.trim();
|
|
39
|
-
if (ctx?.signal?.aborted)
|
|
40
|
+
if (ctx?.signal?.aborted) {
|
|
40
41
|
return { error: 'weather_lookup cancelled' };
|
|
42
|
+
}
|
|
41
43
|
const url = new URL(`https://${WEATHER_HOST}/${encodeURIComponent(location)}?format=j1`);
|
|
42
|
-
const fetch = fetchImpl ??
|
|
44
|
+
const fetch = fetchImpl ?? noRedirectFetch;
|
|
43
45
|
const controller = new AbortController();
|
|
44
46
|
const timeout = setTimeout(() => controller.abort(undefined), LOOKUP_TIMEOUT_MS);
|
|
45
47
|
const onCallerAbort = () => controller.abort(undefined);
|
|
46
48
|
ctx?.signal?.addEventListener('abort', onCallerAbort);
|
|
47
49
|
try {
|
|
48
50
|
const check = await resolveOutboundUrl(url, resolveHost, ctx?.signal);
|
|
49
|
-
if (check.blocked)
|
|
51
|
+
if (check.blocked) {
|
|
50
52
|
return { error: check.blocked };
|
|
53
|
+
}
|
|
51
54
|
const response = await fetch(url, {
|
|
52
55
|
method: 'GET',
|
|
53
56
|
headers: { Accept: 'application/json' },
|
|
@@ -60,21 +63,23 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
60
63
|
return { error: `${WEATHER_HOST} redirected the lookup for "${location}"` };
|
|
61
64
|
}
|
|
62
65
|
const body = await response.text();
|
|
63
|
-
if (/location not found/i.test(body))
|
|
66
|
+
if (/location not found/i.test(body)) {
|
|
64
67
|
return { content: notFound(location) };
|
|
68
|
+
}
|
|
65
69
|
if (response.status !== 200) {
|
|
66
70
|
return { error: unavailable(`it answered HTTP ${response.status}`), code: 'transport' };
|
|
67
71
|
}
|
|
68
72
|
let json;
|
|
69
73
|
try {
|
|
70
|
-
json = JSON.parse(body);
|
|
74
|
+
json = WeatherJsonSchema.parse(JSON.parse(body));
|
|
71
75
|
}
|
|
72
76
|
catch {
|
|
73
77
|
return { error: unavailable('it did not return a forecast') };
|
|
74
78
|
}
|
|
75
79
|
const place = placeOf(json);
|
|
76
|
-
if (place === null)
|
|
80
|
+
if (place === null) {
|
|
77
81
|
return { content: notFound(location) };
|
|
82
|
+
}
|
|
78
83
|
const qualifier = qualifierOf(location);
|
|
79
84
|
const verdict = qualifier === null ? null : checkQualifier(qualifier, place);
|
|
80
85
|
const named = cityMatches(location, place);
|
|
@@ -85,11 +90,12 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
85
90
|
};
|
|
86
91
|
}
|
|
87
92
|
const verified = qualifier === null ? named : verdict === 'match';
|
|
88
|
-
return { content: renderReport(json, place, location, verified) };
|
|
93
|
+
return { content: renderReport(json, place, { asked: location, verified }) };
|
|
89
94
|
}
|
|
90
95
|
catch (err) {
|
|
91
|
-
if (ctx?.signal?.aborted)
|
|
96
|
+
if (ctx?.signal?.aborted) {
|
|
92
97
|
return { error: 'weather_lookup cancelled' };
|
|
98
|
+
}
|
|
93
99
|
if (controller.signal.aborted) {
|
|
94
100
|
return {
|
|
95
101
|
error: `weather_lookup timed out after ${LOOKUP_TIMEOUT_MS} ms`,
|
|
@@ -97,7 +103,7 @@ export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookup
|
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
return {
|
|
100
|
-
error: unavailable(
|
|
106
|
+
error: unavailable(errorMessage(err)),
|
|
101
107
|
code: 'transport'
|
|
102
108
|
};
|
|
103
109
|
}
|