@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
|
@@ -1,6 +1,7 @@
|
|
|
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 { errorMessage } from '../guards.js';
|
|
4
5
|
import { AbortedError } from '../helpers/race-abort.js';
|
|
5
6
|
import { extractEmbeddedState } from '../web-fetch/embedded-state.js';
|
|
6
7
|
import { extractPageText, looksLikeHtml } from '../web-fetch/extract.js';
|
|
@@ -8,7 +9,7 @@ import { imageFileName, pageImageUrls } from '../web-fetch/images.js';
|
|
|
8
9
|
import { pageVideoRef } from '../web-fetch/video.js';
|
|
9
10
|
import { MAX_HTML_CHARS } from '../web-search/fetch-html.js';
|
|
10
11
|
import { BROWSER_UA } from '../web-search/user-agents.js';
|
|
11
|
-
import { isRedirectStatus } from './http-request/
|
|
12
|
+
import { isRedirectStatus } from './http-request/request.js';
|
|
12
13
|
import { resolveOutboundUrl } from './url-guard.js';
|
|
13
14
|
const SMALL_CONTEXT_TOKENS = 12_288;
|
|
14
15
|
const SMALL_CONTEXT_CHARS = 6_000;
|
|
@@ -64,9 +65,10 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
64
65
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
65
66
|
return { error: `Scheme "${url.protocol}" not allowed. Use http: or https:.` };
|
|
66
67
|
}
|
|
67
|
-
if (ctx?.signal?.aborted)
|
|
68
|
+
if (ctx?.signal?.aborted) {
|
|
68
69
|
return { error: 'web_fetch cancelled' };
|
|
69
|
-
|
|
70
|
+
}
|
|
71
|
+
const fetch = fetchImpl ?? noRedirectFetch;
|
|
70
72
|
const controller = new AbortController();
|
|
71
73
|
const timeout = setTimeout(() => controller.abort(undefined), PAGE_TIMEOUT_MS);
|
|
72
74
|
const onCallerAbort = () => controller.abort(undefined);
|
|
@@ -79,8 +81,9 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
79
81
|
maxBytes: MAX_PAGE_BYTES,
|
|
80
82
|
truncate: true
|
|
81
83
|
});
|
|
82
|
-
if (!got.ok)
|
|
84
|
+
if (!got.ok) {
|
|
83
85
|
return { error: got.error };
|
|
86
|
+
}
|
|
84
87
|
const { response, finalUrl: currentUrl } = got;
|
|
85
88
|
if (response.status < 200 || response.status >= 300) {
|
|
86
89
|
return {
|
|
@@ -91,18 +94,24 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
91
94
|
const rawBody = await response.text();
|
|
92
95
|
const body = rawBody.length > MAX_HTML_CHARS ? rawBody.slice(0, MAX_HTML_CHARS) : rawBody;
|
|
93
96
|
let mimeType = response.headers?.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';
|
|
94
|
-
if (body.startsWith('%PDF-'))
|
|
97
|
+
if (body.startsWith('%PDF-')) {
|
|
95
98
|
mimeType = 'application/pdf';
|
|
99
|
+
}
|
|
96
100
|
if (isBinaryMime(mimeType) || body.includes('\u0000')) {
|
|
97
101
|
const attachMime = isBinaryMime(mimeType) ? mimeType : 'application/octet-stream';
|
|
98
|
-
return await attachDocument(response,
|
|
102
|
+
return await attachDocument(response, {
|
|
103
|
+
url: currentUrl,
|
|
104
|
+
mimeType: attachMime,
|
|
105
|
+
attach: ctx?.attach
|
|
106
|
+
});
|
|
99
107
|
}
|
|
100
108
|
const isHtml = looksLikeHtml(body);
|
|
101
109
|
let page = isHtml ? extractPageText(body) : { title: null, text: body.trim() };
|
|
102
110
|
if (isHtml && page.text.length < MIN_MARKUP_CHARS) {
|
|
103
111
|
const embedded = extractEmbeddedState(body);
|
|
104
|
-
if (embedded.length > page.text.length)
|
|
112
|
+
if (embedded.length > page.text.length) {
|
|
105
113
|
page = { title: page.title, text: embedded };
|
|
114
|
+
}
|
|
106
115
|
}
|
|
107
116
|
const budget = charBudget(ctx?.ctxSize);
|
|
108
117
|
const text = page.text.length > budget ? `${page.text.slice(0, budget)}…[truncated]` : page.text;
|
|
@@ -149,7 +158,7 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
|
|
|
149
158
|
if (controller.signal.aborted) {
|
|
150
159
|
return { error: `${url.hostname} timed out after ${PAGE_TIMEOUT_MS}ms`, code: 'timeout' };
|
|
151
160
|
}
|
|
152
|
-
return { error:
|
|
161
|
+
return { error: errorMessage(err), code: 'transport' };
|
|
153
162
|
}
|
|
154
163
|
finally {
|
|
155
164
|
clearTimeout(timeout);
|
|
@@ -164,7 +173,7 @@ function isBinaryMime(mimeType) {
|
|
|
164
173
|
return true;
|
|
165
174
|
return BINARY_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));
|
|
166
175
|
}
|
|
167
|
-
async function attachDocument(response, url, mimeType, attach) {
|
|
176
|
+
async function attachDocument(response, { url, mimeType, attach }) {
|
|
168
177
|
const bytes = await response.bytes?.();
|
|
169
178
|
if (!attach || !bytes || bytes.byteLength === 0) {
|
|
170
179
|
return { error: 'This URL serves a file, not a readable page, and it could not be attached.' };
|
|
@@ -218,33 +227,39 @@ function attachmentFileName(url, mimeType) {
|
|
|
218
227
|
}
|
|
219
228
|
catch { }
|
|
220
229
|
const base = decoded.split(/[/\\]/).filter(Boolean).pop() ?? '';
|
|
221
|
-
if (!base)
|
|
230
|
+
if (!base) {
|
|
222
231
|
return `${url.hostname}.${extensionFor(mimeType)}`;
|
|
232
|
+
}
|
|
223
233
|
const extension = /\.([\w-]{1,8})$/.exec(base)?.[1]?.toLowerCase() ?? '';
|
|
224
|
-
if (extension && acceptableExtensions(mimeType).has(extension))
|
|
234
|
+
if (extension && acceptableExtensions(mimeType).has(extension)) {
|
|
225
235
|
return base;
|
|
226
|
-
|
|
236
|
+
}
|
|
237
|
+
if (mimeType === 'application/octet-stream' && /^[a-z][\w-]*$/.test(extension)) {
|
|
227
238
|
return base;
|
|
239
|
+
}
|
|
228
240
|
return `${base}.${extensionFor(mimeType)}`;
|
|
229
241
|
}
|
|
230
242
|
function acceptableExtensions(mimeType) {
|
|
231
243
|
const accepted = new Set([extensionFor(mimeType)]);
|
|
232
244
|
const subtype = mimeType.split('/')[1] ?? '';
|
|
233
|
-
if (/^[a-z0-9]{1,8}$/.test(subtype))
|
|
245
|
+
if (/^[a-z0-9]{1,8}$/.test(subtype)) {
|
|
234
246
|
accepted.add(subtype);
|
|
247
|
+
}
|
|
235
248
|
return accepted;
|
|
236
249
|
}
|
|
237
250
|
function extensionFor(mimeType) {
|
|
238
251
|
const known = MIME_EXTENSIONS[mimeType];
|
|
239
|
-
if (known)
|
|
252
|
+
if (known) {
|
|
240
253
|
return known;
|
|
254
|
+
}
|
|
241
255
|
const subtype = mimeType.split('/')[1] ?? '';
|
|
242
256
|
return /^[a-z0-9]{1,5}$/.test(subtype) ? subtype : 'bin';
|
|
243
257
|
}
|
|
244
258
|
async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, truncate }) {
|
|
245
259
|
const initialCheck = await resolveOutboundUrl(url, resolveHost, signal);
|
|
246
|
-
if (initialCheck.blocked)
|
|
260
|
+
if (initialCheck.blocked) {
|
|
247
261
|
return { ok: false, error: initialCheck.blocked };
|
|
262
|
+
}
|
|
248
263
|
let currentUrl = url;
|
|
249
264
|
let validatedAddresses = initialCheck.addresses;
|
|
250
265
|
for (let redirects = 0;; redirects++) {
|
|
@@ -257,11 +272,13 @@ async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, t
|
|
|
257
272
|
maxBytes,
|
|
258
273
|
truncate
|
|
259
274
|
});
|
|
260
|
-
if (!isRedirectStatus(response.status))
|
|
275
|
+
if (!isRedirectStatus(response.status)) {
|
|
261
276
|
return { ok: true, response, finalUrl: currentUrl };
|
|
277
|
+
}
|
|
262
278
|
const location = response.headers?.get('location');
|
|
263
|
-
if (!location)
|
|
279
|
+
if (!location) {
|
|
264
280
|
return { ok: true, response, finalUrl: currentUrl };
|
|
281
|
+
}
|
|
265
282
|
let nextUrl;
|
|
266
283
|
try {
|
|
267
284
|
nextUrl = new URL(location, currentUrl);
|
|
@@ -272,11 +289,13 @@ async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, t
|
|
|
272
289
|
if (nextUrl.protocol !== 'http:' && nextUrl.protocol !== 'https:') {
|
|
273
290
|
return { ok: true, response, finalUrl: currentUrl };
|
|
274
291
|
}
|
|
275
|
-
if (redirects >= MAX_REDIRECTS)
|
|
292
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
276
293
|
return { ok: false, error: 'too many redirects' };
|
|
294
|
+
}
|
|
277
295
|
const hopCheck = await resolveOutboundUrl(nextUrl, resolveHost, signal);
|
|
278
|
-
if (hopCheck.blocked)
|
|
296
|
+
if (hopCheck.blocked) {
|
|
279
297
|
return { ok: false, error: hopCheck.blocked };
|
|
298
|
+
}
|
|
280
299
|
currentUrl = nextUrl;
|
|
281
300
|
validatedAddresses = hopCheck.addresses;
|
|
282
301
|
}
|
|
@@ -293,17 +312,21 @@ async function attachPageImages(html, pageUrl, { fetch, resolveHost, attach, sig
|
|
|
293
312
|
accept: 'image/*',
|
|
294
313
|
maxBytes: MAX_IMAGE_BYTES
|
|
295
314
|
});
|
|
296
|
-
if (!got.ok)
|
|
315
|
+
if (!got.ok) {
|
|
297
316
|
continue;
|
|
317
|
+
}
|
|
298
318
|
const { response, finalUrl } = got;
|
|
299
|
-
if (response.status < 200 || response.status >= 300)
|
|
319
|
+
if (response.status < 200 || response.status >= 300) {
|
|
300
320
|
continue;
|
|
321
|
+
}
|
|
301
322
|
const mimeType = response.headers?.get('content-type')?.split(';')[0]?.trim() ?? '';
|
|
302
|
-
if (!mimeType.startsWith('image/'))
|
|
323
|
+
if (!mimeType.startsWith('image/')) {
|
|
303
324
|
continue;
|
|
325
|
+
}
|
|
304
326
|
const bytes = await response.bytes?.();
|
|
305
|
-
if (!bytes || bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES)
|
|
327
|
+
if (!bytes || bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES) {
|
|
306
328
|
continue;
|
|
329
|
+
}
|
|
307
330
|
const record = await attach({ fileName: imageFileName(finalUrl, mimeType), mimeType, bytes });
|
|
308
331
|
attached.push(record.fileName);
|
|
309
332
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import defaultFetch from '#fetch';
|
|
2
|
+
import { errorMessage } from '../guards.js';
|
|
2
3
|
import { formatResultsForPrompt } from '../web-search/format-results.js';
|
|
3
4
|
import { searchWeb } from '../web-search/orchestrator.js';
|
|
4
5
|
const nodeFetch = async (url, init) => {
|
|
@@ -35,9 +36,13 @@ export function createWebSearchTool({ fetch } = {}) {
|
|
|
35
36
|
return { error: 'query must be a non-empty string' };
|
|
36
37
|
}
|
|
37
38
|
try {
|
|
38
|
-
const { results, engineSummary } = await searchWeb(fetcher, query,
|
|
39
|
-
|
|
39
|
+
const { results, engineSummary } = await searchWeb(fetcher, query, {
|
|
40
|
+
maxResults: resultBudget(ctx?.ctxSize),
|
|
41
|
+
signal: ctx?.signal
|
|
42
|
+
});
|
|
43
|
+
if (ctx?.signal?.aborted) {
|
|
40
44
|
return { error: 'web_search cancelled' };
|
|
45
|
+
}
|
|
41
46
|
if (results.length > 0) {
|
|
42
47
|
return { content: formatResultsForPrompt(results), data: { results } };
|
|
43
48
|
}
|
|
@@ -51,7 +56,7 @@ export function createWebSearchTool({ fetch } = {}) {
|
|
|
51
56
|
return { content: `No web results found for "${query}".` };
|
|
52
57
|
}
|
|
53
58
|
catch (err) {
|
|
54
|
-
return { error:
|
|
59
|
+
return { error: errorMessage(err), code: 'transport' };
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export type IPFamily = 4 | 6;
|
|
2
|
-
export
|
|
2
|
+
export interface LookupAddress {
|
|
3
3
|
readonly address: string;
|
|
4
4
|
readonly family: IPFamily;
|
|
5
|
-
}
|
|
5
|
+
}
|
|
6
6
|
export type LookupAllCb = (err: Error | null, addresses: ReadonlyArray<LookupAddress> | null) => void;
|
|
7
|
-
export
|
|
7
|
+
export interface BareLookupAllOpts {
|
|
8
8
|
readonly all: true;
|
|
9
9
|
readonly family?: IPFamily | 0;
|
|
10
10
|
readonly hints?: number;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
export type BareLookupAll = (hostname: string, opts: BareLookupAllOpts, cb: LookupAllCb) => void;
|
|
13
13
|
export declare class BlockedAddressError extends Error {
|
|
14
14
|
readonly code = "EBLOCKEDADDR";
|
|
@@ -2,4 +2,4 @@ export declare const TARGET_SAMPLE_RATE = 16000;
|
|
|
2
2
|
export declare const WAV_HEADER_SIZE = 44;
|
|
3
3
|
export declare const BITS_PER_SAMPLE = 16;
|
|
4
4
|
export declare const CHANNELS = 1;
|
|
5
|
-
export declare function buildWavBuffer(pcm:
|
|
5
|
+
export declare function buildWavBuffer(pcm: Uint8Array): Buffer;
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
1
2
|
export const TARGET_SAMPLE_RATE = 16000;
|
|
2
3
|
export const WAV_HEADER_SIZE = 44;
|
|
3
4
|
export const BITS_PER_SAMPLE = 16;
|
|
4
5
|
export const CHANNELS = 1;
|
|
5
6
|
export function buildWavBuffer(pcm) {
|
|
6
7
|
const dataSize = pcm.length;
|
|
7
|
-
const buf =
|
|
8
|
+
const buf = b4a.alloc(WAV_HEADER_SIZE + dataSize);
|
|
8
9
|
const blockAlign = CHANNELS * (BITS_PER_SAMPLE / 8);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
b4a.write(buf, 'RIFF', 0, 'ascii');
|
|
11
|
+
b4a.writeUInt32LE(buf, WAV_HEADER_SIZE + dataSize - 8, 4);
|
|
12
|
+
b4a.write(buf, 'WAVE', 8, 'ascii');
|
|
13
|
+
b4a.write(buf, 'fmt ', 12, 'ascii');
|
|
14
|
+
b4a.writeUInt32LE(buf, 16, 16);
|
|
14
15
|
buf.writeUInt16LE(1, 20);
|
|
15
16
|
buf.writeUInt16LE(CHANNELS, 22);
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
b4a.writeUInt32LE(buf, TARGET_SAMPLE_RATE, 24);
|
|
18
|
+
b4a.writeUInt32LE(buf, TARGET_SAMPLE_RATE * blockAlign, 28);
|
|
18
19
|
buf.writeUInt16LE(blockAlign, 32);
|
|
19
20
|
buf.writeUInt16LE(BITS_PER_SAMPLE, 34);
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
b4a.write(buf, 'data', 36, 'ascii');
|
|
22
|
+
b4a.writeUInt32LE(buf, dataSize, 40);
|
|
22
23
|
buf.set(pcm, WAV_HEADER_SIZE);
|
|
23
24
|
return buf;
|
|
24
25
|
}
|
|
@@ -22,11 +22,13 @@ function structuredData(html) {
|
|
|
22
22
|
const out = [];
|
|
23
23
|
for (const match of html.matchAll(LDJSON_SCRIPT)) {
|
|
24
24
|
const value = parseJson(match[1] ?? '');
|
|
25
|
-
if (value === null)
|
|
25
|
+
if (value === null) {
|
|
26
26
|
continue;
|
|
27
|
+
}
|
|
27
28
|
walk(value, (key, text) => {
|
|
28
|
-
if (STRUCTURED_PROSE_KEYS.has(key))
|
|
29
|
+
if (STRUCTURED_PROSE_KEYS.has(key)) {
|
|
29
30
|
out.push(text);
|
|
31
|
+
}
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
return out;
|
|
@@ -35,11 +37,13 @@ function hydrationProse(html) {
|
|
|
35
37
|
const out = [];
|
|
36
38
|
for (const match of html.matchAll(JSON_SCRIPT)) {
|
|
37
39
|
const value = parseJson(match[1] ?? '');
|
|
38
|
-
if (value === null)
|
|
40
|
+
if (value === null) {
|
|
39
41
|
continue;
|
|
42
|
+
}
|
|
40
43
|
walk(value, (_key, text) => {
|
|
41
|
-
if (looksLikeProse(text))
|
|
44
|
+
if (looksLikeProse(text)) {
|
|
42
45
|
out.push(text);
|
|
46
|
+
}
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
return out;
|
|
@@ -51,13 +55,15 @@ function scriptLiteralProse(html) {
|
|
|
51
55
|
if (literal.length < MIN_PROSE_CHARS)
|
|
52
56
|
continue;
|
|
53
57
|
const decoded = parseJson(literal);
|
|
54
|
-
if (typeof decoded !== 'string')
|
|
58
|
+
if (typeof decoded !== 'string') {
|
|
55
59
|
continue;
|
|
60
|
+
}
|
|
56
61
|
const inner = decoded.trimStart().startsWith('{') ? parseJson(decoded) : null;
|
|
57
62
|
if (inner !== null) {
|
|
58
63
|
walk(inner, (_key, text) => {
|
|
59
|
-
if (looksLikeProse(text))
|
|
64
|
+
if (looksLikeProse(text)) {
|
|
60
65
|
out.push(text);
|
|
66
|
+
}
|
|
61
67
|
});
|
|
62
68
|
}
|
|
63
69
|
else if (looksLikeProse(decoded.trim())) {
|
|
@@ -71,11 +77,13 @@ function metaTags(html) {
|
|
|
71
77
|
const out = [];
|
|
72
78
|
for (const match of html.matchAll(META_TAG)) {
|
|
73
79
|
const tag = match[0];
|
|
74
|
-
if (!META_ATTR.test(tag))
|
|
80
|
+
if (!META_ATTR.test(tag)) {
|
|
75
81
|
continue;
|
|
82
|
+
}
|
|
76
83
|
const content = META_CONTENT.exec(tag)?.[1];
|
|
77
|
-
if (content)
|
|
84
|
+
if (content) {
|
|
78
85
|
out.push(decodeHtmlEntities(content).trim());
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
return out;
|
|
81
89
|
}
|
|
@@ -92,18 +100,21 @@ function parseJson(raw) {
|
|
|
92
100
|
function walk(value, visit, key = '') {
|
|
93
101
|
if (typeof value === 'string') {
|
|
94
102
|
const text = value.trim();
|
|
95
|
-
if (text)
|
|
103
|
+
if (text) {
|
|
96
104
|
visit(key, text);
|
|
105
|
+
}
|
|
97
106
|
return;
|
|
98
107
|
}
|
|
99
108
|
if (Array.isArray(value)) {
|
|
100
|
-
for (const entry of value)
|
|
109
|
+
for (const entry of value) {
|
|
101
110
|
walk(entry, visit, key);
|
|
111
|
+
}
|
|
102
112
|
return;
|
|
103
113
|
}
|
|
104
114
|
if (value && typeof value === 'object') {
|
|
105
|
-
for (const [k, v] of Object.entries(value))
|
|
115
|
+
for (const [k, v] of Object.entries(value)) {
|
|
106
116
|
walk(v, visit, k);
|
|
117
|
+
}
|
|
107
118
|
}
|
|
108
119
|
}
|
|
109
120
|
function looksLikeProse(text) {
|
|
@@ -112,8 +123,9 @@ function looksLikeProse(text) {
|
|
|
112
123
|
if (/^(?:https?:|data:|\/|#|\.)/.test(text))
|
|
113
124
|
return false;
|
|
114
125
|
const letters = text.match(/\p{L}/gu)?.length ?? 0;
|
|
115
|
-
if (letters / text.length <= 0.6)
|
|
126
|
+
if (letters / text.length <= 0.6) {
|
|
116
127
|
return false;
|
|
128
|
+
}
|
|
117
129
|
return text.includes(' ') || /[\u3000-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(text);
|
|
118
130
|
}
|
|
119
131
|
function dedupe(parts) {
|
|
@@ -25,16 +25,18 @@ export function extractPageText(html) {
|
|
|
25
25
|
}
|
|
26
26
|
function extractTitle(html) {
|
|
27
27
|
const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
28
|
-
if (!match?.[1])
|
|
28
|
+
if (!match?.[1]) {
|
|
29
29
|
return null;
|
|
30
|
+
}
|
|
30
31
|
const title = decodeHtmlEntities(stripHtmlTags(match[1])).replace(/\s+/g, ' ').trim();
|
|
31
32
|
return title.length > 0 ? title : null;
|
|
32
33
|
}
|
|
33
34
|
function pickMainRegion(html) {
|
|
34
35
|
for (const tag of ['main', 'article']) {
|
|
35
36
|
const match = new RegExp(`<${tag}[\\s>][\\s\\S]*?</${tag}>`, 'i').exec(html);
|
|
36
|
-
if (match)
|
|
37
|
+
if (match) {
|
|
37
38
|
return match[0];
|
|
39
|
+
}
|
|
38
40
|
}
|
|
39
41
|
return html;
|
|
40
42
|
}
|
|
@@ -9,11 +9,13 @@ export function pageImageUrls(html, pageUrl) {
|
|
|
9
9
|
if (out.length >= MAX_PAGE_IMAGES)
|
|
10
10
|
break;
|
|
11
11
|
const tag = match[0];
|
|
12
|
-
if (!IMAGE_ATTR.test(tag))
|
|
12
|
+
if (!IMAGE_ATTR.test(tag)) {
|
|
13
13
|
continue;
|
|
14
|
+
}
|
|
14
15
|
const content = META_CONTENT.exec(tag)?.[1];
|
|
15
|
-
if (!content)
|
|
16
|
+
if (!content) {
|
|
16
17
|
continue;
|
|
18
|
+
}
|
|
17
19
|
let resolved;
|
|
18
20
|
try {
|
|
19
21
|
resolved = new URL(decodeHtmlEntities(content).trim(), pageUrl);
|
|
@@ -21,8 +23,9 @@ export function pageImageUrls(html, pageUrl) {
|
|
|
21
23
|
catch {
|
|
22
24
|
continue;
|
|
23
25
|
}
|
|
24
|
-
if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:')
|
|
26
|
+
if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') {
|
|
25
27
|
continue;
|
|
28
|
+
}
|
|
26
29
|
const href = resolved.toString();
|
|
27
30
|
if (!out.some((seen) => seen.replace(/^https?:/, '') === href.replace(/^https?:/, ''))) {
|
|
28
31
|
out.push(href);
|
|
@@ -36,8 +39,9 @@ export function imageFileName(url, mimeType) {
|
|
|
36
39
|
last = decodeURIComponent(last);
|
|
37
40
|
}
|
|
38
41
|
catch { }
|
|
39
|
-
if (/\.(png|jpe?g|gif|webp|avif)$/i.test(last))
|
|
42
|
+
if (/\.(png|jpe?g|gif|webp|avif)$/i.test(last)) {
|
|
40
43
|
return last;
|
|
44
|
+
}
|
|
41
45
|
const ext = mimeType.split('/')[1]?.split('+')[0] ?? 'img';
|
|
42
46
|
return `${url.hostname}.${ext}`;
|
|
43
47
|
}
|
|
@@ -6,11 +6,13 @@ const YOUTUBE_HOSTS = new Set(['youtube.com', 'youtube-nocookie.com', 'youtu.be'
|
|
|
6
6
|
export function pageVideoRef(html, pageUrl) {
|
|
7
7
|
for (const match of html.matchAll(META_TAG)) {
|
|
8
8
|
const tag = match[0];
|
|
9
|
-
if (!VIDEO_ATTR.test(tag))
|
|
9
|
+
if (!VIDEO_ATTR.test(tag)) {
|
|
10
10
|
continue;
|
|
11
|
+
}
|
|
11
12
|
const content = META_CONTENT.exec(tag)?.[1];
|
|
12
|
-
if (!content)
|
|
13
|
+
if (!content) {
|
|
13
14
|
continue;
|
|
15
|
+
}
|
|
14
16
|
let resolved;
|
|
15
17
|
try {
|
|
16
18
|
resolved = new URL(decodeHtmlEntities(content).trim(), pageUrl);
|
|
@@ -18,8 +20,9 @@ export function pageVideoRef(html, pageUrl) {
|
|
|
18
20
|
catch {
|
|
19
21
|
continue;
|
|
20
22
|
}
|
|
21
|
-
if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:')
|
|
23
|
+
if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') {
|
|
22
24
|
continue;
|
|
25
|
+
}
|
|
23
26
|
return classify(resolved);
|
|
24
27
|
}
|
|
25
28
|
return null;
|
|
@@ -36,7 +39,8 @@ function classify(url) {
|
|
|
36
39
|
}
|
|
37
40
|
function watchUrl(url) {
|
|
38
41
|
const embed = /^\/embed\/([\w-]+)/.exec(url.pathname);
|
|
39
|
-
if (!embed)
|
|
42
|
+
if (!embed) {
|
|
40
43
|
return url.toString();
|
|
44
|
+
}
|
|
41
45
|
return `https://www.youtube.com/watch?v=${embed[1]}`;
|
|
42
46
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SearchEngine } from './types.ts';
|
|
2
|
-
export
|
|
2
|
+
export interface EngineEntry {
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly fn: SearchEngine;
|
|
5
|
-
}
|
|
5
|
+
}
|
|
6
6
|
export declare const DEFAULT_MAX_RESULTS = 8;
|
|
7
7
|
export declare const ENGINES: ReadonlyArray<EngineEntry>;
|
|
@@ -72,7 +72,7 @@ function parseRuFallback(html) {
|
|
|
72
72
|
}
|
|
73
73
|
export async function searchAol(query, fetcher, signal) {
|
|
74
74
|
const url = `https://search.aol.com/aol/search?q=${encodeURIComponent(query)}`;
|
|
75
|
-
const html = await fetchHtml(fetcher, url,
|
|
75
|
+
const html = await fetchHtml(fetcher, url, { signal });
|
|
76
76
|
const primary = parseAlgoBlocks(html);
|
|
77
77
|
if (primary.length > 0) {
|
|
78
78
|
return primary;
|
|
@@ -99,7 +99,7 @@ function parseH2Fallback(html) {
|
|
|
99
99
|
}
|
|
100
100
|
export async function searchBing(query, fetcher, signal) {
|
|
101
101
|
const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
|
|
102
|
-
const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA }
|
|
102
|
+
const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA }, signal });
|
|
103
103
|
if (html.includes('captcha') || html.includes('challenge/verify') || html.includes('Turnstile')) {
|
|
104
104
|
throw new Error('Bing CAPTCHA detected');
|
|
105
105
|
}
|
|
@@ -50,7 +50,10 @@ function parseLinkFallback(html) {
|
|
|
50
50
|
}
|
|
51
51
|
export async function searchBrave(query, fetcher, signal) {
|
|
52
52
|
const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}&source=web`;
|
|
53
|
-
const html = await fetchHtml(fetcher, url, {
|
|
53
|
+
const html = await fetchHtml(fetcher, url, {
|
|
54
|
+
cookies: { safesearch: 'off', useLocation: '0', summarizer: '0', country: 'us' },
|
|
55
|
+
signal
|
|
56
|
+
});
|
|
54
57
|
const primary = parseSnippetBlocks(html);
|
|
55
58
|
if (primary.length > 0) {
|
|
56
59
|
return primary;
|
|
@@ -98,8 +98,9 @@ export async function searchDuckDuckGo(query, fetcher, signal) {
|
|
|
98
98
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
99
99
|
Referer: 'https://html.duckduckgo.com/'
|
|
100
100
|
},
|
|
101
|
-
body
|
|
102
|
-
|
|
101
|
+
body,
|
|
102
|
+
signal
|
|
103
|
+
});
|
|
103
104
|
const primary = parseLinksMain(html);
|
|
104
105
|
if (primary.length > 0) {
|
|
105
106
|
return primary;
|
|
@@ -25,7 +25,7 @@ function extractTitle(anchorBody) {
|
|
|
25
25
|
const titleMatch = anchorBody.match(/<(?:div|h3)[^>]*>([\s\S]*?)<\/(?:div|h3)>/i);
|
|
26
26
|
return titleMatch ? clean(titleMatch[1] || '') : clean(anchorBody);
|
|
27
27
|
}
|
|
28
|
-
function parseAnchor(am, html, snippetPat, existing) {
|
|
28
|
+
function parseAnchor(am, { html, snippetPat, existing }) {
|
|
29
29
|
const rawUrl = resolveGoogleUrl(am[1] || '');
|
|
30
30
|
if (isSkippableUrl(rawUrl) || !rawUrl.startsWith('http')) {
|
|
31
31
|
return null;
|
|
@@ -45,7 +45,7 @@ function parseDataVedAnchors(html) {
|
|
|
45
45
|
const snippetPat = /<div[^>]*class="[^"]*VwiC3b[^"]*"[^>]*>([\s\S]*?)<\/div>/gi;
|
|
46
46
|
const anchorPat = /<a[^>]*\shref="(\/url\?q=[^"]+|https?:\/\/[^"]+)"[^>]*data-ved="[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
47
47
|
for (const am of html.matchAll(anchorPat)) {
|
|
48
|
-
const parsed = parseAnchor(am, html, snippetPat, results);
|
|
48
|
+
const parsed = parseAnchor(am, { html, snippetPat, existing: results });
|
|
49
49
|
if (parsed) {
|
|
50
50
|
results.push({ ...parsed, position: results.length + 1 });
|
|
51
51
|
}
|
|
@@ -80,7 +80,11 @@ function parseH3Fallback(html) {
|
|
|
80
80
|
}
|
|
81
81
|
export async function searchGoogle(query, fetcher, signal) {
|
|
82
82
|
const url = `https://www.google.com/search?q=${encodeURIComponent(query)}&hl=en&ie=utf8&oe=utf8&filter=0`;
|
|
83
|
-
const html = await fetchHtml(fetcher, url, {
|
|
83
|
+
const html = await fetchHtml(fetcher, url, {
|
|
84
|
+
headers: { 'User-Agent': GOOGLE_GSA_UA, Accept: '*/*' },
|
|
85
|
+
cookies: { CONSENT: 'YES+' },
|
|
86
|
+
signal
|
|
87
|
+
});
|
|
84
88
|
if (html.includes('detected unusual traffic') || html.includes('g-recaptcha')) {
|
|
85
89
|
throw new Error('Google CAPTCHA detected');
|
|
86
90
|
}
|
|
@@ -4,11 +4,11 @@ function extractSearchItems(data) {
|
|
|
4
4
|
if (typeof data !== 'object' || data === null) {
|
|
5
5
|
return [];
|
|
6
6
|
}
|
|
7
|
-
const query = data.query;
|
|
7
|
+
const query = 'query' in data ? data.query : undefined;
|
|
8
8
|
if (typeof query !== 'object' || query === null) {
|
|
9
9
|
return [];
|
|
10
10
|
}
|
|
11
|
-
const search = query.search;
|
|
11
|
+
const search = 'search' in query ? query.search : undefined;
|
|
12
12
|
if (!Array.isArray(search)) {
|
|
13
13
|
return [];
|
|
14
14
|
}
|
|
@@ -17,7 +17,8 @@ function extractSearchItems(data) {
|
|
|
17
17
|
if (typeof entry !== 'object' || entry === null) {
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const title = 'title' in entry ? entry.title : undefined;
|
|
21
|
+
const snippet = 'snippet' in entry ? entry.snippet : undefined;
|
|
21
22
|
if (typeof title === 'string' && typeof snippet === 'string') {
|
|
22
23
|
items.push({ title, snippet });
|
|
23
24
|
}
|
|
@@ -26,7 +27,7 @@ function extractSearchItems(data) {
|
|
|
26
27
|
}
|
|
27
28
|
export async function searchWikipedia(query, fetcher, signal) {
|
|
28
29
|
const apiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json&srlimit=5&utf8=1`;
|
|
29
|
-
const data = await fetchJson(fetcher, apiUrl,
|
|
30
|
+
const data = await fetchJson(fetcher, apiUrl, { signal });
|
|
30
31
|
const items = extractSearchItems(data);
|
|
31
32
|
return items.map((item, idx) => ({
|
|
32
33
|
title: item.title,
|
|
@@ -72,7 +72,7 @@ function parseRuFallback(html) {
|
|
|
72
72
|
}
|
|
73
73
|
export async function searchYahoo(query, fetcher, signal) {
|
|
74
74
|
const url = `https://search.yahoo.com/search?p=${encodeURIComponent(query)}`;
|
|
75
|
-
const html = await fetchHtml(fetcher, url,
|
|
75
|
+
const html = await fetchHtml(fetcher, url, { signal });
|
|
76
76
|
const primary = parseAlgoBlocks(html);
|
|
77
77
|
if (primary.length > 0) {
|
|
78
78
|
return primary;
|
|
@@ -8,18 +8,19 @@ export declare class TimeoutError extends Error {
|
|
|
8
8
|
export declare class AbortError extends Error {
|
|
9
9
|
constructor();
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
interface FetchOptions {
|
|
12
12
|
readonly method?: string;
|
|
13
13
|
readonly headers?: Readonly<Record<string, string>>;
|
|
14
14
|
readonly body?: string;
|
|
15
15
|
readonly cookies?: Readonly<Record<string, string>>;
|
|
16
16
|
readonly accept?: string;
|
|
17
17
|
readonly timeoutMs?: number;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
readonly signal?: BareAbortSignal;
|
|
19
|
+
}
|
|
20
|
+
export declare function fetchHtml(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<string>;
|
|
20
21
|
export declare class JsonParseError extends Error {
|
|
21
22
|
readonly cause?: unknown;
|
|
22
23
|
constructor(message: string, cause?: unknown);
|
|
23
24
|
}
|
|
24
|
-
export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions
|
|
25
|
+
export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<unknown>;
|
|
25
26
|
export {};
|