mslxdff 0.1.211 → 0.1.212

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.211",
3
+ "version": "0.1.212",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -75,7 +75,7 @@ export async function handleV6Pool(args = []) {
75
75
  console.log("");
76
76
  console.log("-".repeat(66));
77
77
  console.log(` 基线(IPv4/直连):${line(r.baseline)}`);
78
- console.log(` v6 采样:有额度 ${r.v6OkCount}|429 ${r.v6LimitedCount}|不可达 ${r.v6NetCount}(共 ${r.requested})`);
78
+ console.log(` v6 采样:有额度 ${r.v6OkCount}|受限 ${r.v6LimitedCount}|不可达 ${r.v6NetCount}(共 ${r.requested})`);
79
79
  console.log("");
80
80
  const verdicts = {
81
81
  "v6-works": {
@@ -85,7 +85,7 @@ export async function handleV6Pool(args = []) {
85
85
  },
86
86
  "v6-also-limited": {
87
87
  mark: "⛔",
88
- text: `IPv6 **也全 429**:换 IP 没换到新额度`,
88
+ text: `IPv6 **也全受限**(换 IP 没换到新额度)`,
89
89
  next: ` 结论:v6 池对这台机器**无意义**(要么上游不按出口 IP 计额,要么该段没真路由出去)。建议 MSLXDFF_V6_POOL=0。`,
90
90
  },
91
91
  "v6-unreachable": {
@@ -4,14 +4,15 @@
4
4
  // (打上游回 429 FreeUsageLimitError),而某个 IPv6 出口还能 200 ⇒ 池有用(换 IP 换到新额度);
5
5
  // 若 IPv6 出口也 429 ⇒ 要么上游按别的东西计额(账号/session),要么该段没真路由出去。
6
6
  //
7
- // 对照纪律:**除"源地址"外一切相同** —— 同 URL、同 UA、同模型、同 `stream:true`+五核心工具
8
- // (否则会 403 FreeTierError 而非 429,测出来的不是额度);差异只允许来自"哪个源地址出网"。
7
+ // 对照纪律:**除"源地址"外一切相同** —— 同 URL、同 UA、同**身份四件套 header**、同模型、同 `stream:true`+五核心工具
8
+ // (免费层要流式+五核心工具+身份头,缺任一项都会 403 FreeTierError 而非 429,测出来的不是额度);
9
+ // 整轮基线+v6 共用同一个 session。差异只允许来自"哪个源地址出网"。
9
10
  // 判定复用 `warp-pool/verdict.js` 的 classifyWarpSignal(与生产换址判决同源,不自造一套)。
10
11
  //
11
12
  // IO 全部注入(agentFactory/fetch/detect/claimer),零真机依赖,可单测。
12
13
  import { getUndici, timeoutSignal } from "../compat.js";
13
14
  import { classifyWarpSignal } from "../warp-pool/verdict.js";
14
- import { opencodeUa } from "../opencode-identity.js";
15
+ import { opencodeUa, genId } from "../opencode-identity.js";
15
16
  import { CORE_AGENT_TOOL_NAMES } from "../free-lane.js";
16
17
  import { detectPool } from "./detect.js";
17
18
  import { resolveV6Config } from "./config.js";
@@ -49,12 +50,29 @@ async function readBody(res, max = 2048) {
49
50
  }
50
51
  }
51
52
 
53
+ /** 免费层身份头(与生产 buildHeaders 同源,见 src/upstream.js):**缺了必 403 FreeTierError**。
54
+ * 实测 2026-10-19:只发 User-Agent → 403;补上 x-opencode-* 四件套 → 200。
55
+ * session 用随机 genId(对照测试测的是"出口 IP"不是"会话亲和",每个探针独立即可)。 */
56
+ export function freeLaneHeaders({ session = genId("ses_") } = {}) {
57
+ return {
58
+ "Content-Type": "application/json",
59
+ Accept: "text/event-stream",
60
+ "User-Agent": opencodeUa(),
61
+ "x-opencode-client": "desktop",
62
+ "x-opencode-session": session,
63
+ "x-opencode-request": genId("msg_"),
64
+ "x-opencode-project": "global",
65
+ "HTTP-Referer": "https://hermes-agent.nousresearch.com",
66
+ "X-Title": "Hermes Agent",
67
+ };
68
+ }
69
+
52
70
  /**
53
71
  * 从某个源地址打一次免费层 chat,返回判决。
54
72
  * `addr=""` 表示**直连**(走本机默认路由 = IPv4 出口,或双栈默认)。
55
73
  * @returns {Promise<{addr, ok, status, kind, rotate, ms, body, error}>}
56
74
  */
57
- export async function probeUpstreamFrom({ addr = "", model, baseUrl = "https://opencode.ai", agentFactory, fetchImpl, timeoutMs = 20_000, prompt } = {}) {
75
+ export async function probeUpstreamFrom({ addr = "", model, baseUrl = "https://opencode.ai", agentFactory, fetchImpl, timeoutMs = 20_000, prompt, session } = {}) {
58
76
  const t0 = Date.now();
59
77
  let agent = null;
60
78
  try {
@@ -62,7 +80,7 @@ export async function probeUpstreamFrom({ addr = "", model, baseUrl = "https://o
62
80
  const f = fetchImpl || getUndici().fetch;
63
81
  const res = await f(`${baseUrl}/zen/v1/chat/completions`, {
64
82
  method: "POST",
65
- headers: { "Content-Type": "application/json", "User-Agent": opencodeUa(), Accept: "text/event-stream" },
83
+ headers: freeLaneHeaders({ session }),
66
84
  body: JSON.stringify(freeLaneBody(model, prompt)),
67
85
  ...(agent ? { dispatcher: agent } : {}),
68
86
  signal: timeoutSignal(timeoutMs),
@@ -105,10 +123,13 @@ export async function runV6PoolCompare({
105
123
  const agentFactory = deps.agentFactory || ((addr) => new Agent({ connect: { localAddress: addr }, connections: 1, keepAliveTimeout: 5_000 }));
106
124
  const probe = deps.probe || probeUpstreamFrom;
107
125
  const pick = deps.randomAddress || ((p) => randomAddress(p));
126
+ // 整轮共用一个 session:对照纪律「除源地址外一切相同」——让差异只可能来自出口 IP,
127
+ // 而不是会话随机性(免费层按出口 IP 计额,但共用 session 才能把这一点测干净)。
128
+ const session = deps.session || genId("ses_");
108
129
 
109
130
  // ① 基准:直连(IPv4 / 系统默认出口),记下它的额度状态作为对照基线
110
131
  tick({ phase: "baseline-start", prefix: d.prefix, model });
111
- const baseline = await probe({ addr: "", model, baseUrl, agentFactory, fetchImpl: deps.fetchImpl });
132
+ const baseline = await probe({ addr: "", model, baseUrl, agentFactory, fetchImpl: deps.fetchImpl, session });
112
133
  tick({ phase: "baseline-done", ...baseline });
113
134
 
114
135
  // ② IPv6 池采样:每个随机源地址打同一条请求
@@ -122,7 +143,7 @@ export async function runV6PoolCompare({
122
143
  for (let k = 0; k < 16 && mineSet.has(addr); k++) addr = pick(prefix);
123
144
  await claimer.ensure(addr);
124
145
  tick({ phase: "v6-start", i: i + 1, n, addr });
125
- const r = await probe({ addr, model, baseUrl, agentFactory, fetchImpl: deps.fetchImpl });
146
+ const r = await probe({ addr, model, baseUrl, agentFactory, fetchImpl: deps.fetchImpl, session });
126
147
  v6.push(r);
127
148
  tick({ phase: "v6-done", i: i + 1, n, ...r });
128
149
  }
@@ -133,13 +154,16 @@ export async function runV6PoolCompare({
133
154
  // ③ 判语:对照基线
134
155
  const baseLimited = baseline.status === 429 || baseline.kind === "free-quota" || baseline.kind === "free-tier" || baseline.kind === "ip-limit";
135
156
  const v6Ok = v6.filter((r) => r.ok);
136
- const v6Limited = v6.filter((r) => r.status === 429);
157
+ // 受限 = 有 HTTP 响应但拿不到额度(403 FreeTierError / 429 额度 / 其余 4xx-5xx);
158
+ // 只有网络层失败(status=0)才算"够不着"。旧实现只认 429,403 全 0/4 时会掉进兜底 "mixed"
159
+ // 误判成「部分可用」(真机 2026-10-19 实测:4 个 v6 全 403 → 却报 0/4 部分可用)。
160
+ const v6Limited = v6.filter((r) => !r.ok && r.status);
137
161
  const v6Net = v6.filter((r) => !r.status);
138
162
 
139
163
  let verdict;
140
164
  const allOk = v6Ok.length === v6.length && v6.length > 0;
141
165
  if (allOk) verdict = "v6-works"; // 全部 v6 出口都有额度
142
- else if (v6Limited.length === v6.length) verdict = "v6-also-limited"; // 全部 429 ⇒ 没换到新额度
166
+ else if (v6Limited.length === v6.length) verdict = "v6-also-limited"; // 全部受限 ⇒ 没换到新额度
143
167
  else if (v6Net.length === v6.length) verdict = "v6-unreachable"; // 全部够不着 ⇒ 段没路由过来
144
168
  else verdict = "mixed"; // 有的成有的不成
145
169