dsh-provider-proxy 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -7
- package/lib/client.js +752 -93
- package/lib/client.js.map +4 -4
- package/lib/index.js +407 -91
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -44,10 +44,18 @@ function installSharedRouter() {
|
|
|
44
44
|
return originalFetch.call(globalThis, input, init);
|
|
45
45
|
}
|
|
46
46
|
active.onHit?.();
|
|
47
|
-
|
|
47
|
+
const inFlight = undiciFetch(input, {
|
|
48
48
|
...init,
|
|
49
49
|
dispatcher: active.dispatcher
|
|
50
50
|
});
|
|
51
|
+
if (active.onSettle !== void 0) {
|
|
52
|
+
const settle = active.onSettle;
|
|
53
|
+
void inFlight.then(
|
|
54
|
+
(response) => settle(response.status < 500, response),
|
|
55
|
+
(error) => settle(false, error)
|
|
56
|
+
).catch(() => void 0);
|
|
57
|
+
}
|
|
58
|
+
return inFlight;
|
|
51
59
|
};
|
|
52
60
|
Object.defineProperty(globalThis, "fetch", {
|
|
53
61
|
...descriptor ?? { configurable: true, enumerable: true, writable: true },
|
|
@@ -126,6 +134,42 @@ import { ProxyAgent, Socks5ProxyAgent } from "undici";
|
|
|
126
134
|
// src/config.ts
|
|
127
135
|
import { createRequire } from "node:module";
|
|
128
136
|
|
|
137
|
+
// src/proxy-address.ts
|
|
138
|
+
var PROXY_HOST_INVALID = "\u4EE3\u7406\u5730\u5740\u53EA\u80FD\u586B IPv4\u3001IPv6 \u6216\u57DF\u540D";
|
|
139
|
+
var IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
|
|
140
|
+
var DOMAIN = /^(?=.*[a-z])(?=.{1,253}$)(?!.*\.\d+$)[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?)*$/i;
|
|
141
|
+
var IPV6_CHARS = /^[0-9a-f:.]+$/i;
|
|
142
|
+
function proxyHostProblem(rawHost) {
|
|
143
|
+
const host = (rawHost ?? "").trim().replace(/\.$/, "");
|
|
144
|
+
if (host.length === 0) return "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740";
|
|
145
|
+
if (IPV4.test(host)) return void 0;
|
|
146
|
+
if (host.includes(":") || host.startsWith("[")) {
|
|
147
|
+
const literal = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
148
|
+
if (!IPV6_CHARS.test(literal)) return PROXY_HOST_INVALID;
|
|
149
|
+
try {
|
|
150
|
+
new URL(`http://[${literal}]:1`);
|
|
151
|
+
return void 0;
|
|
152
|
+
} catch {
|
|
153
|
+
return PROXY_HOST_INVALID;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return DOMAIN.test(host) ? void 0 : PROXY_HOST_INVALID;
|
|
157
|
+
}
|
|
158
|
+
function defaultProxyProblem(endpoint) {
|
|
159
|
+
const hostProblem = proxyHostProblem(endpoint.host);
|
|
160
|
+
if (hostProblem !== void 0) return hostProblem;
|
|
161
|
+
const port = endpoint.port ?? 0;
|
|
162
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
163
|
+
return "\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09";
|
|
164
|
+
}
|
|
165
|
+
const hasUser = (endpoint.username ?? "").trim().length > 0;
|
|
166
|
+
const hasPassword = (endpoint.password ?? "").length > 0;
|
|
167
|
+
if (hasUser !== hasPassword) {
|
|
168
|
+
return hasUser ? "\u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D" : "\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";
|
|
169
|
+
}
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
129
173
|
// node_modules/@deepseek-ai/schemastery/lib/index.mjs
|
|
130
174
|
var lib_exports = {};
|
|
131
175
|
__export(lib_exports, {
|
|
@@ -1017,9 +1061,27 @@ function vol(field) {
|
|
|
1017
1061
|
return typeof volatile2 === "function" ? volatile2.call(field) : field;
|
|
1018
1062
|
}
|
|
1019
1063
|
var PROTOCOLS = ["http", "https", "socks5"];
|
|
1064
|
+
var DefaultProxy = z.object({
|
|
1065
|
+
protocol: z.union(PROTOCOLS).default("http"),
|
|
1066
|
+
/** Proxy host or IP, without scheme, port, or path. */
|
|
1067
|
+
host: z.string().default(""),
|
|
1068
|
+
/** Proxy port. 0 means "not configured". */
|
|
1069
|
+
port: z.number().default(0),
|
|
1070
|
+
username: z.string().default(""),
|
|
1071
|
+
password: z.string().default("")
|
|
1072
|
+
});
|
|
1020
1073
|
var ProviderProxyRoute = z.object({
|
|
1021
1074
|
/** Whether this provider's calls are routed through the proxy below. */
|
|
1022
1075
|
enabled: z.boolean().default(false),
|
|
1076
|
+
/**
|
|
1077
|
+
* When true, this route's TRANSPORT fields (protocol/host/port/username/
|
|
1078
|
+
* password) come from the top-level `defaultProxy` instead of its own, so the
|
|
1079
|
+
* same endpoint is configured once for every provider that shares it. The
|
|
1080
|
+
* target-host rules and the self-check target stay the route's own. There is
|
|
1081
|
+
* no partial inheritance: to use a different endpoint, turn this off and fill
|
|
1082
|
+
* the fields in.
|
|
1083
|
+
*/
|
|
1084
|
+
useDefault: z.boolean().default(false),
|
|
1023
1085
|
/** Proxy transport. `socks5` is accepted but only honoured when the socks helper loaded. */
|
|
1024
1086
|
protocol: z.union(PROTOCOLS).default("http"),
|
|
1025
1087
|
/** Proxy host or IP, without scheme, port, or path. */
|
|
@@ -1036,11 +1098,31 @@ var ProviderProxyRoute = z.object({
|
|
|
1036
1098
|
*/
|
|
1037
1099
|
onlyHosts: z.string().default(""),
|
|
1038
1100
|
/** Comma/space-separated TARGET hosts that bypass the proxy; takes priority over `onlyHosts`. */
|
|
1039
|
-
bypassHosts: z.string().default("")
|
|
1101
|
+
bypassHosts: z.string().default(""),
|
|
1102
|
+
/**
|
|
1103
|
+
* Optional self-check target for THIS provider's 测试 button. Empty falls back
|
|
1104
|
+
* to the top-level `testUrl`. Only the self-check reads it; routing never
|
|
1105
|
+
* touches it, so a bad value costs a failed check, never a broken route.
|
|
1106
|
+
*/
|
|
1107
|
+
testTarget: z.string().default("")
|
|
1040
1108
|
});
|
|
1041
1109
|
var Config = z.object({
|
|
1042
1110
|
/** Per-provider proxy routes, keyed by provider route id (e.g. `sensenova`). */
|
|
1043
1111
|
providers: vol(z.dict(ProviderProxyRoute).default({})),
|
|
1112
|
+
/**
|
|
1113
|
+
* Shared endpoint for every route that sets `useDefault`. Volatile for the
|
|
1114
|
+
* same reason as `providers`: a settings write is committed into the running
|
|
1115
|
+
* fiber instead of remounting the plugin.
|
|
1116
|
+
*/
|
|
1117
|
+
defaultProxy: vol(DefaultProxy.default({})),
|
|
1118
|
+
/**
|
|
1119
|
+
* Whether a loopback/private-address target is sent DIRECT instead of through
|
|
1120
|
+
* the proxy. On by default: such a target is normally a relay on this very
|
|
1121
|
+
* machine (LiteLLM, Ollama, a local mock), which a forward proxy cannot reach
|
|
1122
|
+
* at all — forcing it through one fails, and the failure reads as "the proxy
|
|
1123
|
+
* is broken". Turn this off only when the proxy itself is the target.
|
|
1124
|
+
*/
|
|
1125
|
+
localDirect: vol(z.boolean().default(true)),
|
|
1044
1126
|
/**
|
|
1045
1127
|
* URL the per-provider connection self-check requests THROUGH the proxy.
|
|
1046
1128
|
* Volatile, so a settings write does not remount the plugin; the default is an
|
|
@@ -1048,25 +1130,41 @@ var Config = z.object({
|
|
|
1048
1130
|
*/
|
|
1049
1131
|
testUrl: vol(z.string().default("https://api.ipify.org?format=json"))
|
|
1050
1132
|
});
|
|
1051
|
-
function
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1133
|
+
function plain(parsed) {
|
|
1134
|
+
const get = parsed.get;
|
|
1135
|
+
return typeof get === "function" ? get.call(parsed) : parsed;
|
|
1136
|
+
}
|
|
1137
|
+
function normalizeDefaultProxy(raw) {
|
|
1138
|
+
const value = plain(DefaultProxy(raw ?? {}));
|
|
1139
|
+
return {
|
|
1140
|
+
protocol: value.protocol,
|
|
1141
|
+
host: value.host.trim(),
|
|
1142
|
+
port: value.port,
|
|
1143
|
+
username: value.username.trim(),
|
|
1144
|
+
password: value.password
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function normalizeRoute(provider, raw, options) {
|
|
1148
|
+
const value = plain(ProviderProxyRoute(raw ?? {}));
|
|
1054
1149
|
const onlyHosts = value.onlyHosts.trim().toLowerCase();
|
|
1055
1150
|
const bypassHosts = value.bypassHosts.trim().toLowerCase();
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
throw new TypeError(`dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740`);
|
|
1151
|
+
const testTarget = value.testTarget.trim();
|
|
1152
|
+
if (!value.enabled && options?.requireEnabled !== false) {
|
|
1153
|
+
return { ...value, port: value.port || 0, onlyHosts, bypassHosts, testTarget };
|
|
1060
1154
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1155
|
+
const host = value.host.trim();
|
|
1156
|
+
const hostProblem = proxyHostProblem(host);
|
|
1157
|
+
if (hostProblem !== void 0) {
|
|
1158
|
+
throw new TypeError(
|
|
1159
|
+
hostProblem === "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740" ? `dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740` : `dsh-provider-proxy: provider "${provider}" \u7684${hostProblem}`
|
|
1160
|
+
);
|
|
1063
1161
|
}
|
|
1064
1162
|
if (!Number.isInteger(value.port) || value.port < 1 || value.port > 65535) {
|
|
1065
1163
|
throw new TypeError(
|
|
1066
1164
|
`dsh-provider-proxy: provider "${provider}" \u7684\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09`
|
|
1067
1165
|
);
|
|
1068
1166
|
}
|
|
1069
|
-
return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts };
|
|
1167
|
+
return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts, testTarget };
|
|
1070
1168
|
}
|
|
1071
1169
|
|
|
1072
1170
|
// src/proxy-table.ts
|
|
@@ -1099,70 +1197,115 @@ function hostMatches(host, rules) {
|
|
|
1099
1197
|
}
|
|
1100
1198
|
return false;
|
|
1101
1199
|
}
|
|
1102
|
-
function
|
|
1200
|
+
function isLocalHost(host) {
|
|
1201
|
+
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
1202
|
+
if (bare === "localhost" || bare.endsWith(".localhost")) return true;
|
|
1203
|
+
if (bare === "::1") return true;
|
|
1204
|
+
const octets = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(bare);
|
|
1205
|
+
if (octets === null) return false;
|
|
1206
|
+
const [a, b, c, d] = octets.slice(1).map(Number);
|
|
1207
|
+
if (a > 255 || b > 255 || c > 255 || d > 255) return false;
|
|
1208
|
+
if (a === 127) return true;
|
|
1209
|
+
if (a === 10) return true;
|
|
1210
|
+
if (a === 192 && b === 168) return true;
|
|
1211
|
+
if (a === 169 && b === 254) return true;
|
|
1212
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
function buildDecide(route, localDirect) {
|
|
1103
1216
|
const only = parseHostList(route.onlyHosts);
|
|
1104
1217
|
const bypass = parseHostList(route.bypassHosts);
|
|
1105
1218
|
return (host) => {
|
|
1106
1219
|
if (host === void 0) return "proxy";
|
|
1220
|
+
if (localDirect && isLocalHost(host)) return "direct";
|
|
1107
1221
|
if (hostMatches(host, bypass)) return "direct";
|
|
1108
1222
|
if (only.length > 0 && !hostMatches(host, only)) return "direct";
|
|
1109
1223
|
return "proxy";
|
|
1110
1224
|
};
|
|
1111
1225
|
}
|
|
1112
|
-
function
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (route.protocol === "socks5" && route.password.length > 0 && route.username.length === 0) {
|
|
1132
|
-
problems.set(
|
|
1133
|
-
provider,
|
|
1134
|
-
"dsh-provider-proxy: SOCKS5 \u914D\u7F6E\u4E86\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801"
|
|
1135
|
-
);
|
|
1136
|
-
continue;
|
|
1137
|
-
}
|
|
1138
|
-
if (route.protocol !== "socks5" && route.password.length > 0 && route.username.length === 0) {
|
|
1139
|
-
problems.set(
|
|
1140
|
-
provider,
|
|
1141
|
-
"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"
|
|
1142
|
-
);
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
if (route.protocol !== "socks5" && route.username.length > 0 && route.password.length === 0) {
|
|
1146
|
-
problems.set(
|
|
1147
|
-
provider,
|
|
1148
|
-
"dsh-provider-proxy: \u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D"
|
|
1149
|
-
);
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
const url = proxyUrl(route);
|
|
1153
|
-
let dispatcher;
|
|
1154
|
-
try {
|
|
1155
|
-
dispatcher = createDispatcher(route, url, route.password);
|
|
1156
|
-
} catch (error) {
|
|
1157
|
-
problems.set(provider, error instanceof Error ? error.message : String(error));
|
|
1158
|
-
continue;
|
|
1226
|
+
function withDefaultProxy(route, defaultProxy) {
|
|
1227
|
+
return {
|
|
1228
|
+
...route,
|
|
1229
|
+
protocol: defaultProxy.protocol,
|
|
1230
|
+
host: defaultProxy.host,
|
|
1231
|
+
port: defaultProxy.port,
|
|
1232
|
+
username: defaultProxy.username,
|
|
1233
|
+
password: defaultProxy.password
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
function buildRouteEntry(provider, raw, options) {
|
|
1237
|
+
let effective = raw;
|
|
1238
|
+
if (raw?.useDefault === true) {
|
|
1239
|
+
const why = defaultProxyProblem(options.defaultProxy);
|
|
1240
|
+
if (why !== void 0) {
|
|
1241
|
+
return {
|
|
1242
|
+
ok: false,
|
|
1243
|
+
problem: `dsh-provider-proxy: provider "${provider}" \u542F\u7528\u4E86\u300C\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406\u300D\uFF0C\u4F46\u9876\u5C42\u9ED8\u8BA4\u4EE3\u7406\u4E0D\u53EF\u7528\uFF1A${why}`
|
|
1244
|
+
};
|
|
1159
1245
|
}
|
|
1160
|
-
|
|
1246
|
+
effective = withDefaultProxy(raw, options.defaultProxy);
|
|
1247
|
+
}
|
|
1248
|
+
let route;
|
|
1249
|
+
try {
|
|
1250
|
+
route = normalizeRoute(provider, effective, { requireEnabled: options.requireEnabled });
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
return { ok: false, problem: error instanceof Error ? error.message : String(error) };
|
|
1253
|
+
}
|
|
1254
|
+
if (route.protocol === "socks5" && route.username.length > 0 && route.password.length === 0) {
|
|
1255
|
+
return {
|
|
1256
|
+
ok: false,
|
|
1257
|
+
problem: "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"
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (route.protocol === "socks5" && route.password.length > 0 && route.username.length === 0) {
|
|
1261
|
+
return {
|
|
1262
|
+
ok: false,
|
|
1263
|
+
problem: "dsh-provider-proxy: SOCKS5 \u914D\u7F6E\u4E86\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801"
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
if (route.protocol !== "socks5" && route.password.length > 0 && route.username.length === 0) {
|
|
1267
|
+
return {
|
|
1268
|
+
ok: false,
|
|
1269
|
+
problem: "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"
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
if (route.protocol !== "socks5" && route.username.length > 0 && route.password.length === 0) {
|
|
1273
|
+
return {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
problem: "dsh-provider-proxy: \u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D"
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
const url = proxyUrl(route);
|
|
1279
|
+
let dispatcher;
|
|
1280
|
+
try {
|
|
1281
|
+
dispatcher = createDispatcher(route, url, route.password);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
return { ok: false, problem: error instanceof Error ? error.message : String(error) };
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
ok: true,
|
|
1287
|
+
entry: {
|
|
1161
1288
|
provider,
|
|
1162
1289
|
proxyOrigin: displayOrigin(route, url),
|
|
1163
1290
|
dispatcher,
|
|
1164
|
-
decide: buildDecide(route)
|
|
1165
|
-
|
|
1291
|
+
decide: buildDecide(route, options.localDirect),
|
|
1292
|
+
testTarget: route.testTarget
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function createProxyTable(config, options = {}) {
|
|
1297
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1298
|
+
const problems = /* @__PURE__ */ new Map();
|
|
1299
|
+
const buildOptions = {
|
|
1300
|
+
defaultProxy: normalizeDefaultProxy(options.defaultProxy),
|
|
1301
|
+
requireEnabled: true,
|
|
1302
|
+
localDirect: options.localDirect !== false
|
|
1303
|
+
};
|
|
1304
|
+
for (const [provider, raw] of Object.entries(config ?? {})) {
|
|
1305
|
+
if (raw?.enabled !== true) continue;
|
|
1306
|
+
const built = buildRouteEntry(provider, raw, buildOptions);
|
|
1307
|
+
if (!built.ok) problems.set(provider, built.problem);
|
|
1308
|
+
else entries.set(provider, built.entry);
|
|
1166
1309
|
}
|
|
1167
1310
|
return { entries, problems };
|
|
1168
1311
|
}
|
|
@@ -1183,6 +1326,20 @@ function plainProviders(config) {
|
|
|
1183
1326
|
const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
|
|
1184
1327
|
return value ?? {};
|
|
1185
1328
|
}
|
|
1329
|
+
function plainDefaultProxy(config) {
|
|
1330
|
+
const raw = config?.defaultProxy;
|
|
1331
|
+
if (raw === void 0) return {};
|
|
1332
|
+
const snapshot2 = raw.get;
|
|
1333
|
+
const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
|
|
1334
|
+
return value ?? {};
|
|
1335
|
+
}
|
|
1336
|
+
function plainLocalDirect(config) {
|
|
1337
|
+
const raw = config?.localDirect;
|
|
1338
|
+
if (raw === void 0) return true;
|
|
1339
|
+
const snapshot2 = raw.get;
|
|
1340
|
+
const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
|
|
1341
|
+
return value !== false;
|
|
1342
|
+
}
|
|
1186
1343
|
function apply(ctx, config) {
|
|
1187
1344
|
const router = acquireFetchRouter();
|
|
1188
1345
|
let current = /* @__PURE__ */ new Map();
|
|
@@ -1190,18 +1347,67 @@ function apply(ctx, config) {
|
|
|
1190
1347
|
let closed = false;
|
|
1191
1348
|
let webAvailable = false;
|
|
1192
1349
|
const hits = /* @__PURE__ */ new Map();
|
|
1350
|
+
const lastUse = /* @__PURE__ */ new Map();
|
|
1193
1351
|
const bump = (provider) => {
|
|
1194
1352
|
hits.set(provider, (hits.get(provider) ?? 0) + 1);
|
|
1353
|
+
lastUse.set(provider, { ...lastUse.get(provider), startedAt: Date.now() });
|
|
1354
|
+
};
|
|
1355
|
+
const settle = (provider) => (ok, detail) => {
|
|
1356
|
+
const previous = lastUse.get(provider);
|
|
1357
|
+
lastUse.set(provider, {
|
|
1358
|
+
startedAt: previous?.startedAt ?? Date.now(),
|
|
1359
|
+
// `at` is the FINISH time, written together with the outcome it belongs to.
|
|
1360
|
+
at: Date.now(),
|
|
1361
|
+
ok,
|
|
1362
|
+
code: ok ? String(detail?.status ?? "") : failureCodeOf(detail)
|
|
1363
|
+
});
|
|
1195
1364
|
};
|
|
1196
1365
|
const DEFAULT_TEST_URL = "https://api.ipify.org?format=json";
|
|
1366
|
+
const MAX_TEST_TARGETS = 5;
|
|
1197
1367
|
const TEST_TIMEOUT_MS = 1e4;
|
|
1198
1368
|
const MAX_REQUEST_BYTES = 8 * 1024;
|
|
1199
1369
|
const selfChecks = /* @__PURE__ */ new Map();
|
|
1200
|
-
const
|
|
1370
|
+
const SELF_CHECK_COOLDOWN_MS = 3e3;
|
|
1371
|
+
const lastChecks = /* @__PURE__ */ new Map();
|
|
1372
|
+
const plainTestUrls = () => {
|
|
1201
1373
|
const raw = config?.testUrl;
|
|
1202
1374
|
const snapshot2 = raw?.get;
|
|
1203
1375
|
const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
|
|
1204
|
-
|
|
1376
|
+
const list = typeof value === "string" ? value.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0) : [];
|
|
1377
|
+
return list.length > 0 ? list.slice(0, MAX_TEST_TARGETS) : [DEFAULT_TEST_URL];
|
|
1378
|
+
};
|
|
1379
|
+
const walk = (value, path) => {
|
|
1380
|
+
let node = value;
|
|
1381
|
+
for (const key of path) {
|
|
1382
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
1383
|
+
node = node[key];
|
|
1384
|
+
}
|
|
1385
|
+
return node;
|
|
1386
|
+
};
|
|
1387
|
+
const upstreamOf = (provider) => {
|
|
1388
|
+
let settings;
|
|
1389
|
+
try {
|
|
1390
|
+
settings = typeof ctx?.get === "function" ? ctx.get("settings") : void 0;
|
|
1391
|
+
} catch {
|
|
1392
|
+
return void 0;
|
|
1393
|
+
}
|
|
1394
|
+
if (settings === void 0 || typeof settings.get !== "function") return void 0;
|
|
1395
|
+
let entry;
|
|
1396
|
+
try {
|
|
1397
|
+
const entries = ctx.llm?.listConfigurableProviders?.() ?? [];
|
|
1398
|
+
entry = entries.find((candidate) => candidate?.provider === provider);
|
|
1399
|
+
} catch {
|
|
1400
|
+
return void 0;
|
|
1401
|
+
}
|
|
1402
|
+
if (entry === void 0) return void 0;
|
|
1403
|
+
let profile;
|
|
1404
|
+
try {
|
|
1405
|
+
profile = walk(settings.get(entry.settingsNs), entry.settingsPath ?? []);
|
|
1406
|
+
} catch {
|
|
1407
|
+
return void 0;
|
|
1408
|
+
}
|
|
1409
|
+
const baseURL = profile?.baseURL;
|
|
1410
|
+
return typeof baseURL === "string" && baseURL.trim().length > 0 ? baseURL.trim() : void 0;
|
|
1205
1411
|
};
|
|
1206
1412
|
const bareMessage = (message) => message.replace(/^dsh-provider-proxy:\s*/, "");
|
|
1207
1413
|
const causesOf = (error) => {
|
|
@@ -1220,10 +1426,11 @@ function apply(ctx, config) {
|
|
|
1220
1426
|
}
|
|
1221
1427
|
return out;
|
|
1222
1428
|
};
|
|
1223
|
-
const classifyTestError = (error) => {
|
|
1429
|
+
const classifyTestError = (error, viaProxy) => {
|
|
1224
1430
|
const chain = causesOf(error);
|
|
1225
1431
|
const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
|
|
1226
1432
|
const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
|
|
1433
|
+
const messages = chain.map((node) => node.message).filter((text) => typeof text === "string");
|
|
1227
1434
|
const message = error instanceof Error ? error.message : String(error);
|
|
1228
1435
|
if (names.includes("TimeoutError") || names.includes("AbortError")) {
|
|
1229
1436
|
return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
|
|
@@ -1231,31 +1438,43 @@ function apply(ctx, config) {
|
|
|
1231
1438
|
const connectivity = ["ECONNREFUSED", "ENOTFOUND", "EHOSTUNREACH", "ENETUNREACH", "ECONNRESET", "EAI_AGAIN"];
|
|
1232
1439
|
const code = connectivity.find((candidate) => codes.includes(candidate));
|
|
1233
1440
|
if (code !== void 0) {
|
|
1234
|
-
return { kind: "proxy-unreachable", error: `\u4EE3\u7406\u4E0D\u53EF\u8FBE\uFF08${code}\uFF09\uFF1A${message}` };
|
|
1441
|
+
return viaProxy ? { kind: "proxy-unreachable", error: `\u4EE3\u7406\u4E0D\u53EF\u8FBE\uFF08${code}\uFF09\uFF1A${message}` } : { kind: "target", error: `\u76EE\u6807\u4E0D\u53EF\u8FBE\uFF08${code}\uFF09\uFF1A${message}` };
|
|
1235
1442
|
}
|
|
1236
|
-
if (names.includes("ProxyAuthentication") ||
|
|
1443
|
+
if (names.includes("ProxyAuthentication") || messages.some((text) => text.includes("407"))) {
|
|
1237
1444
|
return { kind: "proxy-auth", error: `\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09\uFF1A${message}` };
|
|
1238
1445
|
}
|
|
1239
1446
|
return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
|
|
1240
1447
|
};
|
|
1241
|
-
const
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1448
|
+
const failureCodeOf = (error) => {
|
|
1449
|
+
const chain = causesOf(error);
|
|
1450
|
+
const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
|
|
1451
|
+
if (names.includes("TimeoutError")) return "TIMEOUT";
|
|
1452
|
+
if (names.includes("AbortError")) return "ABORTED";
|
|
1453
|
+
for (const node of chain) {
|
|
1454
|
+
const code = node.code;
|
|
1455
|
+
if (typeof code === "string" && code.length > 0) return code;
|
|
1249
1456
|
}
|
|
1457
|
+
return "ERROR";
|
|
1458
|
+
};
|
|
1459
|
+
const probeTarget = async (target, route) => {
|
|
1460
|
+
let host;
|
|
1461
|
+
try {
|
|
1462
|
+
host = new URL(target).hostname;
|
|
1463
|
+
} catch {
|
|
1464
|
+
return { ok: false, kind: "target", error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${target}` };
|
|
1465
|
+
}
|
|
1466
|
+
const viaProxy = route.decide(host) === "proxy";
|
|
1250
1467
|
const started = performance.now();
|
|
1251
1468
|
try {
|
|
1252
|
-
const response = await undiciFetch2(
|
|
1253
|
-
|
|
1469
|
+
const response = await undiciFetch2(target, {
|
|
1470
|
+
// A direct probe must NOT carry the dispatcher, or undici would force it
|
|
1471
|
+
// through the proxy anyway.
|
|
1472
|
+
...viaProxy ? { dispatcher: route.dispatcher } : {},
|
|
1254
1473
|
signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
|
|
1255
1474
|
});
|
|
1256
1475
|
const ms = Math.round(performance.now() - started);
|
|
1257
|
-
if (response.status === 407) {
|
|
1258
|
-
return { ok: false, ms, status: 407, kind: "proxy-auth", error: "\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09" };
|
|
1476
|
+
if (viaProxy && response.status === 407) {
|
|
1477
|
+
return { ok: false, ms, status: 407, kind: "proxy-auth", viaProxy, error: "\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09" };
|
|
1259
1478
|
}
|
|
1260
1479
|
let exitIp;
|
|
1261
1480
|
try {
|
|
@@ -1263,15 +1482,92 @@ function apply(ctx, config) {
|
|
|
1263
1482
|
if (typeof body?.ip === "string" && body.ip.length > 0) exitIp = body.ip;
|
|
1264
1483
|
} catch {
|
|
1265
1484
|
}
|
|
1485
|
+
const reachable = response.status < 500;
|
|
1486
|
+
if (reachable) {
|
|
1487
|
+
return {
|
|
1488
|
+
ok: true,
|
|
1489
|
+
ms,
|
|
1490
|
+
status: response.status,
|
|
1491
|
+
viaProxy,
|
|
1492
|
+
...exitIp === void 0 ? {} : { exitIp },
|
|
1493
|
+
kind: "ok"
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1266
1496
|
return {
|
|
1267
|
-
ok:
|
|
1497
|
+
ok: false,
|
|
1268
1498
|
ms,
|
|
1269
1499
|
status: response.status,
|
|
1270
|
-
|
|
1271
|
-
kind:
|
|
1500
|
+
viaProxy,
|
|
1501
|
+
kind: "target",
|
|
1502
|
+
error: `\u76EE\u6807\u8FD4\u56DE HTTP ${response.status}\uFF085xx\uFF0C\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09`
|
|
1272
1503
|
};
|
|
1273
1504
|
} catch (error) {
|
|
1274
|
-
return {
|
|
1505
|
+
return {
|
|
1506
|
+
ok: false,
|
|
1507
|
+
ms: Math.round(performance.now() - started),
|
|
1508
|
+
viaProxy,
|
|
1509
|
+
...classifyTestError(error, viaProxy)
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
const selfCheckTargets = (provider, testTarget) => {
|
|
1514
|
+
const own = (testTarget ?? "").trim();
|
|
1515
|
+
if (own.length > 0) return [own];
|
|
1516
|
+
const upstream = upstreamOf(provider);
|
|
1517
|
+
if (upstream !== void 0) return [upstream];
|
|
1518
|
+
return plainTestUrls();
|
|
1519
|
+
};
|
|
1520
|
+
const selfCheckKey = (provider) => {
|
|
1521
|
+
const raw = plainProviders(config)[provider] ?? {};
|
|
1522
|
+
const endpoint = raw.useDefault === true ? plainDefaultProxy(config) : raw;
|
|
1523
|
+
return JSON.stringify([
|
|
1524
|
+
provider,
|
|
1525
|
+
endpoint.protocol ?? "http",
|
|
1526
|
+
endpoint.host ?? "",
|
|
1527
|
+
endpoint.port ?? 0,
|
|
1528
|
+
endpoint.username ?? "",
|
|
1529
|
+
endpoint.password ?? "",
|
|
1530
|
+
raw.testTarget ?? "",
|
|
1531
|
+
raw.onlyHosts ?? "",
|
|
1532
|
+
raw.bypassHosts ?? "",
|
|
1533
|
+
plainLocalDirect(config),
|
|
1534
|
+
selfCheckTargets(provider, raw.testTarget)
|
|
1535
|
+
]);
|
|
1536
|
+
};
|
|
1537
|
+
const runSelfCheck = async (provider) => {
|
|
1538
|
+
let route = current.get(provider);
|
|
1539
|
+
let temporary = false;
|
|
1540
|
+
if (route === void 0) {
|
|
1541
|
+
const problem = problems.get(provider);
|
|
1542
|
+
if (problem !== void 0) {
|
|
1543
|
+
return { ok: false, kind: "route-rejected", error: bareMessage(problem) };
|
|
1544
|
+
}
|
|
1545
|
+
const configured = plainProviders(config)[provider];
|
|
1546
|
+
if (configured === void 0 || configured.enabled === true) {
|
|
1547
|
+
return { ok: false, kind: "no-route", error: `provider "${provider}" \u672A\u542F\u7528\u4EE3\u7406` };
|
|
1548
|
+
}
|
|
1549
|
+
const built = buildRouteEntry(provider, configured, {
|
|
1550
|
+
defaultProxy: normalizeDefaultProxy(plainDefaultProxy(config)),
|
|
1551
|
+
requireEnabled: false,
|
|
1552
|
+
localDirect: plainLocalDirect(config)
|
|
1553
|
+
});
|
|
1554
|
+
if (!built.ok) {
|
|
1555
|
+
return { ok: false, kind: "route-rejected", error: bareMessage(built.problem) };
|
|
1556
|
+
}
|
|
1557
|
+
route = built.entry;
|
|
1558
|
+
temporary = true;
|
|
1559
|
+
}
|
|
1560
|
+
try {
|
|
1561
|
+
let last;
|
|
1562
|
+
for (const target of selfCheckTargets(provider, route.testTarget)) {
|
|
1563
|
+
const outcome = await probeTarget(target, route);
|
|
1564
|
+
if (outcome.ok === true) return outcome;
|
|
1565
|
+
last = outcome;
|
|
1566
|
+
if (outcome.kind === "proxy-auth") break;
|
|
1567
|
+
}
|
|
1568
|
+
return last ?? { ok: false, kind: "target", error: "\u6CA1\u6709\u53EF\u7528\u7684\u81EA\u68C0\u76EE\u6807" };
|
|
1569
|
+
} finally {
|
|
1570
|
+
if (temporary) await route.dispatcher.close().catch(() => void 0);
|
|
1275
1571
|
}
|
|
1276
1572
|
};
|
|
1277
1573
|
try {
|
|
@@ -1279,7 +1575,10 @@ function apply(ctx, config) {
|
|
|
1279
1575
|
} catch {
|
|
1280
1576
|
}
|
|
1281
1577
|
const rebuild = async () => {
|
|
1282
|
-
const next = createProxyTable(plainProviders(config)
|
|
1578
|
+
const next = createProxyTable(plainProviders(config), {
|
|
1579
|
+
defaultProxy: plainDefaultProxy(config),
|
|
1580
|
+
localDirect: plainLocalDirect(config)
|
|
1581
|
+
});
|
|
1283
1582
|
const previous = current;
|
|
1284
1583
|
current = next.entries;
|
|
1285
1584
|
problems = next.problems;
|
|
@@ -1331,7 +1630,8 @@ function apply(ctx, config) {
|
|
|
1331
1630
|
problems: Object.fromEntries(
|
|
1332
1631
|
[...problems].map(([id, message]) => [id, bareMessage(message)])
|
|
1333
1632
|
),
|
|
1334
|
-
hits: Object.fromEntries(hits)
|
|
1633
|
+
hits: Object.fromEntries(hits),
|
|
1634
|
+
lastUse: Object.fromEntries(lastUse)
|
|
1335
1635
|
});
|
|
1336
1636
|
res.writeHead(200, {
|
|
1337
1637
|
"content-type": "application/json; charset=utf-8",
|
|
@@ -1400,12 +1700,21 @@ function apply(ctx, config) {
|
|
|
1400
1700
|
send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
|
|
1401
1701
|
return;
|
|
1402
1702
|
}
|
|
1403
|
-
|
|
1703
|
+
const key = selfCheckKey(provider);
|
|
1704
|
+
let pending = selfChecks.get(key);
|
|
1404
1705
|
if (pending === void 0) {
|
|
1405
|
-
|
|
1406
|
-
|
|
1706
|
+
const last = lastChecks.get(key);
|
|
1707
|
+
if (last !== void 0 && Date.now() - last.at < SELF_CHECK_COOLDOWN_MS) {
|
|
1708
|
+
send(200, { ...last.result, cached: true });
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
pending = runSelfCheck(provider).then((result) => {
|
|
1712
|
+
lastChecks.set(key, { at: Date.now(), result });
|
|
1713
|
+
return result;
|
|
1714
|
+
});
|
|
1715
|
+
selfChecks.set(key, pending);
|
|
1407
1716
|
void pending.finally(() => {
|
|
1408
|
-
if (selfChecks.get(
|
|
1717
|
+
if (selfChecks.get(key) === pending) selfChecks.delete(key);
|
|
1409
1718
|
});
|
|
1410
1719
|
}
|
|
1411
1720
|
send(200, await pending);
|
|
@@ -1431,6 +1740,7 @@ function apply(ctx, config) {
|
|
|
1431
1740
|
{
|
|
1432
1741
|
dispatcher: route.dispatcher,
|
|
1433
1742
|
onHit: () => bump(WEB_SEARCH_KEY),
|
|
1743
|
+
onSettle: settle(WEB_SEARCH_KEY),
|
|
1434
1744
|
decide: route.decide
|
|
1435
1745
|
},
|
|
1436
1746
|
() => original.call(this, request, signal)
|
|
@@ -1454,6 +1764,7 @@ function apply(ctx, config) {
|
|
|
1454
1764
|
{
|
|
1455
1765
|
dispatcher: route.dispatcher,
|
|
1456
1766
|
onHit: () => bump(provider),
|
|
1767
|
+
onSettle: settle(provider),
|
|
1457
1768
|
decide: route.decide
|
|
1458
1769
|
},
|
|
1459
1770
|
next
|
|
@@ -1483,7 +1794,8 @@ function apply(ctx, config) {
|
|
|
1483
1794
|
proxyOrigin: entry.proxyOrigin
|
|
1484
1795
|
})),
|
|
1485
1796
|
problems: Object.fromEntries(problems),
|
|
1486
|
-
hits: Object.fromEntries(hits)
|
|
1797
|
+
hits: Object.fromEntries(hits),
|
|
1798
|
+
lastUse: Object.fromEntries(lastUse)
|
|
1487
1799
|
})
|
|
1488
1800
|
});
|
|
1489
1801
|
}
|
|
@@ -1494,11 +1806,15 @@ export {
|
|
|
1494
1806
|
WEB_SEARCH_KEY,
|
|
1495
1807
|
acquireFetchRouter,
|
|
1496
1808
|
apply,
|
|
1809
|
+
buildRouteEntry,
|
|
1497
1810
|
closeTable,
|
|
1498
1811
|
createProxyTable,
|
|
1812
|
+
defaultProxyProblem,
|
|
1499
1813
|
inject,
|
|
1500
1814
|
name,
|
|
1815
|
+
normalizeDefaultProxy,
|
|
1501
1816
|
normalizeRoute,
|
|
1817
|
+
proxyHostProblem,
|
|
1502
1818
|
proxyUrl
|
|
1503
1819
|
};
|
|
1504
1820
|
//# sourceMappingURL=index.js.map
|