@omnicross/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/{BuiltinToolExecutor-CS2WpXhM.d.cts → BuiltinToolExecutor-BHgH-EaZ.d.cts} +25 -2
  2. package/dist/{BuiltinToolExecutor-BluWyeob.d.ts → BuiltinToolExecutor-Bi1LmiTP.d.ts} +25 -2
  3. package/dist/{CompletionService-BiJqftc-.d.cts → CompletionService-BniP7j88.d.cts} +2 -2
  4. package/dist/{CompletionService-NXN_Av5W.d.ts → CompletionService-BqrKPwfA.d.ts} +2 -2
  5. package/dist/{ProviderProxy-CgIcCVwk.d.cts → ProviderProxy-Bz_nkOwq.d.cts} +1 -1
  6. package/dist/{ProviderProxy-DYoY5XoT.d.ts → ProviderProxy-Ckr6oTrE.d.ts} +1 -1
  7. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +2 -2
  8. package/dist/auth/GeminiCodeAssistProjectResolver.js +1 -1
  9. package/dist/{chunk-FJNKXOWE.js → chunk-2C3TTBS7.js} +4 -4
  10. package/dist/{chunk-NZYN7C3P.cjs → chunk-2RCCLQYV.cjs} +53 -53
  11. package/dist/{chunk-CDHS2QDC.js → chunk-4PFWFODB.js} +53 -53
  12. package/dist/{chunk-OST5RJOJ.cjs → chunk-4Z3ZLLC2.cjs} +13 -7
  13. package/dist/chunk-7RJI3NDI.cjs +236 -0
  14. package/dist/chunk-BTBEJRGL.cjs +781 -0
  15. package/dist/chunk-DBBR6RHF.js +236 -0
  16. package/dist/chunk-DXWFBBSK.js +781 -0
  17. package/dist/chunk-E4QHVXV5.cjs +118 -0
  18. package/dist/chunk-L7O2YQ5O.js +386 -0
  19. package/dist/{chunk-WSJE42SG.js → chunk-LKDAN4D7.js} +3684 -2462
  20. package/dist/{chunk-DEIUR2TH.js → chunk-LQYS2N47.js} +12 -8
  21. package/dist/chunk-MZEGQSKQ.js +118 -0
  22. package/dist/chunk-PTU7SXM3.cjs +386 -0
  23. package/dist/{chunk-CTKM773J.cjs → chunk-UPQIWJIE.cjs} +12 -8
  24. package/dist/{chunk-CFSGYFOZ.js → chunk-VL5TUDTM.js} +7 -1
  25. package/dist/{chunk-7B56AJBB.cjs → chunk-VSYQ6XYN.cjs} +2233 -1011
  26. package/dist/{chunk-TVYA5JSQ.cjs → chunk-ZAPN7XA6.cjs} +5 -5
  27. package/dist/completion/BuiltinToolExecutor.cjs +2 -2
  28. package/dist/completion/BuiltinToolExecutor.d.cts +3 -1
  29. package/dist/completion/BuiltinToolExecutor.d.ts +3 -1
  30. package/dist/completion/BuiltinToolExecutor.js +1 -1
  31. package/dist/completion/CompletionService.cjs +11 -10
  32. package/dist/completion/CompletionService.d.cts +6 -3
  33. package/dist/completion/CompletionService.d.ts +6 -3
  34. package/dist/completion/CompletionService.js +10 -9
  35. package/dist/completion.cjs +11 -10
  36. package/dist/completion.d.cts +6 -3
  37. package/dist/completion.d.ts +6 -3
  38. package/dist/completion.js +10 -9
  39. package/dist/egress-I46O_p5L.d.cts +126 -0
  40. package/dist/egress-I46O_p5L.d.ts +126 -0
  41. package/dist/frontends-Doz0RaWu.d.cts +97 -0
  42. package/dist/frontends-Doz0RaWu.d.ts +97 -0
  43. package/dist/image-generation.cjs +2 -2
  44. package/dist/image-generation.d.cts +7 -4
  45. package/dist/image-generation.d.ts +7 -4
  46. package/dist/image-generation.js +1 -1
  47. package/dist/index.cjs +25 -10
  48. package/dist/index.d.cts +12 -8
  49. package/dist/index.d.ts +12 -8
  50. package/dist/index.js +24 -9
  51. package/dist/openai-operation.d.cts +4 -1
  52. package/dist/openai-operation.d.ts +4 -1
  53. package/dist/outbound-api/routeResolver.d.cts +9 -5
  54. package/dist/outbound-api/routeResolver.d.ts +9 -5
  55. package/dist/outbound-api/subscriptionRegistryPort.d.cts +4 -1
  56. package/dist/outbound-api/subscriptionRegistryPort.d.ts +4 -1
  57. package/dist/outbound-api/types.d.cts +8 -4
  58. package/dist/outbound-api/types.d.ts +8 -4
  59. package/dist/outbound-api.cjs +25 -10
  60. package/dist/outbound-api.d.cts +117 -8
  61. package/dist/outbound-api.d.ts +117 -8
  62. package/dist/outbound-api.js +24 -9
  63. package/dist/pipeline/AccountAllowanceScheduling.d.cts +8 -4
  64. package/dist/pipeline/AccountAllowanceScheduling.d.ts +8 -4
  65. package/dist/pipeline/upstreamFetch.cjs +2 -2
  66. package/dist/pipeline/upstreamFetch.js +1 -1
  67. package/dist/ports.d.cts +8 -4
  68. package/dist/ports.d.ts +8 -4
  69. package/dist/provider-proxy/ProviderProxy.cjs +11 -10
  70. package/dist/provider-proxy/ProviderProxy.d.cts +5 -2
  71. package/dist/provider-proxy/ProviderProxy.d.ts +5 -2
  72. package/dist/provider-proxy/ProviderProxy.js +10 -9
  73. package/dist/provider-proxy/ingress/providerProxyShared.cjs +13 -10
  74. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +22 -3
  75. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +22 -3
  76. package/dist/provider-proxy/ingress/providerProxyShared.js +12 -9
  77. package/dist/provider-proxy/types.d.cts +4 -1
  78. package/dist/provider-proxy/types.d.ts +4 -1
  79. package/dist/provider-proxy.cjs +11 -10
  80. package/dist/provider-proxy.d.cts +10 -6
  81. package/dist/provider-proxy.d.ts +10 -6
  82. package/dist/provider-proxy.js +10 -9
  83. package/dist/proxy-UZTtjPZx.d.cts +18 -0
  84. package/dist/proxy-UZTtjPZx.d.ts +18 -0
  85. package/dist/{routeResolver-rpKQLyDO.d.cts → routeResolver-Da0yvkL1.d.cts} +2 -2
  86. package/dist/{routeResolver-CTCcSHWu.d.ts → routeResolver-STwMz_G4.d.ts} +2 -2
  87. package/dist/runtime-5NCT3J3T.cjs +8 -0
  88. package/dist/runtime-JTHUWCBM.js +8 -0
  89. package/dist/runtime-t8AZacFF.d.cts +233 -0
  90. package/dist/runtime-t8AZacFF.d.ts +233 -0
  91. package/dist/search/api.cjs +766 -0
  92. package/dist/search/api.d.cts +385 -0
  93. package/dist/search/api.d.ts +385 -0
  94. package/dist/search/api.js +766 -0
  95. package/dist/search/http.cjs +37 -0
  96. package/dist/search/http.d.cts +323 -0
  97. package/dist/search/http.d.ts +323 -0
  98. package/dist/search/http.js +37 -0
  99. package/dist/search.cjs +57 -0
  100. package/dist/search.d.cts +113 -0
  101. package/dist/search.d.ts +113 -0
  102. package/dist/search.js +57 -0
  103. package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -3
  104. package/dist/transformer/transformers/AnthropicTransformer.js +2 -2
  105. package/dist/transformer/transformers.cjs +7 -7
  106. package/dist/transformer/transformers.js +6 -6
  107. package/dist/transformer.cjs +6 -6
  108. package/dist/transformer.js +5 -5
  109. package/dist/{types-DsHBBk3h.d.cts → types-BSIJdsyl.d.cts} +15 -0
  110. package/dist/{types-CbglB73l.d.cts → types-Be-yt_6T.d.cts} +1 -1
  111. package/dist/{types-CVRge76p.d.ts → types-By9Tg6-I.d.ts} +15 -0
  112. package/dist/types-CbCZqLGg.d.cts +127 -0
  113. package/dist/types-CbCZqLGg.d.ts +127 -0
  114. package/dist/{types-jjU24eYG.d.cts → types-D-oHYusb.d.cts} +73 -4
  115. package/dist/{types-Bd72H4SB.d.ts → types-Dm4_36V4.d.ts} +1 -1
  116. package/dist/{types-5T223WYQ.d.ts → types-Dvlpjn0d.d.ts} +73 -4
  117. package/package.json +6 -3
  118. package/dist/{chunk-6OC3ORW6.js → chunk-EUZRH544.js} +3 -3
  119. package/dist/{chunk-SQMAEDNE.cjs → chunk-POUTD5AM.cjs} +2 -2
@@ -0,0 +1,766 @@
1
+ import {
2
+ createEgressGuardedDispatcher,
3
+ findEgressDenial,
4
+ getSearchProxyDispatcher,
5
+ resolveSearchProxySettings,
6
+ validateEgressUrl
7
+ } from "../chunk-DBBR6RHF.js";
8
+
9
+ // src/search/api/rotator.ts
10
+ var ApiKeyRotator = class {
11
+ index = 0;
12
+ /**
13
+ * Pick the next key from a comma-separated list, advancing the counter.
14
+ *
15
+ * Returns `''` when nothing is configured — never `undefined`, so callers
16
+ * have one falsy case to test rather than two.
17
+ */
18
+ pick(rawKeyString) {
19
+ const keys = splitKeys(rawKeyString);
20
+ if (keys.length === 0) return "";
21
+ if (keys.length === 1) return keys[0];
22
+ if (this.index >= keys.length) this.index = 0;
23
+ const selected = keys[this.index];
24
+ this.index = (this.index + 1) % keys.length;
25
+ return selected;
26
+ }
27
+ /** Reset the rotation counter, e.g. after a configuration update. */
28
+ reset() {
29
+ this.index = 0;
30
+ }
31
+ /** How many usable keys the string holds. */
32
+ countKeys(rawKeyString) {
33
+ return splitKeys(rawKeyString).length;
34
+ }
35
+ /**
36
+ * Every configured key.
37
+ *
38
+ * Not in the Elftia original: redaction has to strip EVERY key a provider
39
+ * could have sent, not just the one this request rotated onto, or a key
40
+ * echoed by an upstream error survives whenever the counter has moved on.
41
+ */
42
+ allKeys(rawKeyString) {
43
+ return splitKeys(rawKeyString);
44
+ }
45
+ };
46
+ function splitKeys(raw) {
47
+ if (!raw) return [];
48
+ return raw.split(",").map((key) => key.trim()).filter((key) => key.length > 0);
49
+ }
50
+
51
+ // src/search/api/transport.ts
52
+ import { SearchProviderError } from "@omnicross/contracts/search-types";
53
+ import { fetch as undiciFetch } from "undici";
54
+
55
+ // src/search/api/types.ts
56
+ var SEARCH_API_TRANSPORT_ID = "undici";
57
+
58
+ // src/search/api/transport.ts
59
+ var API_MAX_REDIRECTS = 5;
60
+ var DEFAULT_API_TIMEOUT_MS = 15e3;
61
+ var API_RESPONSE_BYTES = 2 * 1024 * 1024;
62
+ var MAX_UPSTREAM_TEXT = 300;
63
+ function createSearchApiTransport(options = {}) {
64
+ const policy = options.egressPolicy;
65
+ const fetchImpl = options.fetch ?? createUndiciApiFetch(options.env, policy, options.resolveProxyDispatcher);
66
+ const maxRedirects = options.maxRedirects ?? API_MAX_REDIRECTS;
67
+ return async (request) => {
68
+ const context = {
69
+ providerId: request.providerId,
70
+ label: request.label,
71
+ secrets: request.secrets ?? [],
72
+ host: "unknown-host"
73
+ };
74
+ const initialUrl = assertEgressAllowed(request.url, policy, context);
75
+ context.host = initialUrl.hostname;
76
+ const controller = new AbortController();
77
+ const onAbort = () => controller.abort(request.signal?.reason);
78
+ request.signal?.addEventListener("abort", onAbort, { once: true });
79
+ if (request.signal?.aborted) onAbort();
80
+ const timeoutMs = request.timeoutMs ?? DEFAULT_API_TIMEOUT_MS;
81
+ let deadlineExpired = false;
82
+ const timer = setTimeout(() => {
83
+ deadlineExpired = true;
84
+ controller.abort();
85
+ }, timeoutMs);
86
+ try {
87
+ const response = await fetchWithValidatedRedirects(
88
+ fetchImpl,
89
+ initialUrl,
90
+ request,
91
+ controller.signal,
92
+ maxRedirects,
93
+ policy,
94
+ context
95
+ );
96
+ const rawText = await readJsonBodyWithLimit(response, context);
97
+ assertStatusOk(response.status, rawText, context);
98
+ return parseJsonPayload(rawText, context);
99
+ } catch (error) {
100
+ const denial = findEgressDenial(error);
101
+ if (denial) throw denial;
102
+ if (request.signal?.aborted) {
103
+ throw apiError("cancelled", `${context.label} search was cancelled by the caller`, {
104
+ stage: "fetch",
105
+ context,
106
+ retryable: false,
107
+ cause: error
108
+ });
109
+ }
110
+ if (deadlineExpired) {
111
+ throw apiError(
112
+ "timeout",
113
+ `${context.label} search exceeded its ${timeoutMs}ms budget`,
114
+ { stage: "fetch", context, retryable: true, cause: error }
115
+ );
116
+ }
117
+ throw asApiError(error, context, "connect");
118
+ } finally {
119
+ clearTimeout(timer);
120
+ request.signal?.removeEventListener("abort", onAbort);
121
+ }
122
+ };
123
+ }
124
+ var defaultSearchApiTransport = createSearchApiTransport();
125
+ function selectApiDispatcher(env, policy, override) {
126
+ if (override?.resolveProxyDispatcher) {
127
+ const dispatcher = override.resolveProxyDispatcher(override.url);
128
+ if (dispatcher) return { dispatcher, proxied: true };
129
+ }
130
+ const proxy = resolveSearchProxySettings(env);
131
+ if (proxy) return { dispatcher: getSearchProxyDispatcher(proxy), proxied: true };
132
+ return { dispatcher: createEgressGuardedDispatcher(policy), proxied: false };
133
+ }
134
+ function createUndiciApiFetch(env, policy, resolveProxyDispatcher) {
135
+ return async (url, init) => {
136
+ const { dispatcher } = selectApiDispatcher(env, policy, { url, resolveProxyDispatcher });
137
+ return await undiciFetch(
138
+ url,
139
+ { ...init, dispatcher }
140
+ );
141
+ };
142
+ }
143
+ async function fetchWithValidatedRedirects(fetchImpl, initialUrl, request, signal, maxRedirects, policy, context) {
144
+ let currentUrl = initialUrl;
145
+ let method = request.method;
146
+ let body = request.jsonBody === void 0 ? void 0 : JSON.stringify(request.jsonBody);
147
+ const carriedHeaders = { ...request.headers };
148
+ for (let redirects = 0; redirects <= maxRedirects; redirects += 1) {
149
+ context.host = currentUrl.hostname;
150
+ const headers = { ...carriedHeaders };
151
+ if (body !== void 0) headers["Content-Type"] = "application/json";
152
+ let response;
153
+ try {
154
+ response = await fetchImpl(currentUrl.href, {
155
+ method,
156
+ headers,
157
+ body,
158
+ signal,
159
+ redirect: "manual"
160
+ });
161
+ } catch (error) {
162
+ const denial = findEgressDenial(error);
163
+ if (denial) throw denial;
164
+ if (isAbortError(error)) throw error;
165
+ throw apiError("upstream_unavailable", networkFailureMessage(error, context), {
166
+ stage: "connect",
167
+ context,
168
+ retryable: true,
169
+ cause: error
170
+ });
171
+ }
172
+ if (response.status < 300 || response.status >= 400) return response;
173
+ const location = response.headers.get("location");
174
+ if (!location) return response;
175
+ void response.body?.cancel().catch(() => void 0);
176
+ if (redirects === maxRedirects) throw tooManyRedirects(maxRedirects, context);
177
+ let nextUrl;
178
+ try {
179
+ nextUrl = new URL(location, currentUrl);
180
+ } catch {
181
+ throw apiError("upstream_unavailable", `${context.label} sent an unusable redirect target`, {
182
+ stage: "redirect",
183
+ context,
184
+ retryable: false
185
+ });
186
+ }
187
+ assertEgressAllowed(nextUrl, policy, context);
188
+ if (nextUrl.origin !== currentUrl.origin) stripCredentialHeaders(carriedHeaders);
189
+ if (response.status === 303 || (response.status === 301 || response.status === 302) && method === "POST") {
190
+ method = "GET";
191
+ body = void 0;
192
+ }
193
+ currentUrl = nextUrl;
194
+ }
195
+ throw tooManyRedirects(maxRedirects, context);
196
+ }
197
+ var CREDENTIAL_HEADERS = /* @__PURE__ */ new Set([
198
+ "authorization",
199
+ "cookie",
200
+ "proxy-authorization"
201
+ ]);
202
+ function stripCredentialHeaders(headers) {
203
+ for (const name of Object.keys(headers)) {
204
+ if (CREDENTIAL_HEADERS.has(name.toLowerCase())) delete headers[name];
205
+ }
206
+ }
207
+ function assertEgressAllowed(url, policy, context) {
208
+ try {
209
+ return validateEgressUrl(url, policy, context.providerId);
210
+ } catch (error) {
211
+ if (!(error instanceof SearchProviderError) && !isTaxonomyError(error)) throw error;
212
+ const denial = error;
213
+ throw apiError(denial.code, denial.message, {
214
+ stage: "egress",
215
+ context: { ...context, host: denial.details?.host ?? context.host },
216
+ retryable: false,
217
+ cause: denial,
218
+ details: denial.details
219
+ });
220
+ }
221
+ }
222
+ function tooManyRedirects(maxRedirects, context) {
223
+ return apiError("upstream_unavailable", `too many redirects (>${maxRedirects})`, {
224
+ stage: "redirect",
225
+ context,
226
+ retryable: false,
227
+ details: { maxRedirects: String(maxRedirects) }
228
+ });
229
+ }
230
+ async function readJsonBodyWithLimit(response, context) {
231
+ const reader = response.body?.getReader();
232
+ if (!reader) return "";
233
+ const chunks = [];
234
+ let totalBytes = 0;
235
+ while (true) {
236
+ const { done, value } = await reader.read();
237
+ if (done) break;
238
+ totalBytes += value.byteLength;
239
+ if (totalBytes > API_RESPONSE_BYTES) {
240
+ await reader.cancel().catch(() => void 0);
241
+ throw apiError(
242
+ "upstream_unavailable",
243
+ `${context.label} response exceeded the ${API_RESPONSE_BYTES} byte cap`,
244
+ {
245
+ stage: "body-cap",
246
+ context,
247
+ retryable: false,
248
+ details: { maxResponseBytes: String(API_RESPONSE_BYTES) }
249
+ }
250
+ );
251
+ }
252
+ chunks.push(value);
253
+ }
254
+ const merged = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
255
+ let offset = 0;
256
+ for (const chunk of chunks) {
257
+ merged.set(chunk, offset);
258
+ offset += chunk.byteLength;
259
+ }
260
+ return new TextDecoder("utf-8").decode(merged);
261
+ }
262
+ function assertStatusOk(status, rawText, context) {
263
+ if (status >= 200 && status < 300) return;
264
+ const detail = sanitizeUpstreamText(rawText, context.secrets);
265
+ const suffix = detail ? ` - ${detail}` : "";
266
+ const details = { status: String(status) };
267
+ if (status === 401 || status === 403) {
268
+ throw apiError(
269
+ "auth_failed",
270
+ `${context.label} rejected the credentials (HTTP ${status})${suffix}`,
271
+ { stage: "fetch", context, retryable: false, details }
272
+ );
273
+ }
274
+ if (status === 429) {
275
+ throw apiError("rate_limited", `${context.label} rate limited the request (HTTP ${status})${suffix}`, {
276
+ stage: "fetch",
277
+ context,
278
+ retryable: true,
279
+ details
280
+ });
281
+ }
282
+ throw apiError("upstream_unavailable", `${context.label} search failed: ${status}${suffix}`, {
283
+ stage: "fetch",
284
+ context,
285
+ // 5xx may pass on a retry; a 4xx that is not auth or quota will not.
286
+ retryable: status >= 500,
287
+ details
288
+ });
289
+ }
290
+ function parseJsonPayload(rawText, context) {
291
+ try {
292
+ return JSON.parse(rawText);
293
+ } catch (error) {
294
+ throw apiError("parse_failed", `${context.label} returned a body that is not valid JSON`, {
295
+ stage: "parse",
296
+ context,
297
+ retryable: false,
298
+ cause: error
299
+ });
300
+ }
301
+ }
302
+ function requireResultArray(payload, field, providerId, label) {
303
+ const value = typeof payload === "object" && payload !== null ? payload[field] : void 0;
304
+ if (Array.isArray(value)) return value;
305
+ throw new SearchProviderError(
306
+ "parse_failed",
307
+ `${label} response is missing its "${field}" array`,
308
+ {
309
+ providerId,
310
+ retryable: false,
311
+ details: { transport: SEARCH_API_TRANSPORT_ID, stage: "parse" }
312
+ }
313
+ );
314
+ }
315
+ function payloadText(value) {
316
+ return typeof value === "string" ? value : "";
317
+ }
318
+ function apiConfigMissing(providerId, message) {
319
+ return new SearchProviderError("config_missing", message, {
320
+ providerId,
321
+ retryable: false,
322
+ details: { transport: SEARCH_API_TRANSPORT_ID, stage: "config" }
323
+ });
324
+ }
325
+ function apiError(code, message, init) {
326
+ return new SearchProviderError(code, message, {
327
+ providerId: init.context.providerId,
328
+ retryable: init.retryable,
329
+ cause: init.cause,
330
+ // transport/stage/host last so a caller-supplied `details` cannot shadow them.
331
+ details: {
332
+ ...init.details,
333
+ host: init.context.host,
334
+ transport: SEARCH_API_TRANSPORT_ID,
335
+ stage: init.stage
336
+ }
337
+ });
338
+ }
339
+ function asApiError(value, context, fallbackStage) {
340
+ const denial = findEgressDenial(value);
341
+ if (denial) return denial;
342
+ if (value instanceof SearchProviderError) return value;
343
+ if (isTaxonomyError(value)) return value;
344
+ return apiError("upstream_unavailable", describeError(value, context), {
345
+ stage: fallbackStage,
346
+ context,
347
+ retryable: true,
348
+ cause: value
349
+ });
350
+ }
351
+ function isTaxonomyError(value) {
352
+ return value instanceof Error && typeof value.code === "string" && value.name === "SearchProviderError";
353
+ }
354
+ function describeError(value, context) {
355
+ const raw = value instanceof Error && value.message ? value.message : `${context.label} search failed`;
356
+ return sanitizeUpstreamText(raw, context.secrets) || `${context.label} search failed`;
357
+ }
358
+ function networkFailureMessage(error, context) {
359
+ const code = nestedErrorCode(error);
360
+ const suffix = code ? ` (${code})` : "";
361
+ return `${context.label} request failed${suffix}`;
362
+ }
363
+ function nestedErrorCode(error) {
364
+ let current = error;
365
+ for (let depth = 0; depth < 4 && current instanceof Error; depth += 1) {
366
+ const code = current.code;
367
+ if (typeof code === "string" && /^[A-Z][A-Z0-9_]+$/.test(code)) return code;
368
+ current = current.cause;
369
+ }
370
+ return void 0;
371
+ }
372
+ function isAbortError(error) {
373
+ return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
374
+ }
375
+ var CREDENTIAL_NAMES = "api_?key|access_?token|refresh_?token|token|password|passwd|secret|authorization";
376
+ var CREDENTIAL_FIELD = new RegExp(`("(?:${CREDENTIAL_NAMES})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
377
+ var ESCAPED_CREDENTIAL_FIELD = new RegExp(
378
+ `(\\\\"(?:${CREDENTIAL_NAMES})\\\\"\\s*:\\s*\\\\")(?:(?!\\\\").)*(\\\\")`,
379
+ "gi"
380
+ );
381
+ var AUTH_SCHEME_VALUE = /\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]+/gi;
382
+ var ABSOLUTE_URL = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
383
+ function sanitizeUpstreamText(text, secrets = []) {
384
+ if (!text) return "";
385
+ let out = text.replace(ESCAPED_CREDENTIAL_FIELD, "$1[redacted]$2");
386
+ out = out.replace(CREDENTIAL_FIELD, "$1[redacted]$2");
387
+ out = redactSecretValues(out, secrets);
388
+ out = out.replace(AUTH_SCHEME_VALUE, "$1 [redacted]");
389
+ out = out.replace(ABSOLUTE_URL, "[url]");
390
+ out = out.replace(/\s+/g, " ").trim();
391
+ return out.length > MAX_UPSTREAM_TEXT ? `${out.slice(0, MAX_UPSTREAM_TEXT)}\u2026` : out;
392
+ }
393
+ function redactSecretValues(text, secrets) {
394
+ let out = text;
395
+ for (const secret of secrets) {
396
+ if (!secret) continue;
397
+ out = out.split(secret).join("[redacted]");
398
+ const encoded = encodeURIComponent(secret);
399
+ if (encoded !== secret) out = out.split(encoded).join("[redacted]");
400
+ }
401
+ return out;
402
+ }
403
+
404
+ // src/search/api/JinaReaderClient.ts
405
+ var JINA_READER_HOST = "https://r.jina.ai";
406
+ var LABEL = "Jina Reader";
407
+ var JinaReaderClient = class {
408
+ constructor(config = {}, transport = defaultSearchApiTransport, providerId = "jina") {
409
+ this.config = config;
410
+ this.transport = transport;
411
+ this.providerId = providerId;
412
+ }
413
+ config;
414
+ transport;
415
+ providerId;
416
+ rotator = new ApiKeyRotator();
417
+ /**
418
+ * Read one URL.
419
+ *
420
+ * The key is OPTIONAL, as in the baseline: the reader answers unauthenticated
421
+ * requests at a lower rate limit.
422
+ */
423
+ async readUrl(url, options = {}) {
424
+ const headers = { Accept: "application/json" };
425
+ const apiKey = this.rotator.pick(this.config.apiKey);
426
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
427
+ const payload = await this.transport({
428
+ url: `${JINA_READER_HOST}/${encodeURIComponent(url)}`,
429
+ method: "GET",
430
+ headers,
431
+ providerId: this.providerId,
432
+ label: LABEL,
433
+ timeoutMs: options.timeout,
434
+ signal: options.signal,
435
+ secrets: this.rotator.allKeys(this.config.apiKey)
436
+ });
437
+ return toReadResult(url, payload);
438
+ }
439
+ };
440
+ function toReadResult(url, payload) {
441
+ const root = payload ?? {};
442
+ const nested = root.data ?? {};
443
+ const title = payloadText(nested.title) || payloadText(root.title);
444
+ const content = payloadText(nested.content) || payloadText(root.content);
445
+ return {
446
+ url,
447
+ ...title ? { title } : {},
448
+ ...content ? { content } : {}
449
+ };
450
+ }
451
+
452
+ // src/search/api/JinaSearchProvider.ts
453
+ var JINA_PROVIDER_ID = "jina";
454
+ var JINA_DEFAULT_HOST = "https://s.jina.ai";
455
+ var JINA_DEFAULT_MAX_RESULTS = 5;
456
+ var LABEL2 = "Jina";
457
+ var JinaSearchProvider = class {
458
+ constructor(config = {}, transport = defaultSearchApiTransport, reader) {
459
+ this.config = config;
460
+ this.transport = transport;
461
+ this.reader = reader ?? new JinaReaderClient(config, transport, JINA_PROVIDER_ID);
462
+ }
463
+ config;
464
+ transport;
465
+ id = JINA_PROVIDER_ID;
466
+ rotator = new ApiKeyRotator();
467
+ reader;
468
+ async search(query, options = {}) {
469
+ const apiHost = this.config.apiHost || JINA_DEFAULT_HOST;
470
+ const maxResults = options.maxResults || JINA_DEFAULT_MAX_RESULTS;
471
+ const headers = { Accept: "application/json" };
472
+ const apiKey = this.rotator.pick(this.config.apiKey);
473
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
474
+ if (options.fetchPageContent) headers["X-Engine"] = "direct";
475
+ const payload = await this.transport({
476
+ url: `${apiHost}/${encodeURIComponent(query)}`,
477
+ method: "GET",
478
+ headers,
479
+ providerId: this.id,
480
+ label: LABEL2,
481
+ timeoutMs: options.timeout,
482
+ signal: options.signal,
483
+ secrets: this.rotator.allKeys(this.config.apiKey)
484
+ });
485
+ return requireResultArray(payload, "data", this.id, LABEL2).slice(0, maxResults).map((item) => toResult(item));
486
+ }
487
+ /** Read one URL through the split-out reader client. */
488
+ readUrl(url, options) {
489
+ return this.reader.readUrl(url, options);
490
+ }
491
+ };
492
+ function toResult(item) {
493
+ const record = item ?? {};
494
+ return {
495
+ title: payloadText(record.title),
496
+ url: payloadText(record.url),
497
+ // The baseline's quirk, preserved: Jina returns a snippet under either
498
+ // name depending on the engine that produced the row.
499
+ content: payloadText(record.content) || payloadText(record.description)
500
+ };
501
+ }
502
+
503
+ // src/search/api/SearxngSearchProvider.ts
504
+ var SEARXNG_PROVIDER_ID = "searxng";
505
+ var SEARXNG_DEFAULT_MAX_RESULTS = 5;
506
+ var LABEL3 = "Searxng";
507
+ var SearxngSearchProvider = class {
508
+ constructor(config, transport = defaultSearchApiTransport) {
509
+ this.config = config;
510
+ this.transport = transport;
511
+ }
512
+ config;
513
+ transport;
514
+ id = SEARXNG_PROVIDER_ID;
515
+ async search(query, options = {}) {
516
+ if (!this.config.apiHost) throw apiConfigMissing(this.id, `${LABEL3} API host is required`);
517
+ const maxResults = options.maxResults || SEARXNG_DEFAULT_MAX_RESULTS;
518
+ const params = new URLSearchParams({ q: query, format: "json", pageno: "1" });
519
+ const headers = { Accept: "application/json" };
520
+ const { basicAuthUsername, basicAuthPassword } = this.config;
521
+ const credentials = basicAuthUsername && basicAuthPassword ? Buffer.from(`${basicAuthUsername}:${basicAuthPassword}`).toString("base64") : void 0;
522
+ if (credentials) headers.Authorization = `Basic ${credentials}`;
523
+ const payload = await this.transport({
524
+ url: `${this.config.apiHost}/search?${params.toString()}`,
525
+ method: "GET",
526
+ headers,
527
+ providerId: this.id,
528
+ label: LABEL3,
529
+ timeoutMs: options.timeout,
530
+ signal: options.signal,
531
+ // The encoded blob AND its parts: an upstream error could echo either.
532
+ secrets: [credentials, basicAuthUsername, basicAuthPassword]
533
+ });
534
+ return requireResultArray(payload, "results", this.id, LABEL3).slice(0, maxResults).map((item) => toResult2(item));
535
+ }
536
+ };
537
+ function toResult2(item) {
538
+ const record = item ?? {};
539
+ return {
540
+ title: payloadText(record.title),
541
+ url: payloadText(record.url),
542
+ content: payloadText(record.content)
543
+ };
544
+ }
545
+
546
+ // src/search/api/TavilySearchProvider.ts
547
+ var TAVILY_PROVIDER_ID = "tavily";
548
+ var TAVILY_DEFAULT_HOST = "https://api.tavily.com";
549
+ var TAVILY_DEFAULT_MAX_RESULTS = 5;
550
+ var LABEL4 = "Tavily";
551
+ var TavilySearchProvider = class {
552
+ constructor(config, transport = defaultSearchApiTransport) {
553
+ this.config = config;
554
+ this.transport = transport;
555
+ }
556
+ config;
557
+ transport;
558
+ id = TAVILY_PROVIDER_ID;
559
+ rotator = new ApiKeyRotator();
560
+ async search(query, options = {}) {
561
+ const apiKey = this.rotator.pick(this.config.apiKey);
562
+ if (!apiKey) throw apiConfigMissing(this.id, `${LABEL4} API key is required`);
563
+ const apiHost = this.config.apiHost || TAVILY_DEFAULT_HOST;
564
+ const maxResults = options.maxResults || TAVILY_DEFAULT_MAX_RESULTS;
565
+ const payload = await this.transport({
566
+ url: `${apiHost}/search`,
567
+ method: "POST",
568
+ jsonBody: {
569
+ api_key: apiKey,
570
+ query,
571
+ max_results: maxResults,
572
+ search_depth: "advanced",
573
+ include_answer: false,
574
+ include_raw_content: false
575
+ },
576
+ providerId: this.id,
577
+ label: LABEL4,
578
+ timeoutMs: options.timeout,
579
+ signal: options.signal,
580
+ secrets: this.rotator.allKeys(this.config.apiKey)
581
+ });
582
+ return requireResultArray(payload, "results", this.id, LABEL4).map((item) => toResult3(item));
583
+ }
584
+ };
585
+ function toResult3(item) {
586
+ const record = item ?? {};
587
+ return {
588
+ title: payloadText(record.title),
589
+ url: payloadText(record.url),
590
+ content: payloadText(record.content)
591
+ };
592
+ }
593
+
594
+ // src/search/api/ZhipuSearchProvider.ts
595
+ var ZHIPU_PROVIDER_ID = "zhipu";
596
+ var ZAI_PROVIDER_ID = "z.ai";
597
+ var ZHIPU_DEFAULT_HOSTS = Object.freeze({
598
+ [ZHIPU_PROVIDER_ID]: "https://open.bigmodel.cn/api/paas/v4",
599
+ [ZAI_PROVIDER_ID]: "https://api.z.ai/api/paas/v4"
600
+ });
601
+ var ZHIPU_DEFAULT_MAX_RESULTS = 10;
602
+ function normalizeZhipuApiUrl(apiHost) {
603
+ const url = apiHost.replace(/\/+$/, "");
604
+ if (url.endsWith("/web_search")) return url;
605
+ if (/\/v\d+$/.test(url)) return `${url}/web_search`;
606
+ return `${url}/v4/web_search`;
607
+ }
608
+ var ZhipuSearchProvider = class {
609
+ constructor(id, config, transport = defaultSearchApiTransport) {
610
+ this.config = config;
611
+ this.transport = transport;
612
+ this.id = id;
613
+ }
614
+ config;
615
+ transport;
616
+ id;
617
+ rotator = new ApiKeyRotator();
618
+ async search(query, options = {}) {
619
+ const apiKey = this.rotator.pick(this.config.apiKey);
620
+ if (!apiKey) throw apiConfigMissing(this.id, `${this.id} API key is required`);
621
+ const defaultHost = ZHIPU_DEFAULT_HOSTS[this.id] ?? ZHIPU_DEFAULT_HOSTS[ZHIPU_PROVIDER_ID];
622
+ const apiHost = this.config.apiHost || defaultHost;
623
+ const maxResults = options.maxResults || ZHIPU_DEFAULT_MAX_RESULTS;
624
+ const payload = await this.transport({
625
+ url: normalizeZhipuApiUrl(apiHost),
626
+ method: "POST",
627
+ headers: { Authorization: `Bearer ${apiKey}` },
628
+ jsonBody: { search_query: query, search_engine: "search_pro", count: maxResults },
629
+ providerId: this.id,
630
+ label: this.id,
631
+ timeoutMs: options.timeout,
632
+ signal: options.signal,
633
+ secrets: this.rotator.allKeys(this.config.apiKey)
634
+ });
635
+ return requireResultArray(payload, "search_result", this.id, this.id).slice(0, maxResults).map((item) => toResult4(item));
636
+ }
637
+ };
638
+ function toResult4(item) {
639
+ const record = item ?? {};
640
+ return {
641
+ title: payloadText(record.title),
642
+ // `link`, not `url` — the wire contract, not a typo.
643
+ url: payloadText(record.link),
644
+ content: payloadText(record.content)
645
+ };
646
+ }
647
+
648
+ // src/search/api/contributions.ts
649
+ var SHARED_API_CAPABILITIES = {
650
+ supportsRegion: false,
651
+ supportsLanguage: false,
652
+ supportsTimeRange: false,
653
+ supportsCancellation: true
654
+ };
655
+ var TAVILY_CAPABILITIES = Object.freeze({
656
+ ...SHARED_API_CAPABILITIES,
657
+ requiresApiKey: true,
658
+ supportsUrlRead: false
659
+ });
660
+ var JINA_CAPABILITIES = Object.freeze({
661
+ ...SHARED_API_CAPABILITIES,
662
+ // The search endpoint answers unauthenticated requests (rate-limited), so a
663
+ // key is an upgrade, not a requirement.
664
+ requiresApiKey: false,
665
+ supportsUrlRead: true
666
+ });
667
+ var SEARXNG_CAPABILITIES = Object.freeze({
668
+ ...SHARED_API_CAPABILITIES,
669
+ requiresApiKey: false,
670
+ supportsUrlRead: false
671
+ });
672
+ var ZHIPU_CAPABILITIES = Object.freeze({
673
+ ...SHARED_API_CAPABILITIES,
674
+ requiresApiKey: true,
675
+ supportsUrlRead: false
676
+ });
677
+ function apiSearchContributions(configs, options = {}) {
678
+ const transport = createSearchApiTransport({
679
+ fetch: options.fetchImpl,
680
+ egressPolicy: options.egressPolicy,
681
+ resolveProxyDispatcher: options.resolveProxyDispatcher
682
+ });
683
+ const contributions = [];
684
+ if (configs.tavily) {
685
+ contributions.push({
686
+ id: TAVILY_PROVIDER_ID,
687
+ source: "builtin",
688
+ kind: "api",
689
+ provider: new TavilySearchProvider(configs.tavily, transport),
690
+ capabilities: { ...TAVILY_CAPABILITIES }
691
+ });
692
+ }
693
+ if (configs.jina) {
694
+ contributions.push({
695
+ id: JINA_PROVIDER_ID,
696
+ source: "builtin",
697
+ kind: "api",
698
+ provider: new JinaSearchProvider(configs.jina, transport),
699
+ capabilities: { ...JINA_CAPABILITIES }
700
+ });
701
+ }
702
+ if (configs.searxng) {
703
+ contributions.push({
704
+ id: SEARXNG_PROVIDER_ID,
705
+ source: "builtin",
706
+ kind: "api",
707
+ provider: new SearxngSearchProvider(configs.searxng, transport),
708
+ capabilities: { ...SEARXNG_CAPABILITIES }
709
+ });
710
+ }
711
+ if (configs.zhipu) {
712
+ contributions.push({
713
+ id: ZHIPU_PROVIDER_ID,
714
+ source: "builtin",
715
+ kind: "api",
716
+ provider: new ZhipuSearchProvider(ZHIPU_PROVIDER_ID, configs.zhipu, transport),
717
+ capabilities: { ...ZHIPU_CAPABILITIES }
718
+ });
719
+ }
720
+ if (configs["z.ai"]) {
721
+ contributions.push({
722
+ id: ZAI_PROVIDER_ID,
723
+ source: "builtin",
724
+ kind: "api",
725
+ provider: new ZhipuSearchProvider(ZAI_PROVIDER_ID, configs["z.ai"], transport),
726
+ capabilities: { ...ZHIPU_CAPABILITIES }
727
+ });
728
+ }
729
+ return contributions;
730
+ }
731
+ export {
732
+ API_MAX_REDIRECTS,
733
+ API_RESPONSE_BYTES,
734
+ ApiKeyRotator,
735
+ DEFAULT_API_TIMEOUT_MS,
736
+ JINA_CAPABILITIES,
737
+ JINA_DEFAULT_HOST,
738
+ JINA_DEFAULT_MAX_RESULTS,
739
+ JINA_PROVIDER_ID,
740
+ JINA_READER_HOST,
741
+ JinaReaderClient,
742
+ JinaSearchProvider,
743
+ SEARCH_API_TRANSPORT_ID,
744
+ SEARXNG_CAPABILITIES,
745
+ SEARXNG_DEFAULT_MAX_RESULTS,
746
+ SEARXNG_PROVIDER_ID,
747
+ SearxngSearchProvider,
748
+ TAVILY_CAPABILITIES,
749
+ TAVILY_DEFAULT_HOST,
750
+ TAVILY_DEFAULT_MAX_RESULTS,
751
+ TAVILY_PROVIDER_ID,
752
+ TavilySearchProvider,
753
+ ZAI_PROVIDER_ID,
754
+ ZHIPU_CAPABILITIES,
755
+ ZHIPU_DEFAULT_HOSTS,
756
+ ZHIPU_DEFAULT_MAX_RESULTS,
757
+ ZHIPU_PROVIDER_ID,
758
+ ZhipuSearchProvider,
759
+ apiConfigMissing,
760
+ apiSearchContributions,
761
+ createSearchApiTransport,
762
+ defaultSearchApiTransport,
763
+ normalizeZhipuApiUrl,
764
+ requireResultArray,
765
+ sanitizeUpstreamText
766
+ };