@mini-z/dsh-search-providers 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +80 -49
- package/dsh/index.js +121 -39
- package/dsh/index.js.map +1 -1
- package/package.json +2 -1
- package/search-providers.example.json +28 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Fix Codex credential loading for account stores with `credentials[]` and `activeAccountId`; preserve existing credential formats and never modify or refresh credentials.
|
|
6
|
+
|
|
7
|
+
## 0.2.0
|
|
8
|
+
|
|
9
|
+
### Breaking changes
|
|
10
|
+
|
|
11
|
+
- Load provider settings from `~/.dsh/search-providers.json`, or a file selected by the Cordis `configFile` option. Inline provider settings and environment-variable configuration are no longer supported.
|
|
12
|
+
- Configure Codex credentials with `providers.codex.credentialFile`; relative paths resolve from the JSON configuration directory.
|
|
13
|
+
- Use built-in official service endpoints. Remove provider `baseUrl` and TinyFish `options.fetchBaseUrl` from existing configuration.
|
|
14
|
+
|
|
15
|
+
### Improvements
|
|
16
|
+
|
|
17
|
+
- Validate configuration at startup and report errors without echoing credential values.
|
|
18
|
+
- Include `search-providers.example.json` in the published package. See README for migration steps.
|
|
19
|
+
- Increase the per-fallback outer timeout from 20 seconds to 55 seconds for both search and page reading; aggregate tool deadlines are unchanged.
|
|
20
|
+
- Add configuration loading, official endpoint, and timeout regression tests.
|
package/README.md
CHANGED
|
@@ -7,6 +7,14 @@ A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin th
|
|
|
7
7
|
|
|
8
8
|
**Codex is the default `web_search` provider.** If its static credential is unavailable, or a request fails, the plugin proceeds through the configured fallback chain.
|
|
9
9
|
|
|
10
|
+
## Latest update — 0.2.1
|
|
11
|
+
|
|
12
|
+
- **Fix Codex credential compatibility:** account-store files such as `.openai-codex-auth.json` now work directly. The provider selects the `credentials[]` entry matching `activeAccountId` and reads its `access` and `accountId`.
|
|
13
|
+
- Existing single-credential formats remain supported. Credential files are never modified and tokens are never refreshed by this plugin.
|
|
14
|
+
- If you already use the JSON configuration introduced in 0.2.0, no configuration change is required: update the plugin and restart DSH.
|
|
15
|
+
|
|
16
|
+
**Upgrading from 0.1.x?** Move provider settings from `.env` or inline Cordis configuration into `~/.dsh/search-providers.json`. Official endpoints are built in, and fallback attempts now have a 55-second outer timeout. See [configuration migration](#migration-from-inline-cordis-and-environment-settings).
|
|
17
|
+
|
|
10
18
|
## Features
|
|
11
19
|
|
|
12
20
|
- Replaces the native `web_search` provider through the DSH web seam.
|
|
@@ -23,11 +31,11 @@ A modular [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/dsh) plugin th
|
|
|
23
31
|
|
|
24
32
|
| Provider | `web_search` | `read_page` | Credential |
|
|
25
33
|
|----------|:------------:|:-----------:|------------|
|
|
26
|
-
| Codex | ✅ | ❌ | Static credential JSON referenced by `
|
|
27
|
-
| TinyFish | ✅ | ✅ | `
|
|
28
|
-
| Tavily | ✅ | ❌ | `
|
|
29
|
-
| Exa | ✅ | ✅ | `
|
|
30
|
-
| Firecrawl | ✅ | ✅ | `
|
|
34
|
+
| Codex | ✅ | ❌ | Static credential JSON referenced by `providers.codex.credentialFile` |
|
|
35
|
+
| TinyFish | ✅ | ✅ | `providers.tinyfish.apiKey` in JSON |
|
|
36
|
+
| Tavily | ✅ | ❌ | `providers.tavily.apiKey` in JSON |
|
|
37
|
+
| Exa | ✅ | ✅ | `providers.exa.apiKey` in JSON |
|
|
38
|
+
| Firecrawl | ✅ | ✅ | `providers.firecrawl.apiKey` in JSON |
|
|
31
39
|
| Demo | ✅ | ✅ | None |
|
|
32
40
|
|
|
33
41
|
Default `web_search` order:
|
|
@@ -71,40 +79,66 @@ bun run build
|
|
|
71
79
|
|
|
72
80
|
## Configuration
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
The independent JSON configuration loader replaces inline Cordis settings and environment-variable configuration. Cordis accepts **only** the optional `configFile` setting; when omitted, it defaults to `~/.dsh/search-providers.json`.
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
# Default web_search provider
|
|
78
|
-
CODEX_CREDENTIAL_FILE=/absolute/path/to/codex-credential.json
|
|
84
|
+
The package-root [`search-providers.example.json`](./search-providers.example.json) is included in the package. Copy it to your chosen configuration path and supply credentials for at least one provider. For example, `~/.dsh/search-providers.json`:
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"provider": "codex",
|
|
89
|
+
"fallbackChain": ["tinyfish", "tavily", "exa", "firecrawl"],
|
|
90
|
+
"providers": {
|
|
91
|
+
"codex": {
|
|
92
|
+
"credentialFile": "./codex-credential.json",
|
|
93
|
+
"timeoutMs": 55000
|
|
94
|
+
},
|
|
95
|
+
"exa": {
|
|
96
|
+
"apiKey": "replace-with-your-api-key",
|
|
97
|
+
"timeoutMs": 55000
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
85
101
|
```
|
|
86
102
|
|
|
87
|
-
|
|
103
|
+
The JSON fields are `provider`, `fallbackChain`, and `providers`. Provider entries support `apiKey`, `timeoutMs`, provider-specific `options`, and `credentialFile` for Codex. Official endpoints for Codex, TinyFish, Tavily, Exa, and Firecrawl are built in and cannot be overridden. The default provider request timeout is 55 seconds. Restrict configuration and credential files to the owning user; do not commit secrets.
|
|
88
104
|
|
|
89
|
-
|
|
90
|
-
EXA_BASE_URL=https://api.exa.ai
|
|
91
|
-
EXA_TIMEOUT_MS=55000
|
|
92
|
-
```
|
|
105
|
+
### File paths and loading
|
|
93
106
|
|
|
94
|
-
|
|
107
|
+
- Relative `configFile` paths resolve from the DSH process's current working directory, not the Cordis profile directory.
|
|
108
|
+
- Both `~/` and `~\` expand to the user's home directory.
|
|
109
|
+
- Relative `providers.codex.credentialFile` paths resolve from the JSON configuration file's directory, not the process working directory. This field also supports `~/` and `~\` home expansion.
|
|
110
|
+
- The JSON configuration is loaded once at plugin startup. Restart the corresponding DSH process after changes; there is no live reload.
|
|
111
|
+
- A missing default file logs a warning and uses empty configuration, with no credentials.
|
|
112
|
+
- An explicitly specified missing file, invalid JSON, or a schema-invalid configuration throws a clear error rather than silently falling back.
|
|
113
|
+
- The plugin no longer reads environment variables, including API keys, endpoints, timeouts, Codex credentials, and demo mode. Values in `~/.dsh/.env` are not used by this plugin.
|
|
95
114
|
|
|
96
|
-
###
|
|
115
|
+
### Override the JSON configuration path
|
|
97
116
|
|
|
98
|
-
|
|
117
|
+
To use a different file, add an id-targeted override to `~/.dsh/profiles/<profile>/cordis.patch.yml`:
|
|
99
118
|
|
|
100
119
|
```yaml
|
|
101
120
|
- id: search-providers
|
|
102
121
|
config:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
configFile: ~/.dsh/search-providers-work.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Omit `configFile` to use the default path. Do not put provider settings or secrets in this Cordis override.
|
|
126
|
+
|
|
127
|
+
### Change the active provider or fallback chain
|
|
128
|
+
|
|
129
|
+
Set `provider` and `fallbackChain` in the JSON file, for example:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"provider": "exa",
|
|
134
|
+
"fallbackChain": ["codex", "tinyfish", "tavily", "firecrawl"],
|
|
135
|
+
"providers": {
|
|
136
|
+
"exa": {
|
|
137
|
+
"apiKey": "replace-with-your-api-key",
|
|
138
|
+
"timeoutMs": 55000
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
108
142
|
```
|
|
109
143
|
|
|
110
144
|
Routing rules:
|
|
@@ -115,18 +149,17 @@ Routing rules:
|
|
|
115
149
|
- `fallbackChain: []` disables fallback and uses only the active provider.
|
|
116
150
|
- Providers that do not support the requested capability are omitted from attempts and diagnostics.
|
|
117
151
|
|
|
118
|
-
For TinyFish, Tavily, Exa, and Firecrawl, provider entries support `apiKey`, `
|
|
152
|
+
For TinyFish, Tavily, Exa, and Firecrawl, provider entries support `apiKey`, `timeoutMs`, and provider-specific `options`. Generic `options` remains supported, but does not allow endpoint overrides. TinyFish uses built-in official endpoints for both search and page fetching.
|
|
119
153
|
|
|
120
|
-
|
|
121
|
-
- id: search-providers
|
|
122
|
-
config:
|
|
123
|
-
providers:
|
|
124
|
-
tinyfish:
|
|
125
|
-
options:
|
|
126
|
-
fetchBaseUrl: https://api.fetch.tinyfish.ai
|
|
127
|
-
```
|
|
154
|
+
Codex uses `credentialFile` and `timeoutMs` from its provider configuration. Its endpoint is fixed, and it intentionally ignores `apiKey` and `options`; credentials come exclusively from the file selected by `providers.codex.credentialFile`.
|
|
128
155
|
|
|
129
|
-
|
|
156
|
+
### Migration from inline Cordis and environment settings
|
|
157
|
+
|
|
158
|
+
1. Create the independent JSON configuration using the packaged example. Move inline Cordis `provider`, `providers`, and `fallbackChain` values into that JSON file. Old inline provider/fallback settings are rejected with a migration message; they are not merged or silently ignored.
|
|
159
|
+
2. Move each provider's `*_API_KEY` and `*_TIMEOUT_MS` values into its JSON `apiKey` and `timeoutMs` fields. Move `CODEX_CREDENTIAL_FILE` to `providers.codex.credentialFile` and `CODEX_TIMEOUT_MS` to `providers.codex.timeoutMs`. Check relative credential paths against the JSON file directory. Do not migrate `*_BASE_URL` values: official endpoints are built in.
|
|
160
|
+
3. Replace `SEARCH_PROVIDERS_DEMO` with `"provider": "demo"` in JSON when demo mode is wanted. Environment variables no longer configure any provider.
|
|
161
|
+
4. Remove any existing provider `baseUrl` fields and TinyFish `options.fetchBaseUrl` from the JSON configuration; these legacy endpoint overrides are rejected. Remove an empty `options` object if nothing remains. Keep API keys, timeouts, routing (`provider` and `fallbackChain`), Codex `credentialFile`, and other supported provider options.
|
|
162
|
+
5. Remove the old inline fields from the Cordis plugin configuration, leaving only an optional `configFile`. Restart DSH to load the new file.
|
|
130
163
|
|
|
131
164
|
## Fallback, cooldown, and cancellation behavior
|
|
132
165
|
|
|
@@ -141,7 +174,7 @@ The plugin rechecks provider state before every attempt instead of relying on a
|
|
|
141
174
|
|
|
142
175
|
Transient state is capability-scoped: a search failure does not cool `read_page`, and a fetch failure does not cool `web_search`. Authentication disablement is provider-wide.
|
|
143
176
|
|
|
144
|
-
The first eligible provider uses its configured request timeout. Each subsequent fallback attempt has an additional
|
|
177
|
+
The first eligible provider uses its configured request timeout. Each subsequent fallback attempt has an additional 55-second outer limit, which also stops providers that ignore `AbortSignal`. The registered `read_page` tool has a 200-second aggregate deadline. These request deadlines are separate from the cooldown durations in the table above.
|
|
145
178
|
|
|
146
179
|
Caller cancellation is different from failure: it propagates immediately, preserves the original abort reason, does not continue fallback, and does not alter provider state.
|
|
147
180
|
|
|
@@ -162,6 +195,8 @@ Minimal supported JSON:
|
|
|
162
195
|
|
|
163
196
|
The parser also accepts `credential` and `tokens` wrappers, `access` as an alternative token field, and `accountId` as an alternative account field. If the account id is omitted, the plugin reads the standard `chatgpt_account_id` claim from the access token. Missing or malformed credentials make Codex unavailable. Codex HTTP 401/403 responses are treated as confirmed credential failures and trigger the normal fallback chain.
|
|
164
197
|
|
|
198
|
+
Account-store files such as `.openai-codex-auth.json` are also supported: the parser reads `access` and `accountId` from the `credentials` array entry matching `activeAccountId`. Without an active selector, only a single-entry array is accepted; ambiguous or missing matches are not guessed. The store is read-only, and the original formats remain supported.
|
|
199
|
+
|
|
165
200
|
Restrict the credential file to the owning user. Codex does not implement `read_page`, and its standalone endpoint does not apply this plugin's recency, domain, location, language, or maximum-result filters.
|
|
166
201
|
|
|
167
202
|
## Usage
|
|
@@ -188,20 +223,16 @@ Read this page: https://example.com/article
|
|
|
188
223
|
|
|
189
224
|
## Demo mode
|
|
190
225
|
|
|
191
|
-
|
|
226
|
+
Select the key-free demo provider in your JSON configuration file:
|
|
192
227
|
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
```powershell
|
|
200
|
-
$env:SEARCH_PROVIDERS_DEMO = '1'
|
|
201
|
-
npx -y @deepseek-ai/dsh --profile web --no-open --port 9876
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"provider": "demo",
|
|
231
|
+
"fallbackChain": []
|
|
232
|
+
}
|
|
202
233
|
```
|
|
203
234
|
|
|
204
|
-
|
|
235
|
+
Restart DSH after changing the file. Demo mode returns canned search and page-fetch results for integration testing; no environment variable is needed or read.
|
|
205
236
|
|
|
206
237
|
## Development
|
|
207
238
|
|
package/dsh/index.js
CHANGED
|
@@ -1,19 +1,103 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, dirname, resolve } from "node:path";
|
|
1
4
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { statSync, readFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 55e3;
|
|
5
|
-
function
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
632
|
+
const filename = readNonEmptyString(config.credentialFile);
|
|
633
|
+
this.credentialFile = filename === void 0 ? void 0 : resolve(filename);
|
|
554
634
|
this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : 55e3;
|
|
555
635
|
}
|
|
556
636
|
available() {
|
|
@@ -630,10 +710,6 @@ class CodexProvider {
|
|
|
630
710
|
return mapCodexSearchResponse(payload);
|
|
631
711
|
}
|
|
632
712
|
}
|
|
633
|
-
function configuredCredentialFile() {
|
|
634
|
-
const filename = readNonEmptyString(process.env.CODEX_CREDENTIAL_FILE);
|
|
635
|
-
return filename === void 0 ? void 0 : resolve(filename);
|
|
636
|
-
}
|
|
637
713
|
function loadStaticCredential(filename) {
|
|
638
714
|
try {
|
|
639
715
|
const stats = statSync(filename);
|
|
@@ -675,10 +751,15 @@ function mapCodexSearchResponse(value) {
|
|
|
675
751
|
}
|
|
676
752
|
function parseStaticCredential(value) {
|
|
677
753
|
if (!isRecord(value)) return void 0;
|
|
754
|
+
const stored = Array.isArray(value.credentials) ? value.credentials : [];
|
|
755
|
+
const activeAccountId = readNonEmptyString(value.activeAccountId);
|
|
756
|
+
const selected = activeAccountId ? stored.filter((entry) => isRecord(entry) && (readNonEmptyString(entry.account_id) ?? readNonEmptyString(entry.accountId)) === activeAccountId) : value.activeAccountId === void 0 ? stored : [];
|
|
757
|
+
const storedCredential = selected.length === 1 && isRecord(selected[0]) ? selected[0] : void 0;
|
|
678
758
|
const candidates = [
|
|
679
759
|
value,
|
|
680
760
|
isRecord(value.credential) ? value.credential : void 0,
|
|
681
|
-
isRecord(value.tokens) ? value.tokens : void 0
|
|
761
|
+
isRecord(value.tokens) ? value.tokens : void 0,
|
|
762
|
+
storedCredential
|
|
682
763
|
];
|
|
683
764
|
for (const candidate of candidates) {
|
|
684
765
|
if (!candidate) continue;
|
|
@@ -854,7 +935,7 @@ const fetchSchema = {
|
|
|
854
935
|
},
|
|
855
936
|
required: ["summary", "content", "uncertainty"]
|
|
856
937
|
};
|
|
857
|
-
const FALLBACK_ATTEMPT_TIMEOUT_MS =
|
|
938
|
+
const FALLBACK_ATTEMPT_TIMEOUT_MS = 55e3;
|
|
858
939
|
function resolveCandidateIds(registry, fallbackChain) {
|
|
859
940
|
let activeId;
|
|
860
941
|
try {
|
|
@@ -1179,18 +1260,19 @@ const name = "search-providers";
|
|
|
1179
1260
|
const inject = ["tools", "web"];
|
|
1180
1261
|
const DEFAULT_PROVIDER_ORDER = ["codex", "tinyfish", "tavily", "exa", "firecrawl"];
|
|
1181
1262
|
function apply(ctx, rawConfig = {}) {
|
|
1263
|
+
const config = loadPluginConfig(rawConfig);
|
|
1182
1264
|
const registry = new ProviderRegistry();
|
|
1183
1265
|
const cooldown = new CooldownController();
|
|
1184
|
-
const demoMode =
|
|
1185
|
-
registry.register(new CodexProvider(resolveProviderConfig(
|
|
1186
|
-
registry.register(new TinyfishProvider(resolveProviderConfig(
|
|
1187
|
-
registry.register(new TavilyProvider(resolveProviderConfig(
|
|
1188
|
-
registry.register(new ExaProvider(resolveProviderConfig(
|
|
1189
|
-
registry.register(new FirecrawlProvider(resolveProviderConfig(
|
|
1266
|
+
const demoMode = config.provider === "demo";
|
|
1267
|
+
registry.register(new CodexProvider(resolveProviderConfig(config, "codex")));
|
|
1268
|
+
registry.register(new TinyfishProvider(resolveProviderConfig(config, "tinyfish")));
|
|
1269
|
+
registry.register(new TavilyProvider(resolveProviderConfig(config, "tavily")));
|
|
1270
|
+
registry.register(new ExaProvider(resolveProviderConfig(config, "exa")));
|
|
1271
|
+
registry.register(new FirecrawlProvider(resolveProviderConfig(config, "firecrawl")));
|
|
1190
1272
|
if (demoMode) {
|
|
1191
|
-
registry.register(new DemoProvider(resolveProviderConfig(
|
|
1273
|
+
registry.register(new DemoProvider(resolveProviderConfig(config, "tinyfish")));
|
|
1192
1274
|
}
|
|
1193
|
-
const activeProvider =
|
|
1275
|
+
const activeProvider = config.provider;
|
|
1194
1276
|
if (activeProvider) {
|
|
1195
1277
|
try {
|
|
1196
1278
|
registry.setActive(activeProvider);
|
|
@@ -1198,7 +1280,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1198
1280
|
console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);
|
|
1199
1281
|
}
|
|
1200
1282
|
}
|
|
1201
|
-
const fallbackChain =
|
|
1283
|
+
const fallbackChain = config.fallbackChain ?? DEFAULT_PROVIDER_ORDER;
|
|
1202
1284
|
registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);
|
|
1203
1285
|
registerReadPageTool(ctx, registry, cooldown, fallbackChain);
|
|
1204
1286
|
console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(", ")}]`);
|
package/dsh/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/config.ts","../src/providers/utils.ts","../src/providers/cooldown.ts","../src/providers/registry.ts","../src/providers/tinyfish.ts","../src/providers/tavily.ts","../src/providers/exa.ts","../src/providers/firecrawl.ts","../src/providers/codex.ts","../src/providers/demo.ts","../src/schemas/fetch-schema.ts","../src/tools/fallback-utils.ts","../src/tools/read-page.ts","../src/tools/web-search.ts","../src/index.ts"],"sourcesContent":["export interface ProviderConfigEntry {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n options?: Record<string, unknown>;\n}\n\nexport interface PluginConfig {\n /** Active provider id. */\n provider?: string;\n /** Provider-specific API keys and options. */\n providers?: Record<string, ProviderConfigEntry>;\n /** Provider ids to try after the active provider; an empty array disables fallback. */\n fallbackChain?: string[];\n}\n\nexport interface ResolvedProviderConfig {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs: number;\n options: Record<string, unknown>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 55_000;\n\nexport function resolveProviderConfig(\n pluginConfig: PluginConfig,\n providerId: string,\n envPrefix: string,\n): ResolvedProviderConfig {\n const pc = pluginConfig.providers?.[providerId] ?? {};\n const apiKey =\n process.env[`${envPrefix}_API_KEY`] ??\n pc.apiKey;\n const baseUrl =\n process.env[`${envPrefix}_BASE_URL`] ??\n pc.baseUrl;\n const timeoutMs =\n Number(process.env[`${envPrefix}_TIMEOUT_MS`]) ||\n pc.timeoutMs ||\n DEFAULT_TIMEOUT_MS;\n\n return {\n apiKey,\n baseUrl,\n timeoutMs,\n options: pc.options ?? {},\n };\n}\n","export class ProviderAuthenticationError extends Error {\n readonly kind = 'provider-auth' as const;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProviderAuthenticationError';\n }\n}\n\nexport class ProviderHttpError extends Error {\n readonly kind = 'provider-http' as const;\n\n constructor(\n message: string,\n readonly status: number,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'ProviderHttpError';\n }\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n try {\n const signal = init.signal\n ? combineSignals(init.signal, controller.signal)\n : controller.signal;\n return await fetch(url, { ...init, signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function combineSignals(a: AbortSignal, b: AbortSignal): AbortSignal {\n return AbortSignal.any([a, b]);\n}\n\nexport async function throwOnError(response: Response, label: string): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => '');\n let detail = text.slice(0, 500);\n if (response.status === 401) {\n detail = 'Invalid or missing API key';\n } else if (response.status === 429) {\n detail = 'Rate limit exceeded';\n } else if (response.status === 432) {\n detail = 'Usage limit exceeded';\n }\n const message = `${label} returned ${response.status}: ${detail}`;\n if (response.status === 401) {\n throw new ProviderAuthenticationError(message);\n }\n throw new ProviderHttpError(message, response.status);\n}\n","import { ProviderAuthenticationError, ProviderHttpError } from './utils.js';\n\nexport type ProviderCapability = 'search' | 'fetch';\n\nexport interface CooldownEntry {\n until: number;\n reason: string;\n}\n\nexport class CooldownController {\n private disabled = new Map<string, CooldownEntry>();\n private cooling = new Map<string, CooldownEntry>();\n\n /** Record one capability failure. Confirmed authentication failures are provider-global. */\n record(providerId: string, capability: ProviderCapability, error: unknown): boolean {\n if (error instanceof ProviderAuthenticationError) {\n this.disabled.set(providerId, {\n until: Number.POSITIVE_INFINITY,\n reason: 'invalid credentials',\n });\n return true;\n }\n\n this.cooling.set(this.capabilityKey(providerId, capability), {\n until: Date.now() + this.cooldownDuration(error),\n reason: this.classifyError(error),\n });\n return true;\n }\n\n /** Check whether one provider capability is temporarily cooling. */\n isCooling(providerId: string, capability: ProviderCapability): boolean {\n return this.getEntry(this.cooling, this.capabilityKey(providerId, capability)) !== undefined;\n }\n\n /** Check whether confirmed authentication failure disabled the provider until restart. */\n isDisabled(providerId: string): boolean {\n return this.getEntry(this.disabled, providerId) !== undefined;\n }\n\n /** Get the current failure reason, if any. */\n getReason(providerId: string, capability: ProviderCapability): string | undefined {\n return this.getEntry(this.disabled, providerId)?.reason\n ?? this.getEntry(this.cooling, this.capabilityKey(providerId, capability))?.reason;\n }\n\n /** Build candidates while separating global disablement from capability cooldown. */\n filter(\n chain: string[],\n capability: ProviderCapability,\n ): { ready: string[]; cooling: string[]; disabled: string[] } {\n const ready: string[] = [];\n const cooling: string[] = [];\n const disabled: string[] = [];\n for (const id of chain) {\n if (this.isDisabled(id)) {\n disabled.push(id);\n } else if (this.isCooling(id, capability)) {\n cooling.push(id);\n } else {\n ready.push(id);\n }\n }\n return { ready, cooling, disabled };\n }\n\n private capabilityKey(providerId: string, capability: ProviderCapability): string {\n return `${providerId}:${capability}`;\n }\n\n private getEntry(map: Map<string, CooldownEntry>, key: string): CooldownEntry | undefined {\n const entry = map.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.until) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n private classifyError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (this.isRateLimitError(error, message)) return 'rate limit or quota exceeded';\n if (/timeout|timed out/i.test(message)) return 'timeout';\n if (/network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) return 'network error';\n return 'request failed';\n }\n\n private cooldownDuration(error: unknown): number {\n const message = error instanceof Error ? error.message : String(error);\n // Rate limits / quotas: cool for 5 minutes.\n if (this.isRateLimitError(error, message)) return 5 * 60 * 1000;\n // Timeouts / network errors: cool for 30 seconds.\n if (/timeout|timed out|network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) {\n return 30 * 1000;\n }\n // Generic failures: cool for 10 seconds.\n return 10 * 1000;\n }\n\n private isRateLimitError(error: unknown, message: string): boolean {\n return (\n (error instanceof ProviderHttpError && (error.status === 429 || error.status === 432))\n || /429|432|rate.?limit|quota|usage limit|out of credits|monthly quota/i.test(message)\n );\n }\n}\n","import type { SearchProvider } from './types.js';\n\nexport class ProviderRegistry {\n private providers = new Map<string, SearchProvider>();\n private activeId: string | undefined;\n\n register(provider: SearchProvider): void {\n if (this.providers.has(provider.id)) {\n console.warn(`[search-providers] provider '${provider.id}' already registered; overwriting`);\n }\n this.providers.set(provider.id, provider);\n if (this.activeId === undefined) {\n this.activeId = provider.id;\n }\n }\n\n setActive(id: string): void {\n if (!this.providers.has(id)) {\n throw new Error(`Provider '${id}' is not registered`);\n }\n this.activeId = id;\n }\n\n getActive(): SearchProvider {\n if (this.activeId === undefined) {\n throw new Error('No search provider registered');\n }\n const provider = this.providers.get(this.activeId);\n if (!provider) {\n throw new Error(`Active provider '${this.activeId}' disappeared`);\n }\n return provider;\n }\n\n get(id: string): SearchProvider | undefined {\n return this.providers.get(id);\n }\n\n list(): string[] {\n return Array.from(this.providers.keys());\n }\n}\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type {\n FetchRequest,\n FetchResult,\n SearchProvider,\n SearchRequest,\n SearchResult,\n} from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TinyfishSearchResponse {\n query: string;\n results: Array<{\n position: number;\n site_name?: string;\n title: string;\n snippet: string;\n url: string;\n }>;\n total_results: number;\n page: number;\n}\n\ninterface TinyfishFetchResponse {\n results: Array<{\n url: string;\n final_url?: string;\n title?: string;\n description?: string;\n language?: string;\n format?: string;\n text: string;\n }>;\n errors: Array<{ url: string; error: string }>;\n}\n\nexport class TinyfishProvider implements SearchProvider {\n readonly id = 'tinyfish';\n readonly name = 'TinyFish';\n readonly description = 'TinyFish Search & Fetch (free tier, live web results)';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly searchBaseUrl: string;\n private readonly fetchBaseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.searchBaseUrl = config.baseUrl ?? 'https://api.search.tinyfish.ai';\n this.fetchBaseUrl =\n (config.options?.fetchBaseUrl as string | undefined) ?? 'https://api.fetch.tinyfish.ai';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const url = new URL(this.searchBaseUrl);\n url.searchParams.set('query', request.query);\n if (request.maxResults && request.maxResults > 0) {\n url.searchParams.set('max_results', String(Math.floor(request.maxResults)));\n }\n if (typeof request.recencyDays === 'number' && request.recencyDays > 0) {\n url.searchParams.set('recency_minutes', String(Math.floor(request.recencyDays * 24 * 60)));\n }\n if (request.purpose) {\n url.searchParams.set('purpose', request.purpose);\n }\n if (request.location) {\n url.searchParams.set('location', request.location);\n }\n if (request.language) {\n url.searchParams.set('language', request.language);\n }\n if (request.domainType) {\n url.searchParams.set('domain_type', request.domainType);\n }\n if (request.includeDomains && request.includeDomains.length > 0) {\n url.searchParams.set('include_domains', request.includeDomains.join(','));\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n url.searchParams.set('exclude_domains', request.excludeDomains.join(','));\n }\n\n const response = await fetchWithTimeout(url.toString(), {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey!,\n Accept: 'application/json',\n },\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Search');\n\n const body = (await response.json()) as TinyfishSearchResponse;\n const items = (body.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.snippet,\n source: r.site_name,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${body.total_results ?? items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n urls: [request.url],\n format: request.format ?? 'markdown',\n };\n if (request.query) {\n body.purpose = request.query;\n }\n if (typeof request.ttl === 'number') {\n body.ttl = request.ttl;\n }\n\n const response = await fetchWithTimeout(this.fetchBaseUrl, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Fetch');\n\n const data = (await response.json()) as TinyfishFetchResponse;\n const page = data.results?.[0];\n const error = data.errors?.[0];\n\n if (!page && error) {\n throw new Error(`TinyFish Fetch failed for ${error.url}: ${error.error}`);\n }\n if (!page) {\n throw new Error('TinyFish Fetch returned no content');\n }\n\n return {\n summary: page.title ?? page.description ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error(\n 'TinyFish API key is missing. Set TINYFISH_API_KEY or configure providers.tinyfish.apiKey.',\n );\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TavilySearchResponse {\n query: string;\n results: Array<{\n title: string;\n url: string;\n content?: string;\n raw_content?: string;\n score?: number;\n }>;\n}\n\nexport class TavilyProvider implements SearchProvider {\n readonly id = 'tavily';\n readonly name = 'Tavily';\n readonly description = 'Tavily web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.tavily.com';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n search_depth: 'basic',\n max_results: request.maxResults ?? 5,\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Tavily Search');\n\n const data = (await response.json()) as TavilySearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.content ?? r.raw_content ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Tavily API key is missing. Set TAVILY_API_KEY or configure providers.tavily.apiKey.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface ExaSearchResponse {\n results: Array<{\n title?: string;\n url: string;\n publishedDate?: string;\n author?: string;\n text?: string;\n score?: number;\n }>;\n}\n\ninterface ExaContentsResponse {\n results: Array<{\n url: string;\n text?: string;\n title?: string;\n }>;\n}\n\nexport class ExaProvider implements SearchProvider {\n readonly id = 'exa';\n readonly name = 'Exa';\n readonly description = 'Exa neural web search and contents fetch';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.exa.ai';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n num_results: request.maxResults ?? 5,\n type: 'auto',\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Search');\n\n const data = (await response.json()) as ExaSearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.text ?? '',\n publishedAt: r.publishedDate,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n urls: [request.url],\n text: true,\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/contents`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Contents');\n\n const data = (await response.json()) as ExaContentsResponse;\n const page = data.results?.[0];\n if (!page) {\n throw new Error('Exa Contents returned no content');\n }\n\n return {\n summary: page.title ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Exa API key is missing. Set EXA_API_KEY or configure providers.exa.apiKey.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface FirecrawlSearchResponse {\n success: boolean;\n data: {\n web: Array<{\n title?: string;\n url: string;\n description?: string;\n markdown?: string;\n }>;\n };\n}\n\ninterface FirecrawlScrapeResponse {\n success: boolean;\n data: {\n markdown?: string;\n metadata?: {\n title?: string;\n sourceURL?: string;\n };\n };\n}\n\nexport class FirecrawlProvider implements SearchProvider {\n readonly id = 'firecrawl';\n readonly name = 'Firecrawl';\n readonly description = 'Firecrawl web search and page scrape';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? 'https://api.firecrawl.dev';\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n limit: request.maxResults ?? 5,\n sources: ['web'],\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.includeDomains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.excludeDomains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Search');\n\n const data = (await response.json()) as FirecrawlSearchResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Search returned success: false');\n }\n\n const rawItems = data.data?.web ?? [];\n const items = rawItems.map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.description ?? r.markdown ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n url: request.url,\n formats: ['markdown'],\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/scrape`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Scrape');\n\n const data = (await response.json()) as FirecrawlScrapeResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Scrape returned success: false');\n }\n\n return {\n summary: data.data?.metadata?.title ?? 'Fetched page content',\n content: data.data?.markdown ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Firecrawl API key is missing. Set FIRECRAWL_API_KEY or configure providers.firecrawl.apiKey.');\n }\n }\n}","// Codex search protocol behavior is based on dsh-codex-connect (Apache-2.0).\n// This provider only consumes an existing static credential; it never logs in,\n// refreshes tokens, or writes credential data.\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { ProviderAuthenticationError } from './utils.js';\n\nconst CODEX_SEARCH_URL = 'https://chatgpt.com/backend-api/codex/alpha/search';\nconst MAX_CREDENTIAL_BYTES = 128 * 1024;\nconst MAX_SUCCESS_RESPONSE_BYTES = 4 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\n\ninterface StaticCodexCredential {\n accessToken: string;\n accountId: string;\n}\n\ninterface CodexSearchRequestBody {\n id: string;\n model: string;\n input: Array<{\n type: 'message';\n role: 'user';\n content: Array<{ type: 'input_text'; text: string }>;\n }>;\n commands: { search_query: Array<{ q: string }> };\n settings: {\n search_context_size: 'medium';\n allowed_callers: ['direct'];\n external_web_access: false;\n };\n max_output_tokens: number;\n}\n\nexport class CodexProvider implements SearchProvider {\n readonly id = 'codex';\n readonly name = 'OpenAI Codex';\n readonly description = 'ChatGPT Codex standalone web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly credentialFile?: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.credentialFile = configuredCredentialFile();\n this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0\n ? config.timeoutMs\n : 55_000;\n }\n\n available(): boolean {\n return this.credentialFile !== undefined\n && loadStaticCredential(this.credentialFile) !== undefined;\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n throwIfAborted(signal);\n\n const credential = this.credentialFile === undefined\n ? undefined\n : loadStaticCredential(this.credentialFile);\n if (!credential) {\n throw new Error('OpenAI Codex credential file is not configured, missing or invalid');\n }\n\n const body: CodexSearchRequestBody = {\n id: randomUUID(),\n model: 'gpt-5.6-sol',\n input: [{\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: request.query }],\n }],\n commands: { search_query: [{ q: request.query }] },\n settings: {\n search_context_size: 'medium',\n allowed_callers: ['direct'],\n external_web_access: false,\n },\n max_output_tokens: 10_000,\n };\n\n const requestTimeout = AbortSignal.timeout(this.timeoutMs);\n const requestSignal = signal ? AbortSignal.any([signal, requestTimeout]) : requestTimeout;\n let response: Response;\n try {\n response = await fetch(CODEX_SEARCH_URL, {\n method: 'POST',\n redirect: 'error',\n headers: {\n Authorization: `Bearer ${credential.accessToken}`,\n 'chatgpt-account-id': credential.accountId,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n originator: 'deepseek-harness',\n },\n body: JSON.stringify(body),\n signal: requestSignal,\n });\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n throw new Error('OpenAI Codex search request failed', { cause: error });\n }\n\n let payload: unknown;\n try {\n payload = await readBoundedJson(\n response,\n response.ok ? MAX_SUCCESS_RESPONSE_BYTES : MAX_ERROR_RESPONSE_BYTES,\n requestSignal,\n );\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n if (error instanceof ResponseTooLargeError) throw error;\n throw new Error(`OpenAI Codex returned invalid JSON (HTTP ${response.status})`, { cause: error });\n }\n\n if (!response.ok) {\n const detail = providerMessage(payload);\n const suffix = detail ? `: ${detail}` : '';\n if (response.status === 401 || response.status === 403) {\n throw new ProviderAuthenticationError(\n `OpenAI Codex credential is invalid or expired (HTTP ${response.status})${suffix}`,\n );\n }\n throw new Error(`OpenAI Codex search failed (HTTP ${response.status})${suffix}`);\n }\n\n return mapCodexSearchResponse(payload);\n }\n}\n\nfunction configuredCredentialFile(): string | undefined {\n const filename = readNonEmptyString(process.env.CODEX_CREDENTIAL_FILE);\n return filename === undefined ? undefined : resolve(filename);\n}\n\nfunction loadStaticCredential(filename: string): StaticCodexCredential | undefined {\n try {\n const stats = statSync(filename);\n if (!stats.isFile() || stats.size <= 0 || stats.size > MAX_CREDENTIAL_BYTES) return undefined;\n const value: unknown = JSON.parse(readFileSync(filename, 'utf8'));\n return parseStaticCredential(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapCodexSearchResponse(value: unknown): SearchResult {\n if (!isRecord(value) || typeof value.output !== 'string') {\n throw new Error('OpenAI Codex returned a search response without string output');\n }\n if (value.results !== undefined && !Array.isArray(value.results)) {\n throw new Error('OpenAI Codex returned a search response with non-array results');\n }\n\n const items: SearchResult['items'] = [];\n const seen = new Set<string>();\n for (const item of value.results ?? []) {\n if (!isRecord(item) || item.type !== 'text_result') continue;\n const url = citeableUrl(item.url);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n items.push({\n title: readNonEmptyString(item.title) ?? url,\n url,\n snippet: readNonEmptyString(item.snippet) ?? '',\n });\n }\n\n const summary = value.output;\n return {\n status: summary.length > 0 ? 'ok' : 'degraded',\n source: 'OpenAI Codex',\n summary: summary || `Codex returned ${items.length} source(s) without summary text`,\n items,\n uncertainty: summary.length > 0 ? [] : ['Codex returned no summary text'],\n };\n}\n\nfunction parseStaticCredential(value: unknown): StaticCodexCredential | undefined {\n if (!isRecord(value)) return undefined;\n\n const candidates = [\n value,\n isRecord(value.credential) ? value.credential : undefined,\n isRecord(value.tokens) ? value.tokens : undefined,\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n const accessToken = readNonEmptyString(candidate.access_token)\n ?? readNonEmptyString(candidate.access);\n if (!accessToken) continue;\n\n const accountId = readNonEmptyString(candidate.account_id)\n ?? readNonEmptyString(candidate.accountId)\n ?? accountIdFromToken(accessToken);\n if (!accountId) continue;\n\n return { accessToken, accountId };\n }\n\n return undefined;\n}\n\nfunction decodeJwtPayload(accessToken: string): Record<string, unknown> | undefined {\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload: unknown = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));\n return isRecord(payload) ? payload : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction accountIdFromToken(accessToken: string): string | undefined {\n const auth = decodeJwtPayload(accessToken)?.['https://api.openai.com/auth'];\n return isRecord(auth) ? readNonEmptyString(auth.chatgpt_account_id) : undefined;\n}\n\nclass ResponseTooLargeError extends Error {\n constructor(limit: number) {\n super(`OpenAI Codex search response exceeded ${limit} bytes`);\n this.name = 'ResponseTooLargeError';\n }\n}\n\nasync function readBoundedJson(\n response: Response,\n maxBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel().catch(() => {});\n throw new ResponseTooLargeError(maxBytes);\n }\n if (!response.body) throw new SyntaxError('empty response body');\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await abortable(reader.read(), signal);\n if (done) break;\n if (!value) continue;\n total += value.byteLength;\n if (total > maxBytes) throw new ResponseTooLargeError(maxBytes);\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => {});\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n return JSON.parse(Buffer.concat(chunks, total).toString('utf8')) as unknown;\n}\n\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortedError(signal));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortedError(signal));\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw abortedError(signal);\n}\n\nfunction abortedError(signal: AbortSignal): Error {\n return new Error('OpenAI Codex search aborted', { cause: signal.reason });\n}\n\nfunction providerMessage(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined;\n const raw = typeof value.error === 'string'\n ? value.error\n : isRecord(value.error) && typeof value.error.message === 'string'\n ? value.error.message\n : typeof value.message === 'string'\n ? value.message\n : undefined;\n return raw\n ?.replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu, '[REDACTED]')\n .slice(0, 1_000);\n}\n\nfunction citeableUrl(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction readNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;\n}\n\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\n\nexport class DemoProvider implements SearchProvider {\n readonly id = 'demo';\n readonly name = 'Demo Provider';\n readonly description = 'Returns canned results for integration testing without calling any external API.';\n readonly searchable = true;\n readonly fetchable = true;\n\n constructor(_config: ResolvedProviderConfig) {}\n\n available(): boolean {\n return true;\n }\n\n async search(request: SearchRequest): Promise<SearchResult> {\n return {\n status: 'ok',\n source: this.name,\n summary: `Demo search results for \"${request.query}\"`,\n items: [\n {\n title: 'Demo Result 1',\n url: 'https://example.com/demo1',\n snippet: 'This is a demo search result used to verify the DSH plugin wiring.',\n },\n {\n title: 'Demo Result 2',\n url: 'https://example.com/demo2',\n snippet: 'Another canned result so citation cards have something to render.',\n },\n ],\n uncertainty: ['Demo mode: results are not from a real search engine.'],\n };\n }\n\n async fetch(request: FetchRequest): Promise<FetchResult> {\n return {\n summary: `Demo fetch of ${request.url}`,\n content: `# Demo Page\\n\\nThis is canned content for the URL \"${request.url}\".`,\n links: [\n { text: 'Example', url: 'https://example.com' },\n ],\n uncertainty: ['Demo mode: content is not from a real web page.'],\n warnings: [],\n };\n }\n}","export const fetchSchema = {\n type: 'object' as const,\n properties: {\n summary: { type: 'string' as const },\n content: { type: 'string' as const },\n links: {\n type: 'array' as const,\n items: {\n type: 'object' as const,\n properties: {\n text: { type: 'string' as const },\n url: { type: 'string' as const }\n },\n required: ['text', 'url'] as const\n }\n },\n uncertainty: {\n type: 'array' as const,\n items: { type: 'string' as const }\n },\n warnings: {\n type: 'array' as const,\n items: { type: 'string' as const }\n }\n },\n required: ['summary', 'content', 'uncertainty'] as const\n};\n","import type { ProviderRegistry } from '../providers/registry.js';\n\nexport const FALLBACK_ATTEMPT_TIMEOUT_MS = 20_000;\n\nexport interface AttemptContext {\n signal: AbortSignal;\n timedOut(): boolean;\n dispose(): void;\n}\n\nexport function resolveCandidateIds(\n registry: ProviderRegistry,\n fallbackChain?: string[],\n): string[] {\n let activeId: string | undefined;\n try {\n activeId = registry.getActive().id;\n } catch {\n // An empty registry has no active provider.\n }\n\n const configured = fallbackChain === undefined ? registry.list() : fallbackChain;\n const seen = new Set<string>();\n const candidates: string[] = [];\n for (const id of activeId ? [activeId, ...configured] : configured) {\n if (seen.has(id)) continue;\n seen.add(id);\n candidates.push(id);\n }\n return candidates;\n}\n\nexport function createAttemptContext(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): AttemptContext {\n const controller = new AbortController();\n let didTimeOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onParentAbort: (() => void) | undefined;\n\n if (parent?.aborted) {\n controller.abort(parent.reason);\n } else {\n if (parent) {\n onParentAbort = () => controller.abort(parent.reason);\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n timer = setTimeout(() => {\n didTimeOut = true;\n controller.abort(new Error(`fallback attempt timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n timedOut: () => didTimeOut,\n dispose() {\n if (timer !== undefined) clearTimeout(timer);\n if (parent && onParentAbort) parent.removeEventListener('abort', onParentAbort);\n },\n };\n}\n\nexport function raceWithSignal<T>(operation: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return operation;\n if (signal.aborted) {\n operation.catch(() => {});\n return Promise.reject(signal.reason);\n }\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw signal.reason;\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { FetchRequest, FetchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport { fetchSchema } from '../schemas/fetch-schema.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\nconst READ_PAGE_TIMEOUT_MS = 180_000;\nconst RENDER_CONTENT_CAP = 20_000;\nconst RENDER_LINK_CAP = 20;\n\nexport function registerReadPageTool(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.tools?.register !== 'function') {\n console.error('[search-providers] ctx.tools.register is not available; read_page tool skipped');\n return;\n }\n\n ctx.tools.register({\n name: 'read_page',\n description:\n 'Read one web page through the configured search provider. Use when a message references a specific http(s) URL whose content matters: docs, an article, a changelog, a thread. Returns structured evidence with a summary, the extracted content, outgoing links, uncertainty, and operational warnings. Pass \"query\" to focus the reading on one question.',\n parameters: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The http(s) URL to read',\n },\n query: {\n type: 'string',\n description: 'Optional question to focus the reading on (e.g. \"what are the rate limits\")',\n },\n },\n required: ['url'],\n },\n output: {\n schema: fetchSchema,\n render: (_args: unknown, value: Record<string, unknown>) => [\n { type: 'text', text: renderFetchEvidence(value) },\n ],\n },\n timeoutMs: READ_PAGE_TIMEOUT_MS + 20_000,\n isConcurrencySafe: () => true,\n presentCall: (args: { url: string; query?: string }) => ({\n card: 'generic',\n title: 'read_page',\n kind: 'fetch',\n rawInput: args,\n }),\n async execute(args: { url: string; query?: string }, exec: { signal?: AbortSignal }) {\n if (typeof args?.url !== 'string' || !/^https?:\\/\\//i.test(args.url.trim())) {\n throw new Error('read_page needs an http(s) \"url\".');\n }\n\n const request: FetchRequest = {\n url: args.url.trim(),\n query: args.query,\n format: 'markdown',\n };\n\n return fetchWithFallback(registry, cooldown, fallbackChain, request, exec.signal);\n },\n });\n}\n\nfunction resolveFetchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n}\n\nasync function fetchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: FetchRequest,\n signal: AbortSignal | undefined,\n): Promise<FetchResult> {\n throwIfAborted(signal);\n const candidates = resolveFetchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'fetch');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available()\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.fetch!(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n if (attempted.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Fallback used after ${attempted.join(', ')} failed`);\n }\n if (cooling.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Cooling providers skipped: ${cooling.join(', ')}`);\n }\n if (disabled.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Providers disabled until restart: ${disabled.join(', ')}`);\n }\n return result;\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' fetch failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'fetch', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No fetch provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderFetchEvidence(value: Record<string, unknown>): string {\n const lines: string[] = [String(value.summary ?? '')];\n const content = String((value.content as string) ?? '').trim();\n if (content) {\n lines.push('', 'Content:', content.length > RENDER_CONTENT_CAP ? `${content.slice(0, RENDER_CONTENT_CAP)}…` : content);\n }\n const links = Array.isArray(value.links) ? value.links.slice(0, RENDER_LINK_CAP) : [];\n if (links.length > 0) {\n lines.push('', 'Links:');\n for (const link of links) {\n const text = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).text ?? '') : '';\n const url = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).url ?? '') : '';\n lines.push(`- ${text} — ${url}`);\n }\n }\n const uncertainty = Array.isArray(value.uncertainty) ? value.uncertainty : [];\n if (uncertainty.length > 0) {\n lines.push('', `Uncertain: ${uncertainty.join('; ')}`);\n }\n const warnings = Array.isArray(value.warnings) ? value.warnings : [];\n if (warnings.length > 0) {\n lines.push('', `Warnings: ${warnings.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { SearchRequest, SearchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\ninterface WebSeamSearchRequest {\n query: string;\n maxResults?: number;\n recencyDays?: number;\n purpose?: string;\n location?: string;\n language?: string;\n}\n\nexport function registerWebSearchProvider(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.web?.registerSearchProvider !== 'function') {\n console.error('[search-providers] ctx.web.registerSearchProvider is not available; web_search provider skipped');\n return;\n }\n\n ctx.web.registerSearchProvider({\n id: 'search-providers',\n available: () => anyProviderAvailable(registry, cooldown, fallbackChain),\n async search(request: WebSeamSearchRequest, signal: AbortSignal | undefined) {\n const searchRequest: SearchRequest = {\n query: request.query,\n maxResults: request.maxResults,\n recencyDays: request.recencyDays,\n purpose: request.purpose,\n location: request.location,\n language: request.language,\n };\n\n const { result, attempted, cooling, disabled } = await searchWithFallback(\n registry,\n cooldown,\n fallbackChain,\n searchRequest,\n signal,\n );\n\n return {\n content: renderContent(result, attempted, cooling, disabled),\n sources: result.items.map((item) => ({\n url: item.url,\n ...(item.title ? { title: item.title } : {}),\n ...(item.snippet ? { snippet: item.snippet } : {}),\n ...(item.publishedAt ? { publishedAt: item.publishedAt } : {}),\n })),\n truncated: false,\n };\n },\n });\n}\n\nfunction resolveSearchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined && provider.searchable !== false;\n });\n}\n\nfunction anyProviderAvailable(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n): boolean {\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const { ready } = cooldown.filter(candidates, 'search');\n return ready.some((id) => {\n const provider = registry.get(id);\n return provider?.available() && provider.searchable !== false;\n });\n}\n\ninterface FallbackOutcome {\n result: SearchResult;\n attempted: string[];\n cooling: string[];\n disabled: string[];\n}\n\nasync function searchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: SearchRequest,\n signal: AbortSignal | undefined,\n): Promise<FallbackOutcome> {\n throwIfAborted(signal);\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'search');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available() && provider.searchable !== false;\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.search(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n return { result, attempted, cooling, disabled };\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' search failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'search', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No search provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderContent(\n result: SearchResult,\n attempted: string[],\n cooling: string[],\n disabled: string[],\n): string {\n const lines = [`[Search provider: ${result.source}]`, result.summary];\n if (attempted.length > 0) {\n lines.push(`[Fallback used after ${attempted.join(', ')} failed]`);\n }\n if (cooling.length > 0) {\n lines.push(`[Cooling: ${cooling.join(', ')}]`);\n }\n if (disabled.length > 0) {\n lines.push(`[Disabled until restart: ${disabled.join(', ')}]`);\n }\n if (result.uncertainty.length > 0) {\n lines.push(`Uncertain: ${result.uncertainty.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import { resolveProviderConfig } from './config.js';\nimport { CooldownController } from './providers/cooldown.js';\nimport { ProviderRegistry } from './providers/registry.js';\nimport { TinyfishProvider } from './providers/tinyfish.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { ExaProvider } from './providers/exa.js';\nimport { FirecrawlProvider } from './providers/firecrawl.js';\nimport { CodexProvider } from './providers/codex.js';\nimport { DemoProvider } from './providers/demo.js';\nimport type { PluginConfig } from './config.js';\nimport { registerReadPageTool } from './tools/read-page.js';\nimport { registerWebSearchProvider } from './tools/web-search.js';\n\nexport const name = 'search-providers';\nexport const inject = ['tools', 'web'];\n\nconst DEFAULT_PROVIDER_ORDER = ['codex', 'tinyfish', 'tavily', 'exa', 'firecrawl'];\n\nexport function apply(ctx: any, rawConfig: PluginConfig = {}): void {\n const registry = new ProviderRegistry();\n const cooldown = new CooldownController();\n const demoMode = rawConfig.provider === 'demo' || process.env.SEARCH_PROVIDERS_DEMO === '1';\n\n // Register providers in priority order.\n registry.register(new CodexProvider(resolveProviderConfig(rawConfig, 'codex', 'CODEX')));\n registry.register(new TinyfishProvider(resolveProviderConfig(rawConfig, 'tinyfish', 'TINYFISH')));\n registry.register(new TavilyProvider(resolveProviderConfig(rawConfig, 'tavily', 'TAVILY')));\n registry.register(new ExaProvider(resolveProviderConfig(rawConfig, 'exa', 'EXA')));\n registry.register(new FirecrawlProvider(resolveProviderConfig(rawConfig, 'firecrawl', 'FIRECRAWL')));\n\n if (demoMode) {\n registry.register(new DemoProvider(resolveProviderConfig(rawConfig, 'tinyfish', 'TINYFISH')));\n }\n\n // Determine active provider.\n const activeProvider = rawConfig.provider ?? (demoMode ? 'demo' : undefined);\n if (activeProvider) {\n try {\n registry.setActive(activeProvider);\n } catch (error) {\n console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);\n }\n }\n\n // The active provider is always tried first. An explicit empty chain disables fallback.\n const fallbackChain = rawConfig.fallbackChain ?? DEFAULT_PROVIDER_ORDER;\n\n // Register tools.\n registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);\n registerReadPageTool(ctx, registry, cooldown, fallbackChain);\n\n console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(', ')}]`);\n}"],"names":["throwIfAborted","resolve","provider","cooling","disabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/config.ts","../src/providers/utils.ts","../src/providers/cooldown.ts","../src/providers/registry.ts","../src/providers/tinyfish.ts","../src/providers/tavily.ts","../src/providers/exa.ts","../src/providers/firecrawl.ts","../src/providers/codex.ts","../src/providers/demo.ts","../src/schemas/fetch-schema.ts","../src/tools/fallback-utils.ts","../src/tools/read-page.ts","../src/tools/web-search.ts","../src/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\n\nexport interface ProviderConfigEntry {\n apiKey?: string;\n credentialFile?: string;\n timeoutMs?: number;\n options?: Record<string, unknown>;\n}\n\n/** Cordis only selects the file; provider settings live in that file. */\nexport interface PluginConfig {\n configFile?: string;\n}\n\nexport interface SearchProvidersConfig {\n /** Active provider id. */\n provider?: string;\n providers?: Record<string, ProviderConfigEntry>;\n /** An empty array disables fallback. */\n fallbackChain?: string[];\n}\n\nexport interface ResolvedProviderConfig {\n apiKey?: string;\n credentialFile?: string;\n timeoutMs: number;\n options: Record<string, unknown>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 55_000;\n\nexport function loadPluginConfig(pluginConfig: PluginConfig = {}): SearchProvidersConfig {\n if (!isRecord(pluginConfig) || Object.keys(pluginConfig).some((key) => key !== 'configFile')) {\n throw new Error('search-providers: Cordis config only accepts configFile. Move provider, providers and fallbackChain to search-providers.json.');\n }\n if (pluginConfig.configFile !== undefined && !isNonEmptyString(pluginConfig.configFile)) {\n throw new Error('search-providers: configFile must be a non-empty string.');\n }\n\n const explicit = pluginConfig.configFile !== undefined;\n const filename = resolveConfigPath(pluginConfig.configFile ?? join(homedir(), '.dsh', 'search-providers.json'));\n let text: string;\n try {\n text = readFileSync(filename, 'utf8');\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (!explicit && code === 'ENOENT') {\n console.warn(\n '[search-providers] Config file not found: ' + filename + '. Create it to configure providers; environment variables are not used.',\n );\n return {};\n }\n throw new Error('search-providers: Cannot read config file ' + filename + ' (' + (code ?? 'read error') + ').');\n }\n\n let value: unknown;\n try {\n // Accept a UTF-8 BOM, including files written by Windows editors.\n value = JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);\n } catch {\n // JSON parser messages can contain secrets from the source; do not forward them.\n throw new Error('search-providers: Invalid JSON in ' + filename + '.');\n }\n validateConfig(value, filename);\n for (const entry of Object.values(value.providers ?? {})) {\n if (entry.credentialFile !== undefined) {\n entry.credentialFile = resolveConfigPath(entry.credentialFile, dirname(filename));\n }\n }\n return value;\n}\n\nexport function resolveProviderConfig(\n pluginConfig: SearchProvidersConfig,\n providerId: string,\n): ResolvedProviderConfig {\n const pc = pluginConfig.providers?.[providerId] ?? {};\n return {\n apiKey: pc.apiKey,\n credentialFile: pc.credentialFile,\n timeoutMs: pc.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n options: pc.options ?? {},\n };\n}\n\nfunction resolveConfigPath(filename: string, base = process.cwd()): string {\n const path = filename.trim();\n if (path === '~') return homedir();\n if (path.startsWith('~/') || path.startsWith('~\\\\')) {\n return resolve(homedir(), path.slice(2));\n }\n return resolve(base, path);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction validateConfig(value: unknown, filename: string): asserts value is SearchProvidersConfig {\n function invalid(field: string): never {\n throw new Error('search-providers: Invalid config in ' + filename + ': ' + field + '.');\n }\n if (!isRecord(value)) invalid('expected a JSON object');\n // No values are interpolated into validation errors (they may be credentials).\n if (Object.keys(value).some((key) => !['provider', 'providers', 'fallbackChain'].includes(key))) {\n invalid('unknown top-level field; use provider, providers or fallbackChain');\n }\n if (value.provider !== undefined && !isNonEmptyString(value.provider)) {\n invalid('provider must be a non-empty string');\n }\n if (value.fallbackChain !== undefined &&\n (!Array.isArray(value.fallbackChain) || !value.fallbackChain.every(isNonEmptyString))) {\n invalid('fallbackChain must be an array of non-empty strings');\n }\n if (value.providers === undefined) return;\n if (!isRecord(value.providers)) invalid('providers must be an object');\n for (const entry of Object.values(value.providers)) {\n if (!isRecord(entry)) invalid('each provider must be an object');\n if (Object.keys(entry).some((key) => !['apiKey', 'credentialFile', 'timeoutMs', 'options'].includes(key))) {\n invalid('unknown provider field; use apiKey, credentialFile, timeoutMs or options (official service URLs are built in)');\n }\n if (entry.apiKey !== undefined && typeof entry.apiKey !== 'string') invalid('apiKey must be a string');\n if (entry.credentialFile !== undefined && !isNonEmptyString(entry.credentialFile)) {\n invalid('credentialFile must be a non-empty string');\n }\n if (entry.timeoutMs !== undefined &&\n (typeof entry.timeoutMs !== 'number' || !Number.isSafeInteger(entry.timeoutMs) || entry.timeoutMs <= 0 || entry.timeoutMs > 2_147_483_647)) {\n invalid('timeoutMs must be a positive integer no greater than 2147483647');\n }\n if (entry.options !== undefined) {\n if (!isRecord(entry.options)) invalid('options must be an object');\n if (Object.hasOwn(entry.options, 'fetchBaseUrl')) {\n invalid('remove options.fetchBaseUrl; official service URLs are built in');\n }\n }\n }\n}\n","export class ProviderAuthenticationError extends Error {\n readonly kind = 'provider-auth' as const;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProviderAuthenticationError';\n }\n}\n\nexport class ProviderHttpError extends Error {\n readonly kind = 'provider-http' as const;\n\n constructor(\n message: string,\n readonly status: number,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'ProviderHttpError';\n }\n}\n\nexport async function fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n try {\n const signal = init.signal\n ? combineSignals(init.signal, controller.signal)\n : controller.signal;\n return await fetch(url, { ...init, signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport function combineSignals(a: AbortSignal, b: AbortSignal): AbortSignal {\n return AbortSignal.any([a, b]);\n}\n\nexport async function throwOnError(response: Response, label: string): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => '');\n let detail = text.slice(0, 500);\n if (response.status === 401) {\n detail = 'Invalid or missing API key';\n } else if (response.status === 429) {\n detail = 'Rate limit exceeded';\n } else if (response.status === 432) {\n detail = 'Usage limit exceeded';\n }\n const message = `${label} returned ${response.status}: ${detail}`;\n if (response.status === 401) {\n throw new ProviderAuthenticationError(message);\n }\n throw new ProviderHttpError(message, response.status);\n}\n","import { ProviderAuthenticationError, ProviderHttpError } from './utils.js';\n\nexport type ProviderCapability = 'search' | 'fetch';\n\nexport interface CooldownEntry {\n until: number;\n reason: string;\n}\n\nexport class CooldownController {\n private disabled = new Map<string, CooldownEntry>();\n private cooling = new Map<string, CooldownEntry>();\n\n /** Record one capability failure. Confirmed authentication failures are provider-global. */\n record(providerId: string, capability: ProviderCapability, error: unknown): boolean {\n if (error instanceof ProviderAuthenticationError) {\n this.disabled.set(providerId, {\n until: Number.POSITIVE_INFINITY,\n reason: 'invalid credentials',\n });\n return true;\n }\n\n this.cooling.set(this.capabilityKey(providerId, capability), {\n until: Date.now() + this.cooldownDuration(error),\n reason: this.classifyError(error),\n });\n return true;\n }\n\n /** Check whether one provider capability is temporarily cooling. */\n isCooling(providerId: string, capability: ProviderCapability): boolean {\n return this.getEntry(this.cooling, this.capabilityKey(providerId, capability)) !== undefined;\n }\n\n /** Check whether confirmed authentication failure disabled the provider until restart. */\n isDisabled(providerId: string): boolean {\n return this.getEntry(this.disabled, providerId) !== undefined;\n }\n\n /** Get the current failure reason, if any. */\n getReason(providerId: string, capability: ProviderCapability): string | undefined {\n return this.getEntry(this.disabled, providerId)?.reason\n ?? this.getEntry(this.cooling, this.capabilityKey(providerId, capability))?.reason;\n }\n\n /** Build candidates while separating global disablement from capability cooldown. */\n filter(\n chain: string[],\n capability: ProviderCapability,\n ): { ready: string[]; cooling: string[]; disabled: string[] } {\n const ready: string[] = [];\n const cooling: string[] = [];\n const disabled: string[] = [];\n for (const id of chain) {\n if (this.isDisabled(id)) {\n disabled.push(id);\n } else if (this.isCooling(id, capability)) {\n cooling.push(id);\n } else {\n ready.push(id);\n }\n }\n return { ready, cooling, disabled };\n }\n\n private capabilityKey(providerId: string, capability: ProviderCapability): string {\n return `${providerId}:${capability}`;\n }\n\n private getEntry(map: Map<string, CooldownEntry>, key: string): CooldownEntry | undefined {\n const entry = map.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.until) {\n map.delete(key);\n return undefined;\n }\n return entry;\n }\n\n private classifyError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (this.isRateLimitError(error, message)) return 'rate limit or quota exceeded';\n if (/timeout|timed out/i.test(message)) return 'timeout';\n if (/network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) return 'network error';\n return 'request failed';\n }\n\n private cooldownDuration(error: unknown): number {\n const message = error instanceof Error ? error.message : String(error);\n // Rate limits / quotas: cool for 5 minutes.\n if (this.isRateLimitError(error, message)) return 5 * 60 * 1000;\n // Timeouts / network errors: cool for 30 seconds.\n if (/timeout|timed out|network|ENOTFOUND|ECONNREFUSED|fetch failed/i.test(message)) {\n return 30 * 1000;\n }\n // Generic failures: cool for 10 seconds.\n return 10 * 1000;\n }\n\n private isRateLimitError(error: unknown, message: string): boolean {\n return (\n (error instanceof ProviderHttpError && (error.status === 429 || error.status === 432))\n || /429|432|rate.?limit|quota|usage limit|out of credits|monthly quota/i.test(message)\n );\n }\n}\n","import type { SearchProvider } from './types.js';\n\nexport class ProviderRegistry {\n private providers = new Map<string, SearchProvider>();\n private activeId: string | undefined;\n\n register(provider: SearchProvider): void {\n if (this.providers.has(provider.id)) {\n console.warn(`[search-providers] provider '${provider.id}' already registered; overwriting`);\n }\n this.providers.set(provider.id, provider);\n if (this.activeId === undefined) {\n this.activeId = provider.id;\n }\n }\n\n setActive(id: string): void {\n if (!this.providers.has(id)) {\n throw new Error(`Provider '${id}' is not registered`);\n }\n this.activeId = id;\n }\n\n getActive(): SearchProvider {\n if (this.activeId === undefined) {\n throw new Error('No search provider registered');\n }\n const provider = this.providers.get(this.activeId);\n if (!provider) {\n throw new Error(`Active provider '${this.activeId}' disappeared`);\n }\n return provider;\n }\n\n get(id: string): SearchProvider | undefined {\n return this.providers.get(id);\n }\n\n list(): string[] {\n return Array.from(this.providers.keys());\n }\n}\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type {\n FetchRequest,\n FetchResult,\n SearchProvider,\n SearchRequest,\n SearchResult,\n} from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TinyfishSearchResponse {\n query: string;\n results: Array<{\n position: number;\n site_name?: string;\n title: string;\n snippet: string;\n url: string;\n }>;\n total_results: number;\n page: number;\n}\n\ninterface TinyfishFetchResponse {\n results: Array<{\n url: string;\n final_url?: string;\n title?: string;\n description?: string;\n language?: string;\n format?: string;\n text: string;\n }>;\n errors: Array<{ url: string; error: string }>;\n}\n\nexport class TinyfishProvider implements SearchProvider {\n readonly id = 'tinyfish';\n readonly name = 'TinyFish';\n readonly description = 'TinyFish Search & Fetch (free tier, live web results)';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly searchBaseUrl = 'https://api.search.tinyfish.ai';\n private readonly fetchBaseUrl = 'https://api.fetch.tinyfish.ai';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const url = new URL(this.searchBaseUrl);\n url.searchParams.set('query', request.query);\n if (request.maxResults && request.maxResults > 0) {\n url.searchParams.set('max_results', String(Math.floor(request.maxResults)));\n }\n if (typeof request.recencyDays === 'number' && request.recencyDays > 0) {\n url.searchParams.set('recency_minutes', String(Math.floor(request.recencyDays * 24 * 60)));\n }\n if (request.purpose) {\n url.searchParams.set('purpose', request.purpose);\n }\n if (request.location) {\n url.searchParams.set('location', request.location);\n }\n if (request.language) {\n url.searchParams.set('language', request.language);\n }\n if (request.domainType) {\n url.searchParams.set('domain_type', request.domainType);\n }\n if (request.includeDomains && request.includeDomains.length > 0) {\n url.searchParams.set('include_domains', request.includeDomains.join(','));\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n url.searchParams.set('exclude_domains', request.excludeDomains.join(','));\n }\n\n const response = await fetchWithTimeout(url.toString(), {\n method: 'GET',\n headers: {\n 'X-API-Key': this.apiKey!,\n Accept: 'application/json',\n },\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Search');\n\n const body = (await response.json()) as TinyfishSearchResponse;\n const items = (body.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.snippet,\n source: r.site_name,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${body.total_results ?? items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n urls: [request.url],\n format: request.format ?? 'markdown',\n };\n if (request.query) {\n body.purpose = request.query;\n }\n if (typeof request.ttl === 'number') {\n body.ttl = request.ttl;\n }\n\n const response = await fetchWithTimeout(this.fetchBaseUrl, {\n method: 'POST',\n headers: {\n 'X-API-Key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'TinyFish Fetch');\n\n const data = (await response.json()) as TinyfishFetchResponse;\n const page = data.results?.[0];\n const error = data.errors?.[0];\n\n if (!page && error) {\n throw new Error(`TinyFish Fetch failed for ${error.url}: ${error.error}`);\n }\n if (!page) {\n throw new Error('TinyFish Fetch returned no content');\n }\n\n return {\n summary: page.title ?? page.description ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error(\n 'TinyFish API key is missing. Configure providers.tinyfish.apiKey in the search-providers JSON config file.',\n );\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface TavilySearchResponse {\n query: string;\n results: Array<{\n title: string;\n url: string;\n content?: string;\n raw_content?: string;\n score?: number;\n }>;\n}\n\nexport class TavilyProvider implements SearchProvider {\n readonly id = 'tavily';\n readonly name = 'Tavily';\n readonly description = 'Tavily web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.tavily.com';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n search_depth: 'basic',\n max_results: request.maxResults ?? 5,\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Tavily Search');\n\n const data = (await response.json()) as TavilySearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title,\n url: r.url,\n snippet: r.content ?? r.raw_content ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Tavily API key is missing. Configure providers.tavily.apiKey in the search-providers JSON config file.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface ExaSearchResponse {\n results: Array<{\n title?: string;\n url: string;\n publishedDate?: string;\n author?: string;\n text?: string;\n score?: number;\n }>;\n}\n\ninterface ExaContentsResponse {\n results: Array<{\n url: string;\n text?: string;\n title?: string;\n }>;\n}\n\nexport class ExaProvider implements SearchProvider {\n readonly id = 'exa';\n readonly name = 'Exa';\n readonly description = 'Exa neural web search and contents fetch';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.exa.ai';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n num_results: request.maxResults ?? 5,\n type: 'auto',\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.include_domains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.exclude_domains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/search`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Search');\n\n const data = (await response.json()) as ExaSearchResponse;\n const items = (data.results ?? []).map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.text ?? '',\n publishedAt: r.publishedDate,\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n urls: [request.url],\n text: true,\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/contents`, {\n method: 'POST',\n headers: {\n 'x-api-key': this.apiKey!,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Exa Contents');\n\n const data = (await response.json()) as ExaContentsResponse;\n const page = data.results?.[0];\n if (!page) {\n throw new Error('Exa Contents returned no content');\n }\n\n return {\n summary: page.title ?? 'Fetched page content',\n content: page.text ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Exa API key is missing. Configure providers.exa.apiKey in the search-providers JSON config file.');\n }\n }\n}","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { fetchWithTimeout, throwOnError } from './utils.js';\n\ninterface FirecrawlSearchResponse {\n success: boolean;\n data: {\n web: Array<{\n title?: string;\n url: string;\n description?: string;\n markdown?: string;\n }>;\n };\n}\n\ninterface FirecrawlScrapeResponse {\n success: boolean;\n data: {\n markdown?: string;\n metadata?: {\n title?: string;\n sourceURL?: string;\n };\n };\n}\n\nexport class FirecrawlProvider implements SearchProvider {\n readonly id = 'firecrawl';\n readonly name = 'Firecrawl';\n readonly description = 'Firecrawl web search and page scrape';\n readonly searchable = true;\n readonly fetchable = true;\n\n private readonly apiKey?: string;\n private readonly baseUrl = 'https://api.firecrawl.dev';\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n this.apiKey = config.apiKey;\n this.timeoutMs = config.timeoutMs;\n }\n\n available(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey !== '';\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n this.ensureKey();\n\n const body: Record<string, unknown> = {\n query: request.query,\n limit: request.maxResults ?? 5,\n sources: ['web'],\n };\n if (request.includeDomains && request.includeDomains.length > 0) {\n body.includeDomains = request.includeDomains;\n }\n if (request.excludeDomains && request.excludeDomains.length > 0) {\n body.excludeDomains = request.excludeDomains;\n }\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/search`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Search');\n\n const data = (await response.json()) as FirecrawlSearchResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Search returned success: false');\n }\n\n const rawItems = data.data?.web ?? [];\n const items = rawItems.map((r) => ({\n title: r.title ?? r.url,\n url: r.url,\n snippet: r.description ?? r.markdown ?? '',\n }));\n\n return {\n status: 'ok',\n source: this.name,\n summary: items.length > 0\n ? `Found ${items.length} result(s) for \"${request.query}\"`\n : `No results found for \"${request.query}\"`,\n items,\n uncertainty: items.length === 0 ? ['No search results returned'] : [],\n };\n }\n\n async fetch(request: FetchRequest, signal?: AbortSignal): Promise<FetchResult> {\n this.ensureKey();\n\n const body = {\n url: request.url,\n formats: ['markdown'],\n };\n\n const response = await fetchWithTimeout(`${this.baseUrl}/v2/scrape`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n }, this.timeoutMs);\n\n await throwOnError(response, 'Firecrawl Scrape');\n\n const data = (await response.json()) as FirecrawlScrapeResponse;\n if (data.success === false) {\n throw new Error('Firecrawl Scrape returned success: false');\n }\n\n return {\n summary: data.data?.metadata?.title ?? 'Fetched page content',\n content: data.data?.markdown ?? '',\n uncertainty: [],\n warnings: [],\n };\n }\n\n private ensureKey(): void {\n if (!this.apiKey) {\n throw new Error('Firecrawl API key is missing. Configure providers.firecrawl.apiKey in the search-providers JSON config file.');\n }\n }\n}","// Codex search protocol behavior is based on dsh-codex-connect (Apache-2.0).\n// This provider only consumes an existing static credential; it never logs in,\n// refreshes tokens, or writes credential data.\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { ResolvedProviderConfig } from '../config.js';\nimport type { SearchProvider, SearchRequest, SearchResult } from './types.js';\nimport { ProviderAuthenticationError } from './utils.js';\n\nconst CODEX_SEARCH_URL = 'https://chatgpt.com/backend-api/codex/alpha/search';\nconst MAX_CREDENTIAL_BYTES = 128 * 1024;\nconst MAX_SUCCESS_RESPONSE_BYTES = 4 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\n\ninterface StaticCodexCredential {\n accessToken: string;\n accountId: string;\n}\n\ninterface CodexSearchRequestBody {\n id: string;\n model: string;\n input: Array<{\n type: 'message';\n role: 'user';\n content: Array<{ type: 'input_text'; text: string }>;\n }>;\n commands: { search_query: Array<{ q: string }> };\n settings: {\n search_context_size: 'medium';\n allowed_callers: ['direct'];\n external_web_access: false;\n };\n max_output_tokens: number;\n}\n\nexport class CodexProvider implements SearchProvider {\n readonly id = 'codex';\n readonly name = 'OpenAI Codex';\n readonly description = 'ChatGPT Codex standalone web search';\n readonly searchable = true;\n readonly fetchable = false;\n\n private readonly credentialFile?: string;\n private readonly timeoutMs: number;\n\n constructor(config: ResolvedProviderConfig) {\n const filename = readNonEmptyString(config.credentialFile);\n this.credentialFile = filename === undefined ? undefined : resolve(filename);\n this.timeoutMs = Number.isFinite(config.timeoutMs) && config.timeoutMs > 0\n ? config.timeoutMs\n : 55_000;\n }\n\n available(): boolean {\n return this.credentialFile !== undefined\n && loadStaticCredential(this.credentialFile) !== undefined;\n }\n\n async search(request: SearchRequest, signal?: AbortSignal): Promise<SearchResult> {\n throwIfAborted(signal);\n\n const credential = this.credentialFile === undefined\n ? undefined\n : loadStaticCredential(this.credentialFile);\n if (!credential) {\n throw new Error('OpenAI Codex credential file is not configured, missing or invalid');\n }\n\n const body: CodexSearchRequestBody = {\n id: randomUUID(),\n model: 'gpt-5.6-sol',\n input: [{\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: request.query }],\n }],\n commands: { search_query: [{ q: request.query }] },\n settings: {\n search_context_size: 'medium',\n allowed_callers: ['direct'],\n external_web_access: false,\n },\n max_output_tokens: 10_000,\n };\n\n const requestTimeout = AbortSignal.timeout(this.timeoutMs);\n const requestSignal = signal ? AbortSignal.any([signal, requestTimeout]) : requestTimeout;\n let response: Response;\n try {\n response = await fetch(CODEX_SEARCH_URL, {\n method: 'POST',\n redirect: 'error',\n headers: {\n Authorization: `Bearer ${credential.accessToken}`,\n 'chatgpt-account-id': credential.accountId,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n originator: 'deepseek-harness',\n },\n body: JSON.stringify(body),\n signal: requestSignal,\n });\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n throw new Error('OpenAI Codex search request failed', { cause: error });\n }\n\n let payload: unknown;\n try {\n payload = await readBoundedJson(\n response,\n response.ok ? MAX_SUCCESS_RESPONSE_BYTES : MAX_ERROR_RESPONSE_BYTES,\n requestSignal,\n );\n } catch (error) {\n throwIfAborted(signal);\n if (requestTimeout.aborted) {\n throw new Error(`OpenAI Codex search timed out after ${this.timeoutMs}ms`, { cause: error });\n }\n if (error instanceof ResponseTooLargeError) throw error;\n throw new Error(`OpenAI Codex returned invalid JSON (HTTP ${response.status})`, { cause: error });\n }\n\n if (!response.ok) {\n const detail = providerMessage(payload);\n const suffix = detail ? `: ${detail}` : '';\n if (response.status === 401 || response.status === 403) {\n throw new ProviderAuthenticationError(\n `OpenAI Codex credential is invalid or expired (HTTP ${response.status})${suffix}`,\n );\n }\n throw new Error(`OpenAI Codex search failed (HTTP ${response.status})${suffix}`);\n }\n\n return mapCodexSearchResponse(payload);\n }\n}\n\nfunction loadStaticCredential(filename: string): StaticCodexCredential | undefined {\n try {\n const stats = statSync(filename);\n if (!stats.isFile() || stats.size <= 0 || stats.size > MAX_CREDENTIAL_BYTES) return undefined;\n const value: unknown = JSON.parse(readFileSync(filename, 'utf8'));\n return parseStaticCredential(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapCodexSearchResponse(value: unknown): SearchResult {\n if (!isRecord(value) || typeof value.output !== 'string') {\n throw new Error('OpenAI Codex returned a search response without string output');\n }\n if (value.results !== undefined && !Array.isArray(value.results)) {\n throw new Error('OpenAI Codex returned a search response with non-array results');\n }\n\n const items: SearchResult['items'] = [];\n const seen = new Set<string>();\n for (const item of value.results ?? []) {\n if (!isRecord(item) || item.type !== 'text_result') continue;\n const url = citeableUrl(item.url);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n items.push({\n title: readNonEmptyString(item.title) ?? url,\n url,\n snippet: readNonEmptyString(item.snippet) ?? '',\n });\n }\n\n const summary = value.output;\n return {\n status: summary.length > 0 ? 'ok' : 'degraded',\n source: 'OpenAI Codex',\n summary: summary || `Codex returned ${items.length} source(s) without summary text`,\n items,\n uncertainty: summary.length > 0 ? [] : ['Codex returned no summary text'],\n };\n}\n\nfunction parseStaticCredential(value: unknown): StaticCodexCredential | undefined {\n if (!isRecord(value)) return undefined;\n\n // Account stores keep tokens in credentials[], selected by activeAccountId.\n const stored = Array.isArray(value.credentials) ? value.credentials : [];\n const activeAccountId = readNonEmptyString(value.activeAccountId);\n const selected = activeAccountId\n ? stored.filter((entry) => isRecord(entry)\n && (readNonEmptyString(entry.account_id) ?? readNonEmptyString(entry.accountId)) === activeAccountId)\n : value.activeAccountId === undefined ? stored : [];\n const storedCredential = selected.length === 1 && isRecord(selected[0]) ? selected[0] : undefined;\n\n const candidates = [\n value,\n isRecord(value.credential) ? value.credential : undefined,\n isRecord(value.tokens) ? value.tokens : undefined,\n storedCredential,\n ];\n\n for (const candidate of candidates) {\n if (!candidate) continue;\n const accessToken = readNonEmptyString(candidate.access_token)\n ?? readNonEmptyString(candidate.access);\n if (!accessToken) continue;\n\n const accountId = readNonEmptyString(candidate.account_id)\n ?? readNonEmptyString(candidate.accountId)\n ?? accountIdFromToken(accessToken);\n if (!accountId) continue;\n\n return { accessToken, accountId };\n }\n\n return undefined;\n}\n\nfunction decodeJwtPayload(accessToken: string): Record<string, unknown> | undefined {\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3 || !parts[1]) return undefined;\n const payload: unknown = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));\n return isRecord(payload) ? payload : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction accountIdFromToken(accessToken: string): string | undefined {\n const auth = decodeJwtPayload(accessToken)?.['https://api.openai.com/auth'];\n return isRecord(auth) ? readNonEmptyString(auth.chatgpt_account_id) : undefined;\n}\n\nclass ResponseTooLargeError extends Error {\n constructor(limit: number) {\n super(`OpenAI Codex search response exceeded ${limit} bytes`);\n this.name = 'ResponseTooLargeError';\n }\n}\n\nasync function readBoundedJson(\n response: Response,\n maxBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel().catch(() => {});\n throw new ResponseTooLargeError(maxBytes);\n }\n if (!response.body) throw new SyntaxError('empty response body');\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await abortable(reader.read(), signal);\n if (done) break;\n if (!value) continue;\n total += value.byteLength;\n if (total > maxBytes) throw new ResponseTooLargeError(maxBytes);\n chunks.push(value);\n }\n } catch (error) {\n await reader.cancel().catch(() => {});\n throw error;\n } finally {\n reader.releaseLock();\n }\n\n return JSON.parse(Buffer.concat(chunks, total).toString('utf8')) as unknown;\n}\n\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortedError(signal));\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortedError(signal));\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw abortedError(signal);\n}\n\nfunction abortedError(signal: AbortSignal): Error {\n return new Error('OpenAI Codex search aborted', { cause: signal.reason });\n}\n\nfunction providerMessage(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined;\n const raw = typeof value.error === 'string'\n ? value.error\n : isRecord(value.error) && typeof value.error.message === 'string'\n ? value.error.message\n : typeof value.message === 'string'\n ? value.message\n : undefined;\n return raw\n ?.replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu, '[REDACTED]')\n .slice(0, 1_000);\n}\n\nfunction citeableUrl(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'http:' || url.protocol === 'https:' ? value : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction readNonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;\n}\n\n","import type { ResolvedProviderConfig } from '../config.js';\nimport type { FetchRequest, FetchResult, SearchProvider, SearchRequest, SearchResult } from './types.js';\n\nexport class DemoProvider implements SearchProvider {\n readonly id = 'demo';\n readonly name = 'Demo Provider';\n readonly description = 'Returns canned results for integration testing without calling any external API.';\n readonly searchable = true;\n readonly fetchable = true;\n\n constructor(_config: ResolvedProviderConfig) {}\n\n available(): boolean {\n return true;\n }\n\n async search(request: SearchRequest): Promise<SearchResult> {\n return {\n status: 'ok',\n source: this.name,\n summary: `Demo search results for \"${request.query}\"`,\n items: [\n {\n title: 'Demo Result 1',\n url: 'https://example.com/demo1',\n snippet: 'This is a demo search result used to verify the DSH plugin wiring.',\n },\n {\n title: 'Demo Result 2',\n url: 'https://example.com/demo2',\n snippet: 'Another canned result so citation cards have something to render.',\n },\n ],\n uncertainty: ['Demo mode: results are not from a real search engine.'],\n };\n }\n\n async fetch(request: FetchRequest): Promise<FetchResult> {\n return {\n summary: `Demo fetch of ${request.url}`,\n content: `# Demo Page\\n\\nThis is canned content for the URL \"${request.url}\".`,\n links: [\n { text: 'Example', url: 'https://example.com' },\n ],\n uncertainty: ['Demo mode: content is not from a real web page.'],\n warnings: [],\n };\n }\n}","export const fetchSchema = {\n type: 'object' as const,\n properties: {\n summary: { type: 'string' as const },\n content: { type: 'string' as const },\n links: {\n type: 'array' as const,\n items: {\n type: 'object' as const,\n properties: {\n text: { type: 'string' as const },\n url: { type: 'string' as const }\n },\n required: ['text', 'url'] as const\n }\n },\n uncertainty: {\n type: 'array' as const,\n items: { type: 'string' as const }\n },\n warnings: {\n type: 'array' as const,\n items: { type: 'string' as const }\n }\n },\n required: ['summary', 'content', 'uncertainty'] as const\n};\n","import type { ProviderRegistry } from '../providers/registry.js';\n\nexport const FALLBACK_ATTEMPT_TIMEOUT_MS = 55_000;\n\nexport interface AttemptContext {\n signal: AbortSignal;\n timedOut(): boolean;\n dispose(): void;\n}\n\nexport function resolveCandidateIds(\n registry: ProviderRegistry,\n fallbackChain?: string[],\n): string[] {\n let activeId: string | undefined;\n try {\n activeId = registry.getActive().id;\n } catch {\n // An empty registry has no active provider.\n }\n\n const configured = fallbackChain === undefined ? registry.list() : fallbackChain;\n const seen = new Set<string>();\n const candidates: string[] = [];\n for (const id of activeId ? [activeId, ...configured] : configured) {\n if (seen.has(id)) continue;\n seen.add(id);\n candidates.push(id);\n }\n return candidates;\n}\n\nexport function createAttemptContext(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): AttemptContext {\n const controller = new AbortController();\n let didTimeOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onParentAbort: (() => void) | undefined;\n\n if (parent?.aborted) {\n controller.abort(parent.reason);\n } else {\n if (parent) {\n onParentAbort = () => controller.abort(parent.reason);\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n timer = setTimeout(() => {\n didTimeOut = true;\n controller.abort(new Error(`fallback attempt timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n timedOut: () => didTimeOut,\n dispose() {\n if (timer !== undefined) clearTimeout(timer);\n if (parent && onParentAbort) parent.removeEventListener('abort', onParentAbort);\n },\n };\n}\n\nexport function raceWithSignal<T>(operation: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return operation;\n if (signal.aborted) {\n operation.catch(() => {});\n return Promise.reject(signal.reason);\n }\n\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => {\n signal.removeEventListener('abort', onAbort);\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n operation.then(\n (value) => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n (error) => {\n signal.removeEventListener('abort', onAbort);\n reject(error);\n },\n );\n });\n}\n\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw signal.reason;\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { FetchRequest, FetchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport { fetchSchema } from '../schemas/fetch-schema.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\nconst READ_PAGE_TIMEOUT_MS = 180_000;\nconst RENDER_CONTENT_CAP = 20_000;\nconst RENDER_LINK_CAP = 20;\n\nexport function registerReadPageTool(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.tools?.register !== 'function') {\n console.error('[search-providers] ctx.tools.register is not available; read_page tool skipped');\n return;\n }\n\n ctx.tools.register({\n name: 'read_page',\n description:\n 'Read one web page through the configured search provider. Use when a message references a specific http(s) URL whose content matters: docs, an article, a changelog, a thread. Returns structured evidence with a summary, the extracted content, outgoing links, uncertainty, and operational warnings. Pass \"query\" to focus the reading on one question.',\n parameters: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'The http(s) URL to read',\n },\n query: {\n type: 'string',\n description: 'Optional question to focus the reading on (e.g. \"what are the rate limits\")',\n },\n },\n required: ['url'],\n },\n output: {\n schema: fetchSchema,\n render: (_args: unknown, value: Record<string, unknown>) => [\n { type: 'text', text: renderFetchEvidence(value) },\n ],\n },\n timeoutMs: READ_PAGE_TIMEOUT_MS + 20_000,\n isConcurrencySafe: () => true,\n presentCall: (args: { url: string; query?: string }) => ({\n card: 'generic',\n title: 'read_page',\n kind: 'fetch',\n rawInput: args,\n }),\n async execute(args: { url: string; query?: string }, exec: { signal?: AbortSignal }) {\n if (typeof args?.url !== 'string' || !/^https?:\\/\\//i.test(args.url.trim())) {\n throw new Error('read_page needs an http(s) \"url\".');\n }\n\n const request: FetchRequest = {\n url: args.url.trim(),\n query: args.query,\n format: 'markdown',\n };\n\n return fetchWithFallback(registry, cooldown, fallbackChain, request, exec.signal);\n },\n });\n}\n\nfunction resolveFetchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n}\n\nasync function fetchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: FetchRequest,\n signal: AbortSignal | undefined,\n): Promise<FetchResult> {\n throwIfAborted(signal);\n const candidates = resolveFetchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'fetch');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available()\n && provider.fetchable !== false\n && typeof provider.fetch === 'function';\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.fetch!(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n if (attempted.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Fallback used after ${attempted.join(', ')} failed`);\n }\n if (cooling.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Cooling providers skipped: ${cooling.join(', ')}`);\n }\n if (disabled.length > 0) {\n result.warnings = result.warnings ?? [];\n result.warnings.push(`Providers disabled until restart: ${disabled.join(', ')}`);\n }\n return result;\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' fetch failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'fetch', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'fetch');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No fetch provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderFetchEvidence(value: Record<string, unknown>): string {\n const lines: string[] = [String(value.summary ?? '')];\n const content = String((value.content as string) ?? '').trim();\n if (content) {\n lines.push('', 'Content:', content.length > RENDER_CONTENT_CAP ? `${content.slice(0, RENDER_CONTENT_CAP)}…` : content);\n }\n const links = Array.isArray(value.links) ? value.links.slice(0, RENDER_LINK_CAP) : [];\n if (links.length > 0) {\n lines.push('', 'Links:');\n for (const link of links) {\n const text = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).text ?? '') : '';\n const url = typeof link === 'object' && link !== null ? String((link as Record<string, unknown>).url ?? '') : '';\n lines.push(`- ${text} — ${url}`);\n }\n }\n const uncertainty = Array.isArray(value.uncertainty) ? value.uncertainty : [];\n if (uncertainty.length > 0) {\n lines.push('', `Uncertain: ${uncertainty.join('; ')}`);\n }\n const warnings = Array.isArray(value.warnings) ? value.warnings : [];\n if (warnings.length > 0) {\n lines.push('', `Warnings: ${warnings.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import type { CooldownController } from '../providers/cooldown.js';\nimport type { ProviderRegistry } from '../providers/registry.js';\nimport type { SearchRequest, SearchResult } from '../providers/types.js';\nimport { ProviderAuthenticationError } from '../providers/utils.js';\nimport {\n createAttemptContext,\n FALLBACK_ATTEMPT_TIMEOUT_MS,\n raceWithSignal,\n resolveCandidateIds,\n throwIfAborted,\n} from './fallback-utils.js';\n\ninterface WebSeamSearchRequest {\n query: string;\n maxResults?: number;\n recencyDays?: number;\n purpose?: string;\n location?: string;\n language?: string;\n}\n\nexport function registerWebSearchProvider(\n ctx: any,\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain?: string[],\n): void {\n if (typeof ctx.web?.registerSearchProvider !== 'function') {\n console.error('[search-providers] ctx.web.registerSearchProvider is not available; web_search provider skipped');\n return;\n }\n\n ctx.web.registerSearchProvider({\n id: 'search-providers',\n available: () => anyProviderAvailable(registry, cooldown, fallbackChain),\n async search(request: WebSeamSearchRequest, signal: AbortSignal | undefined) {\n const searchRequest: SearchRequest = {\n query: request.query,\n maxResults: request.maxResults,\n recencyDays: request.recencyDays,\n purpose: request.purpose,\n location: request.location,\n language: request.language,\n };\n\n const { result, attempted, cooling, disabled } = await searchWithFallback(\n registry,\n cooldown,\n fallbackChain,\n searchRequest,\n signal,\n );\n\n return {\n content: renderContent(result, attempted, cooling, disabled),\n sources: result.items.map((item) => ({\n url: item.url,\n ...(item.title ? { title: item.title } : {}),\n ...(item.snippet ? { snippet: item.snippet } : {}),\n ...(item.publishedAt ? { publishedAt: item.publishedAt } : {}),\n })),\n truncated: false,\n };\n },\n });\n}\n\nfunction resolveSearchCandidateIds(\n registry: ProviderRegistry,\n fallbackChain: string[] | undefined,\n): string[] {\n return resolveCandidateIds(registry, fallbackChain).filter((id) => {\n const provider = registry.get(id);\n return provider !== undefined && provider.searchable !== false;\n });\n}\n\nfunction anyProviderAvailable(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n): boolean {\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const { ready } = cooldown.filter(candidates, 'search');\n return ready.some((id) => {\n const provider = registry.get(id);\n return provider?.available() && provider.searchable !== false;\n });\n}\n\ninterface FallbackOutcome {\n result: SearchResult;\n attempted: string[];\n cooling: string[];\n disabled: string[];\n}\n\nasync function searchWithFallback(\n registry: ProviderRegistry,\n cooldown: CooldownController,\n fallbackChain: string[] | undefined,\n request: SearchRequest,\n signal: AbortSignal | undefined,\n): Promise<FallbackOutcome> {\n throwIfAborted(signal);\n const candidates = resolveSearchCandidateIds(registry, fallbackChain);\n const attempted: string[] = [];\n const attemptedIds = new Set<string>();\n let lastError: Error | undefined;\n\n while (true) {\n throwIfAborted(signal);\n const { ready } = cooldown.filter(candidates, 'search');\n const id = ready.find((candidateId) => {\n if (attemptedIds.has(candidateId)) return false;\n const provider = registry.get(candidateId);\n return provider?.available() && provider.searchable !== false;\n });\n if (id === undefined) break;\n\n const provider = registry.get(id)!;\n attemptedIds.add(id);\n const attempt = attempted.length === 0\n ? undefined\n : createAttemptContext(signal, FALLBACK_ATTEMPT_TIMEOUT_MS);\n\n try {\n const attemptSignal = attempt?.signal ?? signal;\n const result = await raceWithSignal(provider.search(request, attemptSignal), attemptSignal);\n throwIfAborted(signal);\n if (attempt?.timedOut()) throw attempt.signal.reason;\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n return { result, attempted, cooling, disabled };\n } catch (error) {\n throwIfAborted(signal);\n const caught = error instanceof Error ? error : new Error(String(error));\n lastError = caught instanceof ProviderAuthenticationError\n ? caught\n : attempt?.timedOut()\n ? new Error(`fallback attempt timed out after ${FALLBACK_ATTEMPT_TIMEOUT_MS}ms`, { cause: caught })\n : caught;\n attempted.push(id);\n console.warn(`[search-providers] provider '${id}' search failed, trying next: ${lastError.message}`);\n cooldown.record(id, 'search', lastError);\n } finally {\n attempt?.dispose();\n }\n }\n\n const { cooling, disabled } = cooldown.filter(candidates, 'search');\n const coolingIds = cooling.length > 0 ? ` [cooling: ${cooling.join(', ')}]` : '';\n const disabledIds = disabled.length > 0 ? ` [disabled until restart: ${disabled.join(', ')}]` : '';\n throw new Error(\n `No search provider succeeded${attempted.length > 0 ? ` (tried: ${attempted.join(', ')})` : ''}${coolingIds}${disabledIds}. ${lastError?.message ?? 'No provider available.'}`,\n { cause: lastError },\n );\n}\n\nfunction renderContent(\n result: SearchResult,\n attempted: string[],\n cooling: string[],\n disabled: string[],\n): string {\n const lines = [`[Search provider: ${result.source}]`, result.summary];\n if (attempted.length > 0) {\n lines.push(`[Fallback used after ${attempted.join(', ')} failed]`);\n }\n if (cooling.length > 0) {\n lines.push(`[Cooling: ${cooling.join(', ')}]`);\n }\n if (disabled.length > 0) {\n lines.push(`[Disabled until restart: ${disabled.join(', ')}]`);\n }\n if (result.uncertainty.length > 0) {\n lines.push(`Uncertain: ${result.uncertainty.join('; ')}`);\n }\n return lines.filter(Boolean).join('\\n');\n}\n","import { loadPluginConfig, resolveProviderConfig } from './config.js';\nimport { CooldownController } from './providers/cooldown.js';\nimport { ProviderRegistry } from './providers/registry.js';\nimport { TinyfishProvider } from './providers/tinyfish.js';\nimport { TavilyProvider } from './providers/tavily.js';\nimport { ExaProvider } from './providers/exa.js';\nimport { FirecrawlProvider } from './providers/firecrawl.js';\nimport { CodexProvider } from './providers/codex.js';\nimport { DemoProvider } from './providers/demo.js';\nimport type { PluginConfig } from './config.js';\nimport { registerReadPageTool } from './tools/read-page.js';\nimport { registerWebSearchProvider } from './tools/web-search.js';\n\nexport const name = 'search-providers';\nexport const inject = ['tools', 'web'];\n\nconst DEFAULT_PROVIDER_ORDER = ['codex', 'tinyfish', 'tavily', 'exa', 'firecrawl'];\n\nexport function apply(ctx: any, rawConfig: PluginConfig = {}): void {\n const config = loadPluginConfig(rawConfig);\n const registry = new ProviderRegistry();\n const cooldown = new CooldownController();\n const demoMode = config.provider === 'demo';\n\n // Register providers in priority order.\n registry.register(new CodexProvider(resolveProviderConfig(config, 'codex')));\n registry.register(new TinyfishProvider(resolveProviderConfig(config, 'tinyfish')));\n registry.register(new TavilyProvider(resolveProviderConfig(config, 'tavily')));\n registry.register(new ExaProvider(resolveProviderConfig(config, 'exa')));\n registry.register(new FirecrawlProvider(resolveProviderConfig(config, 'firecrawl')));\n\n if (demoMode) {\n registry.register(new DemoProvider(resolveProviderConfig(config, 'tinyfish')));\n }\n\n // Determine active provider.\n const activeProvider = config.provider;\n if (activeProvider) {\n try {\n registry.setActive(activeProvider);\n } catch (error) {\n console.warn(`[search-providers] configured provider '${activeProvider}' is not registered; falling back to '${registry.list()[0]}'`);\n }\n }\n\n // The active provider is always tried first. An explicit empty chain disables fallback.\n const fallbackChain = config.fallbackChain ?? DEFAULT_PROVIDER_ORDER;\n\n // Register tools.\n registerWebSearchProvider(ctx, registry, cooldown, fallbackChain);\n registerReadPageTool(ctx, registry, cooldown, fallbackChain);\n\n console.log(`[search-providers] loaded with active provider: ${registry.getActive().id}, fallback after active: [${fallbackChain.join(', ')}]`);\n}"],"names":["isRecord","throwIfAborted","resolve","provider","cooling","disabled"],"mappings":";;;;AA+BA,MAAM,qBAAqB;AAEpB,SAAS,iBAAiB,eAA6B,IAA2B;AACvF,MAAI,CAACA,WAAS,YAAY,KAAK,OAAO,KAAK,YAAY,EAAE,KAAK,CAAC,QAAQ,QAAQ,YAAY,GAAG;AAC5F,UAAM,IAAI,MAAM,+HAA+H;AAAA,EACjJ;AACA,MAAI,aAAa,eAAe,UAAa,CAAC,iBAAiB,aAAa,UAAU,GAAG;AACvF,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AAEA,QAAM,WAAW,aAAa,eAAe;AAC7C,QAAM,WAAW,kBAAkB,aAAa,cAAc,KAAK,QAAA,GAAW,QAAQ,uBAAuB,CAAC;AAC9G,MAAI;AACJ,MAAI;AACF,WAAO,aAAa,UAAU,MAAM;AAAA,EACtC,SAAS,OAAO;AACd,UAAM,OAAQ,MAAgC;AAC9C,QAAI,CAAC,YAAY,SAAS,UAAU;AAClC,cAAQ;AAAA,QACN,+CAA+C,WAAW;AAAA,MAAA;AAE5D,aAAO,CAAA;AAAA,IACT;AACA,UAAM,IAAI,MAAM,+CAA+C,WAAW,QAAQ,QAAQ,gBAAgB,IAAI;AAAA,EAChH;AAEA,MAAI;AACJ,MAAI;AAEF,YAAQ,KAAK,MAAM,KAAK,WAAW,CAAC,MAAM,QAAS,KAAK,MAAM,CAAC,IAAI,IAAI;AAAA,EACzE,QAAQ;AAEN,UAAM,IAAI,MAAM,uCAAuC,WAAW,GAAG;AAAA,EACvE;AACA,iBAAe,OAAO,QAAQ;AAC9B,aAAW,SAAS,OAAO,OAAO,MAAM,aAAa,CAAA,CAAE,GAAG;AACxD,QAAI,MAAM,mBAAmB,QAAW;AACtC,YAAM,iBAAiB,kBAAkB,MAAM,gBAAgB,QAAQ,QAAQ,CAAC;AAAA,IAClF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,sBACd,cACA,YACwB;AACxB,QAAM,KAAK,aAAa,YAAY,UAAU,KAAK,CAAA;AACnD,SAAO;AAAA,IACL,QAAQ,GAAG;AAAA,IACX,gBAAgB,GAAG;AAAA,IACnB,WAAW,GAAG,aAAa;AAAA,IAC3B,SAAS,GAAG,WAAW,CAAA;AAAA,EAAC;AAE5B;AAEA,SAAS,kBAAkB,UAAkB,OAAO,QAAQ,OAAe;AACzE,QAAM,OAAO,SAAS,KAAA;AACtB,MAAI,SAAS,IAAK,QAAO,QAAA;AACzB,MAAI,KAAK,WAAW,IAAI,KAAK,KAAK,WAAW,KAAK,GAAG;AACnD,WAAO,QAAQ,QAAA,GAAW,KAAK,MAAM,CAAC,CAAC;AAAA,EACzC;AACA,SAAO,QAAQ,MAAM,IAAI;AAC3B;AAEA,SAASA,WAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,OAAiC;AACzD,SAAO,OAAO,UAAU,YAAY,MAAM,WAAW;AACvD;AAEA,SAAS,eAAe,OAAgB,UAA0D;AAChG,WAAS,QAAQ,OAAsB;AACrC,UAAM,IAAI,MAAM,yCAAyC,WAAW,OAAO,QAAQ,GAAG;AAAA,EACxF;AACA,MAAI,CAACA,WAAS,KAAK,WAAW,wBAAwB;AAEtD,MAAI,OAAO,KAAK,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,YAAY,aAAa,eAAe,EAAE,SAAS,GAAG,CAAC,GAAG;AAC/F,YAAQ,mEAAmE;AAAA,EAC7E;AACA,MAAI,MAAM,aAAa,UAAa,CAAC,iBAAiB,MAAM,QAAQ,GAAG;AACrE,YAAQ,qCAAqC;AAAA,EAC/C;AACA,MAAI,MAAM,kBAAkB,WACvB,CAAC,MAAM,QAAQ,MAAM,aAAa,KAAK,CAAC,MAAM,cAAc,MAAM,gBAAgB,IAAI;AACzF,YAAQ,qDAAqD;AAAA,EAC/D;AACA,MAAI,MAAM,cAAc,OAAW;AACnC,MAAI,CAACA,WAAS,MAAM,SAAS,WAAW,6BAA6B;AACrE,aAAW,SAAS,OAAO,OAAO,MAAM,SAAS,GAAG;AAClD,QAAI,CAACA,WAAS,KAAK,WAAW,iCAAiC;AAC/D,QAAI,OAAO,KAAK,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,UAAU,kBAAkB,aAAa,SAAS,EAAE,SAAS,GAAG,CAAC,GAAG;AACzG,cAAQ,+GAA+G;AAAA,IACzH;AACA,QAAI,MAAM,WAAW,UAAa,OAAO,MAAM,WAAW,kBAAkB,yBAAyB;AACrG,QAAI,MAAM,mBAAmB,UAAa,CAAC,iBAAiB,MAAM,cAAc,GAAG;AACjF,cAAQ,2CAA2C;AAAA,IACrD;AACA,QAAI,MAAM,cAAc,WACnB,OAAO,MAAM,cAAc,YAAY,CAAC,OAAO,cAAc,MAAM,SAAS,KAAK,MAAM,aAAa,KAAK,MAAM,YAAY,aAAgB;AAC9I,cAAQ,iEAAiE;AAAA,IAC3E;AACA,QAAI,MAAM,YAAY,QAAW;AAC/B,UAAI,CAACA,WAAS,MAAM,OAAO,WAAW,2BAA2B;AACjE,UAAI,OAAO,OAAO,MAAM,SAAS,cAAc,GAAG;AAChD,gBAAQ,iEAAiE;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AACF;AC9IO,MAAM,oCAAoC,MAAM;AAAA,EAC5C,OAAO;AAAA,EAEhB,YAAY,SAAiB,SAAwB;AACnD,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,MAAM,0BAA0B,MAAM;AAAA,EAG3C,YACE,SACS,QACT,SACA;AACA,UAAM,SAAS,OAAO;AAHb,SAAA,SAAA;AAIT,SAAK,OAAO;AAAA,EACd;AAAA,EATS,OAAO;AAUlB;AAEA,eAAsB,iBACpB,KACA,MACA,WACmB;AACnB,QAAM,aAAa,IAAI,gBAAA;AACvB,QAAM,QAAQ;AAAA,IACZ,MAAM,WAAW,MAAM,IAAI,MAAM,2BAA2B,SAAS,IAAI,CAAC;AAAA,IAC1E;AAAA,EAAA;AAEF,MAAI;AACF,UAAM,SAAS,KAAK,SAChB,eAAe,KAAK,QAAQ,WAAW,MAAM,IAC7C,WAAW;AACf,WAAO,MAAM,MAAM,KAAK,EAAE,GAAG,MAAM,QAAQ;AAAA,EAC7C,UAAA;AACE,iBAAa,KAAK;AAAA,EACpB;AACF;AAEO,SAAS,eAAe,GAAgB,GAA6B;AAC1E,SAAO,YAAY,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/B;AAEA,eAAsB,aAAa,UAAoB,OAA8B;AACnF,MAAI,SAAS,GAAI;AACjB,QAAM,OAAO,MAAM,SAAS,OAAO,MAAM,MAAM,EAAE;AACjD,MAAI,SAAS,KAAK,MAAM,GAAG,GAAG;AAC9B,MAAI,SAAS,WAAW,KAAK;AAC3B,aAAS;AAAA,EACX,WAAW,SAAS,WAAW,KAAK;AAClC,aAAS;AAAA,EACX,WAAW,SAAS,WAAW,KAAK;AAClC,aAAS;AAAA,EACX;AACA,QAAM,UAAU,GAAG,KAAK,aAAa,SAAS,MAAM,KAAK,MAAM;AAC/D,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI,4BAA4B,OAAO;AAAA,EAC/C;AACA,QAAM,IAAI,kBAAkB,SAAS,SAAS,MAAM;AACtD;ACrDO,MAAM,mBAAmB;AAAA,EACtB,+BAAe,IAAA;AAAA,EACf,8BAAc,IAAA;AAAA;AAAA,EAGtB,OAAO,YAAoB,YAAgC,OAAyB;AAClF,QAAI,iBAAiB,6BAA6B;AAChD,WAAK,SAAS,IAAI,YAAY;AAAA,QAC5B,OAAO,OAAO;AAAA,QACd,QAAQ;AAAA,MAAA,CACT;AACD,aAAO;AAAA,IACT;AAEA,SAAK,QAAQ,IAAI,KAAK,cAAc,YAAY,UAAU,GAAG;AAAA,MAC3D,OAAO,KAAK,IAAA,IAAQ,KAAK,iBAAiB,KAAK;AAAA,MAC/C,QAAQ,KAAK,cAAc,KAAK;AAAA,IAAA,CACjC;AACD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,UAAU,YAAoB,YAAyC;AACrE,WAAO,KAAK,SAAS,KAAK,SAAS,KAAK,cAAc,YAAY,UAAU,CAAC,MAAM;AAAA,EACrF;AAAA;AAAA,EAGA,WAAW,YAA6B;AACtC,WAAO,KAAK,SAAS,KAAK,UAAU,UAAU,MAAM;AAAA,EACtD;AAAA;AAAA,EAGA,UAAU,YAAoB,YAAoD;AAChF,WAAO,KAAK,SAAS,KAAK,UAAU,UAAU,GAAG,UAC5C,KAAK,SAAS,KAAK,SAAS,KAAK,cAAc,YAAY,UAAU,CAAC,GAAG;AAAA,EAChF;AAAA;AAAA,EAGA,OACE,OACA,YAC4D;AAC5D,UAAM,QAAkB,CAAA;AACxB,UAAM,UAAoB,CAAA;AAC1B,UAAM,WAAqB,CAAA;AAC3B,eAAW,MAAM,OAAO;AACtB,UAAI,KAAK,WAAW,EAAE,GAAG;AACvB,iBAAS,KAAK,EAAE;AAAA,MAClB,WAAW,KAAK,UAAU,IAAI,UAAU,GAAG;AACzC,gBAAQ,KAAK,EAAE;AAAA,MACjB,OAAO;AACL,cAAM,KAAK,EAAE;AAAA,MACf;AAAA,IACF;AACA,WAAO,EAAE,OAAO,SAAS,SAAA;AAAA,EAC3B;AAAA,EAEQ,cAAc,YAAoB,YAAwC;AAChF,WAAO,GAAG,UAAU,IAAI,UAAU;AAAA,EACpC;AAAA,EAEQ,SAAS,KAAiC,KAAwC;AACxF,UAAM,QAAQ,IAAI,IAAI,GAAG;AACzB,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,KAAK,SAAS,MAAM,OAAO;AAC7B,UAAI,OAAO,GAAG;AACd,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,OAAwB;AAC5C,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI,KAAK,iBAAiB,OAAO,OAAO,EAAG,QAAO;AAClD,QAAI,qBAAqB,KAAK,OAAO,EAAG,QAAO;AAC/C,QAAI,+CAA+C,KAAK,OAAO,EAAG,QAAO;AACzE,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,OAAwB;AAC/C,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAErE,QAAI,KAAK,iBAAiB,OAAO,OAAO,EAAG,QAAO,IAAI,KAAK;AAE3D,QAAI,iEAAiE,KAAK,OAAO,GAAG;AAClF,aAAO,KAAK;AAAA,IACd;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,iBAAiB,OAAgB,SAA0B;AACjE,WACG,iBAAiB,sBAAsB,MAAM,WAAW,OAAO,MAAM,WAAW,QAC9E,sEAAsE,KAAK,OAAO;AAAA,EAEzF;AACF;ACxGO,MAAM,iBAAiB;AAAA,EACpB,gCAAgB,IAAA;AAAA,EAChB;AAAA,EAER,SAAS,UAAgC;AACvC,QAAI,KAAK,UAAU,IAAI,SAAS,EAAE,GAAG;AACnC,cAAQ,KAAK,gCAAgC,SAAS,EAAE,mCAAmC;AAAA,IAC7F;AACA,SAAK,UAAU,IAAI,SAAS,IAAI,QAAQ;AACxC,QAAI,KAAK,aAAa,QAAW;AAC/B,WAAK,WAAW,SAAS;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,UAAU,IAAkB;AAC1B,QAAI,CAAC,KAAK,UAAU,IAAI,EAAE,GAAG;AAC3B,YAAM,IAAI,MAAM,aAAa,EAAE,qBAAqB;AAAA,IACtD;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,YAA4B;AAC1B,QAAI,KAAK,aAAa,QAAW;AAC/B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AACA,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK,QAAQ;AACjD,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,oBAAoB,KAAK,QAAQ,eAAe;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,IAAwC;AAC1C,WAAO,KAAK,UAAU,IAAI,EAAE;AAAA,EAC9B;AAAA,EAEA,OAAiB;AACf,WAAO,MAAM,KAAK,KAAK,UAAU,MAAM;AAAA,EACzC;AACF;ACLO,MAAM,iBAA2C;AAAA,EAC7C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAEJ;AAAA,EACA,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf;AAAA,EAEjB,YAAY,QAAgC;AAC1C,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,SAAwB,QAA6C;AAChF,SAAK,UAAA;AAEL,UAAM,MAAM,IAAI,IAAI,KAAK,aAAa;AACtC,QAAI,aAAa,IAAI,SAAS,QAAQ,KAAK;AAC3C,QAAI,QAAQ,cAAc,QAAQ,aAAa,GAAG;AAChD,UAAI,aAAa,IAAI,eAAe,OAAO,KAAK,MAAM,QAAQ,UAAU,CAAC,CAAC;AAAA,IAC5E;AACA,QAAI,OAAO,QAAQ,gBAAgB,YAAY,QAAQ,cAAc,GAAG;AACtE,UAAI,aAAa,IAAI,mBAAmB,OAAO,KAAK,MAAM,QAAQ,cAAc,KAAK,EAAE,CAAC,CAAC;AAAA,IAC3F;AACA,QAAI,QAAQ,SAAS;AACnB,UAAI,aAAa,IAAI,WAAW,QAAQ,OAAO;AAAA,IACjD;AACA,QAAI,QAAQ,UAAU;AACpB,UAAI,aAAa,IAAI,YAAY,QAAQ,QAAQ;AAAA,IACnD;AACA,QAAI,QAAQ,UAAU;AACpB,UAAI,aAAa,IAAI,YAAY,QAAQ,QAAQ;AAAA,IACnD;AACA,QAAI,QAAQ,YAAY;AACtB,UAAI,aAAa,IAAI,eAAe,QAAQ,UAAU;AAAA,IACxD;AACA,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,UAAI,aAAa,IAAI,mBAAmB,QAAQ,eAAe,KAAK,GAAG,CAAC;AAAA,IAC1E;AACA,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,UAAI,aAAa,IAAI,mBAAmB,QAAQ,eAAe,KAAK,GAAG,CAAC;AAAA,IAC1E;AAEA,UAAM,WAAW,MAAM,iBAAiB,IAAI,YAAY;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,QAAQ;AAAA,MAAA;AAAA,MAEV;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,iBAAiB;AAE9C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,UAAM,SAAS,KAAK,WAAW,CAAA,GAAI,IAAI,CAAC,OAAO;AAAA,MAC7C,OAAO,EAAE;AAAA,MACT,KAAK,EAAE;AAAA,MACP,SAAS,EAAE;AAAA,MACX,QAAQ,EAAE;AAAA,IAAA,EACV;AAEF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,SAAS,MAAM,SAAS,IACpB,SAAS,KAAK,iBAAiB,MAAM,MAAM,mBAAmB,QAAQ,KAAK,MAC3E,yBAAyB,QAAQ,KAAK;AAAA,MAC1C;AAAA,MACA,aAAa,MAAM,WAAW,IAAI,CAAC,4BAA4B,IAAI,CAAA;AAAA,IAAC;AAAA,EAExE;AAAA,EAEA,MAAM,MAAM,SAAuB,QAA4C;AAC7E,SAAK,UAAA;AAEL,UAAM,OAAgC;AAAA,MACpC,MAAM,CAAC,QAAQ,GAAG;AAAA,MAClB,QAAQ,QAAQ,UAAU;AAAA,IAAA;AAE5B,QAAI,QAAQ,OAAO;AACjB,WAAK,UAAU,QAAQ;AAAA,IACzB;AACA,QAAI,OAAO,QAAQ,QAAQ,UAAU;AACnC,WAAK,MAAM,QAAQ;AAAA,IACrB;AAEA,UAAM,WAAW,MAAM,iBAAiB,KAAK,cAAc;AAAA,MACzD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,gBAAgB;AAE7C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,UAAM,OAAO,KAAK,UAAU,CAAC;AAC7B,UAAM,QAAQ,KAAK,SAAS,CAAC;AAE7B,QAAI,CAAC,QAAQ,OAAO;AAClB,YAAM,IAAI,MAAM,6BAA6B,MAAM,GAAG,KAAK,MAAM,KAAK,EAAE;AAAA,IAC1E;AACA,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,WAAO;AAAA,MACL,SAAS,KAAK,SAAS,KAAK,eAAe;AAAA,MAC3C,SAAS,KAAK,QAAQ;AAAA,MACtB,aAAa,CAAA;AAAA,MACb,UAAU,CAAA;AAAA,IAAC;AAAA,EAEf;AAAA,EAEQ,YAAkB;AACxB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AC3JO,MAAM,eAAyC;AAAA,EAC3C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EAEjB,YAAY,QAAgC;AAC1C,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,SAAwB,QAA6C;AAChF,SAAK,UAAA;AAEL,UAAM,OAAgC;AAAA,MACpC,OAAO,QAAQ;AAAA,MACf,cAAc;AAAA,MACd,aAAa,QAAQ,cAAc;AAAA,IAAA;AAErC,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,kBAAkB,QAAQ;AAAA,IACjC;AACA,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,kBAAkB,QAAQ;AAAA,IACjC;AAEA,UAAM,WAAW,MAAM,iBAAiB,GAAG,KAAK,OAAO,WAAW;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,MAAM;AAAA,QACpC,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,eAAe;AAE5C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,UAAM,SAAS,KAAK,WAAW,CAAA,GAAI,IAAI,CAAC,OAAO;AAAA,MAC7C,OAAO,EAAE;AAAA,MACT,KAAK,EAAE;AAAA,MACP,SAAS,EAAE,WAAW,EAAE,eAAe;AAAA,IAAA,EACvC;AAEF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,SAAS,MAAM,SAAS,IACpB,SAAS,MAAM,MAAM,mBAAmB,QAAQ,KAAK,MACrD,yBAAyB,QAAQ,KAAK;AAAA,MAC1C;AAAA,MACA,aAAa,MAAM,WAAW,IAAI,CAAC,4BAA4B,IAAI,CAAA;AAAA,IAAC;AAAA,EAExE;AAAA,EAEQ,YAAkB;AACxB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,wGAAwG;AAAA,IAC1H;AAAA,EACF;AACF;AC/DO,MAAM,YAAsC;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EAEjB,YAAY,QAAgC;AAC1C,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,SAAwB,QAA6C;AAChF,SAAK,UAAA;AAEL,UAAM,OAAgC;AAAA,MACpC,OAAO,QAAQ;AAAA,MACf,aAAa,QAAQ,cAAc;AAAA,MACnC,MAAM;AAAA,IAAA;AAER,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,kBAAkB,QAAQ;AAAA,IACjC;AACA,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,kBAAkB,QAAQ;AAAA,IACjC;AAEA,UAAM,WAAW,MAAM,iBAAiB,GAAG,KAAK,OAAO,WAAW;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,YAAY;AAEzC,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,UAAM,SAAS,KAAK,WAAW,CAAA,GAAI,IAAI,CAAC,OAAO;AAAA,MAC7C,OAAO,EAAE,SAAS,EAAE;AAAA,MACpB,KAAK,EAAE;AAAA,MACP,SAAS,EAAE,QAAQ;AAAA,MACnB,aAAa,EAAE;AAAA,IAAA,EACf;AAEF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,SAAS,MAAM,SAAS,IACpB,SAAS,MAAM,MAAM,mBAAmB,QAAQ,KAAK,MACrD,yBAAyB,QAAQ,KAAK;AAAA,MAC1C;AAAA,MACA,aAAa,MAAM,WAAW,IAAI,CAAC,4BAA4B,IAAI,CAAA;AAAA,IAAC;AAAA,EAExE;AAAA,EAEA,MAAM,MAAM,SAAuB,QAA4C;AAC7E,SAAK,UAAA;AAEL,UAAM,OAAO;AAAA,MACX,MAAM,CAAC,QAAQ,GAAG;AAAA,MAClB,MAAM;AAAA,IAAA;AAGR,UAAM,WAAW,MAAM,iBAAiB,GAAG,KAAK,OAAO,aAAa;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,cAAc;AAE3C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,UAAM,OAAO,KAAK,UAAU,CAAC;AAC7B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AAEA,WAAO;AAAA,MACL,SAAS,KAAK,SAAS;AAAA,MACvB,SAAS,KAAK,QAAQ;AAAA,MACtB,aAAa,CAAA;AAAA,MACb,UAAU,CAAA;AAAA,IAAC;AAAA,EAEf;AAAA,EAEQ,YAAkB;AACxB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,kGAAkG;AAAA,IACpH;AAAA,EACF;AACF;ACvGO,MAAM,kBAA4C;AAAA,EAC9C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EAEjB,YAAY,QAAgC;AAC1C,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAC5D;AAAA,EAEA,MAAM,OAAO,SAAwB,QAA6C;AAChF,SAAK,UAAA;AAEL,UAAM,OAAgC;AAAA,MACpC,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ,cAAc;AAAA,MAC7B,SAAS,CAAC,KAAK;AAAA,IAAA;AAEjB,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,iBAAiB,QAAQ;AAAA,IAChC;AACA,QAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAK,iBAAiB,QAAQ;AAAA,IAChC;AAEA,UAAM,WAAW,MAAM,iBAAiB,GAAG,KAAK,OAAO,cAAc;AAAA,MACnE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,MAAM;AAAA,QACpC,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,kBAAkB;AAE/C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,QAAI,KAAK,YAAY,OAAO;AAC1B,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAEA,UAAM,WAAW,KAAK,MAAM,OAAO,CAAA;AACnC,UAAM,QAAQ,SAAS,IAAI,CAAC,OAAO;AAAA,MACjC,OAAO,EAAE,SAAS,EAAE;AAAA,MACpB,KAAK,EAAE;AAAA,MACP,SAAS,EAAE,eAAe,EAAE,YAAY;AAAA,IAAA,EACxC;AAEF,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,SAAS,MAAM,SAAS,IACpB,SAAS,MAAM,MAAM,mBAAmB,QAAQ,KAAK,MACrD,yBAAyB,QAAQ,KAAK;AAAA,MAC1C;AAAA,MACA,aAAa,MAAM,WAAW,IAAI,CAAC,4BAA4B,IAAI,CAAA;AAAA,IAAC;AAAA,EAExE;AAAA,EAEA,MAAM,MAAM,SAAuB,QAA4C;AAC7E,SAAK,UAAA;AAEL,UAAM,OAAO;AAAA,MACX,KAAK,QAAQ;AAAA,MACb,SAAS,CAAC,UAAU;AAAA,IAAA;AAGtB,UAAM,WAAW,MAAM,iBAAiB,GAAG,KAAK,OAAO,cAAc;AAAA,MACnE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,MAAM;AAAA,QACpC,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB;AAAA,IAAA,GACC,KAAK,SAAS;AAEjB,UAAM,aAAa,UAAU,kBAAkB;AAE/C,UAAM,OAAQ,MAAM,SAAS,KAAA;AAC7B,QAAI,KAAK,YAAY,OAAO;AAC1B,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAEA,WAAO;AAAA,MACL,SAAS,KAAK,MAAM,UAAU,SAAS;AAAA,MACvC,SAAS,KAAK,MAAM,YAAY;AAAA,MAChC,aAAa,CAAA;AAAA,MACb,UAAU,CAAA;AAAA,IAAC;AAAA,EAEf;AAAA,EAEQ,YAAkB;AACxB,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,8GAA8G;AAAA,IAChI;AAAA,EACF;AACF;AC/HA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB,MAAM;AACnC,MAAM,6BAA6B,IAAI,OAAO;AAC9C,MAAM,2BAA2B,KAAK;AAwB/B,MAAM,cAAwC;AAAA,EAC1C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAEJ;AAAA,EACA;AAAA,EAEjB,YAAY,QAAgC;AAC1C,UAAM,WAAW,mBAAmB,OAAO,cAAc;AACzD,SAAK,iBAAiB,aAAa,SAAY,SAAY,QAAQ,QAAQ;AAC3E,SAAK,YAAY,OAAO,SAAS,OAAO,SAAS,KAAK,OAAO,YAAY,IACrE,OAAO,YACP;AAAA,EACN;AAAA,EAEA,YAAqB;AACnB,WAAO,KAAK,mBAAmB,UAC1B,qBAAqB,KAAK,cAAc,MAAM;AAAA,EACrD;AAAA,EAEA,MAAM,OAAO,SAAwB,QAA6C;AAChFC,qBAAe,MAAM;AAErB,UAAM,aAAa,KAAK,mBAAmB,SACvC,SACA,qBAAqB,KAAK,cAAc;AAC5C,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,oEAAoE;AAAA,IACtF;AAEA,UAAM,OAA+B;AAAA,MACnC,IAAI,WAAA;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,CAAC;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,SAAS,CAAC,EAAE,MAAM,cAAc,MAAM,QAAQ,OAAO;AAAA,MAAA,CACtD;AAAA,MACD,UAAU,EAAE,cAAc,CAAC,EAAE,GAAG,QAAQ,MAAA,CAAO,EAAA;AAAA,MAC/C,UAAU;AAAA,QACR,qBAAqB;AAAA,QACrB,iBAAiB,CAAC,QAAQ;AAAA,QAC1B,qBAAqB;AAAA,MAAA;AAAA,MAEvB,mBAAmB;AAAA,IAAA;AAGrB,UAAM,iBAAiB,YAAY,QAAQ,KAAK,SAAS;AACzD,UAAM,gBAAgB,SAAS,YAAY,IAAI,CAAC,QAAQ,cAAc,CAAC,IAAI;AAC3E,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,kBAAkB;AAAA,QACvC,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS;AAAA,UACP,eAAe,UAAU,WAAW,WAAW;AAAA,UAC/C,sBAAsB,WAAW;AAAA,UACjC,gBAAgB;AAAA,UAChB,QAAQ;AAAA,UACR,YAAY;AAAA,QAAA;AAAA,QAEd,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,QAAQ;AAAA,MAAA,CACT;AAAA,IACH,SAAS,OAAO;AACdA,uBAAe,MAAM;AACrB,UAAI,eAAe,SAAS;AAC1B,cAAM,IAAI,MAAM,uCAAuC,KAAK,SAAS,MAAM,EAAE,OAAO,OAAO;AAAA,MAC7F;AACA,YAAM,IAAI,MAAM,sCAAsC,EAAE,OAAO,OAAO;AAAA,IACxE;AAEA,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM;AAAA,QACd;AAAA,QACA,SAAS,KAAK,6BAA6B;AAAA,QAC3C;AAAA,MAAA;AAAA,IAEJ,SAAS,OAAO;AACdA,uBAAe,MAAM;AACrB,UAAI,eAAe,SAAS;AAC1B,cAAM,IAAI,MAAM,uCAAuC,KAAK,SAAS,MAAM,EAAE,OAAO,OAAO;AAAA,MAC7F;AACA,UAAI,iBAAiB,sBAAuB,OAAM;AAClD,YAAM,IAAI,MAAM,4CAA4C,SAAS,MAAM,KAAK,EAAE,OAAO,OAAO;AAAA,IAClG;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,gBAAgB,OAAO;AACtC,YAAM,SAAS,SAAS,KAAK,MAAM,KAAK;AACxC,UAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,cAAM,IAAI;AAAA,UACR,uDAAuD,SAAS,MAAM,IAAI,MAAM;AAAA,QAAA;AAAA,MAEpF;AACA,YAAM,IAAI,MAAM,oCAAoC,SAAS,MAAM,IAAI,MAAM,EAAE;AAAA,IACjF;AAEA,WAAO,uBAAuB,OAAO;AAAA,EACvC;AACF;AAEA,SAAS,qBAAqB,UAAqD;AACjF,MAAI;AACF,UAAM,QAAQ,SAAS,QAAQ;AAC/B,QAAI,CAAC,MAAM,OAAA,KAAY,MAAM,QAAQ,KAAK,MAAM,OAAO,qBAAsB,QAAO;AACpF,UAAM,QAAiB,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAChE,WAAO,sBAAsB,KAAK;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,uBAAuB,OAA8B;AAC5D,MAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,WAAW,UAAU;AACxD,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,MAAM,YAAY,UAAa,CAAC,MAAM,QAAQ,MAAM,OAAO,GAAG;AAChE,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AAEA,QAAM,QAA+B,CAAA;AACrC,QAAM,2BAAW,IAAA;AACjB,aAAW,QAAQ,MAAM,WAAW,CAAA,GAAI;AACtC,QAAI,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS,cAAe;AACpD,UAAM,MAAM,YAAY,KAAK,GAAG;AAChC,QAAI,CAAC,OAAO,KAAK,IAAI,GAAG,EAAG;AAC3B,SAAK,IAAI,GAAG;AACZ,UAAM,KAAK;AAAA,MACT,OAAO,mBAAmB,KAAK,KAAK,KAAK;AAAA,MACzC;AAAA,MACA,SAAS,mBAAmB,KAAK,OAAO,KAAK;AAAA,IAAA,CAC9C;AAAA,EACH;AAEA,QAAM,UAAU,MAAM;AACtB,SAAO;AAAA,IACL,QAAQ,QAAQ,SAAS,IAAI,OAAO;AAAA,IACpC,QAAQ;AAAA,IACR,SAAS,WAAW,kBAAkB,MAAM,MAAM;AAAA,IAClD;AAAA,IACA,aAAa,QAAQ,SAAS,IAAI,CAAA,IAAK,CAAC,gCAAgC;AAAA,EAAA;AAE5E;AAEA,SAAS,sBAAsB,OAAmD;AAChF,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAG7B,QAAM,SAAS,MAAM,QAAQ,MAAM,WAAW,IAAI,MAAM,cAAc,CAAA;AACtE,QAAM,kBAAkB,mBAAmB,MAAM,eAAe;AAChE,QAAM,WAAW,kBACb,OAAO,OAAO,CAAC,UAAU,SAAS,KAAK,MACnC,mBAAmB,MAAM,UAAU,KAAK,mBAAmB,MAAM,SAAS,OAAO,eAAe,IACpG,MAAM,oBAAoB,SAAY,SAAS,CAAA;AACnD,QAAM,mBAAmB,SAAS,WAAW,KAAK,SAAS,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI;AAExF,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,SAAS,MAAM,UAAU,IAAI,MAAM,aAAa;AAAA,IAChD,SAAS,MAAM,MAAM,IAAI,MAAM,SAAS;AAAA,IACxC;AAAA,EAAA;AAGF,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,UAAW;AAChB,UAAM,cAAc,mBAAmB,UAAU,YAAY,KACxD,mBAAmB,UAAU,MAAM;AACxC,QAAI,CAAC,YAAa;AAElB,UAAM,YAAY,mBAAmB,UAAU,UAAU,KACpD,mBAAmB,UAAU,SAAS,KACtC,mBAAmB,WAAW;AACnC,QAAI,CAAC,UAAW;AAEhB,WAAO,EAAE,aAAa,UAAA;AAAA,EACxB;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,aAA0D;AAClF,MAAI;AACF,UAAM,QAAQ,YAAY,MAAM,GAAG;AACnC,QAAI,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,EAAG,QAAO;AAC5C,UAAM,UAAmB,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC,GAAG,WAAW,EAAE,SAAS,MAAM,CAAC;AACvF,WAAO,SAAS,OAAO,IAAI,UAAU;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,mBAAmB,aAAyC;AACnE,QAAM,OAAO,iBAAiB,WAAW,IAAI,6BAA6B;AAC1E,SAAO,SAAS,IAAI,IAAI,mBAAmB,KAAK,kBAAkB,IAAI;AACxE;AAEA,MAAM,8BAA8B,MAAM;AAAA,EACxC,YAAY,OAAe;AACzB,UAAM,yCAAyC,KAAK,QAAQ;AAC5D,SAAK,OAAO;AAAA,EACd;AACF;AAEA,eAAe,gBACb,UACA,UACA,QACkB;AAClB,QAAM,iBAAiB,OAAO,SAAS,QAAQ,IAAI,gBAAgB,CAAC;AACpE,MAAI,OAAO,SAAS,cAAc,KAAK,iBAAiB,UAAU;AAChE,UAAM,SAAS,MAAM,OAAA,EAAS,MAAM,MAAM;AAAA,IAAC,CAAC;AAC5C,UAAM,IAAI,sBAAsB,QAAQ;AAAA,EAC1C;AACA,MAAI,CAAC,SAAS,KAAM,OAAM,IAAI,YAAY,qBAAqB;AAE/D,QAAM,SAAS,SAAS,KAAK,UAAA;AAC7B,QAAM,SAAuB,CAAA;AAC7B,MAAI,QAAQ;AACZ,MAAI;AACF,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,UAAU,MAAM,UAAU,OAAO,KAAA,GAAQ,MAAM;AAC7D,UAAI,KAAM;AACV,UAAI,CAAC,MAAO;AACZ,eAAS,MAAM;AACf,UAAI,QAAQ,SAAU,OAAM,IAAI,sBAAsB,QAAQ;AAC9D,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,SAAS,MAAM,MAAM;AAAA,IAAC,CAAC;AACpC,UAAM;AAAA,EACR,UAAA;AACE,WAAO,YAAA;AAAA,EACT;AAEA,SAAO,KAAK,MAAM,OAAO,OAAO,QAAQ,KAAK,EAAE,SAAS,MAAM,CAAC;AACjE;AAEA,SAAS,UAAa,WAAuB,QAAiC;AAC5E,MAAI,OAAO,QAAS,QAAO,QAAQ,OAAO,aAAa,MAAM,CAAC;AAC9D,SAAO,IAAI,QAAW,CAACC,UAAS,WAAW;AACzC,UAAM,UAAU,MAAM,OAAO,aAAa,MAAM,CAAC;AACjD,WAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM;AACxD,cAAU;AAAA,MACR,CAAC,UAAU;AACT,eAAO,oBAAoB,SAAS,OAAO;AAC3CA,iBAAQ,KAAK;AAAA,MACf;AAAA,MACA,CAAC,UAAU;AACT,eAAO,oBAAoB,SAAS,OAAO;AAC3C,eAAO,KAAK;AAAA,MACd;AAAA,IAAA;AAAA,EAEJ,CAAC;AACH;AAEA,SAASD,iBAAe,QAA4B;AAClD,MAAI,QAAQ,QAAS,OAAM,aAAa,MAAM;AAChD;AAEA,SAAS,aAAa,QAA4B;AAChD,SAAO,IAAI,MAAM,+BAA+B,EAAE,OAAO,OAAO,QAAQ;AAC1E;AAEA,SAAS,gBAAgB,OAAoC;AAC3D,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,MAAM,OAAO,MAAM,UAAU,WAC/B,MAAM,QACN,SAAS,MAAM,KAAK,KAAK,OAAO,MAAM,MAAM,YAAY,WACtD,MAAM,MAAM,UACZ,OAAO,MAAM,YAAY,WACvB,MAAM,UACN;AACR,SAAO,KACH,QAAQ,2DAA2D,YAAY,EAChF,MAAM,GAAG,GAAK;AACnB;AAEA,SAAS,YAAY,OAAoC;AACvD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,WAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAAW,QAAQ;AAAA,EACzE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,mBAAmB,OAAoC;AAC9D,SAAO,OAAO,UAAU,YAAY,MAAM,WAAW,KAAK,MAAM,KAAA,IAAS;AAC3E;AC5UO,MAAM,aAAuC;AAAA,EACzC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EAErB,YAAY,SAAiC;AAAA,EAAC;AAAA,EAE9C,YAAqB;AACnB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,SAA+C;AAC1D,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,KAAK;AAAA,MACb,SAAS,4BAA4B,QAAQ,KAAK;AAAA,MAClD,OAAO;AAAA,QACL;AAAA,UACE,OAAO;AAAA,UACP,KAAK;AAAA,UACL,SAAS;AAAA,QAAA;AAAA,QAEX;AAAA,UACE,OAAO;AAAA,UACP,KAAK;AAAA,UACL,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,MAEF,aAAa,CAAC,uDAAuD;AAAA,IAAA;AAAA,EAEzE;AAAA,EAEA,MAAM,MAAM,SAA6C;AACvD,WAAO;AAAA,MACL,SAAS,iBAAiB,QAAQ,GAAG;AAAA,MACrC,SAAS;AAAA;AAAA,sCAAsD,QAAQ,GAAG;AAAA,MAC1E,OAAO;AAAA,QACL,EAAE,MAAM,WAAW,KAAK,sBAAA;AAAA,MAAsB;AAAA,MAEhD,aAAa,CAAC,iDAAiD;AAAA,MAC/D,UAAU,CAAA;AAAA,IAAC;AAAA,EAEf;AACF;AChDO,MAAM,cAAc;AAAA,EACzB,MAAM;AAAA,EACN,YAAY;AAAA,IACV,SAAS,EAAE,MAAM,SAAA;AAAA,IACjB,SAAS,EAAE,MAAM,SAAA;AAAA,IACjB,OAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,UACV,MAAM,EAAE,MAAM,SAAA;AAAA,UACd,KAAK,EAAE,MAAM,SAAA;AAAA,QAAkB;AAAA,QAEjC,UAAU,CAAC,QAAQ,KAAK;AAAA,MAAA;AAAA,IAC1B;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,SAAA;AAAA,IAAkB;AAAA,IAEnC,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,SAAA;AAAA,IAAkB;AAAA,EACnC;AAAA,EAEF,UAAU,CAAC,WAAW,WAAW,aAAa;AAChD;ACxBO,MAAM,8BAA8B;AAQpC,SAAS,oBACd,UACA,eACU;AACV,MAAI;AACJ,MAAI;AACF,eAAW,SAAS,YAAY;AAAA,EAClC,QAAQ;AAAA,EAER;AAEA,QAAM,aAAa,kBAAkB,SAAY,SAAS,SAAS;AACnE,QAAM,2BAAW,IAAA;AACjB,QAAM,aAAuB,CAAA;AAC7B,aAAW,MAAM,WAAW,CAAC,UAAU,GAAG,UAAU,IAAI,YAAY;AAClE,QAAI,KAAK,IAAI,EAAE,EAAG;AAClB,SAAK,IAAI,EAAE;AACX,eAAW,KAAK,EAAE;AAAA,EACpB;AACA,SAAO;AACT;AAEO,SAAS,qBACd,QACA,WACgB;AAChB,QAAM,aAAa,IAAI,gBAAA;AACvB,MAAI,aAAa;AACjB,MAAI;AACJ,MAAI;AAEJ,MAAI,QAAQ,SAAS;AACnB,eAAW,MAAM,OAAO,MAAM;AAAA,EAChC,OAAO;AACL,QAAI,QAAQ;AACV,sBAAgB,MAAM,WAAW,MAAM,OAAO,MAAM;AACpD,aAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM;AAAA,IAChE;AACA,YAAQ,WAAW,MAAM;AACvB,mBAAa;AACb,iBAAW,MAAM,IAAI,MAAM,oCAAoC,SAAS,IAAI,CAAC;AAAA,IAC/E,GAAG,SAAS;AAAA,EACd;AAEA,SAAO;AAAA,IACL,QAAQ,WAAW;AAAA,IACnB,UAAU,MAAM;AAAA,IAChB,UAAU;AACR,UAAI,UAAU,OAAW,cAAa,KAAK;AAC3C,UAAI,UAAU,cAAe,QAAO,oBAAoB,SAAS,aAAa;AAAA,IAChF;AAAA,EAAA;AAEJ;AAEO,SAAS,eAAkB,WAAuB,QAA6C;AACpG,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,SAAS;AAClB,cAAU,MAAM,MAAM;AAAA,IAAC,CAAC;AACxB,WAAO,QAAQ,OAAO,OAAO,MAAM;AAAA,EACrC;AAEA,SAAO,IAAI,QAAW,CAACC,UAAS,WAAW;AACzC,UAAM,UAAU,MAAM;AACpB,aAAO,oBAAoB,SAAS,OAAO;AAC3C,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,WAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM;AACxD,cAAU;AAAA,MACR,CAAC,UAAU;AACT,eAAO,oBAAoB,SAAS,OAAO;AAC3C,QAAAA,SAAQ,KAAK;AAAA,MACf;AAAA,MACA,CAAC,UAAU;AACT,eAAO,oBAAoB,SAAS,OAAO;AAC3C,eAAO,KAAK;AAAA,MACd;AAAA,IAAA;AAAA,EAEJ,CAAC;AACH;AAEO,SAAS,eAAe,QAAuC;AACpE,MAAI,QAAQ,QAAS,OAAM,OAAO;AACpC;AC/EA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;AAEjB,SAAS,qBACd,KACA,UACA,UACA,eACM;AACN,MAAI,OAAO,IAAI,OAAO,aAAa,YAAY;AAC7C,YAAQ,MAAM,gFAAgF;AAC9F;AAAA,EACF;AAEA,MAAI,MAAM,SAAS;AAAA,IACjB,MAAM;AAAA,IACN,aACE;AAAA,IACF,YAAY;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,QACV,KAAK;AAAA,UACH,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,QAEf,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,MAEF,UAAU,CAAC,KAAK;AAAA,IAAA;AAAA,IAElB,QAAQ;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,CAAC,OAAgB,UAAmC;AAAA,QAC1D,EAAE,MAAM,QAAQ,MAAM,oBAAoB,KAAK,EAAA;AAAA,MAAE;AAAA,IACnD;AAAA,IAEF,WAAW,uBAAuB;AAAA,IAClC,mBAAmB,MAAM;AAAA,IACzB,aAAa,CAAC,UAA2C;AAAA,MACvD,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ,MAAM,QAAQ,MAAuC,MAAgC;AACnF,UAAI,OAAO,MAAM,QAAQ,YAAY,CAAC,gBAAgB,KAAK,KAAK,IAAI,KAAA,CAAM,GAAG;AAC3E,cAAM,IAAI,MAAM,mCAAmC;AAAA,MACrD;AAEA,YAAM,UAAwB;AAAA,QAC5B,KAAK,KAAK,IAAI,KAAA;AAAA,QACd,OAAO,KAAK;AAAA,QACZ,QAAQ;AAAA,MAAA;AAGV,aAAO,kBAAkB,UAAU,UAAU,eAAe,SAAS,KAAK,MAAM;AAAA,IAClF;AAAA,EAAA,CACD;AACH;AAEA,SAAS,yBACP,UACA,eACU;AACV,SAAO,oBAAoB,UAAU,aAAa,EAAE,OAAO,CAAC,OAAO;AACjE,UAAM,WAAW,SAAS,IAAI,EAAE;AAChC,WAAO,aAAa,UACf,SAAS,cAAc,SACvB,OAAO,SAAS,UAAU;AAAA,EACjC,CAAC;AACH;AAEA,eAAe,kBACb,UACA,UACA,eACA,SACA,QACsB;AACtB,iBAAe,MAAM;AACrB,QAAM,aAAa,yBAAyB,UAAU,aAAa;AACnE,QAAM,YAAsB,CAAA;AAC5B,QAAM,mCAAmB,IAAA;AACzB,MAAI;AAEJ,SAAO,MAAM;AACX,mBAAe,MAAM;AACrB,UAAM,EAAE,MAAA,IAAU,SAAS,OAAO,YAAY,OAAO;AACrD,UAAM,KAAK,MAAM,KAAK,CAAC,gBAAgB;AACrC,UAAI,aAAa,IAAI,WAAW,EAAG,QAAO;AAC1C,YAAMC,YAAW,SAAS,IAAI,WAAW;AACzC,aAAOA,WAAU,eACZA,UAAS,cAAc,SACvB,OAAOA,UAAS,UAAU;AAAA,IACjC,CAAC;AACD,QAAI,OAAO,OAAW;AAEtB,UAAM,WAAW,SAAS,IAAI,EAAE;AAChC,iBAAa,IAAI,EAAE;AACnB,UAAM,UAAU,UAAU,WAAW,IACjC,SACA,qBAAqB,QAAQ,2BAA2B;AAE5D,QAAI;AACF,YAAM,gBAAgB,SAAS,UAAU;AACzC,YAAM,SAAS,MAAM,eAAe,SAAS,MAAO,SAAS,aAAa,GAAG,aAAa;AAC1F,qBAAe,MAAM;AACrB,UAAI,SAAS,SAAA,EAAY,OAAM,QAAQ,OAAO;AAC9C,YAAM,EAAE,SAAAC,UAAS,UAAAC,cAAa,SAAS,OAAO,YAAY,OAAO;AACjE,UAAI,UAAU,SAAS,GAAG;AACxB,eAAO,WAAW,OAAO,YAAY,CAAA;AACrC,eAAO,SAAS,KAAK,uBAAuB,UAAU,KAAK,IAAI,CAAC,SAAS;AAAA,MAC3E;AACA,UAAID,SAAQ,SAAS,GAAG;AACtB,eAAO,WAAW,OAAO,YAAY,CAAA;AACrC,eAAO,SAAS,KAAK,8BAA8BA,SAAQ,KAAK,IAAI,CAAC,EAAE;AAAA,MACzE;AACA,UAAIC,UAAS,SAAS,GAAG;AACvB,eAAO,WAAW,OAAO,YAAY,CAAA;AACrC,eAAO,SAAS,KAAK,qCAAqCA,UAAS,KAAK,IAAI,CAAC,EAAE;AAAA,MACjF;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,qBAAe,MAAM;AACrB,YAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACvE,kBAAY,kBAAkB,8BAC1B,SACA,SAAS,aACP,IAAI,MAAM,oCAAoC,2BAA2B,MAAM,EAAE,OAAO,OAAA,CAAQ,IAChG;AACN,gBAAU,KAAK,EAAE;AACjB,cAAQ,KAAK,gCAAgC,EAAE,gCAAgC,UAAU,OAAO,EAAE;AAClG,eAAS,OAAO,IAAI,SAAS,SAAS;AAAA,IACxC,UAAA;AACE,eAAS,QAAA;AAAA,IACX;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,SAAA,IAAa,SAAS,OAAO,YAAY,OAAO;AACjE,QAAM,aAAa,QAAQ,SAAS,IAAI,cAAc,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC9E,QAAM,cAAc,SAAS,SAAS,IAAI,6BAA6B,SAAS,KAAK,IAAI,CAAC,MAAM;AAChG,QAAM,IAAI;AAAA,IACR,8BAA8B,UAAU,SAAS,IAAI,YAAY,UAAU,KAAK,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,GAAG,WAAW,KAAK,WAAW,WAAW,wBAAwB;AAAA,IAC3K,EAAE,OAAO,UAAA;AAAA,EAAU;AAEvB;AAEA,SAAS,oBAAoB,OAAwC;AACnE,QAAM,QAAkB,CAAC,OAAO,MAAM,WAAW,EAAE,CAAC;AACpD,QAAM,UAAU,OAAQ,MAAM,WAAsB,EAAE,EAAE,KAAA;AACxD,MAAI,SAAS;AACX,UAAM,KAAK,IAAI,YAAY,QAAQ,SAAS,qBAAqB,GAAG,QAAQ,MAAM,GAAG,kBAAkB,CAAC,MAAM,OAAO;AAAA,EACvH;AACA,QAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,MAAM,MAAM,GAAG,eAAe,IAAI,CAAA;AACnF,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,KAAK,IAAI,QAAQ;AACvB,eAAW,QAAQ,OAAO;AACxB,YAAM,OAAO,OAAO,SAAS,YAAY,SAAS,OAAO,OAAQ,KAAiC,QAAQ,EAAE,IAAI;AAChH,YAAM,MAAM,OAAO,SAAS,YAAY,SAAS,OAAO,OAAQ,KAAiC,OAAO,EAAE,IAAI;AAC9G,YAAM,KAAK,KAAK,IAAI,MAAM,GAAG,EAAE;AAAA,IACjC;AAAA,EACF;AACA,QAAM,cAAc,MAAM,QAAQ,MAAM,WAAW,IAAI,MAAM,cAAc,CAAA;AAC3E,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,KAAK,IAAI,cAAc,YAAY,KAAK,IAAI,CAAC,EAAE;AAAA,EACvD;AACA,QAAM,WAAW,MAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,WAAW,CAAA;AAClE,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,KAAK,IAAI,aAAa,SAAS,KAAK,IAAI,CAAC,EAAE;AAAA,EACnD;AACA,SAAO,MAAM,OAAO,OAAO,EAAE,KAAK,IAAI;AACxC;ACtKO,SAAS,0BACd,KACA,UACA,UACA,eACM;AACN,MAAI,OAAO,IAAI,KAAK,2BAA2B,YAAY;AACzD,YAAQ,MAAM,iGAAiG;AAC/G;AAAA,EACF;AAEA,MAAI,IAAI,uBAAuB;AAAA,IAC7B,IAAI;AAAA,IACJ,WAAW,MAAM,qBAAqB,UAAU,UAAU,aAAa;AAAA,IACvE,MAAM,OAAO,SAA+B,QAAiC;AAC3E,YAAM,gBAA+B;AAAA,QACnC,OAAO,QAAQ;AAAA,QACf,YAAY,QAAQ;AAAA,QACpB,aAAa,QAAQ;AAAA,QACrB,SAAS,QAAQ;AAAA,QACjB,UAAU,QAAQ;AAAA,QAClB,UAAU,QAAQ;AAAA,MAAA;AAGpB,YAAM,EAAE,QAAQ,WAAW,SAAS,SAAA,IAAa,MAAM;AAAA,QACrD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAGF,aAAO;AAAA,QACL,SAAS,cAAc,QAAQ,WAAW,SAAS,QAAQ;AAAA,QAC3D,SAAS,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,UACnC,KAAK,KAAK;AAAA,UACV,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAA,IAAU,CAAA;AAAA,UACzC,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAA,IAAY,CAAA;AAAA,UAC/C,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAA,IAAgB,CAAA;AAAA,QAAC,EAC5D;AAAA,QACF,WAAW;AAAA,MAAA;AAAA,IAEf;AAAA,EAAA,CACD;AACH;AAEA,SAAS,0BACP,UACA,eACU;AACV,SAAO,oBAAoB,UAAU,aAAa,EAAE,OAAO,CAAC,OAAO;AACjE,UAAM,WAAW,SAAS,IAAI,EAAE;AAChC,WAAO,aAAa,UAAa,SAAS,eAAe;AAAA,EAC3D,CAAC;AACH;AAEA,SAAS,qBACP,UACA,UACA,eACS;AACT,QAAM,aAAa,0BAA0B,UAAU,aAAa;AACpE,QAAM,EAAE,MAAA,IAAU,SAAS,OAAO,YAAY,QAAQ;AACtD,SAAO,MAAM,KAAK,CAAC,OAAO;AACxB,UAAM,WAAW,SAAS,IAAI,EAAE;AAChC,WAAO,UAAU,UAAA,KAAe,SAAS,eAAe;AAAA,EAC1D,CAAC;AACH;AASA,eAAe,mBACb,UACA,UACA,eACA,SACA,QAC0B;AAC1B,iBAAe,MAAM;AACrB,QAAM,aAAa,0BAA0B,UAAU,aAAa;AACpE,QAAM,YAAsB,CAAA;AAC5B,QAAM,mCAAmB,IAAA;AACzB,MAAI;AAEJ,SAAO,MAAM;AACX,mBAAe,MAAM;AACrB,UAAM,EAAE,MAAA,IAAU,SAAS,OAAO,YAAY,QAAQ;AACtD,UAAM,KAAK,MAAM,KAAK,CAAC,gBAAgB;AACrC,UAAI,aAAa,IAAI,WAAW,EAAG,QAAO;AAC1C,YAAMF,YAAW,SAAS,IAAI,WAAW;AACzC,aAAOA,WAAU,UAAA,KAAeA,UAAS,eAAe;AAAA,IAC1D,CAAC;AACD,QAAI,OAAO,OAAW;AAEtB,UAAM,WAAW,SAAS,IAAI,EAAE;AAChC,iBAAa,IAAI,EAAE;AACnB,UAAM,UAAU,UAAU,WAAW,IACjC,SACA,qBAAqB,QAAQ,2BAA2B;AAE5D,QAAI;AACF,YAAM,gBAAgB,SAAS,UAAU;AACzC,YAAM,SAAS,MAAM,eAAe,SAAS,OAAO,SAAS,aAAa,GAAG,aAAa;AAC1F,qBAAe,MAAM;AACrB,UAAI,SAAS,SAAA,EAAY,OAAM,QAAQ,OAAO;AAC9C,YAAM,EAAE,SAAAC,UAAS,UAAAC,cAAa,SAAS,OAAO,YAAY,QAAQ;AAClE,aAAO,EAAE,QAAQ,WAAW,SAAAD,UAAS,UAAAC,UAAAA;AAAAA,IACvC,SAAS,OAAO;AACd,qBAAe,MAAM;AACrB,YAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACvE,kBAAY,kBAAkB,8BAC1B,SACA,SAAS,aACP,IAAI,MAAM,oCAAoC,2BAA2B,MAAM,EAAE,OAAO,OAAA,CAAQ,IAChG;AACN,gBAAU,KAAK,EAAE;AACjB,cAAQ,KAAK,gCAAgC,EAAE,iCAAiC,UAAU,OAAO,EAAE;AACnG,eAAS,OAAO,IAAI,UAAU,SAAS;AAAA,IACzC,UAAA;AACE,eAAS,QAAA;AAAA,IACX;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,SAAA,IAAa,SAAS,OAAO,YAAY,QAAQ;AAClE,QAAM,aAAa,QAAQ,SAAS,IAAI,cAAc,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC9E,QAAM,cAAc,SAAS,SAAS,IAAI,6BAA6B,SAAS,KAAK,IAAI,CAAC,MAAM;AAChG,QAAM,IAAI;AAAA,IACR,+BAA+B,UAAU,SAAS,IAAI,YAAY,UAAU,KAAK,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,GAAG,WAAW,KAAK,WAAW,WAAW,wBAAwB;AAAA,IAC5K,EAAE,OAAO,UAAA;AAAA,EAAU;AAEvB;AAEA,SAAS,cACP,QACA,WACA,SACA,UACQ;AACR,QAAM,QAAQ,CAAC,qBAAqB,OAAO,MAAM,KAAK,OAAO,OAAO;AACpE,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,KAAK,wBAAwB,UAAU,KAAK,IAAI,CAAC,UAAU;AAAA,EACnE;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,KAAK,aAAa,QAAQ,KAAK,IAAI,CAAC,GAAG;AAAA,EAC/C;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,KAAK,4BAA4B,SAAS,KAAK,IAAI,CAAC,GAAG;AAAA,EAC/D;AACA,MAAI,OAAO,YAAY,SAAS,GAAG;AACjC,UAAM,KAAK,cAAc,OAAO,YAAY,KAAK,IAAI,CAAC,EAAE;AAAA,EAC1D;AACA,SAAO,MAAM,OAAO,OAAO,EAAE,KAAK,IAAI;AACxC;ACrKO,MAAM,OAAO;AACb,MAAM,SAAS,CAAC,SAAS,KAAK;AAErC,MAAM,yBAAyB,CAAC,SAAS,YAAY,UAAU,OAAO,WAAW;AAE1E,SAAS,MAAM,KAAU,YAA0B,IAAU;AAClE,QAAM,SAAS,iBAAiB,SAAS;AACzC,QAAM,WAAW,IAAI,iBAAA;AACrB,QAAM,WAAW,IAAI,mBAAA;AACrB,QAAM,WAAW,OAAO,aAAa;AAGrC,WAAS,SAAS,IAAI,cAAc,sBAAsB,QAAQ,OAAO,CAAC,CAAC;AAC3E,WAAS,SAAS,IAAI,iBAAiB,sBAAsB,QAAQ,UAAU,CAAC,CAAC;AACjF,WAAS,SAAS,IAAI,eAAe,sBAAsB,QAAQ,QAAQ,CAAC,CAAC;AAC7E,WAAS,SAAS,IAAI,YAAY,sBAAsB,QAAQ,KAAK,CAAC,CAAC;AACvE,WAAS,SAAS,IAAI,kBAAkB,sBAAsB,QAAQ,WAAW,CAAC,CAAC;AAEnF,MAAI,UAAU;AACZ,aAAS,SAAS,IAAI,aAAa,sBAAsB,QAAQ,UAAU,CAAC,CAAC;AAAA,EAC/E;AAGA,QAAM,iBAAiB,OAAO;AAC9B,MAAI,gBAAgB;AAClB,QAAI;AACF,eAAS,UAAU,cAAc;AAAA,IACnC,SAAS,OAAO;AACd,cAAQ,KAAK,2CAA2C,cAAc,yCAAyC,SAAS,KAAA,EAAO,CAAC,CAAC,GAAG;AAAA,IACtI;AAAA,EACF;AAGA,QAAM,gBAAgB,OAAO,iBAAiB;AAG9C,4BAA0B,KAAK,UAAU,UAAU,aAAa;AAChE,uBAAqB,KAAK,UAAU,UAAU,aAAa;AAE3D,UAAQ,IAAI,mDAAmD,SAAS,UAAA,EAAY,EAAE,6BAA6B,cAAc,KAAK,IAAI,CAAC,GAAG;AAChJ;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mini-z/dsh-search-providers",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Modular DSH search plugin with Codex-first web search, page fetching, and capability-aware automatic failover.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"dsh",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"README.md",
|
|
18
|
+
"search-providers.example.json",
|
|
18
19
|
"CHANGELOG.md"
|
|
19
20
|
],
|
|
20
21
|
"keywords": [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider": "codex",
|
|
3
|
+
"fallbackChain": [
|
|
4
|
+
"tinyfish",
|
|
5
|
+
"tavily",
|
|
6
|
+
"exa",
|
|
7
|
+
"firecrawl"
|
|
8
|
+
],
|
|
9
|
+
"providers": {
|
|
10
|
+
"codex": {
|
|
11
|
+
"credentialFile": "./codex-credential.json",
|
|
12
|
+
"timeoutMs": 55000
|
|
13
|
+
},
|
|
14
|
+
"tinyfish": {
|
|
15
|
+
"apiKey": "",
|
|
16
|
+
"timeoutMs": 55000
|
|
17
|
+
},
|
|
18
|
+
"tavily": {
|
|
19
|
+
"apiKey": ""
|
|
20
|
+
},
|
|
21
|
+
"exa": {
|
|
22
|
+
"apiKey": ""
|
|
23
|
+
},
|
|
24
|
+
"firecrawl": {
|
|
25
|
+
"apiKey": ""
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|