Files
ModelRouter/tools/trae_derive_expected.mjs
JianFeeeee 9bec158d2e chore(main): 回流 release/v2.0.x(v2.0.12)—— 内容对齐,main 无独有内容
按发布纪律回流。**注意这是内容对齐而非 merge**,理由与依据:

1. 纪律禁止 merge 回主干,因为 merge 会把发布分支的**版本号变更**带进 main。
   本项目版本号**只由 `git describe --tags` 推导**(Makefile VERSION),
   仓库里没有任何文件存放版本号 ⇒ 回流不存在版本污染风险。
2. 逐项核对确认 **main 是 release 的内容严格子集**:
   - `main` 独有的**文件**:0 个;
   - `internal/lua/plugins.go` / `internal/core/kernel_actions.go` 等内核文件
     main 独有行数:0;
   - `deploy.sh` 那 14 行只是 emoji 清理(release 刻意去掉了 emoji);
   - `channels.lua` 那 333 行是**旧版内容**(旧 exports 表、旧模型名单)。
   - main 独有的 9 个提交按 patch-id **全部已在 release 中等效存在**
     (`git cherry` 逐条列出均为 `-`,即已合入)。
3. 故回流 = 把 release 的内容带到 main,等价于"只捡拾修复、不带版本号",
   且不会丢任何 main 独有的修复。

本次带回 v2.0.12 的全部内容(37 个提交):
- 源串行 + 按模型节流(照 model-gate)
- 已失效模型存储 + 远端目录闸门
- 余额分档选号
- 模型能力元数据(models.dev,isFree 权威判定)
- **凭据续期修复**(此前恒报 ok:true 却一个请求都不发)
- 通用件 TTL 缓存 + 串行锁;account_test/account_adopt/account_delete 修正
- 内核级:出站授权持久化重启后不生效、`__llmsproxy_plugin_dir` ground truth

校验:`git diff release/v2.0.x` = 0 行;`go build -tags luajit ./...` 通过。
2026-10-14 16:52:58 +08:00

75 lines
3.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 生成 Trae 设备身份派生的差分期望值(供 internal/core/channels_trae_derive_test.go 比对)。
//
// 为什么要有这个文件:channels.lua 里的 traeDeriveStream 是**手工移植**的
// SHA-256 确定性伪随机流(Lua 5.1 没有 node:crypto)。移植错了不会报错 ——
// 上游只会返回风控/失败,看起来像"运气不好"。所以拿参考实现(参考仓库
// vendor/channel-pack/src/trae.ts 的 seededStream/seededDigits)生成期望值,
// 再让 Lua 侧算同一组输入做差分。
//
// 用法(改了参考或改了移植后都要重跑):
// node tools/trae_derive_expected.mjs > internal/core/trae_derive_expected.json
//
// 注意:这里刻意**逐行照抄**参考的 seededStream,而不是从参考仓库 import ——
// 参考是 TS/ESM 且依赖其构建链,直接抄这 20 行更能保证"同一算法"。
import { createHash } from 'node:crypto'
/** 照抄 trae.ts:seededStream —— SHA256(utf8(salt:seed) ++ counterBE32) 串联。 */
function seededStream(seed, salt, nbytes) {
const prefix = `${salt}:${seed}`
const result = []
let counter = 0
while (result.length < nbytes) {
const counterBuf = new Uint8Array(4)
counterBuf[0] = (counter >> 24) & 0xff
counterBuf[1] = (counter >> 16) & 0xff
counterBuf[2] = (counter >> 8) & 0xff
counterBuf[3] = counter & 0xff
const h = createHash('sha256')
h.update(prefix, 'utf8')
h.update(counterBuf)
for (const b of h.digest()) {
result.push(b)
if (result.length >= nbytes) break
}
counter++
}
return result.slice(0, nbytes)
}
/** 照抄 trae.ts:seededDigits —— 每字节 % 10,拼成 n 位数字串。 */
function seededDigits(n, seed, salt) {
return seededStream(seed, salt, n).map((b) => b % 10).join('')
}
/** 照抄 trae.ts:deriveDeviceId15。 */
function deriveDeviceId15(userId) {
return seededDigits(15, userId, 'devid')
}
/** 照抄 trae.ts:deriveMarketUserId —— UUID v4 形状。 */
function deriveMarketUserId(userId) {
const bs = seededStream(userId, 'market', 16)
bs[6] = (bs[6] & 0x0f) | 0x40
bs[8] = (bs[8] & 0x3f) | 0x80
const hex = bs.map((b) => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
}
/** 照抄 trae.ts:deriveSessionId —— 64 位 hex。 */
function deriveSessionId(userId) {
return seededStream(userId, 'sess', 32).map((b) => b.toString(16).padStart(2, '0')).join('')
}
// 覆盖边界:纯数字 / 带连字符 / 空串(空串也要能算,不能崩)/ 多字节 UTF-8 /
// 超长(超过一个 SHA-256 块,逼出 counter 递增分支 —— 移植时最易错的地方)。
const ids = ['12345', 'UID-abc', '900000001', 'x', '', '用户1', '1234567890123456789012345']
const out = ids.map((u) => ({
u,
devid: deriveDeviceId15(u),
market: deriveMarketUserId(u),
sess: deriveSessionId(u),
}))
process.stdout.write(`${JSON.stringify(out, null, 1)}\n`)