@mini-z/dsh-search-providers 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Breaking changes
6
+
7
+ - 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.
8
+ - Configure Codex credentials with `providers.codex.credentialFile`; relative paths resolve from the JSON configuration directory.
9
+ - Use built-in official service endpoints. Remove provider `baseUrl` and TinyFish `options.fetchBaseUrl` from existing configuration.
10
+
11
+ ### Improvements
12
+
13
+ - Validate configuration at startup and report errors without echoing credential values.
14
+ - Include `search-providers.example.json` in the published package. See README for migration steps.
15
+ - Increase the per-fallback outer timeout from 20 seconds to 55 seconds for both search and page reading; aggregate tool deadlines are unchanged.
16
+ - Add configuration loading, official endpoint, and timeout regression tests.
package/README.md CHANGED
@@ -1,188 +1,259 @@
1
- # dsh-search-providers
1
+ # @mini-z/dsh-search-providers
2
2
 
3
- A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin that takes over `web_search` and `read_page` with an extensible provider registry and automatic failover.
3
+ [![npm version](https://img.shields.io/npm/v/@mini-z/dsh-search-providers.svg)](https://www.npmjs.com/package/@mini-z/dsh-search-providers)
4
+ [![license](https://img.shields.io/npm/l/@mini-z/dsh-search-providers.svg)](./package.json)
5
+
6
+ A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin that provides `web_search` and `read_page` through multiple search services with capability-aware fallback and cooldown handling.
7
+
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.
4
9
 
5
10
  ## Features
6
11
 
7
- - **Takes over native `web_search`**: registers as the DSH web seam search provider.
8
- - **Adds/replaces `read_page`**: fetches clean page content through the active provider.
9
- - **Multi-provider failover + cooldown**: automatic fallback; providers that fail are temporarily skipped to avoid repeated timeouts.
10
- - **Modular providers**: add more by implementing `SearchProvider`.
11
- - **Standard DSH configuration**: secrets via `~/.dsh/.env`, cordis config via `~/.dsh/profiles/<profile>/cordis.patch.yml`.
12
- - **Demo mode**: test wiring without any API key.
12
+ - Replaces the native `web_search` provider through the DSH web seam.
13
+ - Registers `read_page` for clean, structured page extraction.
14
+ - Uses Codex-first search routing by default.
15
+ - Filters fallback candidates by capability, so search-only providers are never used for page fetching.
16
+ - Temporarily cools failing provider capabilities instead of repeatedly calling them.
17
+ - Disables only confirmed authentication failures until DSH restarts.
18
+ - Propagates caller cancellation without cooling providers or continuing fallback.
19
+ - Supports custom active-provider and fallback order configuration.
20
+ - Includes a key-free demo provider for wiring tests.
21
+
22
+ ## Provider support and default routing
23
+
24
+ | Provider | `web_search` | `read_page` | Credential |
25
+ |----------|:------------:|:-----------:|------------|
26
+ | Codex | ✅ | ❌ | Static credential JSON referenced by `providers.codex.credentialFile` |
27
+ | TinyFish | ✅ | ✅ | `providers.tinyfish.apiKey` in JSON |
28
+ | Tavily | ✅ | ❌ | `providers.tavily.apiKey` in JSON |
29
+ | Exa | ✅ | ✅ | `providers.exa.apiKey` in JSON |
30
+ | Firecrawl | ✅ | ✅ | `providers.firecrawl.apiKey` in JSON |
31
+ | Demo | ✅ | ✅ | None |
32
+
33
+ Default `web_search` order:
34
+
35
+ 1. Codex
36
+ 2. TinyFish
37
+ 3. Tavily
38
+ 4. Exa
39
+ 5. Firecrawl
40
+
41
+ Default `read_page` order:
13
42
 
14
- ## Supported providers
43
+ 1. TinyFish
44
+ 2. Exa
45
+ 3. Firecrawl
15
46
 
16
- | Provider | Search | Fetch | Requires API key |
17
- |----------|--------|-------|------------------|
18
- | TinyFish | ✅ | ✅ | Yes |
19
- | Tavily | ✅ | ❌ | Yes |
20
- | Exa | ✅ | ✅ | Yes |
21
- | Firecrawl| ✅ | ✅ | Yes |
22
- | Codex | ✅ | ❌ | Static credential file in `~/.dsh` |
23
- | Demo | ✅ | ✅ | No |
47
+ Codex and Tavily are skipped automatically for `read_page` because they do not implement page fetching. Providers without usable credentials are skipped without being marked as failed.
24
48
 
25
49
  ## Installation
26
50
 
27
- ### Option 1: Install from npm (when published)
51
+ ### Install from npm
28
52
 
29
53
  ```bash
30
54
  npx -y @deepseek-ai/dsh plugin --profile web add @mini-z/dsh-search-providers
31
55
  ```
32
56
 
33
- ### Option 2: Local / migrate from another machine
34
-
35
- 1. Copy the project folder (or the migration zip) to the target machine.
36
- 2. Install dependencies:
37
-
38
- ```bash
39
- cd dsh-search-providers
40
- bun install --frozen-lockfile
41
- ```
42
-
43
- 3. Configure API keys in `~/.dsh/.env` (see [Configuration](#configuration)).
57
+ Replace `web` with another DSH profile name when needed. Restart the corresponding DSH process after installation or configuration changes.
44
58
 
45
- 4. Link the plugin into your DSH profile:
59
+ ### Install from a local checkout or tarball
46
60
 
47
- ```bash
48
- cd ~/.dsh/profiles/<your-profile>
49
- npx -y @deepseek-ai/dsh plugin add /path/to/dsh-search-providers
50
- ```
51
-
52
- > The built plugin artifacts are already included in `dsh/`, so you do not need to run `bun run build` unless you modify the source.
61
+ ```bash
62
+ npx -y @deepseek-ai/dsh plugin --profile web add /path/to/dsh-search-providers
63
+ ```
53
64
 
54
- ### Option 3: Development install
65
+ The npm package and release tarballs include the built `dsh/` artifacts. A local source checkout only needs rebuilding after source changes:
55
66
 
56
67
  ```bash
57
- git clone <repo-url>
58
- cd dsh-search-providers
59
- bun install
68
+ bun install --frozen-lockfile
60
69
  bun run build
61
70
  ```
62
71
 
63
72
  ## Configuration
64
73
 
65
- This plugin follows DSH conventions: secrets go in `~/.dsh/.env`, and cordis-level config goes in the profile patch file.
66
-
67
- ### Environment variables
74
+ 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`.
68
75
 
69
- Put one or more of these in `~/.dsh/.env`:
76
+ 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`:
70
77
 
71
- ```bash
72
- TINYFISH_API_KEY=...
73
- TAVILY_API_KEY=...
74
- EXA_API_KEY=...
75
- FIRECRAWL_API_KEY=...
76
- CODEX_CREDENTIAL_FILE=C:/Users/you/.dsh/some-codex-auth.json
78
+ ```json
79
+ {
80
+ "provider": "codex",
81
+ "fallbackChain": ["tinyfish", "tavily", "exa", "firecrawl"],
82
+ "providers": {
83
+ "codex": {
84
+ "credentialFile": "./codex-credential.json",
85
+ "timeoutMs": 55000
86
+ },
87
+ "exa": {
88
+ "apiKey": "replace-with-your-api-key",
89
+ "timeoutMs": 55000
90
+ }
91
+ }
92
+ }
77
93
  ```
78
94
 
79
- DSH loads this file automatically when the profile starts. Providers are tried in this default priority order:
95
+ 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.
80
96
 
81
- 1. Codex
82
- 2. TinyFish
83
- 3. Tavily
84
- 4. Exa
85
- 5. Firecrawl
97
+ ### File paths and loading
98
+
99
+ - Relative `configFile` paths resolve from the DSH process's current working directory, not the Cordis profile directory.
100
+ - Both `~/` and `~\` expand to the user's home directory.
101
+ - 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.
102
+ - The JSON configuration is loaded once at plugin startup. Restart the corresponding DSH process after changes; there is no live reload.
103
+ - A missing default file logs a warning and uses empty configuration, with no credentials.
104
+ - An explicitly specified missing file, invalid JSON, or a schema-invalid configuration throws a clear error rather than silently falling back.
105
+ - 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.
106
+
107
+ ### Override the JSON configuration path
108
+
109
+ To use a different file, add an id-targeted override to `~/.dsh/profiles/<profile>/cordis.patch.yml`:
86
110
 
87
- API-key providers are attempted only when their key is configured. Codex is attempted only when `CODEX_CREDENTIAL_FILE` points to a usable static credential file. Missing or malformed configuration is silently skipped. Confirmed authentication failures disable that provider until DSH restarts; ordinary HTTP 403 responses do not. Rate limits use a 5-minute cooldown, network/timeouts 30 seconds, and other failures 10 seconds. Transient cooldowns are isolated between search and page fetching, and caller cancellation never cools a provider. Successful fallback responses identify cooling and disabled providers explicitly.
111
+ ```yaml
112
+ - id: search-providers
113
+ config:
114
+ configFile: ~/.dsh/search-providers-work.json
115
+ ```
88
116
 
89
- ### Codex credential file
117
+ Omit `configFile` to use the default path. Do not put provider settings or secrets in this Cordis override.
90
118
 
91
- Codex is fully independent of login or model plugins. Set `CODEX_CREDENTIAL_FILE` to any specific JSON credential file. When the variable is absent, the Codex provider is disabled. The minimal JSON shape is:
119
+ ### Change the active provider or fallback chain
120
+
121
+ Set `provider` and `fallbackChain` in the JSON file, for example:
92
122
 
93
123
  ```json
94
124
  {
95
- "access_token": "eyJ...",
96
- "account_id": "account-id"
125
+ "provider": "exa",
126
+ "fallbackChain": ["codex", "tinyfish", "tavily", "firecrawl"],
127
+ "providers": {
128
+ "exa": {
129
+ "apiKey": "replace-with-your-api-key",
130
+ "timeoutMs": 55000
131
+ }
132
+ }
97
133
  }
98
134
  ```
99
135
 
100
- The reader explicitly supports top-level fields plus `credential` and `tokens` wrappers. Access-token fields may be named `access_token` or `access`; account fields may be named `account_id` or `accountId`. When the account field is absent, the standard `chatgpt_account_id` JWT claim is used. Extra fields—including `refresh_token`, `expired`, `expires_at`, and `expires`—are ignored.
136
+ Routing rules:
101
137
 
102
- This plugin only reads the file. It never starts a login, refreshes a token, checks local timestamps, or writes credential data. A missing or malformed credential makes Codex unavailable and is skipped. If Codex rejects the credential with HTTP 401/403, Codex is disabled until restart and the normal fallback chain continues. Restrict the file's filesystem permissions to the owning user.
138
+ - `provider` is always attempted first when it is available and supports the requested capability.
139
+ - `fallbackChain` contains the providers attempted afterward.
140
+ - Duplicate provider ids are removed while preserving order.
141
+ - `fallbackChain: []` disables fallback and uses only the active provider.
142
+ - Providers that do not support the requested capability are omitted from attempts and diagnostics.
103
143
 
104
- Codex does not implement `read_page`, and its standalone endpoint ignores this plugin's recency, domain, location, language, and maximum-result filters.
144
+ 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.
105
145
 
106
- ### Override the active provider or fallback order
146
+ 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`.
107
147
 
108
- The active `provider` is always tried first. `fallbackChain` lists the providers to try afterward, is de-duplicated in order, and may be an empty array to disable fallback.
148
+ ### Migration from inline Cordis and environment settings
109
149
 
110
- Edit `~/.dsh/profiles/<profile>/cordis.patch.yml`:
150
+ 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.
151
+ 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.
152
+ 3. Replace `SEARCH_PROVIDERS_DEMO` with `"provider": "demo"` in JSON when demo mode is wanted. Environment variables no longer configure any provider.
153
+ 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.
154
+ 5. Remove the old inline fields from the Cordis plugin configuration, leaving only an optional `configFile`. Restart DSH to load the new file.
111
155
 
112
- ```yaml
113
- - id: search-providers
114
- config:
115
- provider: exa
116
- fallbackChain: [codex, tinyfish, tavily, firecrawl]
117
- providers:
118
- exa:
119
- timeoutMs: 55000
156
+ ## Fallback, cooldown, and cancellation behavior
157
+
158
+ The plugin rechecks provider state before every attempt instead of relying on a stale snapshot. A provider whose cooldown expires while other providers are being tried may participate again in the same request.
159
+
160
+ | Failure class | Effect |
161
+ |---------------|--------|
162
+ | Confirmed authentication failure | Provider disabled globally until DSH restarts |
163
+ | HTTP 429/432, quota, or rate limit | Current capability cooled for 5 minutes |
164
+ | Network error or timeout | Current capability cooled for 30 seconds |
165
+ | Other request failure | Current capability cooled for 10 seconds |
166
+
167
+ 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.
168
+
169
+ 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.
170
+
171
+ Caller cancellation is different from failure: it propagates immediately, preserves the original abort reason, does not continue fallback, and does not alter provider state.
172
+
173
+ Successful search output identifies the provider that actually answered and reports fallback, cooling, and disabled-provider state. Final errors list the providers genuinely attempted and the current live state.
174
+
175
+ ## Codex credential file
176
+
177
+ Codex uses the standalone ChatGPT Codex search endpoint and is independent of model/login plugins. It only reads an existing static credential file; it never starts a login, refreshes a token, checks local expiry timestamps, or writes credential data.
178
+
179
+ Minimal supported JSON:
180
+
181
+ ```json
182
+ {
183
+ "access_token": "eyJ...",
184
+ "account_id": "account-id"
185
+ }
120
186
  ```
121
187
 
122
- ## Usage
188
+ 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.
123
189
 
124
- Once the plugin is loaded by DSH, the model can use the native `web_search` and `read_page` tools. No extra configuration is needed.
190
+ 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.
125
191
 
126
- ### web_search
192
+ ## Usage
127
193
 
128
- Ask the model to search the web:
194
+ After loading the plugin and configuring at least one provider, ask the model to search:
129
195
 
130
196
  ```text
131
197
  Search the web for "latest Rust memory safety features".
132
198
  ```
133
199
 
134
- The plugin will use the active provider and automatically fall back to the next available provider if the first one fails. Every successful result starts with `[Search provider: ...]`, naming the provider that actually returned it.
200
+ Successful results start with the provider that actually answered:
135
201
 
136
- ### read_page
202
+ ```text
203
+ [Search provider: TinyFish]
204
+ ```
137
205
 
138
- Give the model a URL to read:
206
+ Read a specific page with:
139
207
 
140
208
  ```text
141
209
  Read this page: https://example.com/article
142
210
  ```
143
211
 
144
- The plugin will fetch the page content through the active provider (or the first available fetch-capable provider in the fallback chain).
212
+ `read_page` returns structured evidence containing a summary, extracted content, outgoing links, uncertainty, and operational warnings.
145
213
 
146
- > Note: Tavily and Codex support search only, so they are skipped for `read_page`.
214
+ ## Demo mode
147
215
 
148
- ## Demo mode (test without API key)
216
+ Select the key-free demo provider in your JSON configuration file:
149
217
 
150
- ```bash
151
- SEARCH_PROVIDERS_DEMO=1 npx -y @deepseek-ai/dsh --profile web --no-open --port 9876
218
+ ```json
219
+ {
220
+ "provider": "demo",
221
+ "fallbackChain": []
222
+ }
152
223
  ```
153
224
 
154
- Then ask the model to run `web_search` or `read_page`; it will receive canned demo results.
225
+ 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.
155
226
 
156
227
  ## Development
157
228
 
158
229
  ```bash
159
- bun install
230
+ bun install --frozen-lockfile
160
231
  bun run typecheck
161
232
  bun run test
162
233
  bun run build
163
234
  ```
164
235
 
165
- ## Architecture
166
-
167
- - `src/providers/types.ts` — `SearchProvider` interface.
168
- - `src/providers/registry.ts` — provider registry and active-provider selection.
169
- - `src/providers/cooldown.ts` — failure tracking and cooldown logic.
170
- - `src/providers/tinyfish.ts`, `tavily.ts`, `exa.ts`, `firecrawl.ts`, `codex.ts` — provider implementations.
171
- - `src/providers/demo.ts` — canned demo provider for testing.
172
- - `src/tools/web-search.ts` — registers the plugin as `ctx.web.registerSearchProvider` with failover.
173
- - `src/tools/read-page.ts` — registers the `read_page` tool with failover.
174
- - `src/index.ts` — DSH plugin entry (`apply`).
236
+ Main components:
175
237
 
176
- ## Adding a provider
238
+ - `src/providers/types.ts` — provider interfaces and result types.
239
+ - `src/providers/registry.ts` — provider registration and active-provider selection.
240
+ - `src/providers/cooldown.ts` — authentication disablement and capability cooldown state.
241
+ - `src/providers/*.ts` — provider implementations.
242
+ - `src/tools/web-search.ts` — DSH web seam adapter and search fallback.
243
+ - `src/tools/read-page.ts` — `read_page` registration and fetch fallback.
244
+ - `src/index.ts` — plugin entry point and default provider order.
177
245
 
178
- Implement the `SearchProvider` interface, register it in `src/index.ts`, and add its config resolution.
246
+ To add a provider, implement `SearchProvider`, register it in `src/index.ts`, and add its configuration resolution.
179
247
 
180
248
  ## Publishing
181
249
 
182
250
  1. Bump the version in `package.json`.
183
- 2. `npm login`
184
- 3. `bun publish --access public`
251
+ 2. Run the development checks and inspect `bun pm pack --dry-run`.
252
+ 3. Log in with `npm login`.
253
+ 4. Publish the public scoped package with `npm publish --access public`.
254
+
255
+ Published npm versions are immutable; every release requires a new version number.
185
256
 
186
257
  ## License
187
258
 
188
- MIT. The Codex interoperability implementation was developed with reference to [dsh-codex-connect](https://github.com/franksong2702/dsh-codex-connect) (Apache-2.0); no runtime dependency on that package is required.
259
+ MIT. The Codex interoperability implementation was developed with reference to [dsh-codex-connect](https://github.com/franksong2702/dsh-codex-connect) (Apache-2.0); no runtime dependency on that package is required.
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);
@@ -854,7 +930,7 @@ const fetchSchema = {
854
930
  },
855
931
  required: ["summary", "content", "uncertainty"]
856
932
  };
857
- const FALLBACK_ATTEMPT_TIMEOUT_MS = 2e4;
933
+ const FALLBACK_ATTEMPT_TIMEOUT_MS = 55e3;
858
934
  function resolveCandidateIds(registry, fallbackChain) {
859
935
  let activeId;
860
936
  try {
@@ -1179,18 +1255,19 @@ const name = "search-providers";
1179
1255
  const inject = ["tools", "web"];
1180
1256
  const DEFAULT_PROVIDER_ORDER = ["codex", "tinyfish", "tavily", "exa", "firecrawl"];
1181
1257
  function apply(ctx, rawConfig = {}) {
1258
+ const config = loadPluginConfig(rawConfig);
1182
1259
  const registry = new ProviderRegistry();
1183
1260
  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")));
1261
+ const demoMode = config.provider === "demo";
1262
+ registry.register(new CodexProvider(resolveProviderConfig(config, "codex")));
1263
+ registry.register(new TinyfishProvider(resolveProviderConfig(config, "tinyfish")));
1264
+ registry.register(new TavilyProvider(resolveProviderConfig(config, "tavily")));
1265
+ registry.register(new ExaProvider(resolveProviderConfig(config, "exa")));
1266
+ registry.register(new FirecrawlProvider(resolveProviderConfig(config, "firecrawl")));
1190
1267
  if (demoMode) {
1191
- registry.register(new DemoProvider(resolveProviderConfig(rawConfig, "tinyfish", "TINYFISH")));
1268
+ registry.register(new DemoProvider(resolveProviderConfig(config, "tinyfish")));
1192
1269
  }
1193
- const activeProvider = rawConfig.provider ?? (demoMode ? "demo" : void 0);
1270
+ const activeProvider = config.provider;
1194
1271
  if (activeProvider) {
1195
1272
  try {
1196
1273
  registry.setActive(activeProvider);
@@ -1198,7 +1275,7 @@ function apply(ctx, rawConfig = {}) {
1198
1275
  console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);
1199
1276
  }
1200
1277
  }
1201
- const fallbackChain = rawConfig.fallbackChain ?? DEFAULT_PROVIDER_ORDER;
1278
+ const fallbackChain = config.fallbackChain ?? DEFAULT_PROVIDER_ORDER;
1202
1279
  registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);
1203
1280
  registerReadPageTool(ctx, registry, cooldown, fallbackChain);
1204
1281
  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 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 = 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,7 +1,7 @@
1
1
  {
2
2
  "name": "@mini-z/dsh-search-providers",
3
- "version": "0.1.2",
4
- "description": "Modular DSH web search plugin with TinyFish as the default provider. Takes over web_search and read_page with an extensible provider registry.",
3
+ "version": "0.2.0",
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": {
7
7
  "dev": "vite build --watch",
@@ -15,19 +15,24 @@
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": [
21
22
  "search",
22
23
  "web-fetch",
24
+ "codex",
23
25
  "tinyfish",
24
26
  "dsh-plugin",
25
27
  "deepseek-harness",
26
28
  "web-search",
27
29
  "modular-search"
28
30
  ],
29
- "author": "",
31
+ "author": "mini-z",
30
32
  "license": "MIT",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
31
36
  "engines": {
32
37
  "node": ">=22.13"
33
38
  },
@@ -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
+ }