@yanglaofish/dsh-proxy-pro 1.0.8 → 1.0.9
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/client.js +3 -1
- package/lib/proxy-core.js +110 -20
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -322,7 +322,9 @@ window.__ModuleLoader__.load({
|
|
|
322
322
|
if (!fallback) return { text: "未知", tone: "warn" };
|
|
323
323
|
if (fallback.verdict === "usable") return { text: "链路通 · 目标正常响应", tone: "ok" };
|
|
324
324
|
if (fallback.verdict === "degraded") return { text: "链路通 · 这次请求被目标拒绝", tone: "warn" };
|
|
325
|
-
|
|
325
|
+
// 5xx:拿到了 HTTP 应答,链路通但服务端/网关报错(严重性误导修复:不算"链路不通")
|
|
326
|
+
if (fallback.kind === "gateway" || fallback.kind === "server-error") return { text: "链路通 · 目标/网关报错", tone: "warn" };
|
|
327
|
+
if (fallback.unusable === true || fallback.verdict === "unusable") return { text: "链路不通 · 网络/代理问题", tone: "bad" };
|
|
326
328
|
return fallback.ok ? { text: "链路通", tone: "ok" } : { text: "链路不通", tone: "bad" };
|
|
327
329
|
}
|
|
328
330
|
|
package/lib/proxy-core.js
CHANGED
|
@@ -417,10 +417,43 @@ export function classifyTargetFailure(error) {
|
|
|
417
417
|
fix: '对比直连/代理两侧并重试;用 proxy_status 查看生效路由。' }
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
+
/**
|
|
421
|
+
* 单侧通道的徽章:按真实语义给色,杜绝"严重性误导"(LESSONS §32)。
|
|
422
|
+
* - usable(2xx/3xx/401)→ 🟢 链路通 · 目标正常响应
|
|
423
|
+
* - degraded(403/404/405/429 等被目标拒绝)→ 🟡 链路通 · 目标拒绝(…)
|
|
424
|
+
* - 5xx(500/502/503/504 拿到应答但目标/网关报错)→ 🟡 链路通 · 目标/网关报错(…)
|
|
425
|
+
* - 407 认证拦截 → 🔴 链路不通 · 代理要求认证
|
|
426
|
+
* - 其他不可达(DNS/拒绝/超时/本地错误)→ 🔴 链路不通 · …
|
|
427
|
+
* 关键区分:**只要拿到了 HTTP 应答(含 4xx/5xx)链路就是通的**,只有
|
|
428
|
+
* 连接层失败(未拿到应答)才是"链路不通"。404 曾因 ok=false 被误标红色
|
|
429
|
+
* 链路不通 → 截图"代理与直连均失败"误导(2026-09-18 用户实况)。
|
|
430
|
+
*/
|
|
431
|
+
function dualBadge(probe) {
|
|
432
|
+
const detail = (s) => String(s ?? '').replace(/^HTTP \d+ — /, '')
|
|
433
|
+
if (!probe) return { text: '结果未知', tone: 'warn' }
|
|
434
|
+
if (probe.verdict === 'usable') return { text: '链路通 · 目标正常响应', tone: 'ok' }
|
|
435
|
+
if (probe.verdict === 'degraded') {
|
|
436
|
+
return { text: `链路通 · 目标拒绝(${detail(probe.short) || '请求被拒'})`, tone: 'warn' }
|
|
437
|
+
}
|
|
438
|
+
if (probe.kind === 'gateway' || probe.kind === 'server-error') {
|
|
439
|
+
return { text: `链路通 · 目标/网关报错(${detail(probe.short) || String(probe.status ?? '5xx')})`, tone: 'warn' }
|
|
440
|
+
}
|
|
441
|
+
if (probe.kind === 'auth') return { text: '链路不通 · 代理要求认证', tone: 'bad' }
|
|
442
|
+
return { text: `链路不通 · ${detail(probe.short) || '连接失败'}`, tone: 'bad' }
|
|
443
|
+
}
|
|
444
|
+
|
|
420
445
|
/**
|
|
421
446
|
* 双通道综合判定:把「强制走代理」与「强制直连」两次探测合成为一份可给人
|
|
422
447
|
* 看的结果:两条徽章(各自带 tone 色)+ 一条推荐。
|
|
423
448
|
*
|
|
449
|
+
* 三个概念分离(LESSONS §32 严重性误导修复):
|
|
450
|
+
* - 可达(reachable):通道真正拿到了目标应答——usable 或 degraded(4xx)。
|
|
451
|
+
* 5xx 属"链路通但目标/网关报错"(kind=gateway/server-error),不算可达
|
|
452
|
+
* 也不算不通,徽章黄色;407/连接层失败才是"不通"。
|
|
453
|
+
* - 健康(healthy):usbale,目标给了正常应答。
|
|
454
|
+
* - 徽章:由 dualBadge 按实际 verdict/kind 生成,绝不把 404/403 标红。
|
|
455
|
+
* 只有连接层失败才标红"链路不通"。
|
|
456
|
+
*
|
|
424
457
|
* 单侧探针拿不到应答者身份,因此「代理策略拦截」只能靠两侧对比得到方向性
|
|
425
458
|
* 结论(LESSONS §30:直连通+代理失败 → 问题在代理一侧;代理通+直连失败 →
|
|
426
459
|
* 代理是唯一通路;都失败 → 环境/服务端问题;都通 → 保持现状)。
|
|
@@ -431,18 +464,22 @@ export function classifyTargetFailure(error) {
|
|
|
431
464
|
* badge = { text, tone: 'ok'|'warn'|'bad' }。
|
|
432
465
|
*/
|
|
433
466
|
export function combineDualProbe(proxyProbe, directProbe) {
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
|
|
467
|
+
const p = proxyProbe ?? {}
|
|
468
|
+
const d = directProbe ?? {}
|
|
469
|
+
const pReach = p.verdict === 'usable' || p.verdict === 'degraded'
|
|
470
|
+
const dReach = d.verdict === 'usable' || d.verdict === 'degraded'
|
|
471
|
+
const pOk = p.verdict === 'usable'
|
|
472
|
+
const dOk = d.verdict === 'usable'
|
|
473
|
+
const proxyAuth = p.kind === 'auth'
|
|
474
|
+
|| (p.verdict === 'unusable' && /NTLM|407/.test(String(p.short ?? '')))
|
|
438
475
|
|
|
439
476
|
const notReachable = (probe, label) => {
|
|
440
477
|
const detail = (probe?.short ?? '未知原因').replace(/^HTTP \d+ — /, '')
|
|
441
478
|
return `${label}:${detail}`
|
|
442
479
|
}
|
|
443
480
|
|
|
444
|
-
//
|
|
445
|
-
if (
|
|
481
|
+
// ① 都健康 → 都可以(保持现状)
|
|
482
|
+
if (pOk && dOk) {
|
|
446
483
|
return {
|
|
447
484
|
verdict: 'both-ok',
|
|
448
485
|
short: '代理与直连均通',
|
|
@@ -453,45 +490,98 @@ export function combineDualProbe(proxyProbe, directProbe) {
|
|
|
453
490
|
}
|
|
454
491
|
}
|
|
455
492
|
|
|
456
|
-
//
|
|
457
|
-
if (
|
|
493
|
+
// ② 只有代理健康 → 走代理
|
|
494
|
+
if (pOk && !dOk) {
|
|
458
495
|
return {
|
|
459
496
|
verdict: 'proxy-only',
|
|
460
497
|
short: '只有走代理能通',
|
|
461
|
-
why: `直连 ${notReachable(
|
|
498
|
+
why: `直连 ${notReachable(d, '失败')};走代理正常——该目标在当前网络需经代理访问。`,
|
|
462
499
|
fix: '确认该域名不在 NO_PROXY 直连名单里;保持代理开启。',
|
|
463
500
|
proxyBadge: { text: '链路通 · 目标正常响应', tone: 'ok' },
|
|
464
|
-
directBadge:
|
|
501
|
+
directBadge: dualBadge(d),
|
|
465
502
|
}
|
|
466
503
|
}
|
|
467
504
|
|
|
468
|
-
//
|
|
469
|
-
if (!
|
|
505
|
+
// ③ 只有直连健康 → 问题在代理一侧(认证拦截 / 不可达 / 被目标拒绝)
|
|
506
|
+
if (!pOk && dOk) {
|
|
507
|
+
const pSide = proxyAuth
|
|
508
|
+
? '代理要求 NTLM 认证(407),请求没到目标'
|
|
509
|
+
: p.verdict === 'degraded'
|
|
510
|
+
? '经代理已到达目标,但目标拒绝了这次请求(403/404/429 等)'
|
|
511
|
+
: '经代理未能连接目标(可能是代理策略拦截、代理故障或链路异常)'
|
|
470
512
|
const base = proxyAuth
|
|
471
513
|
? {
|
|
472
514
|
why: '代理要求 NTLM 认证(407),而直连正常——该目标不值得绕代理。',
|
|
473
515
|
fix: '该域名走代理会被认证卡住:把它加入 NO_PROXY 直连名单即可;无需打开认证窗口。',
|
|
474
516
|
}
|
|
475
517
|
: {
|
|
476
|
-
why:
|
|
477
|
-
fix:
|
|
518
|
+
why: `${pSide};而直连正常——结论:该域名走直连更可靠。`,
|
|
519
|
+
fix: p.verdict === 'degraded'
|
|
520
|
+
? '把该域名加入 NO_PROXY 直连名单即可绕开代理侧的目标拒绝;若确需走代理,检查 key/UA/权限。'
|
|
521
|
+
: '先重试排除临时故障;仍失败则检查代理可用性或联系管理员。若确认是策略拦截,把该域名加入 NO_PROXY 直连名单。',
|
|
478
522
|
}
|
|
479
523
|
return {
|
|
480
524
|
verdict: proxyAuth ? 'direct-only-auth' : 'direct-only',
|
|
481
|
-
short: proxyAuth ? '直连可通 · 代理需认证' : '直连可通 · 代理被拦',
|
|
525
|
+
short: proxyAuth ? '直连可通 · 代理需认证' : (p.verdict === 'degraded' ? '直连可通 · 代理侧被目标拒绝' : '直连可通 · 代理被拦'),
|
|
482
526
|
...base,
|
|
483
|
-
proxyBadge:
|
|
527
|
+
proxyBadge: dualBadge(p),
|
|
484
528
|
directBadge: { text: '链路通 · 目标正常响应', tone: 'ok' },
|
|
485
529
|
}
|
|
486
530
|
}
|
|
487
531
|
|
|
488
|
-
//
|
|
532
|
+
// ④ 都不健康但都可达(双 4xx:链路通,目标对探测路径有异常应答)
|
|
533
|
+
if (pReach && dReach) {
|
|
534
|
+
const pNote = notReachable(p, '代理').replace(/^失败:/, '代理:')
|
|
535
|
+
const dNote = notReachable(d, '直连').replace(/^失败:/, '直连:')
|
|
536
|
+
return {
|
|
537
|
+
verdict: 'both-reached',
|
|
538
|
+
short: '双链路通 · 目标应答异常',
|
|
539
|
+
why: `两条通道都到达了目标,但目标返回异常状态:${pNote};${dNote}。`,
|
|
540
|
+
fix: '探测的是根路径/裸前缀,目标可能对 HEAD 或未授权请求如此应答——改用真实调用端点(如 /models、/chat/completions)复测,或检查该主机的 API Key。',
|
|
541
|
+
proxyBadge: dualBadge(p),
|
|
542
|
+
directBadge: dualBadge(d),
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ⑤ 只有代理可达(哪怕 404/403,也是唯一通路)——修复重点:
|
|
547
|
+
// 404 不再落入"双失败",恢复为代理是唯一通路(腾讯 Copilot 实况)
|
|
548
|
+
if (pReach && !dReach) {
|
|
549
|
+
const pNote = p.verdict === 'usable' ? '正常响应' : notReachable(p, '请求被拒').replace(/^请求被拒:/, '')
|
|
550
|
+
return {
|
|
551
|
+
verdict: 'proxy-only',
|
|
552
|
+
short: p.verdict === 'usable' ? '只有走代理能通' : '只有走代理可达 · 目标有异常应答',
|
|
553
|
+
why: `直连 ${notReachable(d, '失败')};经代理 ${pNote}——该域名在当前网络只有代理通道能到达目标。`,
|
|
554
|
+
fix: p.verdict === 'usable'
|
|
555
|
+
? '确认该域名不在 NO_PROXY 直连名单里;保持代理开启。'
|
|
556
|
+
: '走代理链路是通的,但目标返回异常(尝试换真实端点复测);同时确认该域名不在 NO_PROXY 直连名单里。',
|
|
557
|
+
proxyBadge: dualBadge(p),
|
|
558
|
+
directBadge: dualBadge(d),
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// ⑥ 只有直连可达(代理侧 4xx/5xx 或不可达)
|
|
563
|
+
if (dReach) {
|
|
564
|
+
return {
|
|
565
|
+
verdict: proxyAuth ? 'direct-only-auth' : 'direct-only',
|
|
566
|
+
short: proxyAuth ? '直连可达 · 代理需认证' : (d.verdict === 'usable' ? '直连可通 · 代理被拦' : '直连可达 · 代理侧异常'),
|
|
567
|
+
why: proxyAuth
|
|
568
|
+
? '代理要求 NTLM 认证(407),而直连正常——该目标不值得绕代理。'
|
|
569
|
+
: `经代理 ${p.kind === 'gateway' || p.kind === 'server-error' ? '返回网关/服务端错误(链路本身通,问题在目标或中间网关)' : `失败(${notReachable(p, '').replace(/^:/, '')})`};直连 ${dOk ? '正常' : notReachable(d, '可达但目标异常')}。`,
|
|
570
|
+
fix: proxyAuth
|
|
571
|
+
? '把该域名加入 NO_PROXY 直连名单即可。'
|
|
572
|
+
: '建议该域名走直连(加入 NO_PROXY);若代理侧是 4xx/5xx,也检查一下真实调用端点。',
|
|
573
|
+
proxyBadge: dualBadge(p),
|
|
574
|
+
directBadge: dualBadge(d),
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ⑦ 都不可达(连接层失败)→ 环境/服务端问题
|
|
489
579
|
return {
|
|
490
580
|
verdict: 'both-bad',
|
|
491
581
|
short: '代理与直连均失败',
|
|
492
|
-
why:
|
|
582
|
+
why: `两条通道都没能建立到目标的连接:代理 ${notReachable(p, '失败')};直连 ${notReachable(d, '失败')}。`,
|
|
493
583
|
fix: '是环境或服务端问题,与代理开关无关:检查网络/目标服务,或稍后重试。',
|
|
494
|
-
proxyBadge:
|
|
495
|
-
directBadge:
|
|
584
|
+
proxyBadge: dualBadge(p),
|
|
585
|
+
directBadge: dualBadge(d),
|
|
496
586
|
}
|
|
497
587
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yanglaofish/dsh-proxy-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "DSH proxy manager: route model requests, web fetches and spawned tools through the Windows system proxy or a custom address; per-URL route diagnosis, one-click toggle from the conversation header, a dedicated Settings page, and keepalive-aware 407 hints.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|