dsh-provider-proxy 0.2.1 → 0.4.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.
- package/README.md +135 -116
- package/index.js +5 -5
- package/lib/client.js +217 -11
- package/lib/client.js.map +3 -3
- package/lib/index.js +278 -23
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -4,10 +4,24 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { fetch as undiciFetch2 } from "undici";
|
|
9
|
+
|
|
7
10
|
// src/fetch-router.ts
|
|
8
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
12
|
import { fetch as undiciFetch } from "undici";
|
|
10
13
|
var STATE_KEY = /* @__PURE__ */ Symbol.for("dsh-provider-proxy.fetch-router.v1");
|
|
14
|
+
function requestHost(input) {
|
|
15
|
+
try {
|
|
16
|
+
if (typeof input === "string") return new URL(input).hostname;
|
|
17
|
+
if (input instanceof URL) return input.hostname;
|
|
18
|
+
const url = input?.url;
|
|
19
|
+
if (typeof url === "string") return new URL(url).hostname;
|
|
20
|
+
} catch {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
11
25
|
function installSharedRouter() {
|
|
12
26
|
const current = globalThis[STATE_KEY];
|
|
13
27
|
if (current !== void 0) {
|
|
@@ -26,6 +40,10 @@ function installSharedRouter() {
|
|
|
26
40
|
if (init !== void 0 && init !== null && init.dispatcher !== void 0) {
|
|
27
41
|
return originalFetch.call(globalThis, input, init);
|
|
28
42
|
}
|
|
43
|
+
if (active.decide !== void 0 && active.decide(requestHost(input)) === "direct") {
|
|
44
|
+
return originalFetch.call(globalThis, input, init);
|
|
45
|
+
}
|
|
46
|
+
active.onHit?.();
|
|
29
47
|
return undiciFetch(input, {
|
|
30
48
|
...init,
|
|
31
49
|
dispatcher: active.dispatcher
|
|
@@ -81,11 +99,11 @@ function acquireFetchRouter() {
|
|
|
81
99
|
const state = installSharedRouter();
|
|
82
100
|
let released = false;
|
|
83
101
|
return {
|
|
84
|
-
run(
|
|
85
|
-
return state.storage.run(
|
|
102
|
+
run(store, callback) {
|
|
103
|
+
return state.storage.run(store, callback);
|
|
86
104
|
},
|
|
87
|
-
wrap(
|
|
88
|
-
return contextualIterable(state.storage,
|
|
105
|
+
wrap(store, factory) {
|
|
106
|
+
return contextualIterable(state.storage, store, factory);
|
|
89
107
|
},
|
|
90
108
|
release() {
|
|
91
109
|
if (released) return;
|
|
@@ -103,7 +121,7 @@ function acquireFetchRouter() {
|
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
// src/proxy-table.ts
|
|
106
|
-
import { ProxyAgent } from "undici";
|
|
124
|
+
import { ProxyAgent, Socks5ProxyAgent } from "undici";
|
|
107
125
|
|
|
108
126
|
// src/config.ts
|
|
109
127
|
import { createRequire } from "node:module";
|
|
@@ -1010,17 +1028,32 @@ var ProviderProxyRoute = z.object({
|
|
|
1010
1028
|
port: z.number().default(0),
|
|
1011
1029
|
/** Optional proxy username. */
|
|
1012
1030
|
username: z.string().default(""),
|
|
1013
|
-
/**
|
|
1014
|
-
|
|
1031
|
+
/** SOCKS5 proxy password. Only used when `protocol` is `socks5`. */
|
|
1032
|
+
password: z.string().default(""),
|
|
1033
|
+
/**
|
|
1034
|
+
* Comma/space-separated TARGET hosts that must go through the proxy. Empty
|
|
1035
|
+
* means "no restriction" (every target is proxied). Suffix match.
|
|
1036
|
+
*/
|
|
1037
|
+
onlyHosts: z.string().default(""),
|
|
1038
|
+
/** Comma/space-separated TARGET hosts that bypass the proxy; takes priority over `onlyHosts`. */
|
|
1039
|
+
bypassHosts: z.string().default("")
|
|
1015
1040
|
});
|
|
1016
1041
|
var Config = z.object({
|
|
1017
1042
|
/** Per-provider proxy routes, keyed by provider route id (e.g. `sensenova`). */
|
|
1018
|
-
providers: vol(z.dict(ProviderProxyRoute).default({}))
|
|
1043
|
+
providers: vol(z.dict(ProviderProxyRoute).default({})),
|
|
1044
|
+
/**
|
|
1045
|
+
* URL the per-provider connection self-check requests THROUGH the proxy.
|
|
1046
|
+
* Volatile, so a settings write does not remount the plugin; the default is an
|
|
1047
|
+
* IP echo service, which lets the check report the exit address.
|
|
1048
|
+
*/
|
|
1049
|
+
testUrl: vol(z.string().default("https://api.ipify.org?format=json"))
|
|
1019
1050
|
});
|
|
1020
1051
|
function normalizeRoute(provider, raw) {
|
|
1021
1052
|
const route = ProviderProxyRoute(raw ?? {});
|
|
1022
1053
|
const value = typeof route.get === "function" ? route.get() : route;
|
|
1023
|
-
|
|
1054
|
+
const onlyHosts = value.onlyHosts.trim().toLowerCase();
|
|
1055
|
+
const bypassHosts = value.bypassHosts.trim().toLowerCase();
|
|
1056
|
+
if (!value.enabled) return { ...value, port: value.port || 0, onlyHosts, bypassHosts };
|
|
1024
1057
|
const host = value.host.trim();
|
|
1025
1058
|
if (host.length === 0) {
|
|
1026
1059
|
throw new TypeError(`dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740`);
|
|
@@ -1033,16 +1066,50 @@ function normalizeRoute(provider, raw) {
|
|
|
1033
1066
|
`dsh-provider-proxy: provider "${provider}" \u7684\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09`
|
|
1034
1067
|
);
|
|
1035
1068
|
}
|
|
1036
|
-
return { ...value, host, username: value.username.trim() };
|
|
1069
|
+
return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts };
|
|
1037
1070
|
}
|
|
1038
1071
|
|
|
1039
1072
|
// src/proxy-table.ts
|
|
1040
1073
|
function proxyUrl(route) {
|
|
1041
|
-
const
|
|
1074
|
+
const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
|
|
1042
1075
|
const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
|
|
1043
|
-
return `${route.protocol}://${
|
|
1076
|
+
return `${route.protocol}://${credentials}${host}:${route.port}`;
|
|
1077
|
+
}
|
|
1078
|
+
function displayOrigin(route, url) {
|
|
1079
|
+
if (route.protocol !== "socks5") return new URL(url).origin;
|
|
1080
|
+
const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
|
|
1081
|
+
return `${route.protocol}://${host}:${route.port}`;
|
|
1082
|
+
}
|
|
1083
|
+
function createDispatcher(route, url, password) {
|
|
1084
|
+
if (route.protocol === "socks5") {
|
|
1085
|
+
const auth = {};
|
|
1086
|
+
if (route.username.length > 0) auth.username = route.username;
|
|
1087
|
+
if (password.length > 0) auth.password = password;
|
|
1088
|
+
return new Socks5ProxyAgent(url, auth);
|
|
1089
|
+
}
|
|
1090
|
+
return new ProxyAgent({ uri: url });
|
|
1091
|
+
}
|
|
1092
|
+
function parseHostList(raw) {
|
|
1093
|
+
return raw.split(/[,\s]+/).map((entry) => entry.trim().replace(/^\*?\./, "")).filter((entry) => entry.length > 0);
|
|
1094
|
+
}
|
|
1095
|
+
function hostMatches(host, rules) {
|
|
1096
|
+
for (const rule of rules) {
|
|
1097
|
+
if (rule === "*") return true;
|
|
1098
|
+
if (host === rule || host.endsWith(`.${rule}`)) return true;
|
|
1099
|
+
}
|
|
1100
|
+
return false;
|
|
1044
1101
|
}
|
|
1045
|
-
|
|
1102
|
+
function buildDecide(route) {
|
|
1103
|
+
const only = parseHostList(route.onlyHosts);
|
|
1104
|
+
const bypass = parseHostList(route.bypassHosts);
|
|
1105
|
+
return (host) => {
|
|
1106
|
+
if (host === void 0) return "proxy";
|
|
1107
|
+
if (hostMatches(host, bypass)) return "direct";
|
|
1108
|
+
if (only.length > 0 && !hostMatches(host, only)) return "direct";
|
|
1109
|
+
return "proxy";
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function createProxyTable(config) {
|
|
1046
1113
|
const entries = /* @__PURE__ */ new Map();
|
|
1047
1114
|
const problems = /* @__PURE__ */ new Map();
|
|
1048
1115
|
for (const [provider, raw] of Object.entries(config ?? {})) {
|
|
@@ -1054,21 +1121,34 @@ async function createProxyTable(config, options = {}) {
|
|
|
1054
1121
|
continue;
|
|
1055
1122
|
}
|
|
1056
1123
|
if (!route.enabled) continue;
|
|
1057
|
-
if (route.protocol === "socks5") {
|
|
1058
|
-
problems.set(
|
|
1124
|
+
if (route.protocol === "socks5" && route.username.length > 0 && route.password.length === 0) {
|
|
1125
|
+
problems.set(
|
|
1126
|
+
provider,
|
|
1127
|
+
"dsh-provider-proxy: SOCKS5 \u914D\u7F6E\u4E86\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D"
|
|
1128
|
+
);
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (route.protocol !== "socks5" && route.password.length > 0 && route.username.length === 0) {
|
|
1132
|
+
problems.set(
|
|
1133
|
+
provider,
|
|
1134
|
+
"dsh-provider-proxy: \u914D\u7F6E\u4E86\u4EE3\u7406\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801"
|
|
1135
|
+
);
|
|
1059
1136
|
continue;
|
|
1060
1137
|
}
|
|
1061
|
-
const password = options.resolvePassword?.(provider, route.passwordRef);
|
|
1062
|
-
void password;
|
|
1063
1138
|
const url = proxyUrl(route);
|
|
1064
1139
|
let dispatcher;
|
|
1065
1140
|
try {
|
|
1066
|
-
dispatcher =
|
|
1141
|
+
dispatcher = createDispatcher(route, url, route.password);
|
|
1067
1142
|
} catch (error) {
|
|
1068
1143
|
problems.set(provider, error instanceof Error ? error.message : String(error));
|
|
1069
1144
|
continue;
|
|
1070
1145
|
}
|
|
1071
|
-
entries.set(provider, {
|
|
1146
|
+
entries.set(provider, {
|
|
1147
|
+
provider,
|
|
1148
|
+
proxyOrigin: displayOrigin(route, url),
|
|
1149
|
+
dispatcher,
|
|
1150
|
+
decide: buildDecide(route)
|
|
1151
|
+
});
|
|
1072
1152
|
}
|
|
1073
1153
|
return { entries, problems };
|
|
1074
1154
|
}
|
|
@@ -1081,6 +1161,7 @@ async function closeTable(entries) {
|
|
|
1081
1161
|
// src/index.ts
|
|
1082
1162
|
var name = "dsh-provider-proxy";
|
|
1083
1163
|
var inject = ["llm"];
|
|
1164
|
+
var WEB_SEARCH_KEY = "web-search";
|
|
1084
1165
|
function plainProviders(config) {
|
|
1085
1166
|
const raw = config?.providers;
|
|
1086
1167
|
if (raw === void 0) return {};
|
|
@@ -1093,12 +1174,92 @@ function apply(ctx, config) {
|
|
|
1093
1174
|
let current = /* @__PURE__ */ new Map();
|
|
1094
1175
|
let problems = /* @__PURE__ */ new Map();
|
|
1095
1176
|
let closed = false;
|
|
1177
|
+
let webAvailable = false;
|
|
1178
|
+
const hits = /* @__PURE__ */ new Map();
|
|
1179
|
+
const bump = (provider) => {
|
|
1180
|
+
hits.set(provider, (hits.get(provider) ?? 0) + 1);
|
|
1181
|
+
};
|
|
1182
|
+
const DEFAULT_TEST_URL = "https://api.ipify.org?format=json";
|
|
1183
|
+
const TEST_TIMEOUT_MS = 1e4;
|
|
1184
|
+
const selfChecks = /* @__PURE__ */ new Map();
|
|
1185
|
+
const plainTestUrl = () => {
|
|
1186
|
+
const raw = config?.testUrl;
|
|
1187
|
+
const snapshot2 = raw?.get;
|
|
1188
|
+
const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
|
|
1189
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : DEFAULT_TEST_URL;
|
|
1190
|
+
};
|
|
1191
|
+
const causesOf = (error) => {
|
|
1192
|
+
const out = [];
|
|
1193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1194
|
+
const queue = [error];
|
|
1195
|
+
while (queue.length > 0) {
|
|
1196
|
+
const node = queue.shift();
|
|
1197
|
+
if (node === null || typeof node !== "object" || seen.has(node)) continue;
|
|
1198
|
+
seen.add(node);
|
|
1199
|
+
out.push(node);
|
|
1200
|
+
const cause = node.cause;
|
|
1201
|
+
if (cause !== void 0 && cause !== null) queue.push(cause);
|
|
1202
|
+
const errors = node.errors;
|
|
1203
|
+
if (Array.isArray(errors)) queue.push(...errors);
|
|
1204
|
+
}
|
|
1205
|
+
return out;
|
|
1206
|
+
};
|
|
1207
|
+
const classifyTestError = (error) => {
|
|
1208
|
+
const chain = causesOf(error);
|
|
1209
|
+
const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
|
|
1210
|
+
const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
|
|
1211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1212
|
+
if (names.includes("TimeoutError") || names.includes("AbortError")) {
|
|
1213
|
+
return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
|
|
1214
|
+
}
|
|
1215
|
+
const connectivity = ["ECONNREFUSED", "ENOTFOUND", "EHOSTUNREACH", "ENETUNREACH", "ECONNRESET", "EAI_AGAIN"];
|
|
1216
|
+
const code = connectivity.find((candidate) => codes.includes(candidate));
|
|
1217
|
+
if (code !== void 0) {
|
|
1218
|
+
return { kind: "proxy-unreachable", error: `\u4EE3\u7406\u4E0D\u53EF\u8FBE\uFF08${code}\uFF09\uFF1A${message}` };
|
|
1219
|
+
}
|
|
1220
|
+
if (names.includes("ProxyAuthentication") || message.includes("407")) {
|
|
1221
|
+
return { kind: "proxy-auth", error: `\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09\uFF1A${message}` };
|
|
1222
|
+
}
|
|
1223
|
+
return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
|
|
1224
|
+
};
|
|
1225
|
+
const runSelfCheck = async (provider) => {
|
|
1226
|
+
const route = current.get(provider);
|
|
1227
|
+
if (route === void 0) {
|
|
1228
|
+
return { ok: false, kind: "no-route", error: `provider "${provider}" \u672A\u542F\u7528\u4EE3\u7406` };
|
|
1229
|
+
}
|
|
1230
|
+
const started = performance.now();
|
|
1231
|
+
try {
|
|
1232
|
+
const response = await undiciFetch2(plainTestUrl(), {
|
|
1233
|
+
dispatcher: route.dispatcher,
|
|
1234
|
+
signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
|
|
1235
|
+
});
|
|
1236
|
+
const ms = Math.round(performance.now() - started);
|
|
1237
|
+
if (response.status === 407) {
|
|
1238
|
+
return { ok: false, ms, status: 407, kind: "proxy-auth", error: "\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09" };
|
|
1239
|
+
}
|
|
1240
|
+
let exitIp;
|
|
1241
|
+
try {
|
|
1242
|
+
const body = await response.json();
|
|
1243
|
+
if (typeof body?.ip === "string" && body.ip.length > 0) exitIp = body.ip;
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
ok: response.ok === true,
|
|
1248
|
+
ms,
|
|
1249
|
+
status: response.status,
|
|
1250
|
+
...exitIp === void 0 ? {} : { exitIp },
|
|
1251
|
+
kind: response.ok === true ? "ok" : "target"
|
|
1252
|
+
};
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
return { ok: false, ms: Math.round(performance.now() - started), ...classifyTestError(error) };
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1096
1257
|
try {
|
|
1097
1258
|
ctx.logger.info("dsh-provider-proxy mounted (pid %d)", process.pid);
|
|
1098
1259
|
} catch {
|
|
1099
1260
|
}
|
|
1100
1261
|
const rebuild = async () => {
|
|
1101
|
-
const next =
|
|
1262
|
+
const next = createProxyTable(plainProviders(config));
|
|
1102
1263
|
const previous = current;
|
|
1103
1264
|
current = next.entries;
|
|
1104
1265
|
problems = next.problems;
|
|
@@ -1132,6 +1293,9 @@ function apply(ctx, config) {
|
|
|
1132
1293
|
}
|
|
1133
1294
|
} catch {
|
|
1134
1295
|
}
|
|
1296
|
+
if (webAvailable && out.every((row) => row.id !== WEB_SEARCH_KEY)) {
|
|
1297
|
+
out.push({ id: WEB_SEARCH_KEY, name: "Web \u641C\u7D22", live: true });
|
|
1298
|
+
}
|
|
1135
1299
|
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
1136
1300
|
return { providers: out };
|
|
1137
1301
|
};
|
|
@@ -1142,7 +1306,11 @@ function apply(ctx, config) {
|
|
|
1142
1306
|
path: "/dsh-provider-proxy/providers",
|
|
1143
1307
|
handler: (_req, res) => {
|
|
1144
1308
|
try {
|
|
1145
|
-
const text = JSON.stringify({
|
|
1309
|
+
const text = JSON.stringify({
|
|
1310
|
+
...catalog(),
|
|
1311
|
+
problems: Object.fromEntries(problems),
|
|
1312
|
+
hits: Object.fromEntries(hits)
|
|
1313
|
+
});
|
|
1146
1314
|
res.writeHead(200, {
|
|
1147
1315
|
"content-type": "application/json; charset=utf-8",
|
|
1148
1316
|
"cache-control": "no-store",
|
|
@@ -1155,10 +1323,87 @@ function apply(ctx, config) {
|
|
|
1155
1323
|
}
|
|
1156
1324
|
}
|
|
1157
1325
|
});
|
|
1326
|
+
wctx?.webServer?.register?.({
|
|
1327
|
+
kind: "exact",
|
|
1328
|
+
path: "/dsh-provider-proxy/test",
|
|
1329
|
+
handler: (req, res) => {
|
|
1330
|
+
const send = (status, payload) => {
|
|
1331
|
+
const text = JSON.stringify(payload);
|
|
1332
|
+
res.writeHead(status, {
|
|
1333
|
+
"content-type": "application/json; charset=utf-8",
|
|
1334
|
+
"cache-control": "no-store",
|
|
1335
|
+
"content-length": Buffer.byteLength(text)
|
|
1336
|
+
});
|
|
1337
|
+
res.end(text);
|
|
1338
|
+
};
|
|
1339
|
+
if (req?.method !== "POST") {
|
|
1340
|
+
send(405, { ok: false, kind: "method", error: "\u81EA\u68C0\u7AEF\u70B9\u53EA\u63A5\u53D7 POST" });
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof req.on !== "function") {
|
|
1344
|
+
send(400, { ok: false, kind: "body", error: "\u65E0\u6CD5\u8BFB\u53D6\u8BF7\u6C42\u4F53" });
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const chunks = [];
|
|
1348
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
1349
|
+
req.on("error", () => send(400, { ok: false, kind: "body", error: "\u8BFB\u53D6\u8BF7\u6C42\u4F53\u5931\u8D25" }));
|
|
1350
|
+
req.on("end", () => {
|
|
1351
|
+
void (async () => {
|
|
1352
|
+
let provider;
|
|
1353
|
+
try {
|
|
1354
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
1355
|
+
provider = parsed?.provider;
|
|
1356
|
+
} catch {
|
|
1357
|
+
send(400, { ok: false, kind: "body", error: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5 JSON" });
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
if (typeof provider !== "string" || provider.length === 0) {
|
|
1361
|
+
send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
let pending = selfChecks.get(provider);
|
|
1365
|
+
if (pending === void 0) {
|
|
1366
|
+
pending = runSelfCheck(provider);
|
|
1367
|
+
selfChecks.set(provider, pending);
|
|
1368
|
+
void pending.finally(() => {
|
|
1369
|
+
if (selfChecks.get(provider) === pending) selfChecks.delete(provider);
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
send(200, await pending);
|
|
1373
|
+
})().catch((error) => {
|
|
1374
|
+
send(500, { ok: false, kind: "internal", error: String(error) });
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1158
1379
|
} catch (error) {
|
|
1159
1380
|
void error;
|
|
1160
1381
|
}
|
|
1161
1382
|
});
|
|
1383
|
+
ctx.inject?.(["web"], (wctx) => {
|
|
1384
|
+
const web = wctx?.web;
|
|
1385
|
+
if (web === void 0 || typeof web.search !== "function") return;
|
|
1386
|
+
webAvailable = true;
|
|
1387
|
+
const original = web.search;
|
|
1388
|
+
const patched = function(request, signal) {
|
|
1389
|
+
const route = current.get(WEB_SEARCH_KEY);
|
|
1390
|
+
if (route === void 0) return original.call(this, request, signal);
|
|
1391
|
+
return router.run(
|
|
1392
|
+
{
|
|
1393
|
+
dispatcher: route.dispatcher,
|
|
1394
|
+
provider: WEB_SEARCH_KEY,
|
|
1395
|
+
onHit: () => bump(WEB_SEARCH_KEY),
|
|
1396
|
+
decide: route.decide
|
|
1397
|
+
},
|
|
1398
|
+
() => original.call(this, request, signal)
|
|
1399
|
+
);
|
|
1400
|
+
};
|
|
1401
|
+
web.search = patched;
|
|
1402
|
+
const registerEffect = typeof wctx?.effect === "function" ? wctx.effect.bind(wctx) : ctx.effect?.bind(ctx);
|
|
1403
|
+
registerEffect?.(() => () => {
|
|
1404
|
+
if (web.search === patched) web.search = original;
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1162
1407
|
ctx.effect(() => {
|
|
1163
1408
|
const stop = ctx.on(
|
|
1164
1409
|
"llm/stream",
|
|
@@ -1167,7 +1412,15 @@ function apply(ctx, config) {
|
|
|
1167
1412
|
if (typeof provider !== "string") return next();
|
|
1168
1413
|
const route = current.get(provider);
|
|
1169
1414
|
if (route === void 0) return next();
|
|
1170
|
-
return router.wrap(
|
|
1415
|
+
return router.wrap(
|
|
1416
|
+
{
|
|
1417
|
+
dispatcher: route.dispatcher,
|
|
1418
|
+
provider,
|
|
1419
|
+
onHit: () => bump(provider),
|
|
1420
|
+
decide: route.decide
|
|
1421
|
+
},
|
|
1422
|
+
next
|
|
1423
|
+
);
|
|
1171
1424
|
},
|
|
1172
1425
|
{ global: true }
|
|
1173
1426
|
);
|
|
@@ -1192,7 +1445,8 @@ function apply(ctx, config) {
|
|
|
1192
1445
|
provider: entry.provider,
|
|
1193
1446
|
proxyOrigin: entry.proxyOrigin
|
|
1194
1447
|
})),
|
|
1195
|
-
problems: Object.fromEntries(problems)
|
|
1448
|
+
problems: Object.fromEntries(problems),
|
|
1449
|
+
hits: Object.fromEntries(hits)
|
|
1196
1450
|
})
|
|
1197
1451
|
});
|
|
1198
1452
|
}
|
|
@@ -1200,6 +1454,7 @@ export {
|
|
|
1200
1454
|
Config,
|
|
1201
1455
|
PROTOCOLS,
|
|
1202
1456
|
ProviderProxyRoute,
|
|
1457
|
+
WEB_SEARCH_KEY,
|
|
1203
1458
|
acquireFetchRouter,
|
|
1204
1459
|
apply,
|
|
1205
1460
|
closeTable,
|