@aria-cli/tools 1.0.9 → 1.0.11
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/package.json +9 -5
- package/src/__tests__/web-fetch-download.test.ts +0 -433
- package/src/__tests__/web-tools.test.ts +0 -619
- package/src/ask-user-interaction.ts +0 -33
- package/src/cache/web-cache.ts +0 -110
- package/src/definitions/arion.ts +0 -118
- package/src/definitions/browser/browser.ts +0 -502
- package/src/definitions/browser/index.ts +0 -5
- package/src/definitions/browser/pw-downloads.ts +0 -142
- package/src/definitions/browser/pw-interactions.ts +0 -282
- package/src/definitions/browser/pw-responses.ts +0 -98
- package/src/definitions/browser/pw-session.ts +0 -405
- package/src/definitions/browser/pw-shared.ts +0 -85
- package/src/definitions/browser/pw-snapshot.ts +0 -383
- package/src/definitions/browser/pw-state.ts +0 -101
- package/src/definitions/browser/types.ts +0 -203
- package/src/definitions/code-intelligence.ts +0 -526
- package/src/definitions/core.ts +0 -118
- package/src/definitions/delegation.ts +0 -567
- package/src/definitions/deploy.ts +0 -73
- package/src/definitions/filesystem.ts +0 -217
- package/src/definitions/frg.ts +0 -67
- package/src/definitions/index.ts +0 -28
- package/src/definitions/memory.ts +0 -150
- package/src/definitions/messaging.ts +0 -734
- package/src/definitions/meta.ts +0 -392
- package/src/definitions/network.ts +0 -179
- package/src/definitions/outlook.ts +0 -318
- package/src/definitions/patch/apply-patch.ts +0 -235
- package/src/definitions/patch/fuzzy-match.ts +0 -217
- package/src/definitions/patch/index.ts +0 -1
- package/src/definitions/patch/patch-parser.ts +0 -297
- package/src/definitions/patch/sandbox-paths.ts +0 -129
- package/src/definitions/process/index.ts +0 -5
- package/src/definitions/process/process-registry.ts +0 -303
- package/src/definitions/process/process.ts +0 -456
- package/src/definitions/process/pty-keys.ts +0 -298
- package/src/definitions/process/session-slug.ts +0 -147
- package/src/definitions/quip.ts +0 -225
- package/src/definitions/search.ts +0 -67
- package/src/definitions/session-history.ts +0 -79
- package/src/definitions/shell.ts +0 -202
- package/src/definitions/slack.ts +0 -211
- package/src/definitions/web.ts +0 -119
- package/src/executors/apply-patch.ts +0 -1035
- package/src/executors/arion.ts +0 -199
- package/src/executors/code-intelligence.ts +0 -1179
- package/src/executors/deploy.ts +0 -1066
- package/src/executors/filesystem.ts +0 -1428
- package/src/executors/frg-freshness.ts +0 -743
- package/src/executors/frg.ts +0 -394
- package/src/executors/index.ts +0 -280
- package/src/executors/learning-meta.ts +0 -1367
- package/src/executors/lsp-client.ts +0 -355
- package/src/executors/memory.ts +0 -978
- package/src/executors/meta.ts +0 -293
- package/src/executors/process-registry.ts +0 -570
- package/src/executors/pty-session-store.ts +0 -43
- package/src/executors/pty.ts +0 -342
- package/src/executors/restart.ts +0 -133
- package/src/executors/search-freshness.ts +0 -249
- package/src/executors/search-types.ts +0 -98
- package/src/executors/search.ts +0 -89
- package/src/executors/self-diagnose.ts +0 -552
- package/src/executors/session-history.ts +0 -435
- package/src/executors/shell-safety.ts +0 -519
- package/src/executors/shell.ts +0 -1243
- package/src/executors/utils.ts +0 -40
- package/src/executors/web.ts +0 -786
- package/src/extraction/content-extraction.ts +0 -281
- package/src/extraction/index.ts +0 -5
- package/src/headless-control-contract.ts +0 -1149
- package/src/index.ts +0 -788
- package/src/local-control-http-auth.ts +0 -2
- package/src/mcp/client.ts +0 -218
- package/src/mcp/connection.ts +0 -568
- package/src/mcp/index.ts +0 -11
- package/src/mcp/jsonrpc.ts +0 -195
- package/src/mcp/types.ts +0 -199
- package/src/network-control-adapter.ts +0 -88
- package/src/network-runtime/address-types.ts +0 -218
- package/src/network-runtime/db-owner-fencing.ts +0 -91
- package/src/network-runtime/delivery-receipts.ts +0 -372
- package/src/network-runtime/direct-endpoint-authority.ts +0 -35
- package/src/network-runtime/index.ts +0 -316
- package/src/network-runtime/local-control-contract.ts +0 -784
- package/src/network-runtime/node-store-contract.ts +0 -46
- package/src/network-runtime/pair-route-contract.ts +0 -97
- package/src/network-runtime/peer-capabilities.ts +0 -48
- package/src/network-runtime/peer-principal-ref.ts +0 -20
- package/src/network-runtime/peer-state-machine.ts +0 -160
- package/src/network-runtime/protocol-schemas.ts +0 -265
- package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
- package/src/outlook/desktop-session.ts +0 -409
- package/src/policy.ts +0 -171
- package/src/providers/brave.ts +0 -80
- package/src/providers/duckduckgo.ts +0 -199
- package/src/providers/exa.ts +0 -85
- package/src/providers/firecrawl.ts +0 -77
- package/src/providers/index.ts +0 -8
- package/src/providers/jina.ts +0 -70
- package/src/providers/router.ts +0 -121
- package/src/providers/search-provider.ts +0 -74
- package/src/providers/tavily.ts +0 -74
- package/src/quip/desktop-session.ts +0 -435
- package/src/registry/index.ts +0 -1
- package/src/registry/registry.ts +0 -905
- package/src/runtime-socket-local-control-client.ts +0 -632
- package/src/security/dns-normalization.ts +0 -34
- package/src/security/dns-pinning.ts +0 -138
- package/src/security/external-content.ts +0 -129
- package/src/security/ssrf.ts +0 -207
- package/src/slack/desktop-session.ts +0 -493
- package/src/tool-factory.ts +0 -91
- package/src/types.ts +0 -1341
- package/src/utils/retry.ts +0 -163
- package/src/utils/safe-parse-json.ts +0 -176
- package/src/utils/url.ts +0 -20
- package/tests/benchmarks/registry.bench.ts +0 -57
- package/tests/cache/web-cache.test.ts +0 -147
- package/tests/critical-integration.test.ts +0 -1465
- package/tests/definitions/apply-patch.test.ts +0 -586
- package/tests/definitions/browser.test.ts +0 -495
- package/tests/definitions/delegation-pause-resume.test.ts +0 -758
- package/tests/definitions/execution.test.ts +0 -671
- package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
- package/tests/definitions/messaging.test.ts +0 -1468
- package/tests/definitions/outlook.test.ts +0 -30
- package/tests/definitions/process.test.ts +0 -469
- package/tests/definitions/slack.test.ts +0 -28
- package/tests/definitions/tool-inventory.test.ts +0 -218
- package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
- package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
- package/tests/executors/apply-patch.test.ts +0 -538
- package/tests/executors/arion.test.ts +0 -309
- package/tests/executors/conversation-primitives.test.ts +0 -250
- package/tests/executors/deploy.test.ts +0 -746
- package/tests/executors/filesystem-tools.test.ts +0 -357
- package/tests/executors/filesystem.test.ts +0 -959
- package/tests/executors/frg-freshness.test.ts +0 -136
- package/tests/executors/frg-merge.test.ts +0 -70
- package/tests/executors/frg-session-content.test.ts +0 -40
- package/tests/executors/frg.test.ts +0 -56
- package/tests/executors/memory-bugfixes.test.ts +0 -257
- package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
- package/tests/executors/memory.test.ts +0 -853
- package/tests/executors/meta-tools.test.ts +0 -411
- package/tests/executors/meta.test.ts +0 -683
- package/tests/executors/path-containment.test.ts +0 -51
- package/tests/executors/process-registry.test.ts +0 -505
- package/tests/executors/pty.test.ts +0 -664
- package/tests/executors/quest-security.test.ts +0 -249
- package/tests/executors/read-file-media.test.ts +0 -230
- package/tests/executors/recall-knowledge-schema.test.ts +0 -209
- package/tests/executors/recall-tags.test.ts +0 -278
- package/tests/executors/remember-null-safety.contract.test.ts +0 -41
- package/tests/executors/restart.test.ts +0 -67
- package/tests/executors/search-unified.test.ts +0 -381
- package/tests/executors/session-history.test.ts +0 -340
- package/tests/executors/session-transcript.test.ts +0 -561
- package/tests/executors/shell-abort.test.ts +0 -416
- package/tests/executors/shell-env-blocklist.test.ts +0 -648
- package/tests/executors/shell-env-process.test.ts +0 -245
- package/tests/executors/shell-process-registry.test.ts +0 -334
- package/tests/executors/shell-tools.test.ts +0 -393
- package/tests/executors/shell.test.ts +0 -690
- package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
- package/tests/executors/web-integration.test.ts +0 -633
- package/tests/executors/web-symlink.test.ts +0 -18
- package/tests/executors/web.test.ts +0 -1400
- package/tests/executors/write-stdin.test.ts +0 -145
- package/tests/extraction/content-extraction.test.ts +0 -153
- package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
- package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
- package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
- package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
- package/tests/helpers/async-waits.ts +0 -53
- package/tests/integration/headless-control-contract.integration.test.ts +0 -153
- package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
- package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
- package/tests/integration/quest-round-trip.test.ts +0 -303
- package/tests/integration/registry-executor-flow.test.ts +0 -85
- package/tests/integration.test.ts +0 -177
- package/tests/loading-tier.test.ts +0 -126
- package/tests/mcp/client-reconnect.test.ts +0 -267
- package/tests/mcp/connection.test.ts +0 -846
- package/tests/mcp/injectable-logger.test.ts +0 -83
- package/tests/mcp/jsonrpc.test.ts +0 -109
- package/tests/mcp/lifecycle.test.ts +0 -879
- package/tests/network-runtime/address-types.contract.test.ts +0 -143
- package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
- package/tests/network-runtime/local-control-contract.test.ts +0 -869
- package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
- package/tests/network-runtime/node-store-contract.test.ts +0 -11
- package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
- package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
- package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
- package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
- package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
- package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
- package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
- package/tests/parallel.test.ts +0 -71
- package/tests/policy.test.ts +0 -184
- package/tests/print-default-test-lane.ts +0 -14
- package/tests/print-test-lane-manifest.ts +0 -22
- package/tests/providers/brave.test.ts +0 -159
- package/tests/providers/duckduckgo.test.ts +0 -207
- package/tests/providers/exa.test.ts +0 -175
- package/tests/providers/firecrawl.test.ts +0 -168
- package/tests/providers/jina.test.ts +0 -144
- package/tests/providers/router.test.ts +0 -328
- package/tests/providers/tavily.test.ts +0 -165
- package/tests/registry/discovery.test.ts +0 -154
- package/tests/registry/injectable-logger.test.ts +0 -230
- package/tests/registry/input-validation.test.ts +0 -361
- package/tests/registry/interface-completeness.test.ts +0 -85
- package/tests/registry/mcp-integration.test.ts +0 -103
- package/tests/registry/mcp-read-only-hint.test.ts +0 -60
- package/tests/registry/memoria-discovery.test.ts +0 -390
- package/tests/registry/nested-validation.test.ts +0 -283
- package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
- package/tests/registry/registration-lifecycle.test.ts +0 -133
- package/tests/registry-validation.test.ts +0 -424
- package/tests/registry.test.ts +0 -460
- package/tests/security/dns-pinning.test.ts +0 -162
- package/tests/security/external-content.test.ts +0 -144
- package/tests/security/ssrf.test.ts +0 -118
- package/tests/shell-safety-integration.test.ts +0 -32
- package/tests/shell-safety.test.ts +0 -365
- package/tests/slack/desktop-session.test.ts +0 -50
- package/tests/test-lane-manifest.ts +0 -440
- package/tests/test-utils.ts +0 -27
- package/tests/tool-factory.test.ts +0 -188
- package/tests/utils/retry.test.ts +0 -231
- package/tests/utils/url.test.ts +0 -63
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -55
- package/vitest.e2e.config.ts +0 -24
- package/vitest.integration.config.ts +0 -24
- package/vitest.native.config.ts +0 -24
package/src/providers/brave.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SearchProvider,
|
|
3
|
-
SearchOptions,
|
|
4
|
-
SearchResult,
|
|
5
|
-
SearchProviderEnv,
|
|
6
|
-
} from "./search-provider.js";
|
|
7
|
-
import { createProviderAbortSignal, resolveSearchProviderEnv } from "./search-provider.js";
|
|
8
|
-
|
|
9
|
-
export class BraveSearchProvider implements SearchProvider {
|
|
10
|
-
readonly name = "brave";
|
|
11
|
-
readonly requiresApiKey = true;
|
|
12
|
-
readonly priority = 1;
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly env: SearchProviderEnv | Record<string, string | undefined> = process.env,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
isAvailable(): boolean {
|
|
19
|
-
const apiKey = resolveSearchProviderEnv(this.env).BRAVE_API_KEY;
|
|
20
|
-
return Boolean(apiKey && apiKey.trim().length > 0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
24
|
-
const apiKey = resolveSearchProviderEnv(this.env).BRAVE_API_KEY;
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
throw new Error("BRAVE_API_KEY environment variable is not set");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const limit = options?.limit ?? 5;
|
|
30
|
-
|
|
31
|
-
const url = new URL("https://api.search.brave.com/res/v1/web/search");
|
|
32
|
-
url.searchParams.set("q", query);
|
|
33
|
-
url.searchParams.set("count", String(limit));
|
|
34
|
-
|
|
35
|
-
// Handle timeRange option (map to Brave's freshness parameter)
|
|
36
|
-
if (options?.timeRange) {
|
|
37
|
-
const freshnessMap: Record<string, string> = {
|
|
38
|
-
day: "pd",
|
|
39
|
-
week: "pw",
|
|
40
|
-
month: "pm",
|
|
41
|
-
year: "py",
|
|
42
|
-
};
|
|
43
|
-
const freshness = freshnessMap[options.timeRange];
|
|
44
|
-
if (freshness) {
|
|
45
|
-
url.searchParams.set("freshness", freshness);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const { signal, cleanup } = createProviderAbortSignal(30_000, options?.signal);
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetch(url.toString(), {
|
|
53
|
-
method: "GET",
|
|
54
|
-
headers: {
|
|
55
|
-
Accept: "application/json",
|
|
56
|
-
"X-Subscription-Token": apiKey,
|
|
57
|
-
},
|
|
58
|
-
signal,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new Error(`Brave Search API error: ${response.status} ${response.statusText}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const json = await response.json();
|
|
66
|
-
const webResults = json.web?.results || [];
|
|
67
|
-
|
|
68
|
-
return webResults.map(
|
|
69
|
-
(r: { title: string; url: string; description: string; relevance_score?: number }) => ({
|
|
70
|
-
title: r.title,
|
|
71
|
-
url: r.url,
|
|
72
|
-
content: r.description,
|
|
73
|
-
score: r.relevance_score,
|
|
74
|
-
}),
|
|
75
|
-
);
|
|
76
|
-
} finally {
|
|
77
|
-
cleanup();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import type { SearchProvider, SearchOptions, SearchResult } from "./search-provider.js";
|
|
2
|
-
import { createProviderAbortSignal } from "./search-provider.js";
|
|
3
|
-
|
|
4
|
-
export class DuckDuckGoSearchProvider implements SearchProvider {
|
|
5
|
-
readonly name = "duckduckgo";
|
|
6
|
-
readonly requiresApiKey = false;
|
|
7
|
-
readonly priority = 6;
|
|
8
|
-
|
|
9
|
-
isAvailable(): boolean {
|
|
10
|
-
// DuckDuckGo is always available (no API key required)
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
15
|
-
const limit = options?.limit ?? 5;
|
|
16
|
-
|
|
17
|
-
// DuckDuckGo HTML search endpoint (we'll parse the HTML response)
|
|
18
|
-
// The Instant Answer API doesn't provide web search results, only factoids
|
|
19
|
-
const url = new URL("https://html.duckduckgo.com/html/");
|
|
20
|
-
url.searchParams.set("q", query);
|
|
21
|
-
|
|
22
|
-
const { signal, cleanup } = createProviderAbortSignal(30_000, options?.signal);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const response = await fetch(url.toString(), {
|
|
26
|
-
method: "GET",
|
|
27
|
-
signal,
|
|
28
|
-
headers: {
|
|
29
|
-
"User-Agent": "Mozilla/5.0 (compatible; ARIA/1.0)",
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (!response.ok) {
|
|
34
|
-
throw new Error(`DuckDuckGo Search error: ${response.status} ${response.statusText}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const html = await response.text();
|
|
38
|
-
|
|
39
|
-
// Parse HTML to extract search results
|
|
40
|
-
// DuckDuckGo HTML results have result__a (title/url) and result__snippet (description)
|
|
41
|
-
const results: SearchResult[] = [];
|
|
42
|
-
|
|
43
|
-
// First pass: extract all result__a links (title + url)
|
|
44
|
-
const linkMap = new Map<string, { title: string; url: string }>();
|
|
45
|
-
const linkRegex =
|
|
46
|
-
/<a(?=[^>]*\bclass=(["'])[^"']*\bresult__a\b[^"']*\1)(?=[^>]*\bhref=(["'])(.*?)\2)[^>]*>([\s\S]*?)<\/a>/gi;
|
|
47
|
-
let match;
|
|
48
|
-
let resultAnchorCount = 0;
|
|
49
|
-
while ((match = linkRegex.exec(html)) !== null) {
|
|
50
|
-
const rawHref = match[3];
|
|
51
|
-
const rawTitle = match[4];
|
|
52
|
-
if (!rawHref || !rawTitle) {
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
resultAnchorCount++;
|
|
56
|
-
const url = this.resolveDuckDuckGoResultUrl(rawHref);
|
|
57
|
-
if (!url) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const title = this.stripHtmlTags(rawTitle);
|
|
61
|
-
if (!title) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
linkMap.set(url, { title, url });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If DDG returned result anchors but we extracted zero usable URLs, parsing is stale.
|
|
68
|
-
// Throw to allow router fallback rather than silently returning empty results.
|
|
69
|
-
if (resultAnchorCount > 0 && linkMap.size === 0) {
|
|
70
|
-
throw new Error("Could not resolve any result URLs from DuckDuckGo response");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Second pass: extract snippets
|
|
74
|
-
const snippetRegex =
|
|
75
|
-
/<(?:a|div|span)(?=[^>]*\bclass=(["'])[^"']*\bresult__snippet\b[^"']*\1)[^>]*>([\s\S]*?)<\/(?:a|div|span)>/gi;
|
|
76
|
-
const snippets: string[] = [];
|
|
77
|
-
while ((match = snippetRegex.exec(html)) !== null) {
|
|
78
|
-
const rawSnippet = match[2];
|
|
79
|
-
snippets.push(this.stripHtmlTags(rawSnippet ?? ""));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Combine results (matching by order, which should align)
|
|
83
|
-
const linkEntries = Array.from(linkMap.values());
|
|
84
|
-
for (let i = 0; i < Math.min(linkEntries.length, limit); i++) {
|
|
85
|
-
const link = linkEntries[i];
|
|
86
|
-
if (!link) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const snippet = snippets[i] || link.title;
|
|
90
|
-
results.push({
|
|
91
|
-
title: link.title,
|
|
92
|
-
url: link.url,
|
|
93
|
-
content: snippet,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return results;
|
|
98
|
-
} finally {
|
|
99
|
-
cleanup();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Strip HTML tags from a string
|
|
105
|
-
*/
|
|
106
|
-
private stripHtmlTags(html: string): string {
|
|
107
|
-
return html
|
|
108
|
-
.replace(/<[^>]*>/g, "")
|
|
109
|
-
.replace(/ /g, " ")
|
|
110
|
-
.replace(/&/g, "&")
|
|
111
|
-
.replace(/</g, "<")
|
|
112
|
-
.replace(/>/g, ">")
|
|
113
|
-
.replace(/"/g, '"')
|
|
114
|
-
.replace(/'/g, "'")
|
|
115
|
-
.trim();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Decode HTML entities in URLs
|
|
120
|
-
*/
|
|
121
|
-
private decodeHtmlEntities(text: string): string {
|
|
122
|
-
return text
|
|
123
|
-
.replace(/&/g, "&")
|
|
124
|
-
.replace(/</g, "<")
|
|
125
|
-
.replace(/>/g, ">")
|
|
126
|
-
.replace(/"/g, '"')
|
|
127
|
-
.replace(/'/g, "'");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private resolveDuckDuckGoResultUrl(rawHref: string): string | null {
|
|
131
|
-
const decodedHref = this.decodeHtmlEntities(rawHref).trim();
|
|
132
|
-
if (!decodedHref) {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let absoluteHref = decodedHref;
|
|
137
|
-
if (absoluteHref.startsWith("//")) {
|
|
138
|
-
absoluteHref = `https:${absoluteHref}`;
|
|
139
|
-
} else if (absoluteHref.startsWith("/")) {
|
|
140
|
-
absoluteHref = `https://duckduckgo.com${absoluteHref}`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
let parsed: URL;
|
|
144
|
-
try {
|
|
145
|
-
parsed = new URL(absoluteHref);
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
151
|
-
if (hostname === "duckduckgo.com" || hostname.endsWith(".duckduckgo.com")) {
|
|
152
|
-
const redirected = parsed.searchParams.get("uddg");
|
|
153
|
-
if (!redirected) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
return this.normalizeResultUrl(redirected);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return this.normalizeResultUrl(absoluteHref);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private normalizeResultUrl(candidate: string): string | null {
|
|
163
|
-
let value = candidate.trim();
|
|
164
|
-
if (!value) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (value.startsWith("//")) {
|
|
169
|
-
value = `https:${value}`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Some uddg values are encoded multiple times; decode at most twice.
|
|
173
|
-
for (let i = 0; i < 2; i++) {
|
|
174
|
-
try {
|
|
175
|
-
const decoded = decodeURIComponent(value);
|
|
176
|
-
if (decoded === value) {
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
value = decoded;
|
|
180
|
-
} catch {
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const parsed = new URL(value);
|
|
187
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
191
|
-
if (hostname === "duckduckgo.com" || hostname.endsWith(".duckduckgo.com")) {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
return parsed.toString();
|
|
195
|
-
} catch {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
package/src/providers/exa.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SearchProvider,
|
|
3
|
-
SearchOptions,
|
|
4
|
-
SearchResult,
|
|
5
|
-
SearchProviderEnv,
|
|
6
|
-
} from "./search-provider.js";
|
|
7
|
-
import { createProviderAbortSignal, resolveSearchProviderEnv } from "./search-provider.js";
|
|
8
|
-
|
|
9
|
-
export class ExaSearchProvider implements SearchProvider {
|
|
10
|
-
readonly name = "exa";
|
|
11
|
-
readonly requiresApiKey = true;
|
|
12
|
-
readonly priority = 3;
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly env: SearchProviderEnv | Record<string, string | undefined> = process.env,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
isAvailable(): boolean {
|
|
19
|
-
const apiKey = resolveSearchProviderEnv(this.env).EXA_API_KEY;
|
|
20
|
-
return Boolean(apiKey && apiKey.trim().length > 0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
24
|
-
const apiKey = resolveSearchProviderEnv(this.env).EXA_API_KEY;
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
throw new Error("EXA_API_KEY environment variable is not set");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const limit = options?.limit ?? 5;
|
|
30
|
-
|
|
31
|
-
const body: Record<string, unknown> = {
|
|
32
|
-
query,
|
|
33
|
-
numResults: limit,
|
|
34
|
-
useAutoprompt: true,
|
|
35
|
-
contents: {
|
|
36
|
-
text: true,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Domain filtering
|
|
41
|
-
if (options?.domains) {
|
|
42
|
-
body.includeDomains = options.domains;
|
|
43
|
-
}
|
|
44
|
-
if (options?.excludeDomains) {
|
|
45
|
-
body.excludeDomains = options.excludeDomains;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { signal, cleanup } = createProviderAbortSignal(30_000, options?.signal);
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const response = await fetch("https://api.exa.ai/search", {
|
|
52
|
-
method: "POST",
|
|
53
|
-
headers: {
|
|
54
|
-
"Content-Type": "application/json",
|
|
55
|
-
"x-api-key": apiKey,
|
|
56
|
-
},
|
|
57
|
-
body: JSON.stringify(body),
|
|
58
|
-
signal,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new Error(`Exa Search API error: ${response.status} ${response.statusText}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const json = await response.json();
|
|
66
|
-
const results = (json.results || []) as Array<{
|
|
67
|
-
title: string;
|
|
68
|
-
url: string;
|
|
69
|
-
text?: string;
|
|
70
|
-
score?: number;
|
|
71
|
-
publishedDate?: string;
|
|
72
|
-
}>;
|
|
73
|
-
|
|
74
|
-
return results.map((r) => ({
|
|
75
|
-
title: r.title,
|
|
76
|
-
url: r.url,
|
|
77
|
-
content: r.text || "",
|
|
78
|
-
score: r.score,
|
|
79
|
-
publishedDate: r.publishedDate,
|
|
80
|
-
}));
|
|
81
|
-
} finally {
|
|
82
|
-
cleanup();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SearchProvider,
|
|
3
|
-
SearchOptions,
|
|
4
|
-
SearchResult,
|
|
5
|
-
SearchProviderEnv,
|
|
6
|
-
} from "./search-provider.js";
|
|
7
|
-
import { createProviderAbortSignal, resolveSearchProviderEnv } from "./search-provider.js";
|
|
8
|
-
|
|
9
|
-
export class FirecrawlSearchProvider implements SearchProvider {
|
|
10
|
-
readonly name = "firecrawl";
|
|
11
|
-
readonly requiresApiKey = true;
|
|
12
|
-
readonly priority = 2;
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly env: SearchProviderEnv | Record<string, string | undefined> = process.env,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
isAvailable(): boolean {
|
|
19
|
-
const apiKey = resolveSearchProviderEnv(this.env).FIRECRAWL_API_KEY;
|
|
20
|
-
return Boolean(apiKey && apiKey.trim().length > 0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
24
|
-
const apiKey = resolveSearchProviderEnv(this.env).FIRECRAWL_API_KEY;
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
throw new Error("FIRECRAWL_API_KEY environment variable is not set");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const limit = options?.limit ?? 5;
|
|
30
|
-
|
|
31
|
-
const body: Record<string, unknown> = {
|
|
32
|
-
query,
|
|
33
|
-
limit,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const { signal, cleanup } = createProviderAbortSignal(30_000, options?.signal);
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const response = await fetch("https://api.firecrawl.dev/v1/search", {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: {
|
|
42
|
-
"Content-Type": "application/json",
|
|
43
|
-
Authorization: `Bearer ${apiKey}`,
|
|
44
|
-
},
|
|
45
|
-
body: JSON.stringify(body),
|
|
46
|
-
signal,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
throw new Error(`Firecrawl Search API error: ${response.status} ${response.statusText}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const json = await response.json();
|
|
54
|
-
|
|
55
|
-
// Firecrawl can return 200 with success: false
|
|
56
|
-
if (json.success === false) {
|
|
57
|
-
throw new Error(`Firecrawl API error: ${json.error || "Unknown error"}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const results = (json.data || []) as Array<{
|
|
61
|
-
title: string;
|
|
62
|
-
url: string;
|
|
63
|
-
markdown?: string;
|
|
64
|
-
score?: number;
|
|
65
|
-
}>;
|
|
66
|
-
|
|
67
|
-
return results.map((r) => ({
|
|
68
|
-
title: r.title,
|
|
69
|
-
url: r.url,
|
|
70
|
-
content: r.markdown || "",
|
|
71
|
-
score: r.score,
|
|
72
|
-
}));
|
|
73
|
-
} finally {
|
|
74
|
-
cleanup();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
package/src/providers/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export type { SearchOptions, SearchResult, SearchProvider } from "./search-provider.js";
|
|
2
|
-
export { JinaSearchProvider } from "./jina.js";
|
|
3
|
-
export { DuckDuckGoSearchProvider } from "./duckduckgo.js";
|
|
4
|
-
export { TavilySearchProvider } from "./tavily.js";
|
|
5
|
-
export { BraveSearchProvider } from "./brave.js";
|
|
6
|
-
export { ExaSearchProvider } from "./exa.js";
|
|
7
|
-
export { FirecrawlSearchProvider } from "./firecrawl.js";
|
|
8
|
-
export { SearchProviderRouter } from "./router.js";
|
package/src/providers/jina.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SearchProvider,
|
|
3
|
-
SearchOptions,
|
|
4
|
-
SearchResult,
|
|
5
|
-
SearchProviderEnv,
|
|
6
|
-
} from "./search-provider.js";
|
|
7
|
-
import { createProviderAbortSignal, resolveSearchProviderEnv } from "./search-provider.js";
|
|
8
|
-
|
|
9
|
-
export class JinaSearchProvider implements SearchProvider {
|
|
10
|
-
readonly name = "jina";
|
|
11
|
-
readonly requiresApiKey = false;
|
|
12
|
-
readonly priority = 5;
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private readonly env: SearchProviderEnv | Record<string, string | undefined> = process.env,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
isAvailable(): boolean {
|
|
19
|
-
// Jina works without an API key (20 req/min), or with a key (200 req/min)
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
24
|
-
const apiKey = resolveSearchProviderEnv(this.env).JINA_API_KEY;
|
|
25
|
-
const limit = options?.limit ?? 5;
|
|
26
|
-
|
|
27
|
-
const url = new URL("https://s.jina.ai/");
|
|
28
|
-
url.searchParams.set("q", query);
|
|
29
|
-
|
|
30
|
-
const headers: HeadersInit = {
|
|
31
|
-
Accept: "application/json",
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
if (apiKey) {
|
|
35
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const { signal, cleanup } = createProviderAbortSignal(30_000, options?.signal);
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const response = await fetch(url.toString(), {
|
|
42
|
-
method: "GET",
|
|
43
|
-
headers,
|
|
44
|
-
signal,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
throw new Error(`Jina Search API error: ${response.status} ${response.statusText}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const json = await response.json();
|
|
52
|
-
const results = (json.data || []) as Array<{
|
|
53
|
-
title: string;
|
|
54
|
-
url: string;
|
|
55
|
-
content: string;
|
|
56
|
-
score?: number;
|
|
57
|
-
}>;
|
|
58
|
-
|
|
59
|
-
// Apply limit client-side
|
|
60
|
-
return results.slice(0, limit).map((r) => ({
|
|
61
|
-
title: r.title,
|
|
62
|
-
url: r.url,
|
|
63
|
-
content: r.content,
|
|
64
|
-
score: r.score,
|
|
65
|
-
}));
|
|
66
|
-
} finally {
|
|
67
|
-
cleanup();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
package/src/providers/router.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { log } from "@aria-cli/types";
|
|
2
|
-
import type {
|
|
3
|
-
SearchProvider,
|
|
4
|
-
SearchOptions,
|
|
5
|
-
SearchResult,
|
|
6
|
-
SearchProviderEnv,
|
|
7
|
-
} from "./search-provider.js";
|
|
8
|
-
import { resolveSearchProviderEnv } from "./search-provider.js";
|
|
9
|
-
|
|
10
|
-
export class SearchProviderRouter {
|
|
11
|
-
constructor(
|
|
12
|
-
private _providers: SearchProvider[],
|
|
13
|
-
private readonly env: SearchProviderEnv | Record<string, string | undefined> = process.env,
|
|
14
|
-
) {}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Resolves the best available search provider.
|
|
18
|
-
* Priority:
|
|
19
|
-
* 1. Explicit override via ARIA_SEARCH_PROVIDER env var
|
|
20
|
-
* 2. Highest-priority available provider (lowest priority number)
|
|
21
|
-
* @throws {Error} if no providers are available or override is invalid
|
|
22
|
-
*/
|
|
23
|
-
resolve(): SearchProvider {
|
|
24
|
-
const env = resolveSearchProviderEnv(this.env);
|
|
25
|
-
// 1. Check for explicit override
|
|
26
|
-
const override = env.ARIA_SEARCH_PROVIDER;
|
|
27
|
-
if (override) {
|
|
28
|
-
const provider = this._providers.find((p) => p.name === override);
|
|
29
|
-
if (!provider) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`ARIA_SEARCH_PROVIDER override '${override}' not found in provider registry`,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
// Verify the overridden provider is actually available (e.g., has API key)
|
|
35
|
-
if (!provider.isAvailable()) {
|
|
36
|
-
// Fall through to default priority-based routing
|
|
37
|
-
log.debug(
|
|
38
|
-
`[SearchProviderRouter] ARIA_SEARCH_PROVIDER override '${override}' is not available, falling back to priority routing`,
|
|
39
|
-
);
|
|
40
|
-
} else {
|
|
41
|
-
return provider;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 2. Select first available provider by priority (lower number = higher priority)
|
|
46
|
-
const sorted = [...this._providers].sort((a, b) => a.priority - b.priority);
|
|
47
|
-
const available = sorted.find((p) => p.isAvailable());
|
|
48
|
-
|
|
49
|
-
if (!available) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
"No search providers available. Set at least one API key (BRAVE_API_KEY, TAVILY_API_KEY, etc.)",
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return available;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Performs a search with automatic fallback.
|
|
60
|
-
* Tries providers in priority order (resolved provider first, then others).
|
|
61
|
-
* Circuit breaker: stops at first successful response.
|
|
62
|
-
* @throws {AggregateError} if all providers fail
|
|
63
|
-
*/
|
|
64
|
-
async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
|
|
65
|
-
if (options?.signal?.aborted) {
|
|
66
|
-
throw new Error("Search aborted");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const env = resolveSearchProviderEnv(this.env);
|
|
70
|
-
// Get all available providers sorted by priority
|
|
71
|
-
const availableProviders = [...this._providers]
|
|
72
|
-
.filter((p) => p.isAvailable())
|
|
73
|
-
.sort((a, b) => a.priority - b.priority);
|
|
74
|
-
|
|
75
|
-
if (availableProviders.length === 0) {
|
|
76
|
-
throw new Error("No search providers available");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Honor ARIA_SEARCH_PROVIDER override: try the override provider first
|
|
80
|
-
const override = env.ARIA_SEARCH_PROVIDER;
|
|
81
|
-
if (override) {
|
|
82
|
-
const overrideProvider = availableProviders.find((p) => p.name === override);
|
|
83
|
-
if (overrideProvider) {
|
|
84
|
-
// Move override provider to front of the list
|
|
85
|
-
const reordered = [
|
|
86
|
-
overrideProvider,
|
|
87
|
-
...availableProviders.filter((p) => p.name !== override),
|
|
88
|
-
];
|
|
89
|
-
availableProviders.length = 0;
|
|
90
|
-
availableProviders.push(...reordered);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const errors: Error[] = [];
|
|
95
|
-
|
|
96
|
-
// Try each provider in order
|
|
97
|
-
for (const provider of availableProviders) {
|
|
98
|
-
if (options?.signal?.aborted) {
|
|
99
|
-
throw new Error("Search aborted");
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
const results = await provider.search(query, options);
|
|
103
|
-
return results; // Circuit breaker: return on first success
|
|
104
|
-
} catch (error) {
|
|
105
|
-
if (options?.signal?.aborted) {
|
|
106
|
-
throw error instanceof Error ? error : new Error("Search aborted");
|
|
107
|
-
}
|
|
108
|
-
errors.push(
|
|
109
|
-
error instanceof Error ? error : new Error(`Unknown error from ${provider.name}`),
|
|
110
|
-
);
|
|
111
|
-
// Continue to next provider
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// All providers failed
|
|
116
|
-
throw new AggregateError(
|
|
117
|
-
errors,
|
|
118
|
-
`All search providers failed (tried ${availableProviders.length} providers)`,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|