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