dsh-provider-proxy 0.7.0 → 0.9.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/lib/index.js CHANGED
@@ -1073,6 +1073,15 @@ var DefaultProxy = z.object({
1073
1073
  var ProviderProxyRoute = z.object({
1074
1074
  /** Whether this provider's calls are routed through the proxy below. */
1075
1075
  enabled: z.boolean().default(false),
1076
+ /**
1077
+ * A named preset from the top-level `proxyProfiles` dictionary.
1078
+ *
1079
+ * Non-empty wins over `useDefault`: the route's TRANSPORT fields come from
1080
+ * that preset. Empty (the default) keeps the 0.8.0 behaviour, where
1081
+ * `useDefault` may borrow the top-level `defaultProxy`. Target-host rules and
1082
+ * the self-check target are always the route's own.
1083
+ */
1084
+ profileId: z.string().default(""),
1076
1085
  /**
1077
1086
  * When true, this route's TRANSPORT fields (protocol/host/port/username/
1078
1087
  * password) come from the top-level `defaultProxy` instead of its own, so the
@@ -1109,6 +1118,12 @@ var ProviderProxyRoute = z.object({
1109
1118
  var Config = z.object({
1110
1119
  /** Per-provider proxy routes, keyed by provider route id (e.g. `sensenova`). */
1111
1120
  providers: vol(z.dict(ProviderProxyRoute).default({})),
1121
+ /**
1122
+ * Named proxy endpoints, keyed by a user-chosen name. A route opts into one
1123
+ * with `profileId`; the top-level `defaultProxy` remains the implicit
1124
+ * "default preset" so an old configuration keeps working unchanged.
1125
+ */
1126
+ proxyProfiles: vol(z.dict(DefaultProxy).default({})),
1112
1127
  /**
1113
1128
  * Shared endpoint for every route that sets `useDefault`. Volatile for the
1114
1129
  * same reason as `providers`: a settings write is committed into the running
@@ -1149,8 +1164,9 @@ function normalizeRoute(provider, raw, options) {
1149
1164
  const onlyHosts = value.onlyHosts.trim().toLowerCase();
1150
1165
  const bypassHosts = value.bypassHosts.trim().toLowerCase();
1151
1166
  const testTarget = value.testTarget.trim();
1167
+ const profileId = value.profileId.trim();
1152
1168
  if (!value.enabled && options?.requireEnabled !== false) {
1153
- return { ...value, port: value.port || 0, onlyHosts, bypassHosts, testTarget };
1169
+ return { ...value, profileId, port: value.port || 0, onlyHosts, bypassHosts, testTarget };
1154
1170
  }
1155
1171
  const host = value.host.trim();
1156
1172
  const hostProblem = proxyHostProblem(host);
@@ -1164,7 +1180,15 @@ function normalizeRoute(provider, raw, options) {
1164
1180
  `dsh-provider-proxy: provider "${provider}" \u7684\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09`
1165
1181
  );
1166
1182
  }
1167
- return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts, testTarget };
1183
+ return {
1184
+ ...value,
1185
+ profileId,
1186
+ host,
1187
+ username: value.username.trim(),
1188
+ onlyHosts,
1189
+ bypassHosts,
1190
+ testTarget
1191
+ };
1168
1192
  }
1169
1193
 
1170
1194
  // src/host-rules.ts
@@ -1201,6 +1225,107 @@ function hostRuleProblem(raw) {
1201
1225
  function deadRuleEntries(raw) {
1202
1226
  return splitHostRules(raw).filter((entry) => hostRuleProblem(entry) !== void 0);
1203
1227
  }
1228
+ var PROVIDER_HOST_TEMPLATES = {
1229
+ openai: { label: "OpenAI", hosts: "api.openai.com, *.openai.com" },
1230
+ anthropic: { label: "Anthropic", hosts: "api.anthropic.com" },
1231
+ google: { label: "Google", hosts: "generativelanguage.googleapis.com" },
1232
+ deepseek: { label: "DeepSeek", hosts: "api.deepseek.com" }
1233
+ };
1234
+ function providerHostTemplate(providerId) {
1235
+ const key = providerId.trim().toLowerCase().split(/[-_/:.]/)[0] ?? "";
1236
+ const template = PROVIDER_HOST_TEMPLATES[key];
1237
+ return template === void 0 ? void 0 : { id: key, ...template };
1238
+ }
1239
+ function appendHostRules(raw, hosts) {
1240
+ const existing = new Set(parseHostList(raw));
1241
+ const additions = [];
1242
+ for (const token of splitHostRules(hosts)) {
1243
+ const normalized = normalizeHostRule(token);
1244
+ if (normalized.length === 0 || existing.has(normalized)) continue;
1245
+ existing.add(normalized);
1246
+ additions.push(token);
1247
+ }
1248
+ if (additions.length === 0) return raw.trim();
1249
+ const prefix = raw.trim();
1250
+ return prefix.length === 0 ? additions.join(", ") : `${prefix}, ${additions.join(", ")}`;
1251
+ }
1252
+
1253
+ // src/presets.ts
1254
+ var DEFAULT_PROFILE_LABEL = "\u9ED8\u8BA4\u9884\u8BBE (Default)";
1255
+ var DEFAULT_PROFILE_BINDING = "__default__";
1256
+ function routeBindingOf(route) {
1257
+ const profileId = (route?.profileId ?? "").trim();
1258
+ if (profileId.length > 0) return { kind: "profile", profileId };
1259
+ if (route?.useDefault === true) return { kind: "default" };
1260
+ return { kind: "own" };
1261
+ }
1262
+ function bindingFields(binding) {
1263
+ switch (binding.kind) {
1264
+ case "profile":
1265
+ return { profileId: binding.profileId, useDefault: false };
1266
+ case "default":
1267
+ return { profileId: "", useDefault: true };
1268
+ case "own":
1269
+ return { profileId: "", useDefault: false };
1270
+ }
1271
+ }
1272
+ function resolveEndpoint(provider, route, defaultProxy, profiles) {
1273
+ const binding = routeBindingOf(route);
1274
+ if (binding.kind === "own") return { ok: true, binding };
1275
+ if (binding.kind === "default") return { ok: true, binding, endpoint: defaultProxy ?? {} };
1276
+ const preset = profiles?.[binding.profileId];
1277
+ if (preset === void 0) {
1278
+ return {
1279
+ ok: false,
1280
+ binding,
1281
+ problem: `dsh-provider-proxy: provider "${provider}" \u7ED1\u5B9A\u7684\u9884\u8BBE\u300C${binding.profileId}\u300D\u4E0D\u5B58\u5728\uFF1B\u8BF7\u9009\u62E9\u4E00\u4E2A\u73B0\u6709\u9884\u8BBE\u6216\u6539\u7528\u81EA\u5B9A\u4E49\u914D\u7F6E`
1282
+ };
1283
+ }
1284
+ return { ok: true, binding, endpoint: preset };
1285
+ }
1286
+ function profileNameProblem(raw) {
1287
+ const name2 = raw.trim();
1288
+ if (name2.length === 0) return "\u9884\u8BBE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
1289
+ if (name2.length > 40) return "\u9884\u8BBE\u540D\u79F0\u4E0D\u80FD\u8D85\u8FC7 40 \u4E2A\u5B57\u7B26";
1290
+ if (/[\u0000-\u001f\u007f]/.test(name2)) return "\u9884\u8BBE\u540D\u79F0\u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26";
1291
+ if (name2 === DEFAULT_PROFILE_LABEL) return "\u8BE5\u540D\u79F0\u7531\u5185\u7F6E\u9ED8\u8BA4\u9884\u8BBE\u4FDD\u7559\uFF0C\u8BF7\u6362\u4E00\u4E2A\u540D\u79F0";
1292
+ if (name2 === DEFAULT_PROFILE_BINDING) return "\u8BE5\u540D\u79F0\u7531\u63D2\u4EF6\u4FDD\u7559\uFF0C\u8BF7\u6362\u4E00\u4E2A\u540D\u79F0";
1293
+ return void 0;
1294
+ }
1295
+ function withEndpoint(route, endpoint) {
1296
+ return {
1297
+ ...route,
1298
+ protocol: endpoint.protocol ?? "http",
1299
+ host: endpoint.host ?? "",
1300
+ port: endpoint.port ?? 0,
1301
+ username: endpoint.username ?? "",
1302
+ password: endpoint.password ?? ""
1303
+ };
1304
+ }
1305
+ function effectiveRoute(route, defaultProxy, profiles) {
1306
+ const base = route ?? {};
1307
+ const binding = routeBindingOf(base);
1308
+ if (binding.kind === "own") return base;
1309
+ const resolved = resolveEndpoint("", base, defaultProxy, profiles);
1310
+ if (!resolved.ok || resolved.endpoint === void 0) return base;
1311
+ return withEndpoint(base, resolved.endpoint);
1312
+ }
1313
+ function profileNames(profiles) {
1314
+ return Object.keys(profiles ?? {}).sort((a, b) => a.localeCompare(b));
1315
+ }
1316
+ function routesBoundTo(routes, profileId) {
1317
+ const out = [];
1318
+ for (const [id, route] of Object.entries(routes ?? {})) {
1319
+ if ((route?.profileId ?? "").trim() === profileId) out.push(id);
1320
+ }
1321
+ return out.sort();
1322
+ }
1323
+ function endpointSummary(endpoint) {
1324
+ const protocol = endpoint?.protocol ?? "http";
1325
+ const host = (endpoint?.host ?? "").trim() || "\u2014";
1326
+ const port = endpoint?.port ?? 0;
1327
+ return `${protocol}://${host}:${port === 0 ? "\u2014" : port}`;
1328
+ }
1204
1329
 
1205
1330
  // src/proxy-table.ts
1206
1331
  function normalizedRuleSet(raw) {
@@ -1270,19 +1395,21 @@ function buildDecide(route, localDirect) {
1270
1395
  return "proxy";
1271
1396
  };
1272
1397
  }
1273
- function withDefaultProxy(route, defaultProxy) {
1274
- return {
1275
- ...route,
1276
- protocol: defaultProxy.protocol,
1277
- host: defaultProxy.host,
1278
- port: defaultProxy.port,
1279
- username: defaultProxy.username,
1280
- password: defaultProxy.password
1281
- };
1282
- }
1283
1398
  function buildRouteEntry(provider, raw, options) {
1284
1399
  let effective = raw;
1285
- if (raw?.useDefault === true) {
1400
+ const resolved = resolveEndpoint(provider, raw, options.defaultProxy, options.profiles);
1401
+ if (!resolved.ok) return { ok: false, problem: resolved.problem };
1402
+ if (resolved.binding.kind === "profile") {
1403
+ const preset = resolved.endpoint ?? {};
1404
+ const why = defaultProxyProblem(preset);
1405
+ if (why !== void 0) {
1406
+ return {
1407
+ ok: false,
1408
+ problem: `dsh-provider-proxy: provider "${provider}" \u7ED1\u5B9A\u7684\u9884\u8BBE\u300C${resolved.binding.profileId}\u300D\u4E0D\u53EF\u7528\uFF1A${why}`
1409
+ };
1410
+ }
1411
+ effective = withEndpoint(raw, normalizeDefaultProxy(preset));
1412
+ } else if (resolved.binding.kind === "default") {
1286
1413
  const why = defaultProxyProblem(options.defaultProxy);
1287
1414
  if (why !== void 0) {
1288
1415
  return {
@@ -1290,7 +1417,7 @@ function buildRouteEntry(provider, raw, options) {
1290
1417
  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}`
1291
1418
  };
1292
1419
  }
1293
- effective = withDefaultProxy(raw, options.defaultProxy);
1420
+ effective = withEndpoint(raw, options.defaultProxy);
1294
1421
  }
1295
1422
  let route;
1296
1423
  try {
@@ -1345,6 +1472,7 @@ function createProxyTable(config, options = {}) {
1345
1472
  const problems = /* @__PURE__ */ new Map();
1346
1473
  const buildOptions = {
1347
1474
  defaultProxy: normalizeDefaultProxy(options.defaultProxy),
1475
+ profiles: options.profiles,
1348
1476
  requireEnabled: true,
1349
1477
  localDirect: options.localDirect !== false
1350
1478
  };
@@ -1362,16 +1490,260 @@ async function closeTable(entries) {
1362
1490
  entries.clear();
1363
1491
  }
1364
1492
 
1493
+ // src/proxy-env.ts
1494
+ var SOURCES = ["HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY"];
1495
+ function explicitPortOf(text) {
1496
+ const authority = text.slice(text.indexOf("://") + 3).split(/[/?#]/, 1)[0] ?? "";
1497
+ const hostPort = authority.slice(authority.lastIndexOf("@") + 1);
1498
+ const match = /^\[[^\]]+\]:(\d+)$/.exec(hostPort) ?? /^[^:]+:(\d+)$/.exec(hostPort);
1499
+ if (match === null) return 0;
1500
+ const parsed = Number.parseInt(match[1], 10);
1501
+ return Number.isNaN(parsed) ? 0 : parsed;
1502
+ }
1503
+ function parseProxyEnvValue(raw) {
1504
+ const text = (raw ?? "").trim();
1505
+ if (text.length === 0) return void 0;
1506
+ const first = text.split(",")[0]?.trim() ?? "";
1507
+ if (first.length === 0) return void 0;
1508
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(first) ? first : `http://${first}`;
1509
+ let url;
1510
+ try {
1511
+ url = new URL(withScheme);
1512
+ } catch {
1513
+ return void 0;
1514
+ }
1515
+ const scheme = url.protocol.slice(0, -1).toLowerCase();
1516
+ const protocol = scheme === "socks" ? "socks5" : scheme === "http" || scheme === "https" || scheme === "socks5" ? scheme : void 0;
1517
+ if (protocol === void 0) return void 0;
1518
+ const host = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
1519
+ if (host.length === 0) return void 0;
1520
+ const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : explicitPortOf(withScheme);
1521
+ return {
1522
+ protocol,
1523
+ host,
1524
+ port: Number.isNaN(port) ? 0 : port,
1525
+ hadCredentials: url.username.length > 0 || url.password.length > 0
1526
+ };
1527
+ }
1528
+ function detectProxyEnv(env = process.env) {
1529
+ for (const source of SOURCES) {
1530
+ const parsed = parseProxyEnvValue(env[source]);
1531
+ if (parsed === void 0) continue;
1532
+ if (parsed.port < 1 || parsed.port > 65535) continue;
1533
+ return { source, ...parsed };
1534
+ }
1535
+ return void 0;
1536
+ }
1537
+
1538
+ // src/proxy-clients.ts
1539
+ var PROXY_CLIENT_PRESETS = [
1540
+ { id: "clash", label: "Clash / Mihomo (127.0.0.1:7890)", protocol: "http", host: "127.0.0.1", port: 7890 },
1541
+ { id: "clash-verge", label: "Clash Verge / Meta (127.0.0.1:7897)", protocol: "http", host: "127.0.0.1", port: 7897 },
1542
+ { id: "v2ray-http", label: "V2Ray / Xray HTTP (127.0.0.1:10809)", protocol: "http", host: "127.0.0.1", port: 10809 },
1543
+ { id: "v2ray-socks", label: "V2Ray / Xray SOCKS5 (127.0.0.1:10808)", protocol: "socks5", host: "127.0.0.1", port: 10808 }
1544
+ ];
1545
+ function proxyClientPreset(id) {
1546
+ return PROXY_CLIENT_PRESETS.find((preset) => preset.id === id);
1547
+ }
1548
+
1549
+ // src/backup.ts
1550
+ var PROTOCOLS2 = ["http", "https", "socks5"];
1551
+ function normalizeEndpoint(raw) {
1552
+ const value = raw ?? {};
1553
+ return {
1554
+ protocol: PROTOCOLS2.includes(value.protocol) ? value.protocol : "http",
1555
+ host: typeof value.host === "string" ? value.host.trim() : "",
1556
+ port: typeof value.port === "number" && Number.isFinite(value.port) ? value.port : 0,
1557
+ username: typeof value.username === "string" ? value.username.trim() : "",
1558
+ password: typeof value.password === "string" ? value.password : ""
1559
+ };
1560
+ }
1561
+ var FALLBACK_TEST_URL = "https://api.ipify.org?format=json";
1562
+ function fillRoute(raw) {
1563
+ const value = raw ?? {};
1564
+ return {
1565
+ enabled: value.enabled === true,
1566
+ profileId: typeof value.profileId === "string" ? value.profileId.trim() : "",
1567
+ useDefault: value.useDefault === true,
1568
+ protocol: PROTOCOLS2.includes(value.protocol) ? value.protocol : "http",
1569
+ host: typeof value.host === "string" ? value.host.trim() : "",
1570
+ port: typeof value.port === "number" && Number.isFinite(value.port) ? value.port : 0,
1571
+ username: typeof value.username === "string" ? value.username.trim() : "",
1572
+ password: typeof value.password === "string" ? value.password : "",
1573
+ onlyHosts: typeof value.onlyHosts === "string" ? value.onlyHosts : "",
1574
+ bypassHosts: typeof value.bypassHosts === "string" ? value.bypassHosts : "",
1575
+ testTarget: typeof value.testTarget === "string" ? value.testTarget : ""
1576
+ };
1577
+ }
1578
+ function buildBackup(source, options = { includePasswords: false }) {
1579
+ const include = options.includePasswords === true;
1580
+ const hide = (password) => include ? password : "";
1581
+ const providers = {};
1582
+ for (const [id, raw] of Object.entries(source.providers ?? {})) {
1583
+ const filled = fillRoute(raw);
1584
+ providers[id] = { ...filled, password: hide(filled.password) };
1585
+ }
1586
+ const proxyProfiles = {};
1587
+ for (const [name2, raw] of Object.entries(source.proxyProfiles ?? {})) {
1588
+ const filled = normalizeEndpoint(raw);
1589
+ proxyProfiles[name2] = { ...filled, password: hide(filled.password) };
1590
+ }
1591
+ const defaultProxy = normalizeEndpoint(source.defaultProxy);
1592
+ return {
1593
+ version: 1,
1594
+ exportedAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
1595
+ providers,
1596
+ proxyProfiles,
1597
+ defaultProxy: { ...defaultProxy, password: hide(defaultProxy.password) },
1598
+ localDirect: source.localDirect !== false,
1599
+ testUrl: typeof source.testUrl === "string" && source.testUrl.trim().length > 0 ? source.testUrl : FALLBACK_TEST_URL
1600
+ };
1601
+ }
1602
+ function backupFileName(now = /* @__PURE__ */ new Date()) {
1603
+ const pad = (n) => String(n).padStart(2, "0");
1604
+ return `dsh-proxy-backup-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}.json`;
1605
+ }
1606
+ function isPlainObject2(value) {
1607
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1608
+ }
1609
+ function parseBackup(text) {
1610
+ let parsed;
1611
+ try {
1612
+ parsed = JSON.parse(text);
1613
+ } catch (error) {
1614
+ return {
1615
+ ok: false,
1616
+ error: `\u4E0D\u662F\u5408\u6CD5\u7684 JSON\uFF1A${error instanceof Error ? error.message : String(error)}`
1617
+ };
1618
+ }
1619
+ if (!isPlainObject2(parsed)) return { ok: false, error: "\u5907\u4EFD\u6587\u4EF6\u7684\u9876\u5C42\u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" };
1620
+ const root = parsed;
1621
+ if (root.providers === void 0 && root.defaultProxy === void 0 && root.proxyProfiles === void 0) {
1622
+ return {
1623
+ ok: false,
1624
+ error: "\u5907\u4EFD\u6587\u4EF6\u7F3A\u5C11 providers / proxyProfiles / defaultProxy\uFF0C\u65E0\u6CD5\u8BC6\u522B\u4E3A\u4EE3\u7406\u914D\u7F6E"
1625
+ };
1626
+ }
1627
+ if (root.providers !== void 0 && !isPlainObject2(root.providers)) {
1628
+ return { ok: false, error: "providers \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" };
1629
+ }
1630
+ if (root.proxyProfiles !== void 0 && !isPlainObject2(root.proxyProfiles)) {
1631
+ return { ok: false, error: "proxyProfiles \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" };
1632
+ }
1633
+ if (root.defaultProxy !== void 0 && !isPlainObject2(root.defaultProxy)) {
1634
+ return { ok: false, error: "defaultProxy \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" };
1635
+ }
1636
+ if (root.localDirect !== void 0 && typeof root.localDirect !== "boolean") {
1637
+ return { ok: false, error: "localDirect \u5FC5\u987B\u662F\u5E03\u5C14\u503C" };
1638
+ }
1639
+ if (root.testUrl !== void 0 && typeof root.testUrl !== "string") {
1640
+ return { ok: false, error: "testUrl \u5FC5\u987B\u662F\u5B57\u7B26\u4E32" };
1641
+ }
1642
+ const endpoint = (value, label) => {
1643
+ if (value.protocol !== void 0 && !PROTOCOLS2.includes(value.protocol)) {
1644
+ return `${label}.protocol \u5FC5\u987B\u662F ${PROTOCOLS2.join(" / ")}`;
1645
+ }
1646
+ for (const key of ["host", "username", "password"]) {
1647
+ if (value[key] !== void 0 && typeof value[key] !== "string") {
1648
+ return `${label}.${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`;
1649
+ }
1650
+ }
1651
+ if (value.port !== void 0 && (typeof value.port !== "number" || !Number.isFinite(value.port))) {
1652
+ return `${label}.port \u5FC5\u987B\u662F\u6570\u5B57`;
1653
+ }
1654
+ return void 0;
1655
+ };
1656
+ const providers = {};
1657
+ for (const [id, value] of Object.entries(root.providers ?? {})) {
1658
+ if (!isPlainObject2(value)) return { ok: false, error: `providers.${id} \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61` };
1659
+ const why = endpoint(value, `providers.${id}`);
1660
+ if (why !== void 0) return { ok: false, error: why };
1661
+ for (const key of ["enabled", "useDefault"]) {
1662
+ if (value[key] !== void 0 && typeof value[key] !== "boolean") {
1663
+ return { ok: false, error: `providers.${id}.${key} \u5FC5\u987B\u662F\u5E03\u5C14\u503C` };
1664
+ }
1665
+ }
1666
+ for (const key of ["profileId", "onlyHosts", "bypassHosts", "testTarget"]) {
1667
+ if (value[key] !== void 0 && typeof value[key] !== "string") {
1668
+ return { ok: false, error: `providers.${id}.${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32` };
1669
+ }
1670
+ }
1671
+ providers[id] = fillRoute(value);
1672
+ }
1673
+ const proxyProfiles = {};
1674
+ for (const [name2, value] of Object.entries(
1675
+ root.proxyProfiles ?? {}
1676
+ )) {
1677
+ if (!isPlainObject2(value)) return { ok: false, error: `proxyProfiles.${name2} \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61` };
1678
+ const why = endpoint(value, `proxyProfiles.${name2}`);
1679
+ if (why !== void 0) return { ok: false, error: why };
1680
+ proxyProfiles[name2] = normalizeEndpoint(value);
1681
+ }
1682
+ if (root.defaultProxy !== void 0) {
1683
+ const why = endpoint(root.defaultProxy, "defaultProxy");
1684
+ if (why !== void 0) return { ok: false, error: why };
1685
+ }
1686
+ const document = {
1687
+ version: 1,
1688
+ exportedAt: typeof root.exportedAt === "string" ? root.exportedAt : "",
1689
+ providers,
1690
+ proxyProfiles,
1691
+ defaultProxy: normalizeEndpoint(root.defaultProxy ?? {}),
1692
+ localDirect: root.localDirect !== false,
1693
+ testUrl: typeof root.testUrl === "string" && root.testUrl.trim().length > 0 ? root.testUrl : FALLBACK_TEST_URL
1694
+ };
1695
+ return { ok: true, document };
1696
+ }
1697
+ function backupOps(document) {
1698
+ return [
1699
+ { op: "set", path: ["providers"], value: document.providers },
1700
+ { op: "set", path: ["proxyProfiles"], value: document.proxyProfiles },
1701
+ { op: "set", path: ["defaultProxy"], value: document.defaultProxy },
1702
+ { op: "set", path: ["localDirect"], value: document.localDirect },
1703
+ { op: "set", path: ["testUrl"], value: document.testUrl }
1704
+ ];
1705
+ }
1706
+ function backupCounts(document) {
1707
+ return {
1708
+ providers: Object.keys(document.providers).length,
1709
+ profiles: Object.keys(document.proxyProfiles).length,
1710
+ enabled: Object.values(document.providers).filter((route) => route.enabled).length
1711
+ };
1712
+ }
1713
+ function validateEnabledRoutes(document) {
1714
+ for (const [id, route] of Object.entries(document.providers)) {
1715
+ if (!route.enabled) continue;
1716
+ if (route.profileId.length > 0 || route.useDefault) continue;
1717
+ const hostProblem = proxyHostProblem(route.host);
1718
+ if (hostProblem !== void 0) {
1719
+ return {
1720
+ provider: id,
1721
+ problem: hostProblem === "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740" ? `provider "${id}" \u542F\u7528\u4E86\u4EE3\u7406\uFF0C\u4F46\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740` : `provider "${id}" \u7684${hostProblem}`
1722
+ };
1723
+ }
1724
+ if (!Number.isInteger(route.port) || route.port < 1 || route.port > 65535) {
1725
+ return { provider: id, problem: `provider "${id}" \u7684\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09` };
1726
+ }
1727
+ }
1728
+ return void 0;
1729
+ }
1730
+
1365
1731
  // src/index.ts
1366
1732
  var name = "dsh-provider-proxy";
1367
1733
  var inject = ["llm"];
1368
1734
  var WEB_SEARCH_KEY = "web-search";
1369
1735
  function plainProviders(config) {
1370
- const raw = config?.providers;
1371
- if (raw === void 0) return {};
1736
+ return plainDict(config?.providers, {});
1737
+ }
1738
+ function plainDict(field, fallback) {
1739
+ const raw = field;
1740
+ if (raw === void 0) return fallback;
1372
1741
  const snapshot2 = raw.get;
1373
1742
  const value = typeof snapshot2 === "function" ? snapshot2.call(raw) : raw;
1374
- return value ?? {};
1743
+ return value ?? fallback;
1744
+ }
1745
+ function plainProfiles(config) {
1746
+ return plainDict(config?.proxyProfiles, {});
1375
1747
  }
1376
1748
  function plainDefaultProxy(config) {
1377
1749
  const raw = config?.defaultProxy;
@@ -1674,7 +2046,7 @@ function apply(ctx, config) {
1674
2046
  };
1675
2047
  const selfCheckKey = (provider) => {
1676
2048
  const raw = plainProviders(config)[provider] ?? {};
1677
- const endpoint = raw.useDefault === true ? plainDefaultProxy(config) : raw;
2049
+ const endpoint = effectiveRoute(raw, plainDefaultProxy(config), plainProfiles(config));
1678
2050
  return JSON.stringify([
1679
2051
  provider,
1680
2052
  endpoint.protocol ?? "http",
@@ -1692,20 +2064,25 @@ function apply(ctx, config) {
1692
2064
  const runSelfCheck = async (provider) => {
1693
2065
  let route = current.get(provider);
1694
2066
  let temporary = false;
1695
- if (route === void 0) {
1696
- const problem = problems.get(provider);
1697
- if (problem !== void 0) {
1698
- return { ok: false, kind: "route-rejected", error: bareMessage(problem) };
2067
+ const saved = plainProviders(config)[provider];
2068
+ const buildOptions = {
2069
+ defaultProxy: normalizeDefaultProxy(plainDefaultProxy(config)),
2070
+ profiles: plainProfiles(config),
2071
+ requireEnabled: false,
2072
+ localDirect: plainLocalDirect(config)
2073
+ };
2074
+ if (route !== void 0) {
2075
+ const expected = saved === void 0 || saved.enabled !== true ? void 0 : buildRouteEntry(provider, saved, buildOptions);
2076
+ if (expected === void 0 || !expected.ok || expected.entry.proxyOrigin !== route.proxyOrigin) {
2077
+ route = void 0;
1699
2078
  }
1700
- const configured = plainProviders(config)[provider];
1701
- if (configured === void 0 || configured.enabled === true) {
2079
+ }
2080
+ if (route === void 0) {
2081
+ const configured = saved;
2082
+ if (configured === void 0) {
1702
2083
  return { ok: false, kind: "no-route", error: `provider "${provider}" \u672A\u542F\u7528\u4EE3\u7406` };
1703
2084
  }
1704
- const built = buildRouteEntry(provider, configured, {
1705
- defaultProxy: normalizeDefaultProxy(plainDefaultProxy(config)),
1706
- requireEnabled: false,
1707
- localDirect: plainLocalDirect(config)
1708
- });
2085
+ const built = buildRouteEntry(provider, configured, buildOptions);
1709
2086
  if (!built.ok) {
1710
2087
  return { ok: false, kind: "route-rejected", error: bareMessage(built.problem) };
1711
2088
  }
@@ -1733,8 +2110,9 @@ function apply(ctx, config) {
1733
2110
  const rebuild = () => {
1734
2111
  const providers = plainProviders(config);
1735
2112
  const defaultProxy = plainDefaultProxy(config);
2113
+ const profiles = plainProfiles(config);
1736
2114
  const localDirect = plainLocalDirect(config);
1737
- const next = createProxyTable(providers, { defaultProxy, localDirect });
2115
+ const next = createProxyTable(providers, { defaultProxy, profiles, localDirect });
1738
2116
  const previous = current;
1739
2117
  current = next.entries;
1740
2118
  problems = next.problems;
@@ -1746,7 +2124,7 @@ function apply(ctx, config) {
1746
2124
  forgetChecks(provider);
1747
2125
  }
1748
2126
  for (const [provider, raw] of Object.entries(providers)) {
1749
- const effective = raw?.useDefault === true ? { ...raw, ...defaultProxy } : raw;
2127
+ const effective = effectiveRoute(raw, defaultProxy, profiles);
1750
2128
  const fingerprint = routeFingerprint({
1751
2129
  ...effective,
1752
2130
  localDirect
@@ -1813,6 +2191,15 @@ function apply(ctx, config) {
1813
2191
  out.sort((a, b) => a.name.localeCompare(b.name));
1814
2192
  return { providers: out };
1815
2193
  };
2194
+ let environmentDetected = false;
2195
+ let environmentProxy;
2196
+ const environmentProxyOf = () => {
2197
+ if (!environmentDetected) {
2198
+ environmentProxy = detectProxyEnv();
2199
+ environmentDetected = true;
2200
+ }
2201
+ return environmentProxy;
2202
+ };
1816
2203
  ctx.inject?.(["webServer"], (wctx) => {
1817
2204
  try {
1818
2205
  wctx?.webServer?.register?.({
@@ -1822,6 +2209,7 @@ function apply(ctx, config) {
1822
2209
  try {
1823
2210
  const text = JSON.stringify({
1824
2211
  ...catalog(),
2212
+ environment: { proxy: environmentProxyOf() ?? null },
1825
2213
  problems: Object.fromEntries(
1826
2214
  [...problems].map(([id, message]) => [id, bareMessage(message)])
1827
2215
  ),
@@ -2023,27 +2411,50 @@ function apply(ctx, config) {
2023
2411
  }
2024
2412
  export {
2025
2413
  Config,
2414
+ DEFAULT_PROFILE_LABEL,
2026
2415
  PROTOCOLS,
2416
+ PROVIDER_HOST_TEMPLATES,
2417
+ PROXY_CLIENT_PRESETS,
2027
2418
  ProviderProxyRoute,
2028
2419
  WEB_SEARCH_KEY,
2029
2420
  acquireFetchRouter,
2421
+ appendHostRules,
2030
2422
  apply,
2423
+ backupCounts,
2424
+ backupFileName,
2425
+ backupOps,
2426
+ bindingFields,
2427
+ buildBackup,
2031
2428
  buildRouteEntry,
2032
2429
  closeTable,
2033
2430
  createProxyTable,
2034
2431
  deadRuleEntries,
2035
2432
  defaultProxyProblem,
2433
+ detectProxyEnv,
2434
+ effectiveRoute,
2435
+ endpointSummary,
2036
2436
  hostMatches,
2037
2437
  hostRuleProblem,
2038
2438
  inject,
2039
2439
  name,
2040
2440
  normalizeDefaultProxy,
2041
2441
  normalizeRoute,
2442
+ parseBackup,
2042
2443
  parseHostList,
2444
+ parseProxyEnvValue,
2445
+ profileNameProblem,
2446
+ profileNames,
2447
+ providerHostTemplate,
2448
+ proxyClientPreset,
2043
2449
  proxyHostProblem,
2044
2450
  proxyUrl,
2451
+ resolveEndpoint,
2452
+ routeBindingOf,
2045
2453
  routeFingerprint,
2046
2454
  routeStateDecision,
2047
- splitHostRules
2455
+ routesBoundTo,
2456
+ splitHostRules,
2457
+ validateEnabledRoutes,
2458
+ withEndpoint
2048
2459
  };
2049
2460
  //# sourceMappingURL=index.js.map