@qvac/harness 0.2.7 → 0.2.9
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/dist/harness.bundle +70 -16
- package/dist/prebuilds-manifest.json +8 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +60 -59
- package/dist-lib/lib/audio-transcode-bare.js +2 -2
- package/dist-lib/lib/capabilities/completion.d.ts +35 -2
- package/dist-lib/lib/capabilities/completion.js +114 -48
- package/dist-lib/lib/capabilities/download.js +2 -2
- package/dist-lib/lib/capabilities/speech-text.js +1 -1
- package/dist-lib/lib/capabilities/tool-prompt-log.js +1 -1
- package/dist-lib/lib/capabilities/tool-selection.js +2 -2
- package/dist-lib/lib/capabilities/tool-turns.js +1 -1
- package/dist-lib/lib/capabilities/usage-breakdown.js +1 -1
- package/dist-lib/lib/capabilities/voice.js +3 -3
- package/dist-lib/lib/connect.js +12 -11
- package/dist-lib/lib/context-window.js +1 -1
- package/dist-lib/lib/contract-types.d.ts +1 -0
- package/dist-lib/lib/docx/index.js +3 -3
- package/dist-lib/lib/hardware/capability-probe.js +3 -3
- package/dist-lib/lib/hardware/gpu-probe-addon.js +2 -2
- package/dist-lib/lib/hardware/gpu-probe-entry.js +1 -1
- package/dist-lib/lib/helpers/markup-stream-filter.js +1 -1
- package/dist-lib/lib/helpers/run-outcome.js +1 -1
- package/dist-lib/lib/helpers/spawn-path.d.ts +1 -1
- package/dist-lib/lib/helpers/thinking.js +2 -2
- package/dist-lib/lib/helpers/tool-intent.js +1 -1
- package/dist-lib/lib/helpers/tool-label.js +3 -3
- package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -1
- package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -1
- package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -1
- package/dist-lib/lib/hooks/built-in/egress.js +2 -2
- package/dist-lib/lib/hooks/built-in/index.js +3 -3
- package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -1
- package/dist-lib/lib/hooks/pipeline.js +4 -4
- package/dist-lib/lib/hooks/registry.js +1 -1
- package/dist-lib/lib/hooks/run.js +1 -1
- package/dist-lib/lib/models/model-catalog.js +4 -4
- package/dist-lib/lib/models/model-delete.js +1 -1
- package/dist-lib/lib/models/model-handle.js +1 -1
- package/dist-lib/lib/models/model-list.js +1 -1
- package/dist-lib/lib/models/model-pool.js +8 -8
- package/dist-lib/lib/models/model-source.js +1 -1
- package/dist-lib/lib/models/music-catalog.d.ts +1 -1
- package/dist-lib/lib/no-redirect-fetch-bare.js +3 -3
- package/dist-lib/lib/no-redirect-fetch-impl.js +1 -1
- package/dist-lib/lib/no-redirect-fetch-node.js +2 -2
- package/dist-lib/lib/resolution-detector.js +1 -1
- package/dist-lib/lib/sdk-loader.js +2 -2
- package/dist-lib/lib/sdk-storage.js +1 -1
- package/dist-lib/lib/serve.js +11 -10
- package/dist-lib/lib/spawn.js +3 -3
- package/dist-lib/lib/staging.js +1 -1
- package/dist-lib/lib/tools/catalog.js +1 -1
- package/dist-lib/lib/tools/create-documents.js +2 -2
- package/dist-lib/lib/tools/edit-documents.js +2 -2
- package/dist-lib/lib/tools/edit-image.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +1 -1
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +1 -1
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +6 -6
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +3 -3
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +5 -5
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +1 -1
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +3 -3
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +4 -4
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +1 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.js +2 -2
- package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
- package/dist-lib/lib/tools/exec/obsidian/command.js +1 -1
- package/dist-lib/lib/tools/exec/obsidian/route.js +7 -7
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-fs.js +6 -6
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +1 -1
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +2 -2
- package/dist-lib/lib/tools/exec/runner.js +1 -1
- package/dist-lib/lib/tools/exec.js +13 -13
- package/dist-lib/lib/tools/file-glob.js +3 -3
- package/dist-lib/lib/tools/fs/local.js +1 -1
- package/dist-lib/lib/tools/fs/remote.js +1 -1
- package/dist-lib/lib/tools/fs/walk.js +2 -2
- package/dist-lib/lib/tools/generate-image.js +4 -4
- package/dist-lib/lib/tools/generate-music.js +6 -6
- package/dist-lib/lib/tools/grep.js +6 -6
- package/dist-lib/lib/tools/http-request/truncate-response.js +1 -1
- package/dist-lib/lib/tools/http-request.js +9 -9
- package/dist-lib/lib/tools/mcp/utils.js +1 -1
- package/dist-lib/lib/tools/mcp-call.js +8 -8
- package/dist-lib/lib/tools/operations/builders.d.ts +1 -1
- package/dist-lib/lib/tools/operations/builders.js +1 -1
- package/dist-lib/lib/tools/operations/compile.js +1 -1
- package/dist-lib/lib/tools/operations/manifest.d.ts +16 -16
- package/dist-lib/lib/tools/operations/manifest.js +1 -1
- package/dist-lib/lib/tools/outbound-guard.js +1 -1
- package/dist-lib/lib/tools/rag-search.js +1 -1
- package/dist-lib/lib/tools/read-files.js +3 -3
- package/dist-lib/lib/tools/skill.js +8 -8
- package/dist-lib/lib/tools/skills/auth-resolver.js +2 -2
- package/dist-lib/lib/tools/skills/bin-check.js +1 -1
- package/dist-lib/lib/tools/skills/bundled-dir.js +2 -2
- package/dist-lib/lib/tools/skills/catalog.js +10 -10
- package/dist-lib/lib/tools/skills/cli-schema-check.js +1 -1
- package/dist-lib/lib/tools/skills/cli-schema-load.js +1 -1
- package/dist-lib/lib/tools/skills/cli-schema.js +4 -4
- package/dist-lib/lib/tools/skills/credential-check.js +3 -3
- package/dist-lib/lib/tools/skills/credential-grants.js +1 -1
- package/dist-lib/lib/tools/skills/credential-health.js +2 -2
- package/dist-lib/lib/tools/skills/frontmatter-parser.js +1 -1
- package/dist-lib/lib/tools/skills/index.js +5 -5
- package/dist-lib/lib/tools/skills/mention.js +1 -1
- package/dist-lib/lib/tools/skills/overlay-crud.js +1 -1
- package/dist-lib/lib/tools/skills/setup-routes.js +1 -1
- package/dist-lib/lib/tools/skills/skill-settings.js +1 -1
- package/dist-lib/lib/tools/skills/skill-state-constants.d.ts +8 -8
- package/dist-lib/lib/tools/skills/skill-state.js +9 -9
- package/dist-lib/lib/tools/toolsets.d.ts +1 -1
- package/dist-lib/lib/tools/url-guard.js +2 -2
- package/dist-lib/lib/tools/weather-lookup/place.d.ts +11 -0
- package/dist-lib/lib/tools/weather-lookup/place.js +152 -0
- package/dist-lib/lib/tools/weather-lookup/report.d.ts +37 -0
- package/dist-lib/lib/tools/weather-lookup/report.js +56 -0
- package/dist-lib/lib/tools/weather-lookup.d.ts +8 -0
- package/dist-lib/lib/tools/weather-lookup.js +110 -0
- package/dist-lib/lib/tools/web-fetch.js +10 -10
- package/dist-lib/lib/tools/web-search.js +2 -2
- package/dist-lib/lib/transport.js +1 -1
- package/dist-lib/lib/validating-lookup.js +1 -1
- package/dist-lib/lib/web-fetch/embedded-state.js +1 -1
- package/dist-lib/lib/web-fetch/extract.js +1 -1
- package/dist-lib/lib/web-fetch/images.js +1 -1
- package/dist-lib/lib/web-fetch/video.js +1 -1
- package/dist-lib/lib/web-search/engine-registry.js +7 -7
- package/dist-lib/lib/web-search/engines/aol.js +3 -3
- package/dist-lib/lib/web-search/engines/bing.js +4 -4
- package/dist-lib/lib/web-search/engines/brave.js +2 -2
- package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -3
- package/dist-lib/lib/web-search/engines/google.js +4 -4
- package/dist-lib/lib/web-search/engines/wikipedia.js +2 -2
- package/dist-lib/lib/web-search/engines/yahoo.js +3 -3
- package/dist-lib/lib/web-search/fetch-html.js +1 -1
- package/dist-lib/lib/web-search/merge-results.js +1 -1
- package/dist-lib/lib/web-search/orchestrator.js +3 -3
- package/dist-lib/lib/wire.js +28 -0
- package/dist-lib/schema/baseline.js +1 -1
- package/dist-lib/schema/build.js +3 -3
- package/dist-lib/schema/harness.js +5 -2
- package/dist-lib/schema/release-tags.d.ts +2 -1
- package/dist-lib/schema/release-tags.js +9 -7
- package/dist-lib/spec/hrpc/messages.js +22 -7
- package/dist-lib/spec/hyperschema/index.js +22 -7
- package/dist-lib/spec/hyperschema/schema.json +14 -1
- package/dist-lib/spec/hyperschema/types.d.ts +3 -2
- package/package.json +21 -8
- package/worker/harness-sidecar.mjs +2 -0
- package/.claude/skills/asana/SKILL.md +0 -86
- package/.claude/skills/gh-stack/SKILL.md +0 -181
- package/.claude/skills/gh-stack/references/commands.md +0 -177
- package/.claude/skills/gh-stack/references/stack-design.md +0 -97
- package/.claude/skills/gh-stack/references/troubleshooting.md +0 -159
- package/.claude/skills/js-house-style/SKILL.md +0 -671
- package/.claude/skills/kvcache/SKILL.md +0 -171
- package/.claude/skills/pr-create/SKILL.md +0 -180
- package/.claude/skills/pr-review/SKILL.md +0 -217
- package/CLAUDE.md +0 -39
- package/docs/decisions/2026-05-29-kvcache-tool-turns.md +0 -75
- package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +0 -127
- package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +0 -126
- package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +0 -88
- package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +0 -79
- package/docs/decisions/2026-08-11-pyodide-worker-thread.md +0 -68
- package/docs/decisions/2026-08-17-harness-hook-pipeline.md +0 -84
- package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +0 -176
- package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +0 -95
- package/docs/decisions/2026-08-20-kvcache-static-placement.md +0 -109
- package/docs/decisions/2026-08-21-vendored-python-wheels.md +0 -85
- package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +0 -64
- package/docs/decisions/2026-08-27-writable-skills-overlay.md +0 -56
- package/docs/decisions/2026-09-01-skill-operation-manifests.md +0 -53
- package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +0 -78
- package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +0 -136
- package/docs/decisions/INDEX.md +0 -38
- package/docs/decisions/TEMPLATE.md +0 -32
- package/docs/kvcache-reference.md +0 -655
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export declare const SKILL_STATE: {
|
|
2
|
-
readonly ready:
|
|
3
|
-
readonly needsSetup:
|
|
4
|
-
readonly disconnected:
|
|
5
|
-
readonly unsupported:
|
|
6
|
-
readonly disabled:
|
|
7
|
-
readonly outdated:
|
|
8
|
-
readonly incompatibleModel:
|
|
9
|
-
readonly failed:
|
|
2
|
+
readonly ready: 'ready';
|
|
3
|
+
readonly needsSetup: 'needs-setup';
|
|
4
|
+
readonly disconnected: 'disconnected';
|
|
5
|
+
readonly unsupported: 'unsupported';
|
|
6
|
+
readonly disabled: 'disabled';
|
|
7
|
+
readonly outdated: 'outdated';
|
|
8
|
+
readonly incompatibleModel: 'incompatible-model';
|
|
9
|
+
readonly failed: 'failed';
|
|
10
10
|
};
|
|
11
11
|
export type SkillState = (typeof SKILL_STATE)[keyof typeof SKILL_STATE];
|
|
12
12
|
export declare function isAvailableState(state: SkillState): boolean;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { credentialRecord, isCredentialExpired } from
|
|
2
|
-
import { parseToolGrant } from
|
|
3
|
-
import { missingBins } from
|
|
4
|
-
import { credentialSlots } from
|
|
5
|
-
import { requiredSettings } from
|
|
6
|
-
import { isCredentialRejected } from
|
|
7
|
-
import { isObsidianVaultPath, isSandboxedWorkbenchInstall, OBSIDIAN_SKILL_NAME, OBSIDIAN_VAULT_PATH_CREDENTIAL_KEY, obsidianCliState, obsidianInstallState } from
|
|
8
|
-
import { isCapabilityProfile, meetsMinCapabilityProfile } from
|
|
9
|
-
import { SKILL_STATE, isAvailableState } from
|
|
1
|
+
import { credentialRecord, isCredentialExpired } from './auth-resolver.js';
|
|
2
|
+
import { parseToolGrant } from './tool-grants.js';
|
|
3
|
+
import { missingBins } from './bin-check.js';
|
|
4
|
+
import { credentialSlots } from './credential-slots.js';
|
|
5
|
+
import { requiredSettings } from './skill-settings.js';
|
|
6
|
+
import { isCredentialRejected } from './credential-health.js';
|
|
7
|
+
import { isObsidianVaultPath, isSandboxedWorkbenchInstall, OBSIDIAN_SKILL_NAME, OBSIDIAN_VAULT_PATH_CREDENTIAL_KEY, obsidianCliState, obsidianInstallState } from './obsidian-readiness.js';
|
|
8
|
+
import { isCapabilityProfile, meetsMinCapabilityProfile } from '../../models/model-catalog.js';
|
|
9
|
+
import { SKILL_STATE, isAvailableState } from './skill-state-constants.js';
|
|
10
10
|
export { SKILL_STATE, isAvailableState };
|
|
11
11
|
export function isStub(entry) {
|
|
12
12
|
return entry.stub === true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const TOOLSETS: readonly [
|
|
1
|
+
export declare const TOOLSETS: readonly ['web-search', 'knowledge', 'files', 'exec', 'http-request', 'mcp-call'];
|
|
2
2
|
export type Toolset = (typeof TOOLSETS)[number];
|
|
3
3
|
export declare function asToolset(value?: string | null): Toolset | null;
|
|
4
4
|
export interface ToolsetScoped {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AbortedError, raceAbort } from
|
|
2
|
-
import { isPrivateOrBlocked, stripIPv6Brackets } from
|
|
1
|
+
import { AbortedError, raceAbort } from '../helpers/race-abort.js';
|
|
2
|
+
import { isPrivateOrBlocked, stripIPv6Brackets } from './private-ip.js';
|
|
3
3
|
const LITERAL_IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/;
|
|
4
4
|
export function checkOutboundHost(url) {
|
|
5
5
|
const host = stripIPv6Brackets(url.hostname);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ResolvedPlace {
|
|
2
|
+
readonly area: string;
|
|
3
|
+
readonly region: string;
|
|
4
|
+
readonly country: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function fold(value: string): string;
|
|
7
|
+
export declare function describePlace(place: ResolvedPlace): string;
|
|
8
|
+
export declare function qualifierOf(location: string): string | null;
|
|
9
|
+
export declare function cityMatches(location: string, place: ResolvedPlace): boolean;
|
|
10
|
+
export type QualifierVerdict = 'match' | 'unverifiable' | 'contradiction';
|
|
11
|
+
export declare function checkQualifier(qualifier: string, place: ResolvedPlace): QualifierVerdict;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const QUALIFIER_ALIASES = [
|
|
2
|
+
['united states of america', 'united states of america'],
|
|
3
|
+
['united states', 'united states of america'],
|
|
4
|
+
['u.s.a.', 'united states of america'],
|
|
5
|
+
['u.s.', 'united states of america'],
|
|
6
|
+
['usa', 'united states of america'],
|
|
7
|
+
['us', 'united states of america'],
|
|
8
|
+
['united kingdom', 'united kingdom'],
|
|
9
|
+
['great britain', 'united kingdom'],
|
|
10
|
+
['england', 'united kingdom'],
|
|
11
|
+
['scotland', 'united kingdom'],
|
|
12
|
+
['wales', 'united kingdom'],
|
|
13
|
+
['northern ireland', 'united kingdom'],
|
|
14
|
+
['u.k.', 'united kingdom'],
|
|
15
|
+
['uk', 'united kingdom'],
|
|
16
|
+
['gb', 'united kingdom'],
|
|
17
|
+
['united arab emirates', 'united arab emirates'],
|
|
18
|
+
['uae', 'united arab emirates'],
|
|
19
|
+
['holland', 'netherlands'],
|
|
20
|
+
['deutschland', 'germany'],
|
|
21
|
+
['espana', 'spain'],
|
|
22
|
+
['italia', 'italy']
|
|
23
|
+
];
|
|
24
|
+
const OTHER_SUBDIVISIONS = {
|
|
25
|
+
ab: 'alberta',
|
|
26
|
+
bc: 'british columbia',
|
|
27
|
+
mb: 'manitoba',
|
|
28
|
+
nb: 'new brunswick',
|
|
29
|
+
nl: 'newfoundland',
|
|
30
|
+
ns: 'nova scotia',
|
|
31
|
+
on: 'ontario',
|
|
32
|
+
pe: 'prince edward island',
|
|
33
|
+
qc: 'quebec',
|
|
34
|
+
sk: 'saskatchewan',
|
|
35
|
+
nsw: 'new south wales',
|
|
36
|
+
vic: 'victoria',
|
|
37
|
+
qld: 'queensland',
|
|
38
|
+
wa: 'western australia',
|
|
39
|
+
sa: 'south australia',
|
|
40
|
+
tas: 'tasmania',
|
|
41
|
+
nt: 'northern territory',
|
|
42
|
+
act: 'australian capital territory'
|
|
43
|
+
};
|
|
44
|
+
const US_STATES = {
|
|
45
|
+
al: 'alabama',
|
|
46
|
+
ak: 'alaska',
|
|
47
|
+
az: 'arizona',
|
|
48
|
+
ar: 'arkansas',
|
|
49
|
+
ca: 'california',
|
|
50
|
+
co: 'colorado',
|
|
51
|
+
ct: 'connecticut',
|
|
52
|
+
de: 'delaware',
|
|
53
|
+
fl: 'florida',
|
|
54
|
+
ga: 'georgia',
|
|
55
|
+
hi: 'hawaii',
|
|
56
|
+
id: 'idaho',
|
|
57
|
+
il: 'illinois',
|
|
58
|
+
in: 'indiana',
|
|
59
|
+
ia: 'iowa',
|
|
60
|
+
ks: 'kansas',
|
|
61
|
+
ky: 'kentucky',
|
|
62
|
+
la: 'louisiana',
|
|
63
|
+
me: 'maine',
|
|
64
|
+
md: 'maryland',
|
|
65
|
+
ma: 'massachusetts',
|
|
66
|
+
mi: 'michigan',
|
|
67
|
+
mn: 'minnesota',
|
|
68
|
+
ms: 'mississippi',
|
|
69
|
+
mo: 'missouri',
|
|
70
|
+
mt: 'montana',
|
|
71
|
+
ne: 'nebraska',
|
|
72
|
+
nv: 'nevada',
|
|
73
|
+
nh: 'new hampshire',
|
|
74
|
+
nj: 'new jersey',
|
|
75
|
+
nm: 'new mexico',
|
|
76
|
+
ny: 'new york',
|
|
77
|
+
nc: 'north carolina',
|
|
78
|
+
nd: 'north dakota',
|
|
79
|
+
oh: 'ohio',
|
|
80
|
+
ok: 'oklahoma',
|
|
81
|
+
or: 'oregon',
|
|
82
|
+
pa: 'pennsylvania',
|
|
83
|
+
ri: 'rhode island',
|
|
84
|
+
sc: 'south carolina',
|
|
85
|
+
sd: 'south dakota',
|
|
86
|
+
tn: 'tennessee',
|
|
87
|
+
tx: 'texas',
|
|
88
|
+
ut: 'utah',
|
|
89
|
+
vt: 'vermont',
|
|
90
|
+
va: 'virginia',
|
|
91
|
+
wa: 'washington',
|
|
92
|
+
wv: 'west virginia',
|
|
93
|
+
wi: 'wisconsin',
|
|
94
|
+
wy: 'wyoming',
|
|
95
|
+
dc: 'district of columbia'
|
|
96
|
+
};
|
|
97
|
+
export function fold(value) {
|
|
98
|
+
return value
|
|
99
|
+
.normalize('NFD')
|
|
100
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
101
|
+
.toLowerCase()
|
|
102
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
103
|
+
.trim();
|
|
104
|
+
}
|
|
105
|
+
export function describePlace(place) {
|
|
106
|
+
return [place.area, place.region, place.country].filter((part) => part.length > 0).join(', ');
|
|
107
|
+
}
|
|
108
|
+
export function qualifierOf(location) {
|
|
109
|
+
const comma = location.indexOf(',');
|
|
110
|
+
if (comma === -1)
|
|
111
|
+
return null;
|
|
112
|
+
const qualifier = fold(location.slice(comma + 1));
|
|
113
|
+
return qualifier.length > 0 ? qualifier : null;
|
|
114
|
+
}
|
|
115
|
+
function expand(qualifier) {
|
|
116
|
+
const code = qualifier.replace(/ /g, '');
|
|
117
|
+
const subdivision = US_STATES[code] ?? OTHER_SUBDIVISIONS[code];
|
|
118
|
+
if (subdivision !== undefined)
|
|
119
|
+
return subdivision;
|
|
120
|
+
for (const [alias, full] of QUALIFIER_ALIASES) {
|
|
121
|
+
if (qualifier === fold(alias))
|
|
122
|
+
return full;
|
|
123
|
+
}
|
|
124
|
+
return qualifier;
|
|
125
|
+
}
|
|
126
|
+
function initialsOf(name) {
|
|
127
|
+
return name
|
|
128
|
+
.split(' ')
|
|
129
|
+
.map((word) => word[0] ?? '')
|
|
130
|
+
.join('');
|
|
131
|
+
}
|
|
132
|
+
const ABBREVIATION = /^[a-z]{1,3}$/;
|
|
133
|
+
export function cityMatches(location, place) {
|
|
134
|
+
const city = fold(location.split(',')[0] ?? '');
|
|
135
|
+
return city.length > 0 && fold(describePlace(place)).includes(city);
|
|
136
|
+
}
|
|
137
|
+
export function checkQualifier(qualifier, place) {
|
|
138
|
+
const wanted = expand(qualifier);
|
|
139
|
+
const code = qualifier.replace(/ /g, '');
|
|
140
|
+
const opaque = wanted === qualifier && ABBREVIATION.test(code);
|
|
141
|
+
for (const candidate of [fold(place.region), fold(place.country)]) {
|
|
142
|
+
if (candidate.length === 0)
|
|
143
|
+
continue;
|
|
144
|
+
if (initialsOf(candidate) === code)
|
|
145
|
+
return 'match';
|
|
146
|
+
if (opaque)
|
|
147
|
+
continue;
|
|
148
|
+
if (candidate.includes(wanted) || wanted.includes(candidate))
|
|
149
|
+
return 'match';
|
|
150
|
+
}
|
|
151
|
+
return opaque ? 'unverifiable' : 'contradiction';
|
|
152
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ResolvedPlace } from './place.ts';
|
|
2
|
+
interface Translated {
|
|
3
|
+
readonly value: string;
|
|
4
|
+
}
|
|
5
|
+
interface CurrentCondition {
|
|
6
|
+
readonly temp_C?: string;
|
|
7
|
+
readonly temp_F?: string;
|
|
8
|
+
readonly FeelsLikeC?: string;
|
|
9
|
+
readonly humidity?: string;
|
|
10
|
+
readonly windspeedKmph?: string;
|
|
11
|
+
readonly weatherDesc?: ReadonlyArray<Translated>;
|
|
12
|
+
}
|
|
13
|
+
interface HourlyEntry {
|
|
14
|
+
readonly time?: string;
|
|
15
|
+
readonly weatherDesc?: ReadonlyArray<Translated>;
|
|
16
|
+
}
|
|
17
|
+
interface DailyForecast {
|
|
18
|
+
readonly date?: string;
|
|
19
|
+
readonly maxtempC?: string;
|
|
20
|
+
readonly mintempC?: string;
|
|
21
|
+
readonly maxtempF?: string;
|
|
22
|
+
readonly mintempF?: string;
|
|
23
|
+
readonly hourly?: ReadonlyArray<HourlyEntry>;
|
|
24
|
+
}
|
|
25
|
+
interface NearestArea {
|
|
26
|
+
readonly areaName?: ReadonlyArray<Translated>;
|
|
27
|
+
readonly region?: ReadonlyArray<Translated>;
|
|
28
|
+
readonly country?: ReadonlyArray<Translated>;
|
|
29
|
+
}
|
|
30
|
+
export interface WeatherJson {
|
|
31
|
+
readonly nearest_area?: ReadonlyArray<NearestArea>;
|
|
32
|
+
readonly current_condition?: ReadonlyArray<CurrentCondition>;
|
|
33
|
+
readonly weather?: ReadonlyArray<DailyForecast>;
|
|
34
|
+
}
|
|
35
|
+
export declare function placeOf(json: WeatherJson): ResolvedPlace | null;
|
|
36
|
+
export declare function renderReport(json: WeatherJson, place: ResolvedPlace, asked: string, verified: boolean): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describePlace } from './place.js';
|
|
2
|
+
const MAX_DAYS = 3;
|
|
3
|
+
const DAY_LABELS = ['today', 'tomorrow'];
|
|
4
|
+
const RANGE = '\u2013';
|
|
5
|
+
const NOON = 4;
|
|
6
|
+
function firstValue(values) {
|
|
7
|
+
return values?.[0]?.value?.trim() ?? '';
|
|
8
|
+
}
|
|
9
|
+
export function placeOf(json) {
|
|
10
|
+
const area = json.nearest_area?.[0];
|
|
11
|
+
if (area === undefined)
|
|
12
|
+
return null;
|
|
13
|
+
const place = {
|
|
14
|
+
area: firstValue(area.areaName),
|
|
15
|
+
region: firstValue(area.region),
|
|
16
|
+
country: firstValue(area.country)
|
|
17
|
+
};
|
|
18
|
+
return place.area || place.region || place.country ? place : null;
|
|
19
|
+
}
|
|
20
|
+
export function renderReport(json, place, asked, verified) {
|
|
21
|
+
const lines = [headline(place, asked, verified)];
|
|
22
|
+
const now = json.current_condition?.[0];
|
|
23
|
+
if (now !== undefined)
|
|
24
|
+
lines.push(currentLine(now));
|
|
25
|
+
const days = json.weather?.slice(0, MAX_DAYS) ?? [];
|
|
26
|
+
for (const [index, day] of days.entries())
|
|
27
|
+
lines.push(dailyLine(day, index));
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}
|
|
30
|
+
function headline(place, asked, verified) {
|
|
31
|
+
const resolved = describePlace(place);
|
|
32
|
+
if (verified)
|
|
33
|
+
return resolved;
|
|
34
|
+
return `${resolved} (not "${asked}" — this is where it resolved)`;
|
|
35
|
+
}
|
|
36
|
+
function currentLine(now) {
|
|
37
|
+
const parts = [`Now: ${now.temp_C ?? '?'}°C / ${now.temp_F ?? '?'}°F`];
|
|
38
|
+
const desc = firstValue(now.weatherDesc);
|
|
39
|
+
if (desc)
|
|
40
|
+
parts.push(desc);
|
|
41
|
+
if (now.FeelsLikeC !== undefined)
|
|
42
|
+
parts.push(`feels like ${now.FeelsLikeC}°C`);
|
|
43
|
+
if (now.humidity !== undefined)
|
|
44
|
+
parts.push(`humidity ${now.humidity}%`);
|
|
45
|
+
if (now.windspeedKmph !== undefined)
|
|
46
|
+
parts.push(`wind ${now.windspeedKmph} km/h`);
|
|
47
|
+
return parts.join(', ');
|
|
48
|
+
}
|
|
49
|
+
function dailyLine(day, index) {
|
|
50
|
+
const label = DAY_LABELS[index];
|
|
51
|
+
const date = `${day.date ?? '?'}${label === undefined ? '' : ` (${label})`}`;
|
|
52
|
+
const celsius = `${day.mintempC ?? '?'}${RANGE}${day.maxtempC ?? '?'}°C`;
|
|
53
|
+
const fahrenheit = `${day.mintempF ?? '?'}${RANGE}${day.maxtempF ?? '?'}°F`;
|
|
54
|
+
const desc = firstValue(day.hourly?.[NOON]?.weatherDesc);
|
|
55
|
+
return `${date}: ${celsius} / ${fahrenheit}${desc ? `, ${desc}` : ''}`;
|
|
56
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../capabilities/completion.ts';
|
|
2
|
+
import type { FetchFn } from './http-request.ts';
|
|
3
|
+
import { type HostResolver } from './url-guard.ts';
|
|
4
|
+
export interface WeatherLookupToolOptions {
|
|
5
|
+
fetch?: FetchFn;
|
|
6
|
+
resolveHost?: HostResolver;
|
|
7
|
+
}
|
|
8
|
+
export declare function createWeatherLookupTool({ fetch: fetchImpl, resolveHost }?: WeatherLookupToolOptions): ToolDefinition;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import defaultFetch from '#no-redirect-fetch';
|
|
2
|
+
import { AbortController } from 'bare-abort-controller';
|
|
3
|
+
import { lookupHost } from '../dns-lookup.js';
|
|
4
|
+
import { isRedirectStatus } from './http-request/utils.js';
|
|
5
|
+
import { resolveOutboundUrl } from './url-guard.js';
|
|
6
|
+
import { checkQualifier, cityMatches, describePlace, qualifierOf } from './weather-lookup/place.js';
|
|
7
|
+
import { placeOf, renderReport } from './weather-lookup/report.js';
|
|
8
|
+
const WEATHER_HOST = 'wttr.in';
|
|
9
|
+
const LOOKUP_TIMEOUT_MS = 15_000;
|
|
10
|
+
const MAX_BODY_BYTES = 400_000;
|
|
11
|
+
export function createWeatherLookupTool({ fetch: fetchImpl, resolveHost = lookupHost } = {}) {
|
|
12
|
+
return {
|
|
13
|
+
name: 'weather_lookup',
|
|
14
|
+
toolset: 'http-request',
|
|
15
|
+
skillGated: true,
|
|
16
|
+
autoApprove: true,
|
|
17
|
+
description: 'Look up the current weather and a three-day forecast for a place, via wttr.in. ' +
|
|
18
|
+
'Returns the place the service resolved on its first line, then the conditions now and ' +
|
|
19
|
+
'one line per day. Use it for any question about weather, temperature or forecast.',
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
location: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'The place exactly as the user wrote it, including any country or state: ' +
|
|
26
|
+
'"Nassau, Bahamas", "Paris, Texas", "London"'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
required: ['location']
|
|
30
|
+
},
|
|
31
|
+
effect: () => 'read',
|
|
32
|
+
resource: () => ({ kind: 'host', label: WEATHER_HOST }),
|
|
33
|
+
async execute(args, ctx) {
|
|
34
|
+
const raw = args.location;
|
|
35
|
+
if (typeof raw !== 'string' || raw.trim().length === 0) {
|
|
36
|
+
return { error: 'location must be a non-empty string' };
|
|
37
|
+
}
|
|
38
|
+
const location = raw.trim();
|
|
39
|
+
if (ctx?.signal?.aborted)
|
|
40
|
+
return { error: 'weather_lookup cancelled' };
|
|
41
|
+
const url = new URL(`https://${WEATHER_HOST}/${encodeURIComponent(location)}?format=j1`);
|
|
42
|
+
const fetch = fetchImpl ?? defaultFetch;
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timeout = setTimeout(() => controller.abort(undefined), LOOKUP_TIMEOUT_MS);
|
|
45
|
+
const onCallerAbort = () => controller.abort(undefined);
|
|
46
|
+
ctx?.signal?.addEventListener('abort', onCallerAbort);
|
|
47
|
+
try {
|
|
48
|
+
const check = await resolveOutboundUrl(url, resolveHost, ctx?.signal);
|
|
49
|
+
if (check.blocked)
|
|
50
|
+
return { error: check.blocked };
|
|
51
|
+
const response = await fetch(url, {
|
|
52
|
+
method: 'GET',
|
|
53
|
+
headers: { Accept: 'application/json' },
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
redirect: 'manual',
|
|
56
|
+
validatedAddresses: check.addresses,
|
|
57
|
+
maxBytes: MAX_BODY_BYTES
|
|
58
|
+
});
|
|
59
|
+
if (isRedirectStatus(response.status)) {
|
|
60
|
+
return { error: `${WEATHER_HOST} redirected the lookup for "${location}"` };
|
|
61
|
+
}
|
|
62
|
+
const body = await response.text();
|
|
63
|
+
if (/location not found/i.test(body))
|
|
64
|
+
return { content: notFound(location) };
|
|
65
|
+
if (response.status !== 200) {
|
|
66
|
+
return { error: unavailable(`it answered HTTP ${response.status}`) };
|
|
67
|
+
}
|
|
68
|
+
let json;
|
|
69
|
+
try {
|
|
70
|
+
json = JSON.parse(body);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { error: unavailable('it did not return a forecast') };
|
|
74
|
+
}
|
|
75
|
+
const place = placeOf(json);
|
|
76
|
+
if (place === null)
|
|
77
|
+
return { content: notFound(location) };
|
|
78
|
+
const qualifier = qualifierOf(location);
|
|
79
|
+
const verdict = qualifier === null ? null : checkQualifier(qualifier, place);
|
|
80
|
+
const named = cityMatches(location, place);
|
|
81
|
+
if (verdict === 'contradiction' && !named) {
|
|
82
|
+
return {
|
|
83
|
+
content: `No such place: "${location}". ${WEATHER_HOST} resolved it to ` +
|
|
84
|
+
`${describePlace(place)}, which is somewhere else.`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const verified = qualifier === null ? named : verdict === 'match';
|
|
88
|
+
return { content: renderReport(json, place, location, verified) };
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (ctx?.signal?.aborted)
|
|
92
|
+
return { error: 'weather_lookup cancelled' };
|
|
93
|
+
if (controller.signal.aborted) {
|
|
94
|
+
return { error: `weather_lookup timed out after ${LOOKUP_TIMEOUT_MS} ms` };
|
|
95
|
+
}
|
|
96
|
+
return { error: unavailable(err instanceof Error ? err.message : String(err)) };
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
ctx?.signal?.removeEventListener('abort', onCallerAbort);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function unavailable(reason) {
|
|
106
|
+
return `The weather service could not be reached: ${reason}.`;
|
|
107
|
+
}
|
|
108
|
+
function notFound(location) {
|
|
109
|
+
return `No such place: "${location}". ${WEATHER_HOST} does not know it.`;
|
|
110
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import defaultFetch from '#no-redirect-fetch';
|
|
2
2
|
import { AbortController } from 'bare-abort-controller';
|
|
3
|
-
import { lookupHost } from
|
|
4
|
-
import { AbortedError } from
|
|
5
|
-
import { extractEmbeddedState } from
|
|
6
|
-
import { extractPageText, looksLikeHtml } from
|
|
7
|
-
import { imageFileName, pageImageUrls } from
|
|
8
|
-
import { pageVideoRef } from
|
|
9
|
-
import { MAX_HTML_CHARS } from
|
|
10
|
-
import { BROWSER_UA } from
|
|
11
|
-
import { isRedirectStatus } from
|
|
12
|
-
import { resolveOutboundUrl } from
|
|
3
|
+
import { lookupHost } from '../dns-lookup.js';
|
|
4
|
+
import { AbortedError } from '../helpers/race-abort.js';
|
|
5
|
+
import { extractEmbeddedState } from '../web-fetch/embedded-state.js';
|
|
6
|
+
import { extractPageText, looksLikeHtml } from '../web-fetch/extract.js';
|
|
7
|
+
import { imageFileName, pageImageUrls } from '../web-fetch/images.js';
|
|
8
|
+
import { pageVideoRef } from '../web-fetch/video.js';
|
|
9
|
+
import { MAX_HTML_CHARS } from '../web-search/fetch-html.js';
|
|
10
|
+
import { BROWSER_UA } from '../web-search/user-agents.js';
|
|
11
|
+
import { isRedirectStatus } from './http-request/utils.js';
|
|
12
|
+
import { resolveOutboundUrl } from './url-guard.js';
|
|
13
13
|
const SMALL_CONTEXT_TOKENS = 12_288;
|
|
14
14
|
const SMALL_CONTEXT_CHARS = 6_000;
|
|
15
15
|
const LARGE_CONTEXT_CHARS = 16_000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import defaultFetch from '#fetch';
|
|
2
|
-
import { formatResultsForPrompt } from
|
|
3
|
-
import { searchWeb } from
|
|
2
|
+
import { formatResultsForPrompt } from '../web-search/format-results.js';
|
|
3
|
+
import { searchWeb } from '../web-search/orchestrator.js';
|
|
4
4
|
const nodeFetch = async (url, init) => {
|
|
5
5
|
const res = await defaultFetch(url, init);
|
|
6
6
|
return { ok: res.ok, status: res.status, text: () => res.text() };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import c from 'compact-encoding';
|
|
2
2
|
import { Duplex } from 'streamx';
|
|
3
|
-
export { settleOnClose } from
|
|
3
|
+
export { settleOnClose } from './rpc-session.js';
|
|
4
4
|
export function clientSessions(pipe) {
|
|
5
5
|
let current = session(pipe);
|
|
6
6
|
pipe.on('data', (chunk) => current.push(chunk));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeHtmlEntities } from
|
|
1
|
+
import { decodeHtmlEntities } from '../web-search/html-helpers.js';
|
|
2
2
|
const LDJSON_SCRIPT = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
3
3
|
const JSON_SCRIPT = /<script[^>]*type=["']application\/json["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
4
4
|
const INLINE_SCRIPT = /<script\b(?![^>]*\bsrc=)(?![^>]*type=["']application\/(?:ld\+)?json["'])[^>]*>([\s\S]*?)<\/script>/gi;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeHtmlEntities } from
|
|
1
|
+
import { decodeHtmlEntities } from '../web-search/html-helpers.js';
|
|
2
2
|
const META_TAG = /<meta\s[^>]*>/gi;
|
|
3
3
|
const IMAGE_ATTR = /(?:property|name)=["'](?:og:image(?::secure_url|:url)?|twitter:image)["']/i;
|
|
4
4
|
const META_CONTENT = /content=["']([^"']+)["']/i;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeHtmlEntities } from
|
|
1
|
+
import { decodeHtmlEntities } from '../web-search/html-helpers.js';
|
|
2
2
|
const META_TAG = /<meta\s[^>]*>/gi;
|
|
3
3
|
const VIDEO_ATTR = /(?:property|name)=["']og:video(?::secure_url|:url)?["']/i;
|
|
4
4
|
const META_CONTENT = /content=["']([^"']+)["']/i;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { searchAol } from
|
|
2
|
-
import { searchBing } from
|
|
3
|
-
import { searchBrave } from
|
|
4
|
-
import { searchDuckDuckGo } from
|
|
5
|
-
import { searchGoogle } from
|
|
6
|
-
import { searchWikipedia } from
|
|
7
|
-
import { searchYahoo } from
|
|
1
|
+
import { searchAol } from './engines/aol.js';
|
|
2
|
+
import { searchBing } from './engines/bing.js';
|
|
3
|
+
import { searchBrave } from './engines/brave.js';
|
|
4
|
+
import { searchDuckDuckGo } from './engines/duckduckgo.js';
|
|
5
|
+
import { searchGoogle } from './engines/google.js';
|
|
6
|
+
import { searchWikipedia } from './engines/wikipedia.js';
|
|
7
|
+
import { searchYahoo } from './engines/yahoo.js';
|
|
8
8
|
export const DEFAULT_MAX_RESULTS = 8;
|
|
9
9
|
export const ENGINES = [
|
|
10
10
|
{ name: 'duckduckgo', fn: searchDuckDuckGo },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
3
|
-
import { urlMatchesHost } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
|
+
import { urlMatchesHost } from '../url-helpers.js';
|
|
4
4
|
function decodeRuUrl(encoded) {
|
|
5
5
|
try {
|
|
6
6
|
const href = decodeURIComponent(encoded);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
3
|
-
import { urlMatchesHost } from
|
|
4
|
-
import { SAFARI_UA } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
|
+
import { urlMatchesHost } from '../url-helpers.js';
|
|
4
|
+
import { SAFARI_UA } from '../user-agents.js';
|
|
5
5
|
function isOrganicBingUrl(url) {
|
|
6
6
|
return url.startsWith('http') && !urlMatchesHost(url, 'bing.com') && !url.includes('msockid=');
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
3
|
function parseSnippetBlock(inner) {
|
|
4
4
|
const urlMatch = inner.match(/<a[^>]*href="(https?:\/\/(?!imgs\.search\.brave)[^"]*)"[^>]*>/i);
|
|
5
5
|
if (!urlMatch) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
3
|
-
import { urlMatchesHost } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
|
+
import { urlMatchesHost } from '../url-helpers.js';
|
|
4
4
|
function extractDdgUrl(raw) {
|
|
5
5
|
if (raw.includes('uddg=')) {
|
|
6
6
|
const m = raw.match(/uddg=([^&]+)/);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
3
|
-
import { urlMatchesHost } from
|
|
4
|
-
import { GOOGLE_GSA_UA } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
|
+
import { urlMatchesHost } from '../url-helpers.js';
|
|
4
|
+
import { GOOGLE_GSA_UA } from '../user-agents.js';
|
|
5
5
|
function resolveGoogleUrl(rawUrl) {
|
|
6
6
|
if (rawUrl.startsWith('/url?q=')) {
|
|
7
7
|
return decodeURIComponent(rawUrl.slice(7).split('&')[0] || '');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fetchJson } from
|
|
2
|
-
import { clean } from
|
|
1
|
+
import { fetchJson } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
3
|
function extractSearchItems(data) {
|
|
4
4
|
if (typeof data !== 'object' || data === null) {
|
|
5
5
|
return [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchHtml } from
|
|
2
|
-
import { clean } from
|
|
3
|
-
import { urlMatchesHost } from
|
|
1
|
+
import { fetchHtml } from '../fetch-html.js';
|
|
2
|
+
import { clean } from '../html-helpers.js';
|
|
3
|
+
import { urlMatchesHost } from '../url-helpers.js';
|
|
4
4
|
function decodeRuUrl(encoded) {
|
|
5
5
|
try {
|
|
6
6
|
const href = decodeURIComponent(encoded);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbortController } from 'bare-abort-controller';
|
|
2
|
-
import { BROWSER_UA } from
|
|
2
|
+
import { BROWSER_UA } from './user-agents.js';
|
|
3
3
|
export const ENGINE_TIMEOUT_MS = 8000;
|
|
4
4
|
export const MAX_HTML_CHARS = 2_000_000;
|
|
5
5
|
export class TimeoutError extends Error {
|