@mini-z/dsh-search-providers 0.1.3 → 0.2.1

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 ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1
4
+
5
+ - Fix Codex credential loading for account stores with `credentials[]` and `activeAccountId`; preserve existing credential formats and never modify or refresh credentials.
6
+
7
+ ## 0.2.0
8
+
9
+ ### Breaking changes
10
+
11
+ - Load provider settings from `~/.dsh/search-providers.json`, or a file selected by the Cordis `configFile` option. Inline provider settings and environment-variable configuration are no longer supported.
12
+ - Configure Codex credentials with `providers.codex.credentialFile`; relative paths resolve from the JSON configuration directory.
13
+ - Use built-in official service endpoints. Remove provider `baseUrl` and TinyFish `options.fetchBaseUrl` from existing configuration.
14
+
15
+ ### Improvements
16
+
17
+ - Validate configuration at startup and report errors without echoing credential values.
18
+ - Include `search-providers.example.json` in the published package. See README for migration steps.
19
+ - Increase the per-fallback outer timeout from 20 seconds to 55 seconds for both search and page reading; aggregate tool deadlines are unchanged.
20
+ - Add configuration loading, official endpoint, and timeout regression tests.
package/README.md CHANGED
@@ -7,6 +7,14 @@ A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin th
7
7
 
8
8
  **Codex is the default `web_search` provider.** If its static credential is unavailable, or a request fails, the plugin proceeds through the configured fallback chain.
9
9
 
10
+ ## Latest update — 0.2.1
11
+
12
+ - **Fix Codex credential compatibility:** account-store files such as `.openai-codex-auth.json` now work directly. The provider selects the `credentials[]` entry matching `activeAccountId` and reads its `access` and `accountId`.
13
+ - Existing single-credential formats remain supported. Credential files are never modified and tokens are never refreshed by this plugin.
14
+ - If you already use the JSON configuration introduced in 0.2.0, no configuration change is required: update the plugin and restart DSH.
15
+
16
+ **Upgrading from 0.1.x?** Move provider settings from `.env` or inline Cordis configuration into `~/.dsh/search-providers.json`. Official endpoints are built in, and fallback attempts now have a 55-second outer timeout. See [configuration migration](#migration-from-inline-cordis-and-environment-settings).
17
+
10
18
  ## Features
11
19
 
12
20
  - Replaces the native `web_search` provider through the DSH web seam.
@@ -23,11 +31,11 @@ A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin th
23
31
 
24
32
  | Provider | `web_search` | `read_page` | Credential |
25
33
  |----------|:------------:|:-----------:|------------|
26
- | Codex | ✅ | ❌ | Static credential JSON referenced by `CODEX_CREDENTIAL_FILE` |
27
- | TinyFish | ✅ | ✅ | `TINYFISH_API_KEY` |
28
- | Tavily | ✅ | ❌ | `TAVILY_API_KEY` |
29
- | Exa | ✅ | ✅ | `EXA_API_KEY` |
30
- | Firecrawl | ✅ | ✅ | `FIRECRAWL_API_KEY` |
34
+ | Codex | ✅ | ❌ | Static credential JSON referenced by `providers.codex.credentialFile` |
35
+ | TinyFish | ✅ | ✅ | `providers.tinyfish.apiKey` in JSON |
36
+ | Tavily | ✅ | ❌ | `providers.tavily.apiKey` in JSON |
37
+ | Exa | ✅ | ✅ | `providers.exa.apiKey` in JSON |
38
+ | Firecrawl | ✅ | ✅ | `providers.firecrawl.apiKey` in JSON |
31
39
  | Demo | ✅ | ✅ | None |
32
40
 
33
41
  Default `web_search` order:
@@ -71,40 +79,66 @@ bun run build
71
79
 
72
80
  ## Configuration
73
81
 
74
- DSH loads secrets from `~/.dsh/.env`. Configure at least one provider:
82
+ The independent JSON configuration loader replaces inline Cordis settings and environment-variable configuration. Cordis accepts **only** the optional `configFile` setting; when omitted, it defaults to `~/.dsh/search-providers.json`.
75
83
 
76
- ```dotenv
77
- # Default web_search provider
78
- CODEX_CREDENTIAL_FILE=/absolute/path/to/codex-credential.json
84
+ The package-root [`search-providers.example.json`](./search-providers.example.json) is included in the package. Copy it to your chosen configuration path and supply credentials for at least one provider. For example, `~/.dsh/search-providers.json`:
79
85
 
80
- # Optional fallback providers
81
- TINYFISH_API_KEY=...
82
- TAVILY_API_KEY=...
83
- EXA_API_KEY=...
84
- FIRECRAWL_API_KEY=...
86
+ ```json
87
+ {
88
+ "provider": "codex",
89
+ "fallbackChain": ["tinyfish", "tavily", "exa", "firecrawl"],
90
+ "providers": {
91
+ "codex": {
92
+ "credentialFile": "./codex-credential.json",
93
+ "timeoutMs": 55000
94
+ },
95
+ "exa": {
96
+ "apiKey": "replace-with-your-api-key",
97
+ "timeoutMs": 55000
98
+ }
99
+ }
100
+ }
85
101
  ```
86
102
 
87
- API-key providers also accept these optional environment variables, using the corresponding prefix (`TINYFISH`, `TAVILY`, `EXA`, or `FIRECRAWL`):
103
+ The JSON fields are `provider`, `fallbackChain`, and `providers`. Provider entries support `apiKey`, `timeoutMs`, provider-specific `options`, and `credentialFile` for Codex. Official endpoints for Codex, TinyFish, Tavily, Exa, and Firecrawl are built in and cannot be overridden. The default provider request timeout is 55 seconds. Restrict configuration and credential files to the owning user; do not commit secrets.
88
104
 
89
- ```dotenv
90
- EXA_BASE_URL=https://api.exa.ai
91
- EXA_TIMEOUT_MS=55000
92
- ```
105
+ ### File paths and loading
93
106
 
94
- Codex supports `CODEX_TIMEOUT_MS`. The default provider request timeout is 55 seconds. Environment variables take precedence over values in the Cordis configuration.
107
+ - Relative `configFile` paths resolve from the DSH process's current working directory, not the Cordis profile directory.
108
+ - Both `~/` and `~\` expand to the user's home directory.
109
+ - Relative `providers.codex.credentialFile` paths resolve from the JSON configuration file's directory, not the process working directory. This field also supports `~/` and `~\` home expansion.
110
+ - The JSON configuration is loaded once at plugin startup. Restart the corresponding DSH process after changes; there is no live reload.
111
+ - A missing default file logs a warning and uses empty configuration, with no credentials.
112
+ - An explicitly specified missing file, invalid JSON, or a schema-invalid configuration throws a clear error rather than silently falling back.
113
+ - The plugin no longer reads environment variables, including API keys, endpoints, timeouts, Codex credentials, and demo mode. Values in `~/.dsh/.env` are not used by this plugin.
95
114
 
96
- ### Change the active provider or fallback chain
115
+ ### Override the JSON configuration path
97
116
 
98
- Add an id-targeted override to `~/.dsh/profiles/<profile>/cordis.patch.yml`:
117
+ To use a different file, add an id-targeted override to `~/.dsh/profiles/<profile>/cordis.patch.yml`:
99
118
 
100
119
  ```yaml
101
120
  - id: search-providers
102
121
  config:
103
- provider: exa
104
- fallbackChain: [codex, tinyfish, tavily, firecrawl]
105
- providers:
106
- exa:
107
- timeoutMs: 55000
122
+ configFile: ~/.dsh/search-providers-work.json
123
+ ```
124
+
125
+ Omit `configFile` to use the default path. Do not put provider settings or secrets in this Cordis override.
126
+
127
+ ### Change the active provider or fallback chain
128
+
129
+ Set `provider` and `fallbackChain` in the JSON file, for example:
130
+
131
+ ```json
132
+ {
133
+ "provider": "exa",
134
+ "fallbackChain": ["codex", "tinyfish", "tavily", "firecrawl"],
135
+ "providers": {
136
+ "exa": {
137
+ "apiKey": "replace-with-your-api-key",
138
+ "timeoutMs": 55000
139
+ }
140
+ }
141
+ }
108
142
  ```
109
143
 
110
144
  Routing rules:
@@ -115,18 +149,17 @@ Routing rules:
115
149
  - `fallbackChain: []` disables fallback and uses only the active provider.
116
150
  - Providers that do not support the requested capability are omitted from attempts and diagnostics.
117
151
 
118
- For TinyFish, Tavily, Exa, and Firecrawl, provider entries support `apiKey`, `baseUrl`, `timeoutMs`, and provider-specific `options`. Prefer `~/.dsh/.env` for secrets rather than storing API keys in profile YAML. TinyFish has one additional page-fetch endpoint option:
152
+ For TinyFish, Tavily, Exa, and Firecrawl, provider entries support `apiKey`, `timeoutMs`, and provider-specific `options`. Generic `options` remains supported, but does not allow endpoint overrides. TinyFish uses built-in official endpoints for both search and page fetching.
119
153
 
120
- ```yaml
121
- - id: search-providers
122
- config:
123
- providers:
124
- tinyfish:
125
- options:
126
- fetchBaseUrl: https://api.fetch.tinyfish.ai
127
- ```
154
+ Codex uses `credentialFile` and `timeoutMs` from its provider configuration. Its endpoint is fixed, and it intentionally ignores `apiKey` and `options`; credentials come exclusively from the file selected by `providers.codex.credentialFile`.
128
155
 
129
- Codex only uses `timeoutMs` from provider configuration. Its endpoint is fixed, and it intentionally ignores `apiKey`, `baseUrl`, and `options`; credentials come exclusively from `CODEX_CREDENTIAL_FILE`.
156
+ ### Migration from inline Cordis and environment settings
157
+
158
+ 1. Create the independent JSON configuration using the packaged example. Move inline Cordis `provider`, `providers`, and `fallbackChain` values into that JSON file. Old inline provider/fallback settings are rejected with a migration message; they are not merged or silently ignored.
159
+ 2. Move each provider's `*_API_KEY` and `*_TIMEOUT_MS` values into its JSON `apiKey` and `timeoutMs` fields. Move `CODEX_CREDENTIAL_FILE` to `providers.codex.credentialFile` and `CODEX_TIMEOUT_MS` to `providers.codex.timeoutMs`. Check relative credential paths against the JSON file directory. Do not migrate `*_BASE_URL` values: official endpoints are built in.
160
+ 3. Replace `SEARCH_PROVIDERS_DEMO` with `"provider": "demo"` in JSON when demo mode is wanted. Environment variables no longer configure any provider.
161
+ 4. Remove any existing provider `baseUrl` fields and TinyFish `options.fetchBaseUrl` from the JSON configuration; these legacy endpoint overrides are rejected. Remove an empty `options` object if nothing remains. Keep API keys, timeouts, routing (`provider` and `fallbackChain`), Codex `credentialFile`, and other supported provider options.
162
+ 5. Remove the old inline fields from the Cordis plugin configuration, leaving only an optional `configFile`. Restart DSH to load the new file.
130
163
 
131
164
  ## Fallback, cooldown, and cancellation behavior
132
165
 
@@ -141,7 +174,7 @@ The plugin rechecks provider state before every attempt instead of relying on a
141
174
 
142
175
  Transient state is capability-scoped: a search failure does not cool `read_page`, and a fetch failure does not cool `web_search`. Authentication disablement is provider-wide.
143
176
 
144
- The first eligible provider uses its configured request timeout. Each subsequent fallback attempt has an additional 20-second outer limit, which also stops providers that ignore `AbortSignal`. The registered `read_page` tool has a 200-second aggregate deadline. These request deadlines are separate from the cooldown durations in the table above.
177
+ The first eligible provider uses its configured request timeout. Each subsequent fallback attempt has an additional 55-second outer limit, which also stops providers that ignore `AbortSignal`. The registered `read_page` tool has a 200-second aggregate deadline. These request deadlines are separate from the cooldown durations in the table above.
145
178
 
146
179
  Caller cancellation is different from failure: it propagates immediately, preserves the original abort reason, does not continue fallback, and does not alter provider state.
147
180
 
@@ -162,6 +195,8 @@ Minimal supported JSON:
162
195
 
163
196
  The parser also accepts `credential` and `tokens` wrappers, `access` as an alternative token field, and `accountId` as an alternative account field. If the account id is omitted, the plugin reads the standard `chatgpt_account_id` claim from the access token. Missing or malformed credentials make Codex unavailable. Codex HTTP 401/403 responses are treated as confirmed credential failures and trigger the normal fallback chain.
164
197
 
198
+ Account-store files such as `.openai-codex-auth.json` are also supported: the parser reads `access` and `accountId` from the `credentials` array entry matching `activeAccountId`. Without an active selector, only a single-entry array is accepted; ambiguous or missing matches are not guessed. The store is read-only, and the original formats remain supported.
199
+
165
200
  Restrict the credential file to the owning user. Codex does not implement `read_page`, and its standalone endpoint does not apply this plugin's recency, domain, location, language, or maximum-result filters.
166
201
 
167
202
  ## Usage
@@ -188,20 +223,16 @@ Read this page: https://example.com/article
188
223
 
189
224
  ## Demo mode
190
225
 
191
- Run DSH with the key-free demo provider on POSIX shells:
226
+ Select the key-free demo provider in your JSON configuration file:
192
227
 
193
- ```bash
194
- SEARCH_PROVIDERS_DEMO=1 npx -y @deepseek-ai/dsh --profile web --no-open --port 9876
195
- ```
196
-
197
- PowerShell:
198
-
199
- ```powershell
200
- $env:SEARCH_PROVIDERS_DEMO = '1'
201
- npx -y @deepseek-ai/dsh --profile web --no-open --port 9876
228
+ ```json
229
+ {
230
+ "provider": "demo",
231
+ "fallbackChain": []
232
+ }
202
233
  ```
203
234
 
204
- Alternatively set `provider: demo` in the plugin configuration. Demo mode returns canned search and page-fetch results for integration testing.
235
+ Restart DSH after changing the file. Demo mode returns canned search and page-fetch results for integration testing; no environment variable is needed or read.
205
236
 
206
237
  ## Development
207
238
 
package/dsh/index.js CHANGED
@@ -1,19 +1,103 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname, resolve } from "node:path";
1
4
  import { randomUUID } from "node:crypto";
2
- import { statSync, readFileSync } from "node:fs";
3
- import { resolve } from "node:path";
4
5
  const DEFAULT_TIMEOUT_MS = 55e3;
5
- function resolveProviderConfig(pluginConfig, providerId, envPrefix) {
6
+ function loadPluginConfig(pluginConfig = {}) {
7
+ if (!isRecord$1(pluginConfig) || Object.keys(pluginConfig).some((key) => key !== "configFile")) {
8
+ throw new Error("search-providers: Cordis config only accepts configFile. Move provider, providers and fallbackChain to search-providers.json.");
9
+ }
10
+ if (pluginConfig.configFile !== void 0 && !isNonEmptyString(pluginConfig.configFile)) {
11
+ throw new Error("search-providers: configFile must be a non-empty string.");
12
+ }
13
+ const explicit = pluginConfig.configFile !== void 0;
14
+ const filename = resolveConfigPath(pluginConfig.configFile ?? join(homedir(), ".dsh", "search-providers.json"));
15
+ let text;
16
+ try {
17
+ text = readFileSync(filename, "utf8");
18
+ } catch (error) {
19
+ const code = error.code;
20
+ if (!explicit && code === "ENOENT") {
21
+ console.warn(
22
+ "[search-providers] Config file not found: " + filename + ". Create it to configure providers; environment variables are not used."
23
+ );
24
+ return {};
25
+ }
26
+ throw new Error("search-providers: Cannot read config file " + filename + " (" + (code ?? "read error") + ").");
27
+ }
28
+ let value;
29
+ try {
30
+ value = JSON.parse(text.charCodeAt(0) === 65279 ? text.slice(1) : text);
31
+ } catch {
32
+ throw new Error("search-providers: Invalid JSON in " + filename + ".");
33
+ }
34
+ validateConfig(value, filename);
35
+ for (const entry of Object.values(value.providers ?? {})) {
36
+ if (entry.credentialFile !== void 0) {
37
+ entry.credentialFile = resolveConfigPath(entry.credentialFile, dirname(filename));
38
+ }
39
+ }
40
+ return value;
41
+ }
42
+ function resolveProviderConfig(pluginConfig, providerId) {
6
43
  const pc = pluginConfig.providers?.[providerId] ?? {};
7
- const apiKey = process.env[`${envPrefix}_API_KEY`] ?? pc.apiKey;
8
- const baseUrl = process.env[`${envPrefix}_BASE_URL`] ?? pc.baseUrl;
9
- const timeoutMs = Number(process.env[`${envPrefix}_TIMEOUT_MS`]) || pc.timeoutMs || DEFAULT_TIMEOUT_MS;
10
44
  return {
11
- apiKey,
12
- baseUrl,
13
- timeoutMs,
45
+ apiKey: pc.apiKey,
46
+ credentialFile: pc.credentialFile,
47
+ timeoutMs: pc.timeoutMs ?? DEFAULT_TIMEOUT_MS,
14
48
  options: pc.options ?? {}
15
49
  };
16
50
  }
51
+ function resolveConfigPath(filename, base = process.cwd()) {
52
+ const path = filename.trim();
53
+ if (path === "~") return homedir();
54
+ if (path.startsWith("~/") || path.startsWith("~\\")) {
55
+ return resolve(homedir(), path.slice(2));
56
+ }
57
+ return resolve(base, path);
58
+ }
59
+ function isRecord$1(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ function isNonEmptyString(value) {
63
+ return typeof value === "string" && value.trim() !== "";
64
+ }
65
+ function validateConfig(value, filename) {
66
+ function invalid(field) {
67
+ throw new Error("search-providers: Invalid config in " + filename + ": " + field + ".");
68
+ }
69
+ if (!isRecord$1(value)) invalid("expected a JSON object");
70
+ if (Object.keys(value).some((key) => !["provider", "providers", "fallbackChain"].includes(key))) {
71
+ invalid("unknown top-level field; use provider, providers or fallbackChain");
72
+ }
73
+ if (value.provider !== void 0 && !isNonEmptyString(value.provider)) {
74
+ invalid("provider must be a non-empty string");
75
+ }
76
+ if (value.fallbackChain !== void 0 && (!Array.isArray(value.fallbackChain) || !value.fallbackChain.every(isNonEmptyString))) {
77
+ invalid("fallbackChain must be an array of non-empty strings");
78
+ }
79
+ if (value.providers === void 0) return;
80
+ if (!isRecord$1(value.providers)) invalid("providers must be an object");
81
+ for (const entry of Object.values(value.providers)) {
82
+ if (!isRecord$1(entry)) invalid("each provider must be an object");
83
+ if (Object.keys(entry).some((key) => !["apiKey", "credentialFile", "timeoutMs", "options"].includes(key))) {
84
+ invalid("unknown provider field; use apiKey, credentialFile, timeoutMs or options (official service URLs are built in)");
85
+ }
86
+ if (entry.apiKey !== void 0 && typeof entry.apiKey !== "string") invalid("apiKey must be a string");
87
+ if (entry.credentialFile !== void 0 && !isNonEmptyString(entry.credentialFile)) {
88
+ invalid("credentialFile must be a non-empty string");
89
+ }
90
+ if (entry.timeoutMs !== void 0 && (typeof entry.timeoutMs !== "number" || !Number.isSafeInteger(entry.timeoutMs) || entry.timeoutMs <= 0 || entry.timeoutMs > 2147483647)) {
91
+ invalid("timeoutMs must be a positive integer no greater than 2147483647");
92
+ }
93
+ if (entry.options !== void 0) {
94
+ if (!isRecord$1(entry.options)) invalid("options must be an object");
95
+ if (Object.hasOwn(entry.options, "fetchBaseUrl")) {
96
+ invalid("remove options.fetchBaseUrl; official service URLs are built in");
97
+ }
98
+ }
99
+ }
100
+ }
17
101
  class ProviderAuthenticationError extends Error {
18
102
  kind = "provider-auth";
19
103
  constructor(message, options) {
@@ -181,13 +265,11 @@ class TinyfishProvider {
181
265
  searchable = true;
182
266
  fetchable = true;
183
267
  apiKey;
184
- searchBaseUrl;
185
- fetchBaseUrl;
268
+ searchBaseUrl = "https://api.search.tinyfish.ai";
269
+ fetchBaseUrl = "https://api.fetch.tinyfish.ai";
186
270
  timeoutMs;
187
271
  constructor(config) {
188
272
  this.apiKey = config.apiKey;
189
- this.searchBaseUrl = config.baseUrl ?? "https://api.search.tinyfish.ai";
190
- this.fetchBaseUrl = config.options?.fetchBaseUrl ?? "https://api.fetch.tinyfish.ai";
191
273
  this.timeoutMs = config.timeoutMs;
192
274
  }
193
275
  available() {
@@ -287,7 +369,7 @@ class TinyfishProvider {
287
369
  ensureKey() {
288
370
  if (!this.apiKey) {
289
371
  throw new Error(
290
- "TinyFish API key is missing. Set TINYFISH_API_KEY or configure providers.tinyfish.apiKey."
372
+ "TinyFish API key is missing. Configure providers.tinyfish.apiKey in the search-providers JSON config file."
291
373
  );
292
374
  }
293
375
  }
@@ -299,11 +381,10 @@ class TavilyProvider {
299
381
  searchable = true;
300
382
  fetchable = false;
301
383
  apiKey;
302
- baseUrl;
384
+ baseUrl = "https://api.tavily.com";
303
385
  timeoutMs;
304
386
  constructor(config) {
305
387
  this.apiKey = config.apiKey;
306
- this.baseUrl = config.baseUrl ?? "https://api.tavily.com";
307
388
  this.timeoutMs = config.timeoutMs;
308
389
  }
309
390
  available() {
@@ -349,7 +430,7 @@ class TavilyProvider {
349
430
  }
350
431
  ensureKey() {
351
432
  if (!this.apiKey) {
352
- throw new Error("Tavily API key is missing. Set TAVILY_API_KEY or configure providers.tavily.apiKey.");
433
+ throw new Error("Tavily API key is missing. Configure providers.tavily.apiKey in the search-providers JSON config file.");
353
434
  }
354
435
  }
355
436
  }
@@ -360,11 +441,10 @@ class ExaProvider {
360
441
  searchable = true;
361
442
  fetchable = true;
362
443
  apiKey;
363
- baseUrl;
444
+ baseUrl = "https://api.exa.ai";
364
445
  timeoutMs;
365
446
  constructor(config) {
366
447
  this.apiKey = config.apiKey;
367
- this.baseUrl = config.baseUrl ?? "https://api.exa.ai";
368
448
  this.timeoutMs = config.timeoutMs;
369
449
  }
370
450
  available() {
@@ -440,7 +520,7 @@ class ExaProvider {
440
520
  }
441
521
  ensureKey() {
442
522
  if (!this.apiKey) {
443
- throw new Error("Exa API key is missing. Set EXA_API_KEY or configure providers.exa.apiKey.");
523
+ throw new Error("Exa API key is missing. Configure providers.exa.apiKey in the search-providers JSON config file.");
444
524
  }
445
525
  }
446
526
  }
@@ -451,11 +531,10 @@ class FirecrawlProvider {
451
531
  searchable = true;
452
532
  fetchable = true;
453
533
  apiKey;
454
- baseUrl;
534
+ baseUrl = "https://api.firecrawl.dev";
455
535
  timeoutMs;
456
536
  constructor(config) {
457
537
  this.apiKey = config.apiKey;
458
- this.baseUrl = config.baseUrl ?? "https://api.firecrawl.dev";
459
538
  this.timeoutMs = config.timeoutMs;
460
539
  }
461
540
  available() {
@@ -533,7 +612,7 @@ class FirecrawlProvider {
533
612
  }
534
613
  ensureKey() {
535
614
  if (!this.apiKey) {
536
- throw new Error("Firecrawl API key is missing. Set FIRECRAWL_API_KEY or configure providers.firecrawl.apiKey.");
615
+ throw new Error("Firecrawl API key is missing. Configure providers.firecrawl.apiKey in the search-providers JSON config file.");
537
616
  }
538
617
  }
539
618
  }
@@ -550,7 +629,8 @@ class CodexProvider {
550
629
  credentialFile;
551
630
  timeoutMs;
552
631
  constructor(config) {
553
- this.credentialFile = configuredCredentialFile();
632
+ const filename = readNonEmptyString(config.credentialFile);
633
+ this.credentialFile = filename === void 0 ? void 0 : resolve(filename);
554
634
  this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : 55e3;
555
635
  }
556
636
  available() {
@@ -630,10 +710,6 @@ class CodexProvider {
630
710
  return mapCodexSearchResponse(payload);
631
711
  }
632
712
  }
633
- function configuredCredentialFile() {
634
- const filename = readNonEmptyString(process.env.CODEX_CREDENTIAL_FILE);
635
- return filename === void 0 ? void 0 : resolve(filename);
636
- }
637
713
  function loadStaticCredential(filename) {
638
714
  try {
639
715
  const stats = statSync(filename);
@@ -675,10 +751,15 @@ function mapCodexSearchResponse(value) {
675
751
  }
676
752
  function parseStaticCredential(value) {
677
753
  if (!isRecord(value)) return void 0;
754
+ const stored = Array.isArray(value.credentials) ? value.credentials : [];
755
+ const activeAccountId = readNonEmptyString(value.activeAccountId);
756
+ const selected = activeAccountId ? stored.filter((entry) => isRecord(entry) && (readNonEmptyString(entry.account_id) ?? readNonEmptyString(entry.accountId)) === activeAccountId) : value.activeAccountId === void 0 ? stored : [];
757
+ const storedCredential = selected.length === 1 && isRecord(selected[0]) ? selected[0] : void 0;
678
758
  const candidates = [
679
759
  value,
680
760
  isRecord(value.credential) ? value.credential : void 0,
681
- isRecord(value.tokens) ? value.tokens : void 0
761
+ isRecord(value.tokens) ? value.tokens : void 0,
762
+ storedCredential
682
763
  ];
683
764
  for (const candidate of candidates) {
684
765
  if (!candidate) continue;
@@ -854,7 +935,7 @@ const fetchSchema = {
854
935
  },
855
936
  required: ["summary", "content", "uncertainty"]
856
937
  };
857
- const FALLBACK_ATTEMPT_TIMEOUT_MS = 2e4;
938
+ const FALLBACK_ATTEMPT_TIMEOUT_MS = 55e3;
858
939
  function resolveCandidateIds(registry, fallbackChain) {
859
940
  let activeId;
860
941
  try {
@@ -1179,18 +1260,19 @@ const name = "search-providers";
1179
1260
  const inject = ["tools", "web"];
1180
1261
  const DEFAULT_PROVIDER_ORDER = ["codex", "tinyfish", "tavily", "exa", "firecrawl"];
1181
1262
  function apply(ctx, rawConfig = {}) {
1263
+ const config = loadPluginConfig(rawConfig);
1182
1264
  const registry = new ProviderRegistry();
1183
1265
  const cooldown = new CooldownController();
1184
- const demoMode = rawConfig.provider === "demo" || process.env.SEARCH_PROVIDERS_DEMO === "1";
1185
- registry.register(new CodexProvider(resolveProviderConfig(rawConfig, "codex", "CODEX")));
1186
- registry.register(new TinyfishProvider(resolveProviderConfig(rawConfig, "tinyfish", "TINYFISH")));
1187
- registry.register(new TavilyProvider(resolveProviderConfig(rawConfig, "tavily", "TAVILY")));
1188
- registry.register(new ExaProvider(resolveProviderConfig(rawConfig, "exa", "EXA")));
1189
- registry.register(new FirecrawlProvider(resolveProviderConfig(rawConfig, "firecrawl", "FIRECRAWL")));
1266
+ const demoMode = config.provider === "demo";
1267
+ registry.register(new CodexProvider(resolveProviderConfig(config, "codex")));
1268
+ registry.register(new TinyfishProvider(resolveProviderConfig(config, "tinyfish")));
1269
+ registry.register(new TavilyProvider(resolveProviderConfig(config, "tavily")));
1270
+ registry.register(new ExaProvider(resolveProviderConfig(config, "exa")));
1271
+ registry.register(new FirecrawlProvider(resolveProviderConfig(config, "firecrawl")));
1190
1272
  if (demoMode) {
1191
- registry.register(new DemoProvider(resolveProviderConfig(rawConfig, "tinyfish", "TINYFISH")));
1273
+ registry.register(new DemoProvider(resolveProviderConfig(config, "tinyfish")));
1192
1274
  }
1193
- const activeProvider = rawConfig.provider ?? (demoMode ? "demo" : void 0);
1275
+ const activeProvider = config.provider;
1194
1276
  if (activeProvider) {
1195
1277
  try {
1196
1278
  registry.setActive(activeProvider);
@@ -1198,7 +1280,7 @@ function apply(ctx, rawConfig = {}) {
1198
1280
  console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);
1199
1281
  }
1200
1282
  }
1201
- const fallbackChain = rawConfig.fallbackChain ?? DEFAULT_PROVIDER_ORDER;
1283
+ const fallbackChain = config.fallbackChain ?? DEFAULT_PROVIDER_ORDER;
1202
1284
  registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);
1203
1285
  registerReadPageTool(ctx, registry, cooldown, fallbackChain);
1204
1286
  console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(", ")}]`);
package/dsh/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/config.ts","../src/providers/utils.ts","../src/providers/cooldown.ts","../src/providers/registry.ts","../src/providers/tinyfish.ts","../src/providers/tavily.ts","../src/providers/exa.ts","../src/providers/firecrawl.ts","../src/providers/codex.ts","../src/providers/demo.ts","../src/schemas/fetch-schema.ts","../src/tools/fallback-utils.ts","../src/tools/read-page.ts","../src/tools/web-search.ts","../src/index.ts"],"sourcesContent":["export interface ProviderConfigEntry {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n options?: Record<string, unknown>;\n}\n\nexport interface PluginConfig {\n /** Active provider id. */\n provider?: string;\n /** Provider-specific API keys and options. */\n providers?: Record<string, ProviderConfigEntry>;\n /** Provider ids to try after the active provider; an empty array disables fallback. */\n fallbackChain?: string[];\n}\n\nexport interface ResolvedProviderConfig {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs: number;\n options: Record<string, unknown>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 55_000;\n\nexport function resolveProviderConfig(\n pluginConfig: PluginConfig,\n providerId: string,\n envPrefix: string,\n): ResolvedProviderConfig {\n const pc = pluginConfig.providers?.[providerId] ?? {};\n const apiKey =\n process.env[`${envPrefix}_API_KEY`] ??\n pc.apiKey;\n const baseUrl =\n process.env[`${envPrefix}_BASE_URL`] ??\n pc.baseUrl;\n const timeoutMs =\n Number(process.env[`${envPrefix}_TIMEOUT_MS`]) ||\n pc.timeoutMs ||\n DEFAULT_TIMEOUT_MS;\n\n return {\n apiKey,\n baseUrl,\n timeoutMs,\n options: pc.options ?? {},\n };\n}\n","export class ProviderAuthenticationError extends Error {\n readonly kind = 'provider-auth' as const;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProviderAuthenticationError';\n }\n}\n\nexport class ProviderHttpError extends Error {\n readonly kind = 'provider-http' as const;\n\n constructor(\n message: string,\n readonly status: number,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'ProviderHttpError';\n }\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n try {\n const signal = init.signal\n ? combineSignals(init.signal, controller.signal)\n : controller.signal;\n return await fetch(url, { ...init, signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function combineSignals(a: AbortSignal, b: AbortSignal): AbortSignal {\n return AbortSignal.any([a, b]);\n}\n\nexport async function throwOnError(response: Response, label: string): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => '');\n let detail = text.slice(0, 500);\n if (response.status === 401) {\n detail = 'Invalid or missing API key';\n } else if (response.status === 429) {\n detail = 'Rate limit exceeded';\n } else if (response.status === 432) {\n detail = 'Usage limit exceeded';\n }\n const message = `${label} returned ${response.status}: ${detail}`;\n if (response.status === 401) {\n throw new ProviderAuthenticationError(message);\n }\n throw new ProviderHttpError(message, response.status);\n}\n","import { ProviderAuthenticationError, ProviderHttpError } from './utils.js';\n\nexport type ProviderCapability = 'search' | 'fetch';\n\nexport interface CooldownEntry {\n until: number;\n reason: string;\n}\n\nexport class CooldownController {\n private disabled = new Map<string, CooldownEntry>();\n private cooling = new Map<string, CooldownEntry>();\n\n /** Record one capability failure. Confirmed authentication failures are provider-global. */\n record(providerId: string, capability: ProviderCapability, error: unknown): boolean {\n if (error instanceof ProviderAuthenticationError) {\n this.disabled.set(providerId, {\n until: Number.POSITIVE_INFINITY,\n reason: 'invalid credentials',\n });\n return true;\n }\n\n this.cooling.set(this.capabilityKey(providerId, capability), {\n until: Date.now() + this.cooldownDuration(error),\n reason: this.classifyError(error),\n });\n return true;\n }\n\n /** Check whether one provider capability is temporarily cooling. */\n isCooling(providerId: string, capability: ProviderCapability): boolean {\n return this.getEntry(this.cooling, this.capabilityKey(providerId, capability)) !== undefined;\n }\n\n /** Check whether confirmed authentication failure disabled the provider until restart. */\n isDisabled(providerId: string): boolean {\n return this.getEntry(this.disabled, providerId) !== undefined;\n }\n\n /** Get the current failure reason, if any. */\n getReason(providerId: string, capability: ProviderCapability): string | undefined {\n return this.getEntry(this.disabled, providerId)?.reason\n ?? this.getEntry(this.cooling, this.capabilityKey(providerId, capability))?.reason;\n }\n\n /** Build candidates while separating global disablement from capability cooldown. */\n filter(\n chain: string[],\n capability: ProviderCapability,\n ): { ready: string[]; cooling: string[]; disabled: string[] } {\n const ready: string[] = [];\n const cooling: string[] = [];\n const disabled: string[] = [];\n for (const id of chain) {\n if (this.isDisabled(id)) {\n disabled.push(id);\n } else if (this.isCooling(id, capability)) {\n cooling.push(id);\n } else {\n ready.push(id);\n }\n }\n return { ready, cooling, disabled };\n }\n\n private capabilityKey(providerId: string, capability: ProviderCapability): string {\n return `${providerId}:${capability}`;\n }\n\n private getEntry(map: Map<string, CooldownEntry>, key: string): CooldownEntry | undefined {\n const entry = map.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.until) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n private classifyError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (this.isRateLimitError(error, message)) return 'rate limit or quota exceeded';\n if (/timeout|timed out/i.test(message)) return 'timeout';\n if (/network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) return 'network error';\n return 'request failed';\n }\n\n private cooldownDuration(error: unknown): number {\n const message = error instanceof Error ? error.message : String(error);\n // Rate limits / quotas: cool for 5 minutes.\n if (this.isRateLimitError(error, message)) return 5 * 60 * 1000;\n // Timeouts / network errors: cool for 30 seconds.\n if (/timeout|timed out|network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) {\n return 30 * 1000;\n }\n // Generic failures: cool for 10 seconds.\n return 10 * 1000;\n }\n\n private isRateLimitError(error: unknown, message: string): boolean {\n return (\n (error instanceof ProviderHttpError && (error.status === 429 || error.status === 432))\n || /429|432|rate.?limit|quota|usage limit|out of credits|monthly quota/i.test(message)\n );\n }\n}\n","import type { SearchProvider } from './types.js';\n\nexport class ProviderRegistry {\n private providers = new Map<string, SearchProvider>();\n private activeId: string | undefined;\n\n register(provider: SearchProvider): void {\n if (this.providers.has(provider.id)) {\n console.warn(`[search-providers] provider '${provider.id}' already registered; overwriting`);\n }\n this.providers.set(provider.id, provider);\n if (this.activeId === undefined) {\n this.activeId = provider.id;\n }\n }\n\n setActive(id: string): void {\n if (!this.providers.has(id)) {\n throw new Error(`Provider '${id}' is not registered`);\n }\n this.activeId = id;\n }\n\n getActive(): SearchProvider {\n if (this.activeId === undefined) {\n throw new Error('No search provider registered');\n }\n const provider = this.providers.get(this.activeId);\n if (!provider) {\n throw new Error(`Active provider '${this.activeId}' disappeared`);\n }\n return provider;\n }\n\n get(id: string): SearchProvider | undefined {\n return this.providers.get(id);\n }\n\n list(): string[] {\n return Array.from(this.providers.keys());\n }\n}\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type {\n FetchRequest,\n FetchResult,\n SearchProvider,\n SearchRequest,\n SearchResult,\n} from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TinyfishSearchResponse {\n query: string;\n results: Array<{\n position: number;\n site_name?: string;\n title: string;\n snippet: string;\n url: string;\n }>;\n total_results: number;\n page: number;\n}\n\ninterface TinyfishFetchResponse {\n results: Array<{\n url: string;\n final_url?: string;\n title?: string;\n description?: string;\n language?: string;\n format?: string;\n text: string;\n }>;\n errors: Array<{ url: string; error: string }>;\n}\n\nexport class TinyfishProvider implements SearchProvider {\n readonly id = 'tinyfish';\n readonly name = 'TinyFish';\n readonly description = 'TinyFish Search & Fetch (free tier, live web results)';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly searchBaseUrl: string;\n private readonly fetchBaseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.searchBaseUrl = config.baseUrl ?? 'https://api.search.tinyfish.ai';\n this.fetchBaseUrl =\n (config.options?.fetchBaseUrl as string | undefined) ?? 'https://api.fetch.tinyfish.ai';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const url = new URL(this.searchBaseUrl);\n url.searchParams.set('query', request.query);\n if (request.maxResults && request.maxResults > 0) {\n url.searchParams.set('max_results', String(Math.floor(request.maxResults)));\n }\n if (typeof request.recencyDays === 'number' && request.recencyDays > 0) {\n url.searchParams.set('recency_minutes', String(Math.floor(request.recencyDays * 24 * 60)));\n }\n if (request.purpose) {\n url.searchParams.set('purpose', request.purpose);\n }\n if (request.location) {\n url.searchParams.set('location', request.location);\n }\n if (request.language) {\n url.searchParams.set('language', request.language);\n }\n if (request.domainType) {\n url.searchParams.set('domain_type', request.domainType);\n }\n if (request.includeDomains && request.includeDomains.length > 0) {\n url.searchParams.set('include_domains', request.includeDomains.join(','));\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n url.searchParams.set('exclude_domains', request.excludeDomains.join(','));\n }\n\n const response = await fetchWithTimeout(url.toString(), {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey!,\n Accept: 'application/json',\n },\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Search');\n\n const body = (await response.json()) as TinyfishSearchResponse;\n const items = (body.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.snippet,\n source: r.site_name,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${body.total_results ?? items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n urls: [request.url],\n format: request.format ?? 'markdown',\n };\n if (request.query) {\n body.purpose = request.query;\n }\n if (typeof request.ttl === 'number') {\n body.ttl = request.ttl;\n }\n\n const response = await fetchWithTimeout(this.fetchBaseUrl, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Fetch');\n\n const data = (await response.json()) as TinyfishFetchResponse;\n const page = data.results?.[0];\n const error = data.errors?.[0];\n\n if (!page && error) {\n throw new Error(`TinyFish Fetch failed for ${error.url}: ${error.error}`);\n }\n if (!page) {\n throw new Error('TinyFish Fetch returned no content');\n }\n\n return {\n summary: page.title ?? page.description ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error(\n 'TinyFish API key is missing. Set TINYFISH_API_KEY or configure providers.tinyfish.apiKey.',\n );\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TavilySearchResponse {\n query: string;\n results: Array<{\n title: string;\n url: string;\n content?: string;\n raw_content?: string;\n score?: number;\n }>;\n}\n\nexport class TavilyProvider implements SearchProvider {\n readonly id = 'tavily';\n readonly name = 'Tavily';\n readonly description = 'Tavily web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.tavily.com';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n search_depth: 'basic',\n max_results: request.maxResults ?? 5,\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Tavily Search');\n\n const data = (await response.json()) as TavilySearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.content ?? r.raw_content ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Tavily API key is missing. Set TAVILY_API_KEY or configure providers.tavily.apiKey.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface ExaSearchResponse {\n results: Array<{\n title?: string;\n url: string;\n publishedDate?: string;\n author?: string;\n text?: string;\n score?: number;\n }>;\n}\n\ninterface ExaContentsResponse {\n results: Array<{\n url: string;\n text?: string;\n title?: string;\n }>;\n}\n\nexport class ExaProvider implements SearchProvider {\n readonly id = 'exa';\n readonly name = 'Exa';\n readonly description = 'Exa neural web search and contents fetch';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.exa.ai';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n num_results: request.maxResults ?? 5,\n type: 'auto',\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Search');\n\n const data = (await response.json()) as ExaSearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.text ?? '',\n publishedAt: r.publishedDate,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n urls: [request.url],\n text: true,\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/contents`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Contents');\n\n const data = (await response.json()) as ExaContentsResponse;\n const page = data.results?.[0];\n if (!page) {\n throw new Error('Exa Contents returned no content');\n }\n\n return {\n summary: page.title ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Exa API key is missing. Set EXA_API_KEY or configure providers.exa.apiKey.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface FirecrawlSearchResponse {\n success: boolean;\n data: {\n web: Array<{\n title?: string;\n url: string;\n description?: string;\n markdown?: string;\n }>;\n };\n}\n\ninterface FirecrawlScrapeResponse {\n success: boolean;\n data: {\n markdown?: string;\n metadata?: {\n title?: string;\n sourceURL?: string;\n };\n };\n}\n\nexport class FirecrawlProvider implements SearchProvider {\n readonly id = 'firecrawl';\n readonly name = 'Firecrawl';\n readonly description = 'Firecrawl web search and page scrape';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.firecrawl.dev';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n limit: request.maxResults ?? 5,\n sources: ['web'],\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.includeDomains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.excludeDomains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Search');\n\n const data = (await response.json()) as FirecrawlSearchResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Search returned success: false');\n }\n\n const rawItems = data.data?.web ?? [];\n const items = rawItems.map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.description ?? r.markdown ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n url: request.url,\n formats: ['markdown'],\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/scrape`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Scrape');\n\n const data = (await response.json()) as FirecrawlScrapeResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Scrape returned success: false');\n }\n\n return {\n summary: data.data?.metadata?.title ?? 'Fetched page content',\n content: data.data?.markdown ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Firecrawl API key is missing. Set FIRECRAWL_API_KEY or configure providers.firecrawl.apiKey.');\n }\n }\n}","// Codex search protocol behavior is based on dsh-codex-connect (Apache-2.0).\n// This provider only consumes an existing static credential; it never logs in,\n// refreshes tokens, or writes credential data.\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { ProviderAuthenticationError } from './utils.js';\n\nconst CODEX_SEARCH_URL = 'https://chatgpt.com/backend-api/codex/alpha/search';\nconst MAX_CREDENTIAL_BYTES = 128 * 1024;\nconst MAX_SUCCESS_RESPONSE_BYTES = 4 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\n\ninterface StaticCodexCredential {\n accessToken: string;\n accountId: string;\n}\n\ninterface CodexSearchRequestBody {\n id: string;\n model: string;\n input: Array<{\n type: 'message';\n role: 'user';\n content: Array<{ type: 'input_text'; text: string }>;\n }>;\n commands: { search_query: Array<{ q: string }> };\n settings: {\n search_context_size: 'medium';\n allowed_callers: ['direct'];\n external_web_access: false;\n };\n max_output_tokens: number;\n}\n\nexport class CodexProvider implements SearchProvider {\n readonly id = 'codex';\n readonly name = 'OpenAI Codex';\n readonly description = 'ChatGPT Codex standalone web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly credentialFile?: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.credentialFile = configuredCredentialFile();\n this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0\n ? config.timeoutMs\n : 55_000;\n }\n\n available(): boolean {\n return this.credentialFile !== undefined\n && loadStaticCredential(this.credentialFile) !== undefined;\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n throwIfAborted(signal);\n\n const credential = this.credentialFile === undefined\n ? undefined\n : loadStaticCredential(this.credentialFile);\n if (!credential) {\n throw new Error('OpenAI Codex credential file is not configured, missing or invalid');\n }\n\n const body: CodexSearchRequestBody = {\n id: randomUUID(),\n model: 'gpt-5.6-sol',\n input: [{\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: request.query }],\n }],\n commands: { search_query: [{ q: request.query }] },\n settings: {\n search_context_size: 'medium',\n allowed_callers: ['direct'],\n external_web_access: false,\n },\n max_output_tokens: 10_000,\n };\n\n const requestTimeout = AbortSignal.timeout(this.timeoutMs);\n const requestSignal = signal ? AbortSignal.any([signal, requestTimeout]) : requestTimeout;\n let response: Response;\n try {\n response = await fetch(CODEX_SEARCH_URL, {\n method: 'POST',\n redirect: 'error',\n headers: {\n Authorization: `Bearer ${credential.accessToken}`,\n 'chatgpt-account-id': credential.accountId,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n originator: 'deepseek-harness',\n },\n body: JSON.stringify(body),\n signal: requestSignal,\n });\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n throw new Error('OpenAI Codex search request failed', { cause: error });\n }\n\n let payload: unknown;\n try {\n payload = await readBoundedJson(\n response,\n response.ok ? MAX_SUCCESS_RESPONSE_BYTES : MAX_ERROR_RESPONSE_BYTES,\n requestSignal,\n );\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n if (error instanceof ResponseTooLargeError) throw error;\n throw new Error(`OpenAI Codex returned invalid JSON (HTTP ${response.status})`, { cause: error });\n }\n\n if (!response.ok) {\n const detail = providerMessage(payload);\n const suffix = detail ? `: ${detail}` : '';\n if (response.status === 401 || response.status === 403) {\n throw new ProviderAuthenticationError(\n `OpenAI Codex credential is invalid or expired (HTTP ${response.status})${suffix}`,\n );\n }\n throw new Error(`OpenAI Codex search failed (HTTP ${response.status})${suffix}`);\n }\n\n return mapCodexSearchResponse(payload);\n }\n}\n\nfunction configuredCredentialFile(): string | undefined {\n const filename = readNonEmptyString(process.env.CODEX_CREDENTIAL_FILE);\n return filename === undefined ? undefined : resolve(filename);\n}\n\nfunction loadStaticCredential(filename: string): StaticCodexCredential | undefined {\n try {\n const stats = statSync(filename);\n if (!stats.isFile() || stats.size <= 0 || stats.size > MAX_CREDENTIAL_BYTES) return undefined;\n const value: unknown = JSON.parse(readFileSync(filename, 'utf8'));\n return parseStaticCredential(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapCodexSearchResponse(value: unknown): SearchResult {\n if (!isRecord(value) || typeof value.output !== 'string') {\n throw new Error('OpenAI Codex returned a search response without string output');\n }\n if (value.results !== undefined && !Array.isArray(value.results)) {\n throw new Error('OpenAI Codex returned a search response with non-array results');\n }\n\n const items: SearchResult['items'] = [];\n const seen = new Set<string>();\n for (const item of value.results ?? []) {\n if (!isRecord(item) || item.type !== 'text_result') continue;\n const url = citeableUrl(item.url);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n items.push({\n title: readNonEmptyString(item.title) ?? url,\n url,\n snippet: readNonEmptyString(item.snippet) ?? '',\n });\n }\n\n const summary = value.output;\n return {\n status: summary.length > 0 ? 'ok' : 'degraded',\n source: 'OpenAI Codex',\n summary: summary || `Codex returned ${items.length} source(s) without summary text`,\n items,\n uncertainty: summary.length > 0 ? [] : ['Codex returned no summary text'],\n };\n}\n\nfunction parseStaticCredential(value: unknown): StaticCodexCredential | undefined {\n if (!isRecord(value)) return undefined;\n\n const candidates = [\n value,\n isRecord(value.credential) ? value.credential : undefined,\n isRecord(value.tokens) ? value.tokens : undefined,\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n const accessToken = readNonEmptyString(candidate.access_token)\n ?? readNonEmptyString(candidate.access);\n if (!accessToken) continue;\n\n const accountId = readNonEmptyString(candidate.account_id)\n ?? readNonEmptyString(candidate.accountId)\n ?? accountIdFromToken(accessToken);\n if (!accountId) continue;\n\n return { accessToken, accountId };\n }\n\n return undefined;\n}\n\nfunction decodeJwtPayload(accessToken: string): Record<string, unknown> | undefined {\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload: unknown = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));\n return isRecord(payload) ? payload : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction accountIdFromToken(accessToken: string): string | undefined {\n const auth = decodeJwtPayload(accessToken)?.['https://api.openai.com/auth'];\n return isRecord(auth) ? readNonEmptyString(auth.chatgpt_account_id) : undefined;\n}\n\nclass ResponseTooLargeError extends Error {\n constructor(limit: number) {\n super(`OpenAI Codex search response exceeded ${limit} bytes`);\n this.name = 'ResponseTooLargeError';\n }\n}\n\nasync function readBoundedJson(\n response: Response,\n maxBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel().catch(() => {});\n throw new ResponseTooLargeError(maxBytes);\n }\n if (!response.body) throw new SyntaxError('empty response body');\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await abortable(reader.read(), signal);\n if (done) break;\n if (!value) continue;\n total += value.byteLength;\n if (total > maxBytes) throw new ResponseTooLargeError(maxBytes);\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => {});\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n return JSON.parse(Buffer.concat(chunks, total).toString('utf8')) as unknown;\n}\n\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortedError(signal));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortedError(signal));\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw abortedError(signal);\n}\n\nfunction abortedError(signal: AbortSignal): Error {\n return new Error('OpenAI Codex search aborted', { cause: signal.reason });\n}\n\nfunction providerMessage(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined;\n const raw = typeof value.error === 'string'\n ? value.error\n : isRecord(value.error) && typeof value.error.message === 'string'\n ? value.error.message\n : typeof value.message === 'string'\n ? value.message\n : undefined;\n return raw\n ?.replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu, '[REDACTED]')\n .slice(0, 1_000);\n}\n\nfunction citeableUrl(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction readNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;\n}\n\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\n\nexport class DemoProvider implements SearchProvider {\n readonly id = 'demo';\n readonly name = 'Demo Provider';\n readonly description = 'Returns canned results for integration testing without calling any external API.';\n readonly searchable = true;\n readonly fetchable = true;\n\n constructor(_config: ResolvedProviderConfig) {}\n\n available(): boolean {\n return true;\n }\n\n async search(request: SearchRequest): Promise<SearchResult> {\n return {\n status: 'ok',\n source: this.name,\n summary: `Demo search results for \"${request.query}\"`,\n items: [\n {\n title: 'Demo Result 1',\n url: 'https://example.com/demo1',\n snippet: 'This is a demo search result used to verify the DSH plugin wiring.',\n },\n {\n title: 'Demo Result 2',\n url: 'https://example.com/demo2',\n snippet: 'Another canned result so citation cards have something to render.',\n },\n ],\n uncertainty: ['Demo mode: results are not from a real search engine.'],\n };\n }\n\n async fetch(request: FetchRequest): Promise<FetchResult> {\n return {\n summary: `Demo fetch of ${request.url}`,\n content: `# Demo Page\\n\\nThis is canned content for the URL \"${request.url}\".`,\n links: [\n { text: 'Example', url: 'https://example.com' },\n ],\n uncertainty: ['Demo mode: content is not from a real web page.'],\n warnings: [],\n };\n }\n}","export const fetchSchema = {\n type: 'object' as const,\n properties: {\n summary: { type: 'string' as const },\n content: { type: 'string' as const },\n links: {\n type: 'array' as const,\n items: {\n type: 'object' as const,\n properties: {\n text: { type: 'string' as const },\n url: { type: 'string' as const }\n },\n required: ['text', 'url'] as const\n }\n },\n uncertainty: {\n type: 'array' as const,\n items: { type: 'string' as const }\n },\n warnings: {\n type: 'array' as const,\n items: { type: 'string' as const }\n }\n },\n required: ['summary', 'content', 'uncertainty'] as const\n};\n","import type { ProviderRegistry } from '../providers/registry.js';\n\nexport const FALLBACK_ATTEMPT_TIMEOUT_MS = 20_000;\n\nexport interface AttemptContext {\n signal: AbortSignal;\n timedOut(): boolean;\n dispose(): void;\n}\n\nexport function resolveCandidateIds(\n registry: ProviderRegistry,\n fallbackChain?: string[],\n): string[] {\n let activeId: string | undefined;\n try {\n activeId = registry.getActive().id;\n } catch {\n // An empty registry has no active provider.\n }\n\n const configured = fallbackChain === undefined ? registry.list() : fallbackChain;\n const seen = new Set<string>();\n const candidates: string[] = [];\n for (const id of activeId ? [activeId, ...configured] : configured) {\n if (seen.has(id)) continue;\n seen.add(id);\n candidates.push(id);\n }\n return candidates;\n}\n\nexport function createAttemptContext(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): AttemptContext {\n const controller = new AbortController();\n let didTimeOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onParentAbort: (() => void) | undefined;\n\n if (parent?.aborted) {\n controller.abort(parent.reason);\n } else {\n if (parent) {\n onParentAbort = () => controller.abort(parent.reason);\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n timer = setTimeout(() => {\n didTimeOut = true;\n controller.abort(new Error(`fallback attempt timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n timedOut: () => didTimeOut,\n dispose() {\n if (timer !== undefined) clearTimeout(timer);\n if (parent && onParentAbort) parent.removeEventListener('abort', onParentAbort);\n },\n };\n}\n\nexport function raceWithSignal<T>(operation: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return operation;\n if (signal.aborted) {\n operation.catch(() => {});\n return Promise.reject(signal.reason);\n }\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw signal.reason;\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { FetchRequest, FetchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport { fetchSchema } from '../schemas/fetch-schema.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\nconst READ_PAGE_TIMEOUT_MS = 180_000;\nconst RENDER_CONTENT_CAP = 20_000;\nconst RENDER_LINK_CAP = 20;\n\nexport function registerReadPageTool(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.tools?.register !== 'function') {\n console.error('[search-providers] ctx.tools.register is not available; read_page tool skipped');\n return;\n }\n\n ctx.tools.register({\n name: 'read_page',\n description:\n 'Read one web page through the configured search provider. Use when a message references a specific http(s) URL whose content matters: docs, an article, a changelog, a thread. Returns structured evidence with a summary, the extracted content, outgoing links, uncertainty, and operational warnings. Pass \"query\" to focus the reading on one question.',\n parameters: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The http(s) URL to read',\n },\n query: {\n type: 'string',\n description: 'Optional question to focus the reading on (e.g. \"what are the rate limits\")',\n },\n },\n required: ['url'],\n },\n output: {\n schema: fetchSchema,\n render: (_args: unknown, value: Record<string, unknown>) => [\n { type: 'text', text: renderFetchEvidence(value) },\n ],\n },\n timeoutMs: READ_PAGE_TIMEOUT_MS + 20_000,\n isConcurrencySafe: () => true,\n presentCall: (args: { url: string; query?: string }) => ({\n card: 'generic',\n title: 'read_page',\n kind: 'fetch',\n rawInput: args,\n }),\n async execute(args: { url: string; query?: string }, exec: { signal?: AbortSignal }) {\n if (typeof args?.url !== 'string' || !/^https?:\\/\\//i.test(args.url.trim())) {\n throw new Error('read_page needs an http(s) \"url\".');\n }\n\n const request: FetchRequest = {\n url: args.url.trim(),\n query: args.query,\n format: 'markdown',\n };\n\n return fetchWithFallback(registry, cooldown, fallbackChain, request, exec.signal);\n },\n });\n}\n\nfunction resolveFetchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n}\n\nasync function fetchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: FetchRequest,\n signal: AbortSignal | undefined,\n): Promise<FetchResult> {\n throwIfAborted(signal);\n const candidates = resolveFetchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'fetch');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available()\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.fetch!(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n if (attempted.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Fallback used after ${attempted.join(', ')} failed`);\n }\n if (cooling.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Cooling providers skipped: ${cooling.join(', ')}`);\n }\n if (disabled.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Providers disabled until restart: ${disabled.join(', ')}`);\n }\n return result;\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' fetch failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'fetch', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No fetch provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderFetchEvidence(value: Record<string, unknown>): string {\n const lines: string[] = [String(value.summary ?? '')];\n const content = String((value.content as string) ?? '').trim();\n if (content) {\n lines.push('', 'Content:', content.length > RENDER_CONTENT_CAP ? `${content.slice(0, RENDER_CONTENT_CAP)}…` : content);\n }\n const links = Array.isArray(value.links) ? value.links.slice(0, RENDER_LINK_CAP) : [];\n if (links.length > 0) {\n lines.push('', 'Links:');\n for (const link of links) {\n const text = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).text ?? '') : '';\n const url = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).url ?? '') : '';\n lines.push(`- ${text} — ${url}`);\n }\n }\n const uncertainty = Array.isArray(value.uncertainty) ? value.uncertainty : [];\n if (uncertainty.length > 0) {\n lines.push('', `Uncertain: ${uncertainty.join('; ')}`);\n }\n const warnings = Array.isArray(value.warnings) ? value.warnings : [];\n if (warnings.length > 0) {\n lines.push('', `Warnings: ${warnings.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { SearchRequest, SearchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\ninterface WebSeamSearchRequest {\n query: string;\n maxResults?: number;\n recencyDays?: number;\n purpose?: string;\n location?: string;\n language?: string;\n}\n\nexport function registerWebSearchProvider(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.web?.registerSearchProvider !== 'function') {\n console.error('[search-providers] ctx.web.registerSearchProvider is not available; web_search provider skipped');\n return;\n }\n\n ctx.web.registerSearchProvider({\n id: 'search-providers',\n available: () => anyProviderAvailable(registry, cooldown, fallbackChain),\n async search(request: WebSeamSearchRequest, signal: AbortSignal | undefined) {\n const searchRequest: SearchRequest = {\n query: request.query,\n maxResults: request.maxResults,\n recencyDays: request.recencyDays,\n purpose: request.purpose,\n location: request.location,\n language: request.language,\n };\n\n const { result, attempted, cooling, disabled } = await searchWithFallback(\n registry,\n cooldown,\n fallbackChain,\n searchRequest,\n signal,\n );\n\n return {\n content: renderContent(result, attempted, cooling, disabled),\n sources: result.items.map((item) => ({\n url: item.url,\n ...(item.title ? { title: item.title } : {}),\n ...(item.snippet ? { snippet: item.snippet } : {}),\n ...(item.publishedAt ? { publishedAt: item.publishedAt } : {}),\n })),\n truncated: false,\n };\n },\n });\n}\n\nfunction resolveSearchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined && provider.searchable !== false;\n });\n}\n\nfunction anyProviderAvailable(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n): boolean {\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const { ready } = cooldown.filter(candidates, 'search');\n return ready.some((id) => {\n const provider = registry.get(id);\n return provider?.available() && provider.searchable !== false;\n });\n}\n\ninterface FallbackOutcome {\n result: SearchResult;\n attempted: string[];\n cooling: string[];\n disabled: string[];\n}\n\nasync function searchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: SearchRequest,\n signal: AbortSignal | undefined,\n): Promise<FallbackOutcome> {\n throwIfAborted(signal);\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'search');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available() && provider.searchable !== false;\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.search(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n return { result, attempted, cooling, disabled };\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' search failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'search', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No search provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderContent(\n result: SearchResult,\n attempted: string[],\n cooling: string[],\n disabled: string[],\n): string {\n const lines = [`[Search provider: ${result.source}]`, result.summary];\n if (attempted.length > 0) {\n lines.push(`[Fallback used after ${attempted.join(', ')} failed]`);\n }\n if (cooling.length > 0) {\n lines.push(`[Cooling: ${cooling.join(', ')}]`);\n }\n if (disabled.length > 0) {\n lines.push(`[Disabled until restart: ${disabled.join(', ')}]`);\n }\n if (result.uncertainty.length > 0) {\n lines.push(`Uncertain: ${result.uncertainty.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import { resolveProviderConfig } from './config.js';\nimport { CooldownController } from './providers/cooldown.js';\nimport { ProviderRegistry } from './providers/registry.js';\nimport { TinyfishProvider } from './providers/tinyfish.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { ExaProvider } from './providers/exa.js';\nimport { FirecrawlProvider } from './providers/firecrawl.js';\nimport { CodexProvider } from './providers/codex.js';\nimport { DemoProvider } from './providers/demo.js';\nimport type { PluginConfig } from './config.js';\nimport { registerReadPageTool } from './tools/read-page.js';\nimport { registerWebSearchProvider } from './tools/web-search.js';\n\nexport const name = 'search-providers';\nexport const inject = ['tools', 'web'];\n\nconst DEFAULT_PROVIDER_ORDER = ['codex', 'tinyfish', 'tavily', 'exa', 'firecrawl'];\n\nexport function apply(ctx: any, rawConfig: PluginConfig = {}): void {\n const registry = new ProviderRegistry();\n const cooldown = new CooldownController();\n const demoMode = rawConfig.provider === 'demo' || process.env.SEARCH_PROVIDERS_DEMO === '1';\n\n // Register providers in priority order.\n registry.register(new CodexProvider(resolveProviderConfig(rawConfig, 'codex', 'CODEX')));\n registry.register(new TinyfishProvider(resolveProviderConfig(rawConfig, 'tinyfish', 'TINYFISH')));\n registry.register(new TavilyProvider(resolveProviderConfig(rawConfig, 'tavily', 'TAVILY')));\n registry.register(new ExaProvider(resolveProviderConfig(rawConfig, 'exa', 'EXA')));\n registry.register(new FirecrawlProvider(resolveProviderConfig(rawConfig, 'firecrawl', 'FIRECRAWL')));\n\n if (demoMode) {\n registry.register(new DemoProvider(resolveProviderConfig(rawConfig, 'tinyfish', 'TINYFISH')));\n }\n\n // Determine active provider.\n const activeProvider = rawConfig.provider ?? (demoMode ? 'demo' : undefined);\n if (activeProvider) {\n try {\n registry.setActive(activeProvider);\n } catch (error) {\n console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);\n }\n }\n\n // The active provider is always tried first. An explicit empty chain disables fallback.\n const fallbackChain = rawConfig.fallbackChain ?? DEFAULT_PROVIDER_ORDER;\n\n // Register tools.\n registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);\n registerReadPageTool(ctx, registry, cooldown, fallbackChain);\n\n console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(', ')}]`);\n}"],"names":["throwIfAborted","resolve","provider","cooling","disabled"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/config.ts","../src/providers/utils.ts","../src/providers/cooldown.ts","../src/providers/registry.ts","../src/providers/tinyfish.ts","../src/providers/tavily.ts","../src/providers/exa.ts","../src/providers/firecrawl.ts","../src/providers/codex.ts","../src/providers/demo.ts","../src/schemas/fetch-schema.ts","../src/tools/fallback-utils.ts","../src/tools/read-page.ts","../src/tools/web-search.ts","../src/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\n\nexport interface ProviderConfigEntry {\n apiKey?: string;\n credentialFile?: string;\n timeoutMs?: number;\n options?: Record<string, unknown>;\n}\n\n/** Cordis only selects the file; provider settings live in that file. */\nexport interface PluginConfig {\n configFile?: string;\n}\n\nexport interface SearchProvidersConfig {\n /** Active provider id. */\n provider?: string;\n providers?: Record<string, ProviderConfigEntry>;\n /** An empty array disables fallback. */\n fallbackChain?: string[];\n}\n\nexport interface ResolvedProviderConfig {\n apiKey?: string;\n credentialFile?: string;\n timeoutMs: number;\n options: Record<string, unknown>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 55_000;\n\nexport function loadPluginConfig(pluginConfig: PluginConfig = {}): SearchProvidersConfig {\n if (!isRecord(pluginConfig) || Object.keys(pluginConfig).some((key) => key !== 'configFile')) {\n throw new Error('search-providers: Cordis config only accepts configFile. Move provider, providers and fallbackChain to search-providers.json.');\n }\n if (pluginConfig.configFile !== undefined && !isNonEmptyString(pluginConfig.configFile)) {\n throw new Error('search-providers: configFile must be a non-empty string.');\n }\n\n const explicit = pluginConfig.configFile !== undefined;\n const filename = resolveConfigPath(pluginConfig.configFile ?? join(homedir(), '.dsh', 'search-providers.json'));\n let text: string;\n try {\n text = readFileSync(filename, 'utf8');\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (!explicit && code === 'ENOENT') {\n console.warn(\n '[search-providers] Config file not found: ' + filename + '. Create it to configure providers; environment variables are not used.',\n );\n return {};\n }\n throw new Error('search-providers: Cannot read config file ' + filename + ' (' + (code ?? 'read error') + ').');\n }\n\n let value: unknown;\n try {\n // Accept a UTF-8 BOM, including files written by Windows editors.\n value = JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);\n } catch {\n // JSON parser messages can contain secrets from the source; do not forward them.\n throw new Error('search-providers: Invalid JSON in ' + filename + '.');\n }\n validateConfig(value, filename);\n for (const entry of Object.values(value.providers ?? {})) {\n if (entry.credentialFile !== undefined) {\n entry.credentialFile = resolveConfigPath(entry.credentialFile, dirname(filename));\n }\n }\n return value;\n}\n\nexport function resolveProviderConfig(\n pluginConfig: SearchProvidersConfig,\n providerId: string,\n): ResolvedProviderConfig {\n const pc = pluginConfig.providers?.[providerId] ?? {};\n return {\n apiKey: pc.apiKey,\n credentialFile: pc.credentialFile,\n timeoutMs: pc.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n options: pc.options ?? {},\n };\n}\n\nfunction resolveConfigPath(filename: string, base = process.cwd()): string {\n const path = filename.trim();\n if (path === '~') return homedir();\n if (path.startsWith('~/') || path.startsWith('~\\\\')) {\n return resolve(homedir(), path.slice(2));\n }\n return resolve(base, path);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction validateConfig(value: unknown, filename: string): asserts value is SearchProvidersConfig {\n function invalid(field: string): never {\n throw new Error('search-providers: Invalid config in ' + filename + ': ' + field + '.');\n }\n if (!isRecord(value)) invalid('expected a JSON object');\n // No values are interpolated into validation errors (they may be credentials).\n if (Object.keys(value).some((key) => !['provider', 'providers', 'fallbackChain'].includes(key))) {\n invalid('unknown top-level field; use provider, providers or fallbackChain');\n }\n if (value.provider !== undefined && !isNonEmptyString(value.provider)) {\n invalid('provider must be a non-empty string');\n }\n if (value.fallbackChain !== undefined &&\n (!Array.isArray(value.fallbackChain) || !value.fallbackChain.every(isNonEmptyString))) {\n invalid('fallbackChain must be an array of non-empty strings');\n }\n if (value.providers === undefined) return;\n if (!isRecord(value.providers)) invalid('providers must be an object');\n for (const entry of Object.values(value.providers)) {\n if (!isRecord(entry)) invalid('each provider must be an object');\n if (Object.keys(entry).some((key) => !['apiKey', 'credentialFile', 'timeoutMs', 'options'].includes(key))) {\n invalid('unknown provider field; use apiKey, credentialFile, timeoutMs or options (official service URLs are built in)');\n }\n if (entry.apiKey !== undefined && typeof entry.apiKey !== 'string') invalid('apiKey must be a string');\n if (entry.credentialFile !== undefined && !isNonEmptyString(entry.credentialFile)) {\n invalid('credentialFile must be a non-empty string');\n }\n if (entry.timeoutMs !== undefined &&\n (typeof entry.timeoutMs !== 'number' || !Number.isSafeInteger(entry.timeoutMs) || entry.timeoutMs <= 0 || entry.timeoutMs > 2_147_483_647)) {\n invalid('timeoutMs must be a positive integer no greater than 2147483647');\n }\n if (entry.options !== undefined) {\n if (!isRecord(entry.options)) invalid('options must be an object');\n if (Object.hasOwn(entry.options, 'fetchBaseUrl')) {\n invalid('remove options.fetchBaseUrl; official service URLs are built in');\n }\n }\n }\n}\n","export class ProviderAuthenticationError extends Error {\n readonly kind = 'provider-auth' as const;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProviderAuthenticationError';\n }\n}\n\nexport class ProviderHttpError extends Error {\n readonly kind = 'provider-http' as const;\n\n constructor(\n message: string,\n readonly status: number,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'ProviderHttpError';\n }\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n try {\n const signal = init.signal\n ? combineSignals(init.signal, controller.signal)\n : controller.signal;\n return await fetch(url, { ...init, signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function combineSignals(a: AbortSignal, b: AbortSignal): AbortSignal {\n return AbortSignal.any([a, b]);\n}\n\nexport async function throwOnError(response: Response, label: string): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => '');\n let detail = text.slice(0, 500);\n if (response.status === 401) {\n detail = 'Invalid or missing API key';\n } else if (response.status === 429) {\n detail = 'Rate limit exceeded';\n } else if (response.status === 432) {\n detail = 'Usage limit exceeded';\n }\n const message = `${label} returned ${response.status}: ${detail}`;\n if (response.status === 401) {\n throw new ProviderAuthenticationError(message);\n }\n throw new ProviderHttpError(message, response.status);\n}\n","import { ProviderAuthenticationError, ProviderHttpError } from './utils.js';\n\nexport type ProviderCapability = 'search' | 'fetch';\n\nexport interface CooldownEntry {\n until: number;\n reason: string;\n}\n\nexport class CooldownController {\n private disabled = new Map<string, CooldownEntry>();\n private cooling = new Map<string, CooldownEntry>();\n\n /** Record one capability failure. Confirmed authentication failures are provider-global. */\n record(providerId: string, capability: ProviderCapability, error: unknown): boolean {\n if (error instanceof ProviderAuthenticationError) {\n this.disabled.set(providerId, {\n until: Number.POSITIVE_INFINITY,\n reason: 'invalid credentials',\n });\n return true;\n }\n\n this.cooling.set(this.capabilityKey(providerId, capability), {\n until: Date.now() + this.cooldownDuration(error),\n reason: this.classifyError(error),\n });\n return true;\n }\n\n /** Check whether one provider capability is temporarily cooling. */\n isCooling(providerId: string, capability: ProviderCapability): boolean {\n return this.getEntry(this.cooling, this.capabilityKey(providerId, capability)) !== undefined;\n }\n\n /** Check whether confirmed authentication failure disabled the provider until restart. */\n isDisabled(providerId: string): boolean {\n return this.getEntry(this.disabled, providerId) !== undefined;\n }\n\n /** Get the current failure reason, if any. */\n getReason(providerId: string, capability: ProviderCapability): string | undefined {\n return this.getEntry(this.disabled, providerId)?.reason\n ?? this.getEntry(this.cooling, this.capabilityKey(providerId, capability))?.reason;\n }\n\n /** Build candidates while separating global disablement from capability cooldown. */\n filter(\n chain: string[],\n capability: ProviderCapability,\n ): { ready: string[]; cooling: string[]; disabled: string[] } {\n const ready: string[] = [];\n const cooling: string[] = [];\n const disabled: string[] = [];\n for (const id of chain) {\n if (this.isDisabled(id)) {\n disabled.push(id);\n } else if (this.isCooling(id, capability)) {\n cooling.push(id);\n } else {\n ready.push(id);\n }\n }\n return { ready, cooling, disabled };\n }\n\n private capabilityKey(providerId: string, capability: ProviderCapability): string {\n return `${providerId}:${capability}`;\n }\n\n private getEntry(map: Map<string, CooldownEntry>, key: string): CooldownEntry | undefined {\n const entry = map.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.until) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n private classifyError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (this.isRateLimitError(error, message)) return 'rate limit or quota exceeded';\n if (/timeout|timed out/i.test(message)) return 'timeout';\n if (/network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) return 'network error';\n return 'request failed';\n }\n\n private cooldownDuration(error: unknown): number {\n const message = error instanceof Error ? error.message : String(error);\n // Rate limits / quotas: cool for 5 minutes.\n if (this.isRateLimitError(error, message)) return 5 * 60 * 1000;\n // Timeouts / network errors: cool for 30 seconds.\n if (/timeout|timed out|network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) {\n return 30 * 1000;\n }\n // Generic failures: cool for 10 seconds.\n return 10 * 1000;\n }\n\n private isRateLimitError(error: unknown, message: string): boolean {\n return (\n (error instanceof ProviderHttpError && (error.status === 429 || error.status === 432))\n || /429|432|rate.?limit|quota|usage limit|out of credits|monthly quota/i.test(message)\n );\n }\n}\n","import type { SearchProvider } from './types.js';\n\nexport class ProviderRegistry {\n private providers = new Map<string, SearchProvider>();\n private activeId: string | undefined;\n\n register(provider: SearchProvider): void {\n if (this.providers.has(provider.id)) {\n console.warn(`[search-providers] provider '${provider.id}' already registered; overwriting`);\n }\n this.providers.set(provider.id, provider);\n if (this.activeId === undefined) {\n this.activeId = provider.id;\n }\n }\n\n setActive(id: string): void {\n if (!this.providers.has(id)) {\n throw new Error(`Provider '${id}' is not registered`);\n }\n this.activeId = id;\n }\n\n getActive(): SearchProvider {\n if (this.activeId === undefined) {\n throw new Error('No search provider registered');\n }\n const provider = this.providers.get(this.activeId);\n if (!provider) {\n throw new Error(`Active provider '${this.activeId}' disappeared`);\n }\n return provider;\n }\n\n get(id: string): SearchProvider | undefined {\n return this.providers.get(id);\n }\n\n list(): string[] {\n return Array.from(this.providers.keys());\n }\n}\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type {\n FetchRequest,\n FetchResult,\n SearchProvider,\n SearchRequest,\n SearchResult,\n} from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TinyfishSearchResponse {\n query: string;\n results: Array<{\n position: number;\n site_name?: string;\n title: string;\n snippet: string;\n url: string;\n }>;\n total_results: number;\n page: number;\n}\n\ninterface TinyfishFetchResponse {\n results: Array<{\n url: string;\n final_url?: string;\n title?: string;\n description?: string;\n language?: string;\n format?: string;\n text: string;\n }>;\n errors: Array<{ url: string; error: string }>;\n}\n\nexport class TinyfishProvider implements SearchProvider {\n readonly id = 'tinyfish';\n readonly name = 'TinyFish';\n readonly description = 'TinyFish Search & Fetch (free tier, live web results)';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly searchBaseUrl = 'https://api.search.tinyfish.ai';\n private readonly fetchBaseUrl = 'https://api.fetch.tinyfish.ai';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const url = new URL(this.searchBaseUrl);\n url.searchParams.set('query', request.query);\n if (request.maxResults && request.maxResults > 0) {\n url.searchParams.set('max_results', String(Math.floor(request.maxResults)));\n }\n if (typeof request.recencyDays === 'number' && request.recencyDays > 0) {\n url.searchParams.set('recency_minutes', String(Math.floor(request.recencyDays * 24 * 60)));\n }\n if (request.purpose) {\n url.searchParams.set('purpose', request.purpose);\n }\n if (request.location) {\n url.searchParams.set('location', request.location);\n }\n if (request.language) {\n url.searchParams.set('language', request.language);\n }\n if (request.domainType) {\n url.searchParams.set('domain_type', request.domainType);\n }\n if (request.includeDomains && request.includeDomains.length > 0) {\n url.searchParams.set('include_domains', request.includeDomains.join(','));\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n url.searchParams.set('exclude_domains', request.excludeDomains.join(','));\n }\n\n const response = await fetchWithTimeout(url.toString(), {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey!,\n Accept: 'application/json',\n },\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Search');\n\n const body = (await response.json()) as TinyfishSearchResponse;\n const items = (body.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.snippet,\n source: r.site_name,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${body.total_results ?? items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n urls: [request.url],\n format: request.format ?? 'markdown',\n };\n if (request.query) {\n body.purpose = request.query;\n }\n if (typeof request.ttl === 'number') {\n body.ttl = request.ttl;\n }\n\n const response = await fetchWithTimeout(this.fetchBaseUrl, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Fetch');\n\n const data = (await response.json()) as TinyfishFetchResponse;\n const page = data.results?.[0];\n const error = data.errors?.[0];\n\n if (!page && error) {\n throw new Error(`TinyFish Fetch failed for ${error.url}: ${error.error}`);\n }\n if (!page) {\n throw new Error('TinyFish Fetch returned no content');\n }\n\n return {\n summary: page.title ?? page.description ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error(\n 'TinyFish API key is missing. Configure providers.tinyfish.apiKey in the search-providers JSON config file.',\n );\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TavilySearchResponse {\n query: string;\n results: Array<{\n title: string;\n url: string;\n content?: string;\n raw_content?: string;\n score?: number;\n }>;\n}\n\nexport class TavilyProvider implements SearchProvider {\n readonly id = 'tavily';\n readonly name = 'Tavily';\n readonly description = 'Tavily web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.tavily.com';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n search_depth: 'basic',\n max_results: request.maxResults ?? 5,\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Tavily Search');\n\n const data = (await response.json()) as TavilySearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.content ?? r.raw_content ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Tavily API key is missing. Configure providers.tavily.apiKey in the search-providers JSON config file.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface ExaSearchResponse {\n results: Array<{\n title?: string;\n url: string;\n publishedDate?: string;\n author?: string;\n text?: string;\n score?: number;\n }>;\n}\n\ninterface ExaContentsResponse {\n results: Array<{\n url: string;\n text?: string;\n title?: string;\n }>;\n}\n\nexport class ExaProvider implements SearchProvider {\n readonly id = 'exa';\n readonly name = 'Exa';\n readonly description = 'Exa neural web search and contents fetch';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.exa.ai';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n num_results: request.maxResults ?? 5,\n type: 'auto',\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Search');\n\n const data = (await response.json()) as ExaSearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.text ?? '',\n publishedAt: r.publishedDate,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n urls: [request.url],\n text: true,\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/contents`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Contents');\n\n const data = (await response.json()) as ExaContentsResponse;\n const page = data.results?.[0];\n if (!page) {\n throw new Error('Exa Contents returned no content');\n }\n\n return {\n summary: page.title ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Exa API key is missing. Configure providers.exa.apiKey in the search-providers JSON config file.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface FirecrawlSearchResponse {\n success: boolean;\n data: {\n web: Array<{\n title?: string;\n url: string;\n description?: string;\n markdown?: string;\n }>;\n };\n}\n\ninterface FirecrawlScrapeResponse {\n success: boolean;\n data: {\n markdown?: string;\n metadata?: {\n title?: string;\n sourceURL?: string;\n };\n };\n}\n\nexport class FirecrawlProvider implements SearchProvider {\n readonly id = 'firecrawl';\n readonly name = 'Firecrawl';\n readonly description = 'Firecrawl web search and page scrape';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.firecrawl.dev';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n limit: request.maxResults ?? 5,\n sources: ['web'],\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.includeDomains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.excludeDomains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Search');\n\n const data = (await response.json()) as FirecrawlSearchResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Search returned success: false');\n }\n\n const rawItems = data.data?.web ?? [];\n const items = rawItems.map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.description ?? r.markdown ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n url: request.url,\n formats: ['markdown'],\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/scrape`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Scrape');\n\n const data = (await response.json()) as FirecrawlScrapeResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Scrape returned success: false');\n }\n\n return {\n summary: data.data?.metadata?.title ?? 'Fetched page content',\n content: data.data?.markdown ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Firecrawl API key is missing. Configure providers.firecrawl.apiKey in the search-providers JSON config file.');\n }\n }\n}","// Codex search protocol behavior is based on dsh-codex-connect (Apache-2.0).\n// This provider only consumes an existing static credential; it never logs in,\n// refreshes tokens, or writes credential data.\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { ProviderAuthenticationError } from './utils.js';\n\nconst CODEX_SEARCH_URL = 'https://chatgpt.com/backend-api/codex/alpha/search';\nconst MAX_CREDENTIAL_BYTES = 128 * 1024;\nconst MAX_SUCCESS_RESPONSE_BYTES = 4 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\n\ninterface StaticCodexCredential {\n accessToken: string;\n accountId: string;\n}\n\ninterface CodexSearchRequestBody {\n id: string;\n model: string;\n input: Array<{\n type: 'message';\n role: 'user';\n content: Array<{ type: 'input_text'; text: string }>;\n }>;\n commands: { search_query: Array<{ q: string }> };\n settings: {\n search_context_size: 'medium';\n allowed_callers: ['direct'];\n external_web_access: false;\n };\n max_output_tokens: number;\n}\n\nexport class CodexProvider implements SearchProvider {\n readonly id = 'codex';\n readonly name = 'OpenAI Codex';\n readonly description = 'ChatGPT Codex standalone web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly credentialFile?: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n const filename = readNonEmptyString(config.credentialFile);\n this.credentialFile = filename === undefined ? undefined : resolve(filename);\n this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0\n ? config.timeoutMs\n : 55_000;\n }\n\n available(): boolean {\n return this.credentialFile !== undefined\n && loadStaticCredential(this.credentialFile) !== undefined;\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n throwIfAborted(signal);\n\n const credential = this.credentialFile === undefined\n ? undefined\n : loadStaticCredential(this.credentialFile);\n if (!credential) {\n throw new Error('OpenAI Codex credential file is not configured, missing or invalid');\n }\n\n const body: CodexSearchRequestBody = {\n id: randomUUID(),\n model: 'gpt-5.6-sol',\n input: [{\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: request.query }],\n }],\n commands: { search_query: [{ q: request.query }] },\n settings: {\n search_context_size: 'medium',\n allowed_callers: ['direct'],\n external_web_access: false,\n },\n max_output_tokens: 10_000,\n };\n\n const requestTimeout = AbortSignal.timeout(this.timeoutMs);\n const requestSignal = signal ? AbortSignal.any([signal, requestTimeout]) : requestTimeout;\n let response: Response;\n try {\n response = await fetch(CODEX_SEARCH_URL, {\n method: 'POST',\n redirect: 'error',\n headers: {\n Authorization: `Bearer ${credential.accessToken}`,\n 'chatgpt-account-id': credential.accountId,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n originator: 'deepseek-harness',\n },\n body: JSON.stringify(body),\n signal: requestSignal,\n });\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n throw new Error('OpenAI Codex search request failed', { cause: error });\n }\n\n let payload: unknown;\n try {\n payload = await readBoundedJson(\n response,\n response.ok ? MAX_SUCCESS_RESPONSE_BYTES : MAX_ERROR_RESPONSE_BYTES,\n requestSignal,\n );\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n if (error instanceof ResponseTooLargeError) throw error;\n throw new Error(`OpenAI Codex returned invalid JSON (HTTP ${response.status})`, { cause: error });\n }\n\n if (!response.ok) {\n const detail = providerMessage(payload);\n const suffix = detail ? `: ${detail}` : '';\n if (response.status === 401 || response.status === 403) {\n throw new ProviderAuthenticationError(\n `OpenAI Codex credential is invalid or expired (HTTP ${response.status})${suffix}`,\n );\n }\n throw new Error(`OpenAI Codex search failed (HTTP ${response.status})${suffix}`);\n }\n\n return mapCodexSearchResponse(payload);\n }\n}\n\nfunction loadStaticCredential(filename: string): StaticCodexCredential | undefined {\n try {\n const stats = statSync(filename);\n if (!stats.isFile() || stats.size <= 0 || stats.size > MAX_CREDENTIAL_BYTES) return undefined;\n const value: unknown = JSON.parse(readFileSync(filename, 'utf8'));\n return parseStaticCredential(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapCodexSearchResponse(value: unknown): SearchResult {\n if (!isRecord(value) || typeof value.output !== 'string') {\n throw new Error('OpenAI Codex returned a search response without string output');\n }\n if (value.results !== undefined && !Array.isArray(value.results)) {\n throw new Error('OpenAI Codex returned a search response with non-array results');\n }\n\n const items: SearchResult['items'] = [];\n const seen = new Set<string>();\n for (const item of value.results ?? []) {\n if (!isRecord(item) || item.type !== 'text_result') continue;\n const url = citeableUrl(item.url);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n items.push({\n title: readNonEmptyString(item.title) ?? url,\n url,\n snippet: readNonEmptyString(item.snippet) ?? '',\n });\n }\n\n const summary = value.output;\n return {\n status: summary.length > 0 ? 'ok' : 'degraded',\n source: 'OpenAI Codex',\n summary: summary || `Codex returned ${items.length} source(s) without summary text`,\n items,\n uncertainty: summary.length > 0 ? [] : ['Codex returned no summary text'],\n };\n}\n\nfunction parseStaticCredential(value: unknown): StaticCodexCredential | undefined {\n if (!isRecord(value)) return undefined;\n\n // Account stores keep tokens in credentials[], selected by activeAccountId.\n const stored = Array.isArray(value.credentials) ? value.credentials : [];\n const activeAccountId = readNonEmptyString(value.activeAccountId);\n const selected = activeAccountId\n ? stored.filter((entry) => isRecord(entry)\n && (readNonEmptyString(entry.account_id) ?? readNonEmptyString(entry.accountId)) === activeAccountId)\n : value.activeAccountId === undefined ? stored : [];\n const storedCredential = selected.length === 1 && isRecord(selected[0]) ? selected[0] : undefined;\n\n const candidates = [\n value,\n isRecord(value.credential) ? value.credential : undefined,\n isRecord(value.tokens) ? value.tokens : undefined,\n storedCredential,\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n const accessToken = readNonEmptyString(candidate.access_token)\n ?? readNonEmptyString(candidate.access);\n if (!accessToken) continue;\n\n const accountId = readNonEmptyString(candidate.account_id)\n ?? readNonEmptyString(candidate.accountId)\n ?? accountIdFromToken(accessToken);\n if (!accountId) continue;\n\n return { accessToken, accountId };\n }\n\n return undefined;\n}\n\nfunction decodeJwtPayload(accessToken: string): Record<string, unknown> | undefined {\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload: unknown = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));\n return isRecord(payload) ? payload : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction accountIdFromToken(accessToken: string): string | undefined {\n const auth = decodeJwtPayload(accessToken)?.['https://api.openai.com/auth'];\n return isRecord(auth) ? readNonEmptyString(auth.chatgpt_account_id) : undefined;\n}\n\nclass ResponseTooLargeError extends Error {\n constructor(limit: number) {\n super(`OpenAI Codex search response exceeded ${limit} bytes`);\n this.name = 'ResponseTooLargeError';\n }\n}\n\nasync function readBoundedJson(\n response: Response,\n maxBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel().catch(() => {});\n throw new ResponseTooLargeError(maxBytes);\n }\n if (!response.body) throw new SyntaxError('empty response body');\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await abortable(reader.read(), signal);\n if (done) break;\n if (!value) continue;\n total += value.byteLength;\n if (total > maxBytes) throw new ResponseTooLargeError(maxBytes);\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => {});\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n return JSON.parse(Buffer.concat(chunks, total).toString('utf8')) as unknown;\n}\n\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortedError(signal));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortedError(signal));\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw abortedError(signal);\n}\n\nfunction abortedError(signal: AbortSignal): Error {\n return new Error('OpenAI Codex search aborted', { cause: signal.reason });\n}\n\nfunction providerMessage(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined;\n const raw = typeof value.error === 'string'\n ? value.error\n : isRecord(value.error) && typeof value.error.message === 'string'\n ? value.error.message\n : typeof value.message === 'string'\n ? value.message\n : undefined;\n return raw\n ?.replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu, '[REDACTED]')\n .slice(0, 1_000);\n}\n\nfunction citeableUrl(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction readNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;\n}\n\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\n\nexport class DemoProvider implements SearchProvider {\n readonly id = 'demo';\n readonly name = 'Demo Provider';\n readonly description = 'Returns canned results for integration testing without calling any external API.';\n readonly searchable = true;\n readonly fetchable = true;\n\n constructor(_config: ResolvedProviderConfig) {}\n\n available(): boolean {\n return true;\n }\n\n async search(request: SearchRequest): Promise<SearchResult> {\n return {\n status: 'ok',\n source: this.name,\n summary: `Demo search results for \"${request.query}\"`,\n items: [\n {\n title: 'Demo Result 1',\n url: 'https://example.com/demo1',\n snippet: 'This is a demo search result used to verify the DSH plugin wiring.',\n },\n {\n title: 'Demo Result 2',\n url: 'https://example.com/demo2',\n snippet: 'Another canned result so citation cards have something to render.',\n },\n ],\n uncertainty: ['Demo mode: results are not from a real search engine.'],\n };\n }\n\n async fetch(request: FetchRequest): Promise<FetchResult> {\n return {\n summary: `Demo fetch of ${request.url}`,\n content: `# Demo Page\\n\\nThis is canned content for the URL \"${request.url}\".`,\n links: [\n { text: 'Example', url: 'https://example.com' },\n ],\n uncertainty: ['Demo mode: content is not from a real web page.'],\n warnings: [],\n };\n }\n}","export const fetchSchema = {\n type: 'object' as const,\n properties: {\n summary: { type: 'string' as const },\n content: { type: 'string' as const },\n links: {\n type: 'array' as const,\n items: {\n type: 'object' as const,\n properties: {\n text: { type: 'string' as const },\n url: { type: 'string' as const }\n },\n required: ['text', 'url'] as const\n }\n },\n uncertainty: {\n type: 'array' as const,\n items: { type: 'string' as const }\n },\n warnings: {\n type: 'array' as const,\n items: { type: 'string' as const }\n }\n },\n required: ['summary', 'content', 'uncertainty'] as const\n};\n","import type { ProviderRegistry } from '../providers/registry.js';\n\nexport const FALLBACK_ATTEMPT_TIMEOUT_MS = 55_000;\n\nexport interface AttemptContext {\n signal: AbortSignal;\n timedOut(): boolean;\n dispose(): void;\n}\n\nexport function resolveCandidateIds(\n registry: ProviderRegistry,\n fallbackChain?: string[],\n): string[] {\n let activeId: string | undefined;\n try {\n activeId = registry.getActive().id;\n } catch {\n // An empty registry has no active provider.\n }\n\n const configured = fallbackChain === undefined ? registry.list() : fallbackChain;\n const seen = new Set<string>();\n const candidates: string[] = [];\n for (const id of activeId ? [activeId, ...configured] : configured) {\n if (seen.has(id)) continue;\n seen.add(id);\n candidates.push(id);\n }\n return candidates;\n}\n\nexport function createAttemptContext(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): AttemptContext {\n const controller = new AbortController();\n let didTimeOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onParentAbort: (() => void) | undefined;\n\n if (parent?.aborted) {\n controller.abort(parent.reason);\n } else {\n if (parent) {\n onParentAbort = () => controller.abort(parent.reason);\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n timer = setTimeout(() => {\n didTimeOut = true;\n controller.abort(new Error(`fallback attempt timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n timedOut: () => didTimeOut,\n dispose() {\n if (timer !== undefined) clearTimeout(timer);\n if (parent && onParentAbort) parent.removeEventListener('abort', onParentAbort);\n },\n };\n}\n\nexport function raceWithSignal<T>(operation: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return operation;\n if (signal.aborted) {\n operation.catch(() => {});\n return Promise.reject(signal.reason);\n }\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw signal.reason;\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { FetchRequest, FetchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport { fetchSchema } from '../schemas/fetch-schema.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\nconst READ_PAGE_TIMEOUT_MS = 180_000;\nconst RENDER_CONTENT_CAP = 20_000;\nconst RENDER_LINK_CAP = 20;\n\nexport function registerReadPageTool(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.tools?.register !== 'function') {\n console.error('[search-providers] ctx.tools.register is not available; read_page tool skipped');\n return;\n }\n\n ctx.tools.register({\n name: 'read_page',\n description:\n 'Read one web page through the configured search provider. Use when a message references a specific http(s) URL whose content matters: docs, an article, a changelog, a thread. Returns structured evidence with a summary, the extracted content, outgoing links, uncertainty, and operational warnings. Pass \"query\" to focus the reading on one question.',\n parameters: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The http(s) URL to read',\n },\n query: {\n type: 'string',\n description: 'Optional question to focus the reading on (e.g. \"what are the rate limits\")',\n },\n },\n required: ['url'],\n },\n output: {\n schema: fetchSchema,\n render: (_args: unknown, value: Record<string, unknown>) => [\n { type: 'text', text: renderFetchEvidence(value) },\n ],\n },\n timeoutMs: READ_PAGE_TIMEOUT_MS + 20_000,\n isConcurrencySafe: () => true,\n presentCall: (args: { url: string; query?: string }) => ({\n card: 'generic',\n title: 'read_page',\n kind: 'fetch',\n rawInput: args,\n }),\n async execute(args: { url: string; query?: string }, exec: { signal?: AbortSignal }) {\n if (typeof args?.url !== 'string' || !/^https?:\\/\\//i.test(args.url.trim())) {\n throw new Error('read_page needs an http(s) \"url\".');\n }\n\n const request: FetchRequest = {\n url: args.url.trim(),\n query: args.query,\n format: 'markdown',\n };\n\n return fetchWithFallback(registry, cooldown, fallbackChain, request, exec.signal);\n },\n });\n}\n\nfunction resolveFetchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n}\n\nasync function fetchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: FetchRequest,\n signal: AbortSignal | undefined,\n): Promise<FetchResult> {\n throwIfAborted(signal);\n const candidates = resolveFetchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'fetch');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available()\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.fetch!(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n if (attempted.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Fallback used after ${attempted.join(', ')} failed`);\n }\n if (cooling.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Cooling providers skipped: ${cooling.join(', ')}`);\n }\n if (disabled.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Providers disabled until restart: ${disabled.join(', ')}`);\n }\n return result;\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' fetch failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'fetch', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No fetch provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderFetchEvidence(value: Record<string, unknown>): string {\n const lines: string[] = [String(value.summary ?? '')];\n const content = String((value.content as string) ?? '').trim();\n if (content) {\n lines.push('', 'Content:', content.length > RENDER_CONTENT_CAP ? `${content.slice(0, RENDER_CONTENT_CAP)}…` : content);\n }\n const links = Array.isArray(value.links) ? value.links.slice(0, RENDER_LINK_CAP) : [];\n if (links.length > 0) {\n lines.push('', 'Links:');\n for (const link of links) {\n const text = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).text ?? '') : '';\n const url = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).url ?? '') : '';\n lines.push(`- ${text} — ${url}`);\n }\n }\n const uncertainty = Array.isArray(value.uncertainty) ? value.uncertainty : [];\n if (uncertainty.length > 0) {\n lines.push('', `Uncertain: ${uncertainty.join('; ')}`);\n }\n const warnings = Array.isArray(value.warnings) ? value.warnings : [];\n if (warnings.length > 0) {\n lines.push('', `Warnings: ${warnings.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { SearchRequest, SearchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\ninterface WebSeamSearchRequest {\n query: string;\n maxResults?: number;\n recencyDays?: number;\n purpose?: string;\n location?: string;\n language?: string;\n}\n\nexport function registerWebSearchProvider(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.web?.registerSearchProvider !== 'function') {\n console.error('[search-providers] ctx.web.registerSearchProvider is not available; web_search provider skipped');\n return;\n }\n\n ctx.web.registerSearchProvider({\n id: 'search-providers',\n available: () => anyProviderAvailable(registry, cooldown, fallbackChain),\n async search(request: WebSeamSearchRequest, signal: AbortSignal | undefined) {\n const searchRequest: SearchRequest = {\n query: request.query,\n maxResults: request.maxResults,\n recencyDays: request.recencyDays,\n purpose: request.purpose,\n location: request.location,\n language: request.language,\n };\n\n const { result, attempted, cooling, disabled } = await searchWithFallback(\n registry,\n cooldown,\n fallbackChain,\n searchRequest,\n signal,\n );\n\n return {\n content: renderContent(result, attempted, cooling, disabled),\n sources: result.items.map((item) => ({\n url: item.url,\n ...(item.title ? { title: item.title } : {}),\n ...(item.snippet ? { snippet: item.snippet } : {}),\n ...(item.publishedAt ? { publishedAt: item.publishedAt } : {}),\n })),\n truncated: false,\n };\n },\n });\n}\n\nfunction resolveSearchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined && provider.searchable !== false;\n });\n}\n\nfunction anyProviderAvailable(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n): boolean {\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const { ready } = cooldown.filter(candidates, 'search');\n return ready.some((id) => {\n const provider = registry.get(id);\n return provider?.available() && provider.searchable !== false;\n });\n}\n\ninterface FallbackOutcome {\n result: SearchResult;\n attempted: string[];\n cooling: string[];\n disabled: string[];\n}\n\nasync function searchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: SearchRequest,\n signal: AbortSignal | undefined,\n): Promise<FallbackOutcome> {\n throwIfAborted(signal);\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'search');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available() && provider.searchable !== false;\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.search(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n return { result, attempted, cooling, disabled };\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' search failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'search', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No search provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderContent(\n result: SearchResult,\n attempted: string[],\n cooling: string[],\n disabled: string[],\n): string {\n const lines = [`[Search provider: ${result.source}]`, result.summary];\n if (attempted.length > 0) {\n lines.push(`[Fallback used after ${attempted.join(', ')} failed]`);\n }\n if (cooling.length > 0) {\n lines.push(`[Cooling: ${cooling.join(', ')}]`);\n }\n if (disabled.length > 0) {\n lines.push(`[Disabled until restart: ${disabled.join(', ')}]`);\n }\n if (result.uncertainty.length > 0) {\n lines.push(`Uncertain: ${result.uncertainty.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import { loadPluginConfig, resolveProviderConfig } from './config.js';\nimport { CooldownController } from './providers/cooldown.js';\nimport { ProviderRegistry } from './providers/registry.js';\nimport { TinyfishProvider } from './providers/tinyfish.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { ExaProvider } from './providers/exa.js';\nimport { FirecrawlProvider } from './providers/firecrawl.js';\nimport { CodexProvider } from './providers/codex.js';\nimport { DemoProvider } from './providers/demo.js';\nimport type { PluginConfig } from './config.js';\nimport { registerReadPageTool } from './tools/read-page.js';\nimport { registerWebSearchProvider } from './tools/web-search.js';\n\nexport const name = 'search-providers';\nexport const inject = ['tools', 'web'];\n\nconst DEFAULT_PROVIDER_ORDER = ['codex', 'tinyfish', 'tavily', 'exa', 'firecrawl'];\n\nexport function apply(ctx: any, rawConfig: PluginConfig = {}): void {\n const config = loadPluginConfig(rawConfig);\n const registry = new ProviderRegistry();\n const cooldown = new CooldownController();\n const demoMode = config.provider === 'demo';\n\n // Register providers in priority order.\n registry.register(new CodexProvider(resolveProviderConfig(config, 'codex')));\n registry.register(new TinyfishProvider(resolveProviderConfig(config, 'tinyfish')));\n registry.register(new TavilyProvider(resolveProviderConfig(config, 'tavily')));\n registry.register(new ExaProvider(resolveProviderConfig(config, 'exa')));\n registry.register(new FirecrawlProvider(resolveProviderConfig(config, 'firecrawl')));\n\n if (demoMode) {\n registry.register(new DemoProvider(resolveProviderConfig(config, 'tinyfish')));\n }\n\n // Determine active provider.\n const activeProvider = config.provider;\n if (activeProvider) {\n try {\n registry.setActive(activeProvider);\n } catch (error) {\n console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);\n }\n }\n\n // The active provider is always tried first. An explicit empty chain disables fallback.\n const fallbackChain = config.fallbackChain ?? DEFAULT_PROVIDER_ORDER;\n\n // Register tools.\n registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);\n registerReadPageTool(ctx, registry, cooldown, fallbackChain);\n\n console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(', ')}]`);\n}"],"names":["isRecord","throwIfAborted","resolve","provider","cooling","disabled"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mini-z/dsh-search-providers",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Modular DSH search plugin with Codex-first web search, page fetching, and capability-aware automatic failover.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -15,6 +15,7 @@
15
15
  "dsh",
16
16
  "cordis.patch.yml",
17
17
  "README.md",
18
+ "search-providers.example.json",
18
19
  "CHANGELOG.md"
19
20
  ],
20
21
  "keywords": [
@@ -0,0 +1,28 @@
1
+ {
2
+ "provider": "codex",
3
+ "fallbackChain": [
4
+ "tinyfish",
5
+ "tavily",
6
+ "exa",
7
+ "firecrawl"
8
+ ],
9
+ "providers": {
10
+ "codex": {
11
+ "credentialFile": "./codex-credential.json",
12
+ "timeoutMs": 55000
13
+ },
14
+ "tinyfish": {
15
+ "apiKey": "",
16
+ "timeoutMs": 55000
17
+ },
18
+ "tavily": {
19
+ "apiKey": ""
20
+ },
21
+ "exa": {
22
+ "apiKey": ""
23
+ },
24
+ "firecrawl": {
25
+ "apiKey": ""
26
+ }
27
+ }
28
+ }