deepseek-web-retrieval 0.4.0 → 0.5.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +99 -244
  2. package/README.md +16 -11
  3. package/dist/cli.js +1366 -615
  4. package/docs/agents/codex.md +28 -7
  5. package/docs/agents/opencode-v2.md +1 -1
  6. package/docs/agents/pi.md +88 -86
  7. package/docs/design/installer-design.md +72 -9
  8. package/docs/hosts.jsonc +2 -2
  9. package/docs/integration.md +14 -9
  10. package/docs/research/control-matrix.md +48 -13
  11. package/docs/research/mcp-compatibility.md +9 -4
  12. package/examples/pi.settings.jsonc +17 -0
  13. package/index.ts +2 -2
  14. package/package.json +35 -4
  15. package/src/core/auth.ts +4 -6
  16. package/src/core/client.ts +65 -53
  17. package/src/core/constants.ts +24 -24
  18. package/src/core/descriptions.ts +30 -6
  19. package/src/core/domains.ts +56 -67
  20. package/src/core/env.ts +112 -0
  21. package/src/core/format.ts +53 -50
  22. package/src/core/index.ts +9 -9
  23. package/src/core/jsonc.ts +6 -6
  24. package/src/core/object.ts +4 -0
  25. package/src/core/options.ts +20 -25
  26. package/src/core/search.ts +45 -44
  27. package/src/core/transport.ts +109 -113
  28. package/src/core/types.ts +47 -47
  29. package/src/core/url.ts +20 -20
  30. package/src/installer/apply.ts +256 -81
  31. package/src/installer/cli.ts +135 -104
  32. package/src/installer/detect.ts +46 -31
  33. package/src/installer/formats/index.ts +26 -28
  34. package/src/installer/formats/json.ts +81 -77
  35. package/src/installer/formats/rules.ts +55 -28
  36. package/src/installer/formats/toml.ts +88 -81
  37. package/src/installer/formats/value.ts +34 -30
  38. package/src/installer/hosts/codex.ts +100 -54
  39. package/src/installer/hosts/index.ts +126 -41
  40. package/src/installer/hosts/opencode.ts +70 -51
  41. package/src/installer/hosts/pi.ts +269 -75
  42. package/src/installer/hosts/shared.ts +19 -14
  43. package/src/installer/operation-lock.ts +54 -0
  44. package/src/installer/ownership.ts +129 -0
  45. package/src/installer/paths.ts +7 -5
  46. package/src/installer/persistence.ts +66 -0
  47. package/src/installer/render.ts +29 -29
  48. package/src/installer/revert.ts +33 -29
  49. package/src/installer/rulesContent.ts +24 -13
  50. package/src/installer/state.ts +116 -31
  51. package/src/installer/types.ts +74 -56
  52. package/src/installer/uninstall.ts +40 -17
  53. package/src/mcp/auth.ts +7 -8
  54. package/src/mcp/cli.ts +38 -20
  55. package/src/mcp/client.ts +35 -0
  56. package/src/mcp/server.ts +68 -97
  57. package/src/opencode/auth.ts +24 -26
  58. package/src/opencode/plugin.ts +99 -87
  59. package/src/pi/extension.ts +164 -0
  60. package/src/pi/settings.ts +49 -0
  61. package/src/mcp/config.ts +0 -112
@@ -1,7 +1,7 @@
1
- import { normalizeDomains } from "./domains"
2
- import { truncate } from "./format"
3
- import type { DeepSeekSearchResult, SearchOptions, SearchResult } from "./types"
4
- import { normalizeResultUrl } from "./url"
1
+ import { normalizeDomains } from "./domains";
2
+ import { truncate } from "./format";
3
+ import type { DeepSeekSearchResult, SearchOptions, SearchResult } from "./types";
4
+ import { normalizeResultUrl } from "./url";
5
5
 
6
6
  /**
7
7
  * Appends DeepSeek search operators:
@@ -13,72 +13,73 @@ export function buildQuery(
13
13
  query: string,
14
14
  options: Pick<SearchOptions, "blockedDomains" | "includeDomains" | "language">,
15
15
  ): string {
16
- const parts = [query.trim()]
17
- for (const domain of normalizeDomains(options.blockedDomains)) parts.push(`-site:${domain}`)
18
- const include = normalizeDomains(options.includeDomains)
19
- if (include.length) parts.push(include.map((domain) => `site:${domain}`).join(" OR "))
20
- const language = options.language?.trim().replace(/^lang:/i, "")
21
- if (language) parts.push(`lang:${language}`)
22
- return parts.filter((part) => part.length > 0).join(" ")
16
+ const parts = [query.trim()];
17
+ for (const domain of normalizeDomains(options.blockedDomains)) parts.push(`-site:${domain}`);
18
+ const include = normalizeDomains(options.includeDomains);
19
+ if (include.length) parts.push(include.map((domain) => `site:${domain}`).join(" OR "));
20
+ const language = options.language?.trim().replace(/^lang:/i, "");
21
+ if (language) parts.push(`lang:${language}`);
22
+ return parts.filter((part) => part.length > 0).join(" ");
23
23
  }
24
24
 
25
25
  export function parsePublished(value: unknown): number | undefined {
26
- if (typeof value !== "string" || !value.trim()) return undefined
27
- const timestamp = Date.parse(value)
28
- return Number.isFinite(timestamp) ? timestamp : undefined
26
+ if (typeof value !== "string" || !value.trim()) return undefined;
27
+ const timestamp = Date.parse(value);
28
+ return Number.isFinite(timestamp) ? timestamp : undefined;
29
29
  }
30
30
 
31
31
  /**
32
32
  * Filters and maps DeepSeek results into the neutral SearchResult shape.
33
- * Missing/non-finite rerank_score never passes. Authority -1/missing is
33
+ * A present rerank_score always compares against minRerank; a missing or
34
+ * non-finite score passes only while no positive threshold is set, so the
35
+ * default returns exactly what DeepSeek ranked. Authority -1/missing is
34
36
  * "unknown" and only passes when allowUnknownAuthority is enabled.
35
37
  */
36
- export function processResults(
37
- raw: readonly DeepSeekSearchResult[],
38
- options: SearchOptions,
39
- ): SearchResult[] {
40
- const results: SearchResult[] = []
41
- const seen = new Set<string>()
38
+ export function processResults(raw: readonly DeepSeekSearchResult[], options: SearchOptions): SearchResult[] {
39
+ const results: SearchResult[] = [];
40
+ const seen = new Set<string>();
42
41
 
43
42
  for (const item of raw) {
44
- if (results.length >= options.maxResults) break
45
- if (!item || typeof item !== "object") continue
43
+ if (results.length >= options.maxResults) break;
44
+ if (!item || typeof item !== "object") continue;
46
45
 
47
- const url = normalizeResultUrl(item.url)
48
- if (!url || seen.has(url)) continue
46
+ const url = normalizeResultUrl(item.url);
47
+ if (!url || seen.has(url)) continue;
49
48
 
50
- const rerank = item.rerank_score
51
- if (typeof rerank !== "number" || !Number.isFinite(rerank) || rerank < options.minRerank) continue
49
+ const rerank = item.rerank_score;
50
+ if (typeof rerank === "number" && Number.isFinite(rerank)) {
51
+ if (rerank < options.minRerank) continue;
52
+ } else if (options.minRerank > 0) {
53
+ // A threshold was explicitly raised, so a result that cannot be scored
54
+ // against it must not slip through.
55
+ continue;
56
+ }
52
57
 
53
- const authority = item.authority
54
- const knownAuthority =
55
- typeof authority === "number" && Number.isFinite(authority) && authority >= 0
58
+ const authority = item.authority;
59
+ const knownAuthority = typeof authority === "number" && Number.isFinite(authority) && authority >= 0;
56
60
  if (knownAuthority) {
57
- if (authority < options.minAuthority) continue
61
+ if (authority < options.minAuthority) continue;
58
62
  } else if (!options.allowUnknownAuthority) {
59
- continue
63
+ continue;
60
64
  }
61
65
 
62
- seen.add(url)
63
- const content = truncate(
64
- optionalText(item.summary) ?? optionalText(item.snippet) ?? "",
65
- options.maxSummaryChars,
66
- )
67
- const published = parsePublished(item.date)
68
- const title = optionalText(item.title)
66
+ seen.add(url);
67
+ const content = truncate(optionalText(item.summary) ?? optionalText(item.snippet) ?? "", options.maxSummaryChars);
68
+ const published = parsePublished(item.date);
69
+ const title = optionalText(item.title);
69
70
  results.push({
70
71
  url,
71
72
  ...(title ? { title } : {}),
72
73
  ...(content ? { content } : {}),
73
74
  ...(published !== undefined ? { published } : {}),
74
- })
75
+ });
75
76
  }
76
77
 
77
- return results
78
+ return results;
78
79
  }
79
80
 
80
81
  function optionalText(value: unknown): string | undefined {
81
- if (typeof value !== "string") return undefined
82
- const trimmed = value.trim()
83
- return trimmed ? trimmed : undefined
82
+ if (typeof value !== "string") return undefined;
83
+ const trimmed = value.trim();
84
+ return trimmed ? trimmed : undefined;
84
85
  }
@@ -1,41 +1,41 @@
1
- import { MAX_ERROR_BODY_CHARS, MAX_RESPONSE_BYTES, MAX_RETRY_DELAY_MS } from "./constants"
2
- import { formatApiError, formatTimeoutError, redactSecrets, truncate } from "./format"
3
- import type { RequestAuth, RetryOptions } from "./types"
1
+ import { MAX_ERROR_BODY_CHARS, MAX_RESPONSE_BYTES, MAX_RETRY_DELAY_MS } from "./constants";
2
+ import { formatApiError, formatTimeoutError, redactSecrets, truncate } from "./format";
3
+ import type { RequestAuth, RetryOptions } from "./types";
4
4
 
5
5
  /** Everything one POST needs; `fetchImpl` is injectable so tests need no monkey-patching. */
6
6
  export interface TransportOptions {
7
- readonly signal: AbortSignal
8
- readonly auth: RequestAuth
9
- readonly retry: RetryOptions
10
- readonly timeoutMs: number
11
- readonly fetchImpl?: typeof fetch
7
+ readonly signal: AbortSignal;
8
+ readonly auth: RequestAuth;
9
+ readonly retry: RetryOptions;
10
+ readonly timeoutMs: number;
11
+ readonly fetchImpl?: typeof fetch;
12
12
  }
13
13
 
14
14
  export class DeepSeekApiError extends Error {
15
- readonly operation: string
16
- readonly status: number
17
- readonly body: string
18
- readonly retryable: boolean
19
- readonly retryAfterMs?: number
15
+ readonly operation: string;
16
+ readonly status: number;
17
+ readonly body: string;
18
+ readonly retryable: boolean;
19
+ readonly retryAfterMs?: number;
20
20
 
21
21
  constructor(input: {
22
- operation: string
23
- status: number
24
- body: string
25
- retryAfterMs?: number
22
+ operation: string;
23
+ status: number;
24
+ body: string;
25
+ retryAfterMs?: number;
26
26
  }) {
27
- const body = redactSecrets(input.body)
28
- super(formatApiError(input.operation, input.status, body))
29
- this.name = "DeepSeekApiError"
30
- this.operation = input.operation
31
- this.status = input.status
32
- this.body = body
27
+ const body = redactSecrets(input.body);
28
+ super(formatApiError(input.operation, input.status, body));
29
+ this.name = "DeepSeekApiError";
30
+ this.operation = input.operation;
31
+ this.status = input.status;
32
+ this.body = body;
33
33
  this.retryable =
34
34
  input.status === 408 ||
35
35
  input.status === 425 ||
36
36
  input.status === 429 ||
37
- (input.status >= 500 && input.status <= 599)
38
- this.retryAfterMs = input.retryAfterMs
37
+ (input.status >= 500 && input.status <= 599);
38
+ this.retryAfterMs = input.retryAfterMs;
39
39
  }
40
40
  }
41
41
 
@@ -45,23 +45,23 @@ export async function postJson<T>(
45
45
  operation: string,
46
46
  opts: TransportOptions,
47
47
  ): Promise<T> {
48
- const fetchImpl = opts.fetchImpl ?? globalThis.fetch
49
- const attempts = opts.retry.maxRetries + 1
50
- let lastError: unknown
48
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
49
+ const attempts = opts.retry.maxRetries + 1;
50
+ let lastError: unknown;
51
51
 
52
52
  for (let attempt = 0; attempt < attempts; attempt++) {
53
53
  try {
54
- return await requestOnce<T>(endpoint, body, operation, opts, fetchImpl)
54
+ return await requestOnce<T>(endpoint, body, operation, opts, fetchImpl);
55
55
  } catch (error) {
56
- lastError = error
56
+ lastError = error;
57
57
  if (!(error instanceof DeepSeekApiError) || !error.retryable || attempt === attempts - 1) {
58
- throw error
58
+ throw error;
59
59
  }
60
- await sleep(retryDelay(error.retryAfterMs, opts.retry.baseDelayMs, attempt), opts.signal)
60
+ await sleep(retryDelay(error.retryAfterMs, opts.retry.baseDelayMs, attempt), opts.signal);
61
61
  }
62
62
  }
63
63
 
64
- throw lastError
64
+ throw lastError;
65
65
  }
66
66
 
67
67
  async function requestOnce<T>(
@@ -87,25 +87,25 @@ async function requestOnce<T>(
87
87
  fetchImpl,
88
88
  async (response) => {
89
89
  if (!response.ok) {
90
- const text = await readBoundedText(response, MAX_ERROR_BODY_CHARS)
90
+ const text = await readBoundedText(response, MAX_ERROR_BODY_CHARS);
91
91
  throw new DeepSeekApiError({
92
92
  operation,
93
93
  status: response.status,
94
94
  body: text,
95
95
  retryAfterMs: parseRetryAfter(response.headers.get("retry-after")),
96
- })
96
+ });
97
97
  }
98
98
 
99
- const text = await readResponseText(response, MAX_RESPONSE_BYTES, operation)
100
- if (!text.trim()) throw new Error(`${operation} returned an empty response body`)
99
+ const text = await readResponseText(response, MAX_RESPONSE_BYTES, operation);
100
+ if (!text.trim()) throw new Error(`${operation} returned an empty response body`);
101
101
 
102
102
  try {
103
- return JSON.parse(text) as T
103
+ return JSON.parse(text) as T;
104
104
  } catch {
105
- throw new Error(`${operation} returned invalid JSON: ${truncate(redactSecrets(text), 500)}`)
105
+ throw new Error(`${operation} returned invalid JSON: ${truncate(redactSecrets(text), 500)}`);
106
106
  }
107
107
  },
108
- )
108
+ );
109
109
  }
110
110
 
111
111
  /**
@@ -122,132 +122,128 @@ async function fetchWithTimeout<T>(
122
122
  fetchImpl: typeof fetch,
123
123
  handleResponse: (response: Response) => Promise<T>,
124
124
  ): Promise<T> {
125
- const controller = new AbortController()
126
- let timedOut = false
127
- const forwardAbort = () => controller.abort(signal.reason)
128
- if (signal.aborted) forwardAbort()
129
- else signal.addEventListener("abort", forwardAbort, { once: true })
125
+ const controller = new AbortController();
126
+ let timedOut = false;
127
+ const forwardAbort = () => controller.abort(signal.reason);
128
+ if (signal.aborted) forwardAbort();
129
+ else signal.addEventListener("abort", forwardAbort, { once: true });
130
130
 
131
131
  const timer = signal.aborted
132
132
  ? undefined
133
133
  : setTimeout(() => {
134
- timedOut = true
135
- controller.abort(new Error("timeout"))
136
- }, timeoutMs)
134
+ timedOut = true;
135
+ controller.abort(new Error("timeout"));
136
+ }, timeoutMs);
137
137
 
138
138
  try {
139
- const response = await fetchImpl(endpoint, { ...init, signal: controller.signal })
140
- return await handleResponse(response)
139
+ const response = await fetchImpl(endpoint, { ...init, signal: controller.signal });
140
+ return await handleResponse(response);
141
141
  } catch (error) {
142
- if (timedOut) throw new Error(formatTimeoutError(operation, timeoutMs))
143
- if (signal.aborted) throw abortReason(signal, operation)
144
- throw error
142
+ if (timedOut) throw new Error(formatTimeoutError(operation, timeoutMs));
143
+ if (signal.aborted) throw abortReason(signal, operation);
144
+ throw error;
145
145
  } finally {
146
- if (timer !== undefined) clearTimeout(timer)
147
- signal.removeEventListener("abort", forwardAbort)
146
+ if (timer !== undefined) clearTimeout(timer);
147
+ signal.removeEventListener("abort", forwardAbort);
148
148
  }
149
149
  }
150
150
 
151
151
  export function parseRetryAfter(value: string | null): number | undefined {
152
- if (!value) return undefined
153
- const trimmed = value.trim()
154
- if (!trimmed) return undefined
152
+ if (!value) return undefined;
153
+ const trimmed = value.trim();
154
+ if (!trimmed) return undefined;
155
155
 
156
156
  if (/^\d+$/.test(trimmed)) {
157
- return Math.min(MAX_RETRY_DELAY_MS, Number(trimmed) * 1000)
157
+ return Math.min(MAX_RETRY_DELAY_MS, Number(trimmed) * 1000);
158
158
  }
159
159
 
160
- const timestamp = Date.parse(trimmed)
161
- if (!Number.isFinite(timestamp)) return undefined
162
- return Math.min(MAX_RETRY_DELAY_MS, Math.max(0, timestamp - Date.now()))
160
+ const timestamp = Date.parse(trimmed);
161
+ if (!Number.isFinite(timestamp)) return undefined;
162
+ return Math.min(MAX_RETRY_DELAY_MS, Math.max(0, timestamp - Date.now()));
163
163
  }
164
164
 
165
165
  export function retryDelay(retryAfterMs: number | undefined, baseDelayMs: number, attempt: number): number {
166
- if (retryAfterMs !== undefined) return retryAfterMs
167
- return Math.min(MAX_RETRY_DELAY_MS, baseDelayMs * 2 ** attempt)
166
+ if (retryAfterMs !== undefined) return retryAfterMs;
167
+ return Math.min(MAX_RETRY_DELAY_MS, baseDelayMs * 2 ** attempt);
168
168
  }
169
169
 
170
170
  function sleep(ms: number, signal: AbortSignal): Promise<void> {
171
- if (ms <= 0) return Promise.resolve()
171
+ if (ms <= 0) return Promise.resolve();
172
172
  return new Promise((resolve, reject) => {
173
173
  const timer = setTimeout(() => {
174
- signal.removeEventListener("abort", onAbort)
175
- resolve()
176
- }, ms)
174
+ signal.removeEventListener("abort", onAbort);
175
+ resolve();
176
+ }, ms);
177
177
  const onAbort = () => {
178
- clearTimeout(timer)
179
- reject(abortReason(signal, "DeepSeek request"))
180
- }
178
+ clearTimeout(timer);
179
+ reject(abortReason(signal, "DeepSeek request"));
180
+ };
181
181
  if (signal.aborted) {
182
- clearTimeout(timer)
183
- reject(abortReason(signal, "DeepSeek request"))
184
- return
182
+ clearTimeout(timer);
183
+ reject(abortReason(signal, "DeepSeek request"));
184
+ return;
185
185
  }
186
- signal.addEventListener("abort", onAbort, { once: true })
187
- })
186
+ signal.addEventListener("abort", onAbort, { once: true });
187
+ });
188
188
  }
189
189
 
190
190
  function abortReason(signal: AbortSignal, operation: string): unknown {
191
- return signal.reason instanceof Error ? signal.reason : new Error(`${operation} request aborted`)
191
+ return signal.reason instanceof Error ? signal.reason : new Error(`${operation} request aborted`);
192
192
  }
193
193
 
194
194
  /** Reads at most `maxChars + 1` characters, enough for formatApiError to add its marker. */
195
195
  async function readBoundedText(response: Response, maxChars: number): Promise<string> {
196
- if (!response.body) return truncate(await response.text(), maxChars + 1)
196
+ if (!response.body) return truncate(await response.text(), maxChars + 1);
197
197
 
198
- const reader = response.body.getReader()
199
- const decoder = new TextDecoder()
200
- const limit = maxChars + 1
201
- let text = ""
202
- let chars = 0
198
+ const reader = response.body.getReader();
199
+ const decoder = new TextDecoder();
200
+ const limit = maxChars + 1;
201
+ let text = "";
202
+ let chars = 0;
203
203
  try {
204
204
  while (chars < limit) {
205
- const { done, value } = await reader.read()
206
- if (done) break
207
- const chunk = decoder.decode(value, { stream: true })
208
- text += chunk
209
- chars += Array.from(chunk).length
205
+ const { done, value } = await reader.read();
206
+ if (done) break;
207
+ const chunk = decoder.decode(value, { stream: true });
208
+ text += chunk;
209
+ chars += Array.from(chunk).length;
210
210
  }
211
- text += decoder.decode()
211
+ text += decoder.decode();
212
212
  } finally {
213
- await reader.cancel().catch(() => undefined)
214
- reader.releaseLock()
213
+ await reader.cancel().catch(() => undefined);
214
+ reader.releaseLock();
215
215
  }
216
- return truncate(text, limit)
216
+ return truncate(text, limit);
217
217
  }
218
218
 
219
219
  /** Reads a successful response body, rejecting payloads above `maxBytes`. */
220
- async function readResponseText(
221
- response: Response,
222
- maxBytes: number,
223
- operation: string,
224
- ): Promise<string> {
220
+ async function readResponseText(response: Response, maxBytes: number, operation: string): Promise<string> {
225
221
  if (!response.body) {
226
- const text = await response.text()
222
+ const text = await response.text();
227
223
  if (new TextEncoder().encode(text).byteLength > maxBytes) {
228
- throw new Error(`${operation} response exceeded ${maxBytes} bytes`)
224
+ throw new Error(`${operation} response exceeded ${maxBytes} bytes`);
229
225
  }
230
- return text
226
+ return text;
231
227
  }
232
228
 
233
- const reader = response.body.getReader()
234
- const decoder = new TextDecoder()
235
- let text = ""
236
- let bytes = 0
229
+ const reader = response.body.getReader();
230
+ const decoder = new TextDecoder();
231
+ let text = "";
232
+ let bytes = 0;
237
233
  try {
238
234
  while (true) {
239
- const { done, value } = await reader.read()
240
- if (done) break
241
- bytes += value.byteLength
235
+ const { done, value } = await reader.read();
236
+ if (done) break;
237
+ bytes += value.byteLength;
242
238
  if (bytes > maxBytes) {
243
- throw new Error(`${operation} response exceeded ${maxBytes} bytes`)
239
+ throw new Error(`${operation} response exceeded ${maxBytes} bytes`);
244
240
  }
245
- text += decoder.decode(value, { stream: true })
241
+ text += decoder.decode(value, { stream: true });
246
242
  }
247
- text += decoder.decode()
243
+ text += decoder.decode();
248
244
  } finally {
249
- await reader.cancel().catch(() => undefined)
250
- reader.releaseLock()
245
+ await reader.cancel().catch(() => undefined);
246
+ reader.releaseLock();
251
247
  }
252
- return text
248
+ return text;
253
249
  }
package/src/core/types.ts CHANGED
@@ -1,85 +1,85 @@
1
- export type ApiKeySource = "auto" | "opencode" | "env"
1
+ export type ApiKeySource = "auto" | "opencode" | "env";
2
2
 
3
3
  export interface PluginOptions {
4
- readonly [key: string]: unknown
4
+ readonly [key: string]: unknown;
5
5
  }
6
6
 
7
7
  export interface SearchOptions {
8
- readonly count: number
9
- readonly maxResults: number
10
- readonly minRerank: number
11
- readonly minAuthority: number
12
- readonly allowUnknownAuthority: boolean
13
- readonly blockedDomains: readonly string[]
14
- readonly includeDomains: readonly string[]
15
- readonly language?: string
16
- readonly maxSummaryChars: number
17
- readonly timeoutMs: number
18
- readonly enabled: boolean
8
+ readonly count: number;
9
+ readonly maxResults: number;
10
+ readonly minRerank: number;
11
+ readonly minAuthority: number;
12
+ readonly allowUnknownAuthority: boolean;
13
+ readonly blockedDomains: readonly string[];
14
+ readonly includeDomains: readonly string[];
15
+ readonly language?: string;
16
+ readonly maxSummaryChars: number;
17
+ readonly timeoutMs: number;
18
+ readonly enabled: boolean;
19
19
  }
20
20
 
21
21
  export interface FetchOptions {
22
- readonly maxChars: number
23
- readonly allowedDomains: readonly string[]
24
- readonly blockedDomains: readonly string[]
25
- readonly timeoutMs: number
26
- readonly blockPrivateHosts: boolean
27
- readonly enabled: boolean
22
+ readonly maxChars: number;
23
+ readonly allowedDomains: readonly string[];
24
+ readonly blockedDomains: readonly string[];
25
+ readonly timeoutMs: number;
26
+ readonly blockPrivateHosts: boolean;
27
+ readonly enabled: boolean;
28
28
  }
29
29
 
30
30
  export interface AuthOptions {
31
- readonly source: ApiKeySource
31
+ readonly source: ApiKeySource;
32
32
  }
33
33
 
34
34
  export interface RetryOptions {
35
- readonly maxRetries: number
36
- readonly baseDelayMs: number
35
+ readonly maxRetries: number;
36
+ readonly baseDelayMs: number;
37
37
  }
38
38
 
39
39
  export interface ResolvedOptions {
40
- readonly search: SearchOptions
41
- readonly fetch: FetchOptions
42
- readonly auth: AuthOptions
43
- readonly retry: RetryOptions
40
+ readonly search: SearchOptions;
41
+ readonly fetch: FetchOptions;
42
+ readonly auth: AuthOptions;
43
+ readonly retry: RetryOptions;
44
44
  }
45
45
 
46
46
  export interface DeepSeekSearchResult {
47
- readonly rerank_score?: number
48
- readonly authority?: number
49
- readonly title?: string | null
50
- readonly url?: string | null
51
- readonly snippet?: string | null
52
- readonly summary?: string | null
53
- readonly date?: string | null
47
+ readonly rerank_score?: number;
48
+ readonly authority?: number;
49
+ readonly title?: string | null;
50
+ readonly url?: string | null;
51
+ readonly snippet?: string | null;
52
+ readonly summary?: string | null;
53
+ readonly date?: string | null;
54
54
  }
55
55
 
56
56
  /** A neutral, host-agnostic search result. */
57
57
  export interface SearchResult {
58
- readonly url: string
59
- readonly title?: string
60
- readonly content?: string
61
- readonly published?: number
58
+ readonly url: string;
59
+ readonly title?: string;
60
+ readonly content?: string;
61
+ readonly published?: number;
62
62
  }
63
63
 
64
64
  export interface FetchedPage {
65
- readonly url: string
66
- readonly title: string
67
- readonly content: string
65
+ readonly url: string;
66
+ readonly title: string;
67
+ readonly content: string;
68
68
  }
69
69
 
70
70
  export interface FormattedFetchContent {
71
- readonly content: string
72
- readonly truncated: boolean
71
+ readonly content: string;
72
+ readonly truncated: boolean;
73
73
  }
74
74
 
75
75
  /** A fetched page formatted for the model, with truncation reported. */
76
76
  export interface FetchedDocument {
77
- readonly url: string
78
- readonly title: string
79
- readonly content: string
80
- readonly truncated: boolean
77
+ readonly url: string;
78
+ readonly title: string;
79
+ readonly content: string;
80
+ readonly truncated: boolean;
81
81
  }
82
82
 
83
83
  export interface RequestAuth {
84
- readonly apiKey: string
84
+ readonly apiKey: string;
85
85
  }
package/src/core/url.ts CHANGED
@@ -1,42 +1,42 @@
1
- import { MAX_URL_CHARS } from "./constants"
2
- import { normalizeHostname } from "./domains"
1
+ import { MAX_URL_CHARS } from "./constants";
2
+ import { normalizeHostname } from "./domains";
3
3
 
4
4
  export function parseFetchUrl(value: string): URL {
5
- const input = value.trim()
6
- if (!input) throw new Error("URL must not be empty")
5
+ const input = value.trim();
6
+ if (!input) throw new Error("URL must not be empty");
7
7
  if (input.length > MAX_URL_CHARS) {
8
- throw new Error(`URL must be ${MAX_URL_CHARS} characters or fewer`)
8
+ throw new Error(`URL must be ${MAX_URL_CHARS} characters or fewer`);
9
9
  }
10
10
 
11
- let url: URL
11
+ let url: URL;
12
12
  try {
13
- url = new URL(input)
13
+ url = new URL(input);
14
14
  } catch {
15
- throw new Error("URL must be an absolute http:// or https:// URL")
15
+ throw new Error("URL must be an absolute http:// or https:// URL");
16
16
  }
17
17
  if (url.protocol !== "http:" && url.protocol !== "https:") {
18
- throw new Error("URL must use http:// or https://")
18
+ throw new Error("URL must use http:// or https://");
19
19
  }
20
20
  if (!url.hostname || !normalizeHostname(url.hostname)) {
21
- throw new Error("URL must include a host")
21
+ throw new Error("URL must include a host");
22
22
  }
23
23
  if (url.username || url.password) {
24
- throw new Error("URL must not contain credentials")
24
+ throw new Error("URL must not contain credentials");
25
25
  }
26
- return url
26
+ return url;
27
27
  }
28
28
 
29
29
  /** Normalizes a search-result URL and drops anything that is not public HTTP(S). */
30
30
  export function normalizeResultUrl(value: unknown): string | undefined {
31
- if (typeof value !== "string") return undefined
32
- const input = value.trim()
33
- if (!input || input.length > MAX_URL_CHARS) return undefined
31
+ if (typeof value !== "string") return undefined;
32
+ const input = value.trim();
33
+ if (!input || input.length > MAX_URL_CHARS) return undefined;
34
34
  try {
35
- const url = new URL(input)
36
- if (url.protocol !== "http:" && url.protocol !== "https:") return undefined
37
- if (!url.hostname || url.username || url.password) return undefined
38
- return url.href
35
+ const url = new URL(input);
36
+ if (url.protocol !== "http:" && url.protocol !== "https:") return undefined;
37
+ if (!url.hostname || url.username || url.password) return undefined;
38
+ return url.href;
39
39
  } catch {
40
- return undefined
40
+ return undefined;
41
41
  }
42
42
  }