@tonydua/dsh-web-search-exa 0.1.4 → 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/README.i18n.yaml +2 -2
- package/README.md +246 -194
- package/README.zh.md +237 -104
- package/lib/index.d.ts +437 -0
- package/lib/index.js +509 -236
- package/package.json +33 -12
- package/lib/types/index.d.ts +0 -70
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { WebError, WebSearchProvider, WebSearchRequest, WebSearchResult } from "@deepseek-ai/dsh-web";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Wire and configuration types for `@tonydua/dsh-web-search-exa`.
|
|
7
|
+
*
|
|
8
|
+
* Two independent wire shapes live here: Exa's REST `POST /search` response
|
|
9
|
+
* (used when an API key is configured) and the normalized MCP payload returned
|
|
10
|
+
* by Exa's hosted MCP server (the anonymous, keyless path). Neither is exported
|
|
11
|
+
* to consumers — the provider normalizes both into the seam's
|
|
12
|
+
* `WebSearchResult` vocabulary.
|
|
13
|
+
*
|
|
14
|
+
* @module @tonydua/dsh-web-search-exa/types
|
|
15
|
+
*/
|
|
16
|
+
/** Retrieval mode sent to Exa's REST `type` field. */
|
|
17
|
+
type ExaSearchType = 'auto' | 'keyword' | 'neural';
|
|
18
|
+
/** One `results[]` entry of Exa's REST response (fields we actually read). */
|
|
19
|
+
interface ExaRestResult {
|
|
20
|
+
readonly url: string;
|
|
21
|
+
readonly title?: string | null;
|
|
22
|
+
/** Exa's per-result highlight sentences; the only portable snippet source. */
|
|
23
|
+
readonly highlights?: readonly string[] | null;
|
|
24
|
+
/** Exa's publication/crawl timestamp, passed through as `publishedAt`. */
|
|
25
|
+
readonly publishedDate?: string | null;
|
|
26
|
+
}
|
|
27
|
+
/** Exa's REST `POST /search` response body. */
|
|
28
|
+
interface ExaRestResponse {
|
|
29
|
+
readonly results?: readonly ExaRestResult[] | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One `content[]` block of a normalized MCP result payload.
|
|
33
|
+
* Non-text blocks (images, resources) are skipped by the collector.
|
|
34
|
+
*/
|
|
35
|
+
interface McpContentItem {
|
|
36
|
+
readonly type?: string;
|
|
37
|
+
readonly text?: string;
|
|
38
|
+
}
|
|
39
|
+
/** The `result` member of a JSON-RPC 2.0 response to a `tools/call`. */
|
|
40
|
+
interface McpToolResult {
|
|
41
|
+
readonly content?: readonly McpContentItem[] | null;
|
|
42
|
+
/** True when the tool itself reported a failure (as opposed to a transport error). */
|
|
43
|
+
readonly isError?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The `error` member of a JSON-RPC 2.0 response. */
|
|
46
|
+
interface McpJsonRpcError {
|
|
47
|
+
readonly code?: number;
|
|
48
|
+
readonly message?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A JSON-RPC 2.0 envelope as returned by Exa's hosted MCP server. */
|
|
51
|
+
interface McpPayload {
|
|
52
|
+
readonly result?: McpToolResult | null;
|
|
53
|
+
readonly error?: McpJsonRpcError | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One parsed `Title:`-led section of Exa MCP text output. Every field is
|
|
57
|
+
* best-effort: Exa omits or emits `N/A` for several of them.
|
|
58
|
+
*/
|
|
59
|
+
interface ExaMcpSection {
|
|
60
|
+
url?: string;
|
|
61
|
+
title?: string;
|
|
62
|
+
publishedAt?: string;
|
|
63
|
+
author?: string;
|
|
64
|
+
highlights?: string[];
|
|
65
|
+
/** Full-page text, used only as a snippet fallback when no highlight exists. */
|
|
66
|
+
text?: string;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/provider.d.ts
|
|
70
|
+
/** MCP tool names the anonymous path can call. */
|
|
71
|
+
type ExaMcpTool = 'web_search_exa' | 'web_search_advanced_exa';
|
|
72
|
+
/**
|
|
73
|
+
* Fully resolved options the provider serves one search with. Produced by
|
|
74
|
+
* {@link resolveOptions} from the current Settings section, so every field is
|
|
75
|
+
* already defaulted by the time the provider reads it.
|
|
76
|
+
*
|
|
77
|
+
* The optional members are declared `| undefined` rather than merely optional:
|
|
78
|
+
* `resolveOptions` always sets them (possibly to `undefined`), and this
|
|
79
|
+
* package type-checks under `exactOptionalPropertyTypes`.
|
|
80
|
+
*/
|
|
81
|
+
interface ExaSearchProviderOptions {
|
|
82
|
+
readonly providerId?: string | undefined;
|
|
83
|
+
readonly apiKey: string;
|
|
84
|
+
readonly apiKeyEnv: string;
|
|
85
|
+
readonly baseURL: string;
|
|
86
|
+
readonly apiURL?: string | undefined;
|
|
87
|
+
readonly mcpURL: string;
|
|
88
|
+
readonly mcpTool: ExaMcpTool;
|
|
89
|
+
readonly searchType: ExaSearchType;
|
|
90
|
+
readonly numResults?: number | undefined;
|
|
91
|
+
readonly highlightsPerResult: number;
|
|
92
|
+
}
|
|
93
|
+
/** The dsh Settings section shape (fields optional at the load boundary). */
|
|
94
|
+
interface ExaSearchProviderConfig {
|
|
95
|
+
providerId?: string;
|
|
96
|
+
apiKey?: string;
|
|
97
|
+
apiKeyEnv?: string;
|
|
98
|
+
baseURL?: string;
|
|
99
|
+
/** @deprecated Use `baseURL`; this full endpoint remains supported for compatibility. */
|
|
100
|
+
apiURL?: string;
|
|
101
|
+
mcpURL?: string;
|
|
102
|
+
mcpTool?: ExaMcpTool;
|
|
103
|
+
searchType?: ExaSearchType;
|
|
104
|
+
numResults?: number;
|
|
105
|
+
highlightsPerResult?: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The launch-environment snapshot the provider resolves credentials against.
|
|
109
|
+
* Structural rather than the concrete dsh type so direct library use can pass
|
|
110
|
+
* any `get`-shaped source.
|
|
111
|
+
*/
|
|
112
|
+
interface ExaKeyEnvironment {
|
|
113
|
+
get(name: string): {
|
|
114
|
+
value: string;
|
|
115
|
+
} | undefined;
|
|
116
|
+
}
|
|
117
|
+
/** Resolve one search's API key; `undefined` selects the anonymous MCP path. */
|
|
118
|
+
type ExaApiKeyResolver = (options: ExaSearchProviderOptions) => string | undefined;
|
|
119
|
+
/** Options thunk: called per operation so live Settings edits take effect next search. */
|
|
120
|
+
type ExaOptionsResolver = () => ExaSearchProviderOptions;
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the API key: literal config first, then the environment variable.
|
|
123
|
+
* `undefined` means the anonymous MCP path is used.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resolveApiKeyFromProcess(options: ExaSearchProviderOptions): string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Resolve a key against dsh's immutable launch-environment snapshot. The
|
|
128
|
+
* process fallback keeps direct library use and older hosts working.
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveApiKey(options: ExaSearchProviderOptions, environment?: ExaKeyEnvironment): string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* How long a tripped breaker keeps `available()` false before the next search
|
|
133
|
+
* is allowed to probe the anonymous endpoint again.
|
|
134
|
+
*/
|
|
135
|
+
export declare const DEFAULT_BREAKER_COOLDOWN_MS = 300000;
|
|
136
|
+
/**
|
|
137
|
+
* Consecutive transient failures that trip the breaker.
|
|
138
|
+
*
|
|
139
|
+
* Only 5xx, 429, and network-level failures count: they say the anonymous
|
|
140
|
+
* endpoint is having a bad time, not that the request was wrong. A 4xx (other
|
|
141
|
+
* than 429) is a configuration error and would fail identically forever, so it
|
|
142
|
+
* deliberately does NOT trip the breaker — hiding a bad endpoint behind a
|
|
143
|
+
* cooldown would just delay the same error.
|
|
144
|
+
*/
|
|
145
|
+
export declare const DEFAULT_BREAKER_THRESHOLD = 3;
|
|
146
|
+
/** A failure worth retrying later, as opposed to a permanent configuration error. */
|
|
147
|
+
export declare class ExaTransientError extends WebError {
|
|
148
|
+
constructor(message: string, cause?: unknown);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A rate limit, kept distinct from a generic provider failure so the model (and
|
|
152
|
+
* a human reading the transcript) can tell "Exa is throttling the keyless
|
|
153
|
+
* channel, configure a key" apart from "the network is broken".
|
|
154
|
+
*
|
|
155
|
+
* `code` is an open string in the seam's vocabulary, so a plugin-specific code
|
|
156
|
+
* is the supported way to route this; consumers must tolerate unknown codes.
|
|
157
|
+
*/
|
|
158
|
+
export declare class ExaRateLimitError extends WebError {
|
|
159
|
+
constructor(message: string);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* An anonymous response body that exceeded {@link MAX_MCP_RESPONSE_BYTES}.
|
|
163
|
+
*
|
|
164
|
+
* Extends {@link ExaTransientError} because the endpoint, not the caller's
|
|
165
|
+
* configuration, produced it: a body this size is a bad day on Exa's side, and
|
|
166
|
+
* the breaker should get to count it.
|
|
167
|
+
*/
|
|
168
|
+
export declare class ExaResponseTooLargeError extends ExaTransientError {
|
|
169
|
+
/** The number of bytes observed, or `undefined` when the server declared the size. */
|
|
170
|
+
readonly observedBytes: number | undefined;
|
|
171
|
+
constructor(limit: number, observedBytes?: number);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Consecutive-transient-failure breaker for the keyless path.
|
|
175
|
+
*
|
|
176
|
+
* The public MCP endpoint is best-effort: when it is throttling or down, every
|
|
177
|
+
* search would otherwise fail. Reporting that state through {@link
|
|
178
|
+
* ExaSearchProvider.available} is what lets a deployment recover — the seam
|
|
179
|
+
* skips an unavailable provider, so an unconfigured profile falls back to
|
|
180
|
+
* another registered provider instead of surfacing a hard error.
|
|
181
|
+
*
|
|
182
|
+
* State is per provider instance (one per plugin mount) and never persisted:
|
|
183
|
+
* a restart is a fresh chance, and one successful search resets the count.
|
|
184
|
+
*/
|
|
185
|
+
export declare class ExaAvailabilityBreaker {
|
|
186
|
+
#private;
|
|
187
|
+
constructor(threshold?: number, cooldownMs?: number);
|
|
188
|
+
/** True while the breaker is open and the cooldown has not elapsed. */
|
|
189
|
+
get blocked(): boolean;
|
|
190
|
+
/** Record one successful operation: the endpoint is healthy again. */
|
|
191
|
+
succeeded(): void;
|
|
192
|
+
/** Record one transient failure, opening the breaker at the threshold. */
|
|
193
|
+
failed(): void;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Project one resolved configuration section into the options the provider
|
|
197
|
+
* serves its next search with. Called per operation so live Settings edits
|
|
198
|
+
* take effect on the next search.
|
|
199
|
+
*/
|
|
200
|
+
export declare function resolveOptions(section: ExaSearchProviderConfig): ExaSearchProviderOptions;
|
|
201
|
+
/**
|
|
202
|
+
* Exa-backed search with an anonymous fallback.
|
|
203
|
+
*
|
|
204
|
+
* Path selection is per search, not per install: a key appearing later (for
|
|
205
|
+
* example after a Settings edit) upgrades the next search to REST without a
|
|
206
|
+
* restart, and removing it falls back to the anonymous endpoint.
|
|
207
|
+
*/
|
|
208
|
+
export declare class ExaSearchProvider implements WebSearchProvider {
|
|
209
|
+
#private;
|
|
210
|
+
/**
|
|
211
|
+
* @param resolveOptions - thunk returning the options for the NEXT
|
|
212
|
+
* operation, snapshotted once at each operation's entry so one search
|
|
213
|
+
* never mixes two settings sections (same pattern as the official
|
|
214
|
+
* DeepSeek provider).
|
|
215
|
+
* @param resolveApiKey - optional key resolver; dsh hosts pass their
|
|
216
|
+
* launch-environment snapshot while direct users retain process.env fallback.
|
|
217
|
+
* @param breaker - health tracker for the keyless path; injectable so tests
|
|
218
|
+
* need no clock control.
|
|
219
|
+
*/
|
|
220
|
+
constructor(resolveOptions: ExaOptionsResolver, resolveApiKey?: ExaApiKeyResolver, breaker?: ExaAvailabilityBreaker);
|
|
221
|
+
/**
|
|
222
|
+
* Read per operation rather than frozen at construction: a Settings edit to
|
|
223
|
+
* `providerId` must not leave the provider reporting an id the registry does
|
|
224
|
+
* not key it under. Registering under the new id is the user's job (the
|
|
225
|
+
* loader re-reads config on reload), but the reported value stays honest.
|
|
226
|
+
*/
|
|
227
|
+
get id(): string;
|
|
228
|
+
/**
|
|
229
|
+
* Cheap local usability check — no network call, per the seam contract.
|
|
230
|
+
*
|
|
231
|
+
* False when the local options are unusable, or while the keyless channel's
|
|
232
|
+
* breaker is open after repeated transient failures. Reporting that honestly
|
|
233
|
+
* is what lets the seam fall back to another provider instead of failing the
|
|
234
|
+
* search: a pinned `searchProvider` surfaces
|
|
235
|
+
* `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`, an unpinned one simply selects
|
|
236
|
+
* another registered provider.
|
|
237
|
+
*/
|
|
238
|
+
available(): boolean;
|
|
239
|
+
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/constants.d.ts
|
|
243
|
+
/**
|
|
244
|
+
* Defaults and stable identifiers for `@tonydua/dsh-web-search-exa`.
|
|
245
|
+
*
|
|
246
|
+
* Every value here is also restated in the Settings schema with the same
|
|
247
|
+
* default, so a reader can see the effective value without following the
|
|
248
|
+
* import. Keep the two in sync: the schema is the user-facing contract, these
|
|
249
|
+
* are the values the provider falls back to when a field is absent.
|
|
250
|
+
*
|
|
251
|
+
* @module @tonydua/dsh-web-search-exa/constants
|
|
252
|
+
*/
|
|
253
|
+
/** Default provider id this provider registers under (`ctx.web` registry key). */
|
|
254
|
+
export declare const DEFAULT_PROVIDER_ID = "exa";
|
|
255
|
+
/** Backward-compatible alias for the default provider id. */
|
|
256
|
+
export declare const PROVIDER_ID = "exa";
|
|
257
|
+
/** Exa REST search endpoint; used only when an API key is configured. */
|
|
258
|
+
export declare const DEFAULT_BASE_URL = "https://api.exa.ai";
|
|
259
|
+
/** Legacy full REST endpoint; `baseURL` is the canonical dsh-compatible option. */
|
|
260
|
+
export declare const DEFAULT_API_URL = "https://api.exa.ai/search";
|
|
261
|
+
/**
|
|
262
|
+
* Exa hosted MCP endpoint; the anonymous fallback path.
|
|
263
|
+
*
|
|
264
|
+
* The `tools` query is part of the default because `web_search_advanced_exa`
|
|
265
|
+
* is not servable without it — a request naming it against the bare endpoint
|
|
266
|
+
* fails with `MCP error -32602: Tool web_search_advanced_exa not found`.
|
|
267
|
+
* A configured `mcpURL` that omits `tools` gets the query spliced in at request
|
|
268
|
+
* time, so existing configurations keep working.
|
|
269
|
+
*/
|
|
270
|
+
export declare const DEFAULT_MCP_URL = "https://mcp.exa.ai/mcp?tools=web_search_exa,web_search_advanced_exa";
|
|
271
|
+
/** Environment variable consulted when no literal `apiKey` is configured. */
|
|
272
|
+
export declare const DEFAULT_API_KEY_ENV = "EXA_API_KEY";
|
|
273
|
+
/** Default retrieval mode for the REST path: let Exa pick. */
|
|
274
|
+
export declare const DEFAULT_SEARCH_TYPE = "auto";
|
|
275
|
+
/** Default number of highlight sentences requested per result (REST path). */
|
|
276
|
+
export declare const DEFAULT_HIGHLIGHTS_PER_RESULT = 1;
|
|
277
|
+
/** MCP tool name for plain web search on Exa's hosted server (text-blob output). */
|
|
278
|
+
export declare const MCP_TOOL = "web_search_exa";
|
|
279
|
+
/** MCP tool whose text content is a sanitized structured search response. */
|
|
280
|
+
export declare const MCP_TOOL_ADVANCED = "web_search_advanced_exa";
|
|
281
|
+
/** The tool the anonymous path calls by default. */
|
|
282
|
+
export declare const DEFAULT_MCP_TOOL = "web_search_advanced_exa";
|
|
283
|
+
/** Query parameter enabling both MCP tools when a configured URL omits it. */
|
|
284
|
+
export declare const MCP_TOOLS_QUERY = "tools=web_search_exa,web_search_advanced_exa";
|
|
285
|
+
/**
|
|
286
|
+
* Reject anonymous MCP responses larger than this.
|
|
287
|
+
*
|
|
288
|
+
* Structured results are kilobytes; anything past this is a malformed or
|
|
289
|
+
* hostile body and parsing it would only burn memory before failing anyway.
|
|
290
|
+
*/
|
|
291
|
+
export declare const MAX_MCP_RESPONSE_BYTES: number;
|
|
292
|
+
/**
|
|
293
|
+
* Attribution header sent on anonymous MCP requests. This is the only signal
|
|
294
|
+
* Exa's public endpoint receives about the caller, so it is deliberately a
|
|
295
|
+
* product-level name rather than a per-install identifier.
|
|
296
|
+
*/
|
|
297
|
+
export declare const MCP_SOURCE = "dsh-anything";
|
|
298
|
+
/**
|
|
299
|
+
* User agent for REST requests.
|
|
300
|
+
*
|
|
301
|
+
* Annotated `: string` rather than left inferred: this constant is re-exported
|
|
302
|
+
* from the package root, and a `const` string infers a LITERAL type, which
|
|
303
|
+
* would bake today's version number into every consumer's type-checking.
|
|
304
|
+
*/
|
|
305
|
+
export declare const USER_AGENT: string;
|
|
306
|
+
/** Snippet cap for text-derived snippets (matching oh-my-pi's choice). */
|
|
307
|
+
export declare const MAX_SNIPPET_CHARS = 500;
|
|
308
|
+
/** Settings namespace carrying this provider's configuration. */
|
|
309
|
+
export declare const SETTINGS_NAMESPACE = "web-search-exa";
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/index.d.ts
|
|
312
|
+
declare const Config: z<Schemastery.ObjectS<{
|
|
313
|
+
/**
|
|
314
|
+
* Provider id registered into `ctx.web`. Defaults to `exa` (same as the
|
|
315
|
+
* official `@deepseek-ai/dsh-web-search-exa`). Change it only when BOTH
|
|
316
|
+
* packages are installed in one profile — the seam rejects duplicate ids
|
|
317
|
+
* with `WEB_DUPLICATE_PROVIDER`. There is no silent override: pick a
|
|
318
|
+
* distinct id here (e.g. `exa-anon`) and select it explicitly with
|
|
319
|
+
* `searchProvider` / `$DSH_WEB_SEARCH_PROVIDER`.
|
|
320
|
+
*/
|
|
321
|
+
providerId: z<string, string>;
|
|
322
|
+
/** Literal Exa API key; an empty/missing value enables the anonymous MCP path. */
|
|
323
|
+
apiKey: z<string, string>;
|
|
324
|
+
/** Environment variable consulted when no literal `apiKey` is configured. */
|
|
325
|
+
apiKeyEnv: z<string, string>;
|
|
326
|
+
/** Exa API base URL; `/search` is appended for the keyed REST path. */
|
|
327
|
+
baseURL: z<string, string>;
|
|
328
|
+
/**
|
|
329
|
+
* Legacy full REST endpoint. When set, it takes precedence over `baseURL`;
|
|
330
|
+
* new configurations should use `baseURL` to match the official provider.
|
|
331
|
+
*/
|
|
332
|
+
apiURL: z<string, string>;
|
|
333
|
+
/** Exa hosted MCP endpoint, used by the anonymous fallback. */
|
|
334
|
+
mcpURL: z<string, string>;
|
|
335
|
+
/**
|
|
336
|
+
* MCP tool the anonymous path calls. `web_search_advanced_exa` returns a
|
|
337
|
+
* sanitized structured JSON response; `web_search_exa` returns the
|
|
338
|
+
* `Title:`-section text blob. The structured tool is the default because it
|
|
339
|
+
* needs no text parsing, and the text path stays available as a fallback.
|
|
340
|
+
*/
|
|
341
|
+
mcpTool: z<"web_search_exa" | "web_search_advanced_exa", "web_search_exa" | "web_search_advanced_exa">;
|
|
342
|
+
/** REST retrieval mode: `auto`, `keyword`, or `neural`. */
|
|
343
|
+
searchType: z<"auto" | "keyword" | "neural", "auto" | "keyword" | "neural">;
|
|
344
|
+
/** Default result count when the request carries no `maxResults`. */
|
|
345
|
+
numResults: z<number, number>;
|
|
346
|
+
/** Highlight sentences requested per result on the REST path. */
|
|
347
|
+
highlightsPerResult: z<number, number>;
|
|
348
|
+
}>, Schemastery.ObjectT<{
|
|
349
|
+
/**
|
|
350
|
+
* Provider id registered into `ctx.web`. Defaults to `exa` (same as the
|
|
351
|
+
* official `@deepseek-ai/dsh-web-search-exa`). Change it only when BOTH
|
|
352
|
+
* packages are installed in one profile — the seam rejects duplicate ids
|
|
353
|
+
* with `WEB_DUPLICATE_PROVIDER`. There is no silent override: pick a
|
|
354
|
+
* distinct id here (e.g. `exa-anon`) and select it explicitly with
|
|
355
|
+
* `searchProvider` / `$DSH_WEB_SEARCH_PROVIDER`.
|
|
356
|
+
*/
|
|
357
|
+
providerId: z<string, string>;
|
|
358
|
+
/** Literal Exa API key; an empty/missing value enables the anonymous MCP path. */
|
|
359
|
+
apiKey: z<string, string>;
|
|
360
|
+
/** Environment variable consulted when no literal `apiKey` is configured. */
|
|
361
|
+
apiKeyEnv: z<string, string>;
|
|
362
|
+
/** Exa API base URL; `/search` is appended for the keyed REST path. */
|
|
363
|
+
baseURL: z<string, string>;
|
|
364
|
+
/**
|
|
365
|
+
* Legacy full REST endpoint. When set, it takes precedence over `baseURL`;
|
|
366
|
+
* new configurations should use `baseURL` to match the official provider.
|
|
367
|
+
*/
|
|
368
|
+
apiURL: z<string, string>;
|
|
369
|
+
/** Exa hosted MCP endpoint, used by the anonymous fallback. */
|
|
370
|
+
mcpURL: z<string, string>;
|
|
371
|
+
/**
|
|
372
|
+
* MCP tool the anonymous path calls. `web_search_advanced_exa` returns a
|
|
373
|
+
* sanitized structured JSON response; `web_search_exa` returns the
|
|
374
|
+
* `Title:`-section text blob. The structured tool is the default because it
|
|
375
|
+
* needs no text parsing, and the text path stays available as a fallback.
|
|
376
|
+
*/
|
|
377
|
+
mcpTool: z<"web_search_exa" | "web_search_advanced_exa", "web_search_exa" | "web_search_advanced_exa">;
|
|
378
|
+
/** REST retrieval mode: `auto`, `keyword`, or `neural`. */
|
|
379
|
+
searchType: z<"auto" | "keyword" | "neural", "auto" | "keyword" | "neural">;
|
|
380
|
+
/** Default result count when the request carries no `maxResults`. */
|
|
381
|
+
numResults: z<number, number>;
|
|
382
|
+
/** Highlight sentences requested per result on the REST path. */
|
|
383
|
+
highlightsPerResult: z<number, number>;
|
|
384
|
+
}>>;
|
|
385
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
386
|
+
export declare const name = "web-search-exa";
|
|
387
|
+
/** The web seam this provider registers into. */
|
|
388
|
+
export declare const inject: readonly ["web"];
|
|
389
|
+
/**
|
|
390
|
+
* The subset of the settings service this plugin uses, described structurally
|
|
391
|
+
* rather than by the concrete `dsh-settings` class, because that API changed
|
|
392
|
+
* shape and the plugin must work on both sides of the change.
|
|
393
|
+
*
|
|
394
|
+
* - **dsh ≤ 0.1.6** exposes `SettingsProvider.installSection`, which registered
|
|
395
|
+
* a namespace and — the part the provider actually depends on — handed back
|
|
396
|
+
* the authoritative section thunk through `setSource`.
|
|
397
|
+
* - **dsh ≥ 0.1.7** replaces that with `SettingsForms`, which derives a page
|
|
398
|
+
* from the Config schema the Loader already holds for this entry
|
|
399
|
+
* (`SettingsDescriptor.schema`, `autoGenerate`). There is no namespace to
|
|
400
|
+
* install, so this plugin has nothing to register and must simply not
|
|
401
|
+
* crash.
|
|
402
|
+
*
|
|
403
|
+
* Both members are optional: a host with neither still gets a working provider,
|
|
404
|
+
* only without live Settings-driven reconfiguration.
|
|
405
|
+
*/
|
|
406
|
+
interface SettingsServiceLike {
|
|
407
|
+
installSection?: (owner: unknown, ns: string, schema: unknown, entry: unknown, hooks: {
|
|
408
|
+
setSource: (source: () => ExaSearchProviderConfig) => void;
|
|
409
|
+
onChange: () => void;
|
|
410
|
+
}) => void;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Wire the settings service when the host exposes the pre-0.1.7 registration
|
|
414
|
+
* API; do nothing (rather than throw) on hosts that do not.
|
|
415
|
+
*
|
|
416
|
+
* @param settings - the mounted settings service, of either generation.
|
|
417
|
+
* @param owner - the consuming context `installSection` attributes the section to.
|
|
418
|
+
* @param config - the composition entry, used as the section's base value.
|
|
419
|
+
* @param adopt - receives the authoritative section thunk so later searches read
|
|
420
|
+
* live edits instead of the boot-time config.
|
|
421
|
+
* @returns true when a section was installed.
|
|
422
|
+
*/
|
|
423
|
+
declare function installSettingsSection(settings: SettingsServiceLike, owner: unknown, config: ExaSearchProviderConfig, adopt: (source: () => ExaSearchProviderConfig) => void): boolean;
|
|
424
|
+
/**
|
|
425
|
+
* Register the Exa search provider with `ctx.web` and, when the settings
|
|
426
|
+
* service is mounted *and* exposes the pre-0.1.7 registration API, install its
|
|
427
|
+
* Settings section.
|
|
428
|
+
*
|
|
429
|
+
* The settings work is deliberately inside `ctx.inject`, so a profile that
|
|
430
|
+
* omits `dsh-settings` still mounts the provider — keyless search must not
|
|
431
|
+
* depend on the Settings UI being present. The provider registration happens
|
|
432
|
+
* outside it for the same reason, and is never conditional on the settings
|
|
433
|
+
* generation.
|
|
434
|
+
*/
|
|
435
|
+
export declare function apply(ctx: Context, config: ExaSearchProviderConfig): void;
|
|
436
|
+
//#endregion
|
|
437
|
+
export { Config, type ExaApiKeyResolver, type ExaKeyEnvironment, type ExaMcpSection, type ExaMcpTool, type ExaOptionsResolver, type ExaRestResponse, type ExaRestResult, type ExaSearchProviderConfig, type ExaSearchProviderOptions, type ExaSearchType, type McpContentItem, type McpJsonRpcError, type McpPayload, type McpToolResult, installSettingsSection };
|