@tokenbuddy/tokenbuddy 1.0.12 → 1.0.14
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/dist/src/buyer-store.d.ts +61 -0
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +12 -0
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts +47 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +287 -63
- package/dist/src/cli.js.map +1 -1
- package/dist/src/credit-tracker.d.ts +26 -0
- package/dist/src/credit-tracker.d.ts.map +1 -1
- package/dist/src/credit-tracker.js +8 -0
- package/dist/src/credit-tracker.js.map +1 -1
- package/dist/src/daemon.d.ts +29 -3
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +292 -65
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-clawtip-wallet.d.ts +25 -0
- package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -1
- package/dist/src/doctor-clawtip-wallet.js +13 -0
- package/dist/src/doctor-clawtip-wallet.js.map +1 -1
- package/dist/src/doctor-diagnostics.d.ts +63 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -1
- package/dist/src/doctor-diagnostics.js +39 -1
- package/dist/src/doctor-diagnostics.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/init-clawtip-activation.d.ts +103 -0
- package/dist/src/init-clawtip-activation.d.ts.map +1 -1
- package/dist/src/init-clawtip-activation.js +60 -0
- package/dist/src/init-clawtip-activation.js.map +1 -1
- package/dist/src/init-payment-options.d.ts +124 -0
- package/dist/src/init-payment-options.d.ts.map +1 -1
- package/dist/src/init-payment-options.js +68 -0
- package/dist/src/init-payment-options.js.map +1 -1
- package/dist/src/model-index.d.ts +9 -0
- package/dist/src/model-index.d.ts.map +1 -1
- package/dist/src/model-index.js.map +1 -1
- package/dist/src/prewarm-cache.d.ts +89 -0
- package/dist/src/prewarm-cache.d.ts.map +1 -1
- package/dist/src/prewarm-cache.js +14 -1
- package/dist/src/prewarm-cache.js.map +1 -1
- package/dist/src/prewarm-scheduler.d.ts +62 -3
- package/dist/src/prewarm-scheduler.d.ts.map +1 -1
- package/dist/src/prewarm-scheduler.js +39 -8
- package/dist/src/prewarm-scheduler.js.map +1 -1
- package/dist/src/provider-install.d.ts +89 -3
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +77 -19
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/route-failover.d.ts +48 -0
- package/dist/src/route-failover.d.ts.map +1 -1
- package/dist/src/route-failover.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +158 -10
- package/dist/src/seller-catalog.d.ts.map +1 -1
- package/dist/src/seller-catalog.js +79 -5
- package/dist/src/seller-catalog.js.map +1 -1
- package/dist/src/seller-metadata-cache.d.ts +29 -0
- package/dist/src/seller-metadata-cache.d.ts.map +1 -0
- package/dist/src/seller-metadata-cache.js +71 -0
- package/dist/src/seller-metadata-cache.js.map +1 -0
- package/dist/src/seller-pool.d.ts +71 -0
- package/dist/src/seller-pool.d.ts.map +1 -1
- package/dist/src/seller-pool.js +6 -1
- package/dist/src/seller-pool.js.map +1 -1
- package/dist/src/seller-route-planner.d.ts +118 -0
- package/dist/src/seller-route-planner.d.ts.map +1 -0
- package/dist/src/seller-route-planner.js +160 -0
- package/dist/src/seller-route-planner.js.map +1 -0
- package/dist/src/seller-routing-config.d.ts +69 -0
- package/dist/src/seller-routing-config.d.ts.map +1 -0
- package/dist/src/seller-routing-config.js +164 -0
- package/dist/src/seller-routing-config.js.map +1 -0
- package/dist/src/seller-routing-strategy.d.ts +118 -0
- package/dist/src/seller-routing-strategy.d.ts.map +1 -0
- package/dist/src/seller-routing-strategy.js +183 -0
- package/dist/src/seller-routing-strategy.js.map +1 -0
- package/dist/src/stream-failover.d.ts +23 -0
- package/dist/src/stream-failover.d.ts.map +1 -1
- package/dist/src/stream-failover.js +4 -0
- package/dist/src/stream-failover.js.map +1 -1
- package/dist/src/tb-proxyd.js +7 -21
- package/dist/src/tb-proxyd.js.map +1 -1
- package/dist/src/terminal-detect.d.ts +51 -0
- package/dist/src/terminal-detect.d.ts.map +1 -1
- package/dist/src/terminal-detect.js +42 -0
- package/dist/src/terminal-detect.js.map +1 -1
- package/dist/src/terminal-image.d.ts +41 -0
- package/dist/src/terminal-image.d.ts.map +1 -1
- package/dist/src/terminal-image.js +15 -0
- package/dist/src/terminal-image.js.map +1 -1
- package/package.json +1 -1
- package/src/buyer-store.ts +61 -0
- package/src/cli.ts +330 -68
- package/src/credit-tracker.ts +26 -0
- package/src/daemon.ts +363 -72
- package/src/doctor-clawtip-wallet.ts +25 -0
- package/src/doctor-diagnostics.ts +63 -1
- package/src/index.ts +4 -0
- package/src/init-clawtip-activation.ts +103 -0
- package/src/init-payment-options.ts +124 -0
- package/src/model-index.ts +9 -0
- package/src/prewarm-cache.ts +99 -1
- package/src/prewarm-scheduler.ts +97 -12
- package/src/provider-install.ts +125 -27
- package/src/route-failover.ts +48 -0
- package/src/seller-catalog.ts +158 -12
- package/src/seller-metadata-cache.ts +91 -0
- package/src/seller-pool.ts +77 -1
- package/src/seller-route-planner.ts +323 -0
- package/src/seller-routing-config.ts +198 -0
- package/src/seller-routing-strategy.ts +316 -0
- package/src/stream-failover.ts +23 -0
- package/src/tb-proxyd.ts +7 -23
- package/src/terminal-detect.ts +51 -0
- package/src/terminal-image.ts +41 -0
- package/tests/cli-routing.test.ts +287 -0
- package/tests/daemon-classify.test.ts +431 -0
- package/tests/daemon-roles.test.ts +92 -0
- package/tests/seller-catalog-utilities.test.ts +70 -0
- package/tests/seller-metadata-cache.test.ts +89 -0
- package/tests/seller-route-planner.test.ts +150 -0
- package/tests/seller-routing-config.test.ts +111 -0
- package/tests/seller-routing-strategy.test.ts +166 -0
- package/tests/tokenbuddy.test.ts +446 -34
- /package/{src → tests}/credit-tracker.test.ts +0 -0
- /package/{src → tests}/model-index.test.ts +0 -0
- /package/{src → tests}/prewarm-cache.test.ts +0 -0
- /package/{src → tests}/prewarm-scheduler.test.ts +0 -0
- /package/{src → tests}/route-failover.test.ts +0 -0
- /package/{src → tests}/seller-catalog-413.test.ts +0 -0
- /package/{src → tests}/seller-pool.test.ts +0 -0
- /package/{src → tests}/stream-failover.test.ts +0 -0
- /package/{src → tests}/thousand-seller.test.ts +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/** buyer 配置存盘时使用的顶层 key,对应 `BuyerStore.set("routing", ...)` */
|
|
2
|
+
export const ROUTING_CONFIG_KEY = "routing";
|
|
3
|
+
/** 缺省 scorer:健康 + 速度 + 折扣三方面均衡 */
|
|
4
|
+
export const DEFAULT_ROUTING_SCORER = "balanced";
|
|
5
|
+
/**
|
|
6
|
+
* 返回 buyer 默认的 seller 路由配置:`fullAuto` + `balanced`。
|
|
7
|
+
* 用于首次启动没有持久化配置时的兜底。
|
|
8
|
+
*
|
|
9
|
+
* @returns buyer 默认路由配置
|
|
10
|
+
*/
|
|
11
|
+
export function defaultSellerRoutingConfig() {
|
|
12
|
+
return {
|
|
13
|
+
mode: "fullAuto",
|
|
14
|
+
scorer: DEFAULT_ROUTING_SCORER
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 把任意 `unknown`(通常来自 buyer-store 读盘或外部输入)归一化为合法的
|
|
19
|
+
* `BuyerSellerRoutingConfig`。非法 `mode` / `scorer` 抛错,缺字段则回退到默认。
|
|
20
|
+
*
|
|
21
|
+
* @param value 任意输入值(对象 / undefined / 字符串等)
|
|
22
|
+
* @returns 归一化后的路由配置
|
|
23
|
+
* @throws 当 `value.mode` 是非 `fixed` / `fixedSet` / `fullAuto` 时
|
|
24
|
+
* @throws 当 `value.scorer` 是非 `speed` / `discount` / `balanced` 时
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeSellerRoutingConfig(value) {
|
|
27
|
+
if (!isObject(value)) {
|
|
28
|
+
return defaultSellerRoutingConfig();
|
|
29
|
+
}
|
|
30
|
+
const mode = readMode(value.mode);
|
|
31
|
+
const scorer = readScorer(value.scorer);
|
|
32
|
+
if (mode === "fixed") {
|
|
33
|
+
return {
|
|
34
|
+
mode,
|
|
35
|
+
sellerId: readOptionalString(value.sellerId),
|
|
36
|
+
scorer
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (mode === "fixedSet") {
|
|
40
|
+
return {
|
|
41
|
+
mode,
|
|
42
|
+
sellerIds: readSellerIds(value.sellerIds),
|
|
43
|
+
scorer
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
mode: "fullAuto",
|
|
48
|
+
scorer
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 从 `process.env`(可注入)里解析出 `TB_PROXYD_ROUTING_*` 覆盖项。
|
|
53
|
+
* 至少有一个变量非空时才返回 `Partial<BuyerSellerRoutingConfig>`,否则返回 `undefined`。
|
|
54
|
+
* 当 `mode` 未指定时,会根据 `sellerId` / `sellerIds` 是否存在自动选择 `fixed` / `fixedSet` / `fullAuto`。
|
|
55
|
+
*
|
|
56
|
+
* @param env 进程环境变量(默认 `process.env`,测试可注入)
|
|
57
|
+
* @returns 解析出的覆盖项;无任何相关变量时返回 `undefined`
|
|
58
|
+
* @throws 当 `TB_PROXYD_ROUTING_MODE` 或 `TB_PROXYD_ROUTING_SCORER` 取值非法时
|
|
59
|
+
*/
|
|
60
|
+
export function parseSellerRoutingEnv(env = process.env) {
|
|
61
|
+
const modeRaw = env.TB_PROXYD_ROUTING_MODE?.trim();
|
|
62
|
+
const scorerRaw = env.TB_PROXYD_ROUTING_SCORER?.trim();
|
|
63
|
+
const sellerId = env.TB_PROXYD_ROUTING_SELLER_ID?.trim();
|
|
64
|
+
const sellerIdsRaw = env.TB_PROXYD_ROUTING_SELLER_IDS?.trim();
|
|
65
|
+
if (!modeRaw && !scorerRaw && !sellerId && !sellerIdsRaw) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const mode = modeRaw
|
|
69
|
+
? readMode(modeRaw)
|
|
70
|
+
: sellerId
|
|
71
|
+
? "fixed"
|
|
72
|
+
: sellerIdsRaw
|
|
73
|
+
? "fixedSet"
|
|
74
|
+
: "fullAuto";
|
|
75
|
+
const scorer = scorerRaw ? readScorer(scorerRaw) : DEFAULT_ROUTING_SCORER;
|
|
76
|
+
if (mode === "fixed") {
|
|
77
|
+
return { mode, scorer, sellerId: sellerId || undefined };
|
|
78
|
+
}
|
|
79
|
+
if (mode === "fixedSet") {
|
|
80
|
+
return { mode, scorer, sellerIds: parseSellerIdList(sellerIdsRaw || "") };
|
|
81
|
+
}
|
|
82
|
+
return { mode: "fullAuto", scorer };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 合并"buyer-store 持久化值"和"运行时 override":override 字段优先,
|
|
86
|
+
* 但合并结果会再次走 `normalizeSellerRoutingConfig` 校验。
|
|
87
|
+
*
|
|
88
|
+
* @param stored buyer-store 读出的原始值(可能是 undefined 或非法对象)
|
|
89
|
+
* @param override 运行时 override(CLI / 临时开关)
|
|
90
|
+
* @returns 合并并归一化后的配置
|
|
91
|
+
* @throws 当合并结果包含非法 `mode` / `scorer` 时
|
|
92
|
+
*/
|
|
93
|
+
export function mergeSellerRoutingConfig(stored, override) {
|
|
94
|
+
const base = normalizeSellerRoutingConfig(stored);
|
|
95
|
+
if (!override) {
|
|
96
|
+
return base;
|
|
97
|
+
}
|
|
98
|
+
return normalizeSellerRoutingConfig({
|
|
99
|
+
...base,
|
|
100
|
+
...override
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 把逗号分隔的 seller ID 列表解析为去重、trim、过滤空串后的字符串数组。
|
|
105
|
+
*
|
|
106
|
+
* @param value 原始字符串,例:`"a,b,b, c,,"`
|
|
107
|
+
* @returns 去重后的 seller ID 列表
|
|
108
|
+
*/
|
|
109
|
+
export function parseSellerIdList(value) {
|
|
110
|
+
return value
|
|
111
|
+
.split(",")
|
|
112
|
+
.map((entry) => entry.trim())
|
|
113
|
+
.filter((entry, index, all) => entry.length > 0 && all.indexOf(entry) === index);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 校验 `BuyerSellerRoutingConfig` 是否满足当前模式的必要字段。
|
|
117
|
+
* `fixed` 模式必须有非空 `sellerId`;`fixedSet` 模式必须有非空 `sellerIds`。
|
|
118
|
+
*
|
|
119
|
+
* @param config 待校验的路由配置
|
|
120
|
+
* @throws 当 `fixed` 模式缺少 `sellerId` 时
|
|
121
|
+
* @throws 当 `fixedSet` 模式缺少 `sellerIds` 时
|
|
122
|
+
*/
|
|
123
|
+
export function assertSellerRoutingConfig(config) {
|
|
124
|
+
if (config.mode === "fixed" && !config.sellerId?.trim()) {
|
|
125
|
+
throw new Error("fixed routing requires --seller <sellerId>");
|
|
126
|
+
}
|
|
127
|
+
if (config.mode === "fixedSet" && (!config.sellerIds || config.sellerIds.length === 0)) {
|
|
128
|
+
throw new Error("fixedSet routing requires --seller-set <sellerId[,sellerId...]>");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function readMode(value) {
|
|
132
|
+
if (value === "fixed" || value === "fixedSet" || value === "fullAuto") {
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
throw new Error("seller routing mode must be fixed, fixedSet, or fullAuto");
|
|
136
|
+
}
|
|
137
|
+
function readScorer(value) {
|
|
138
|
+
if (value === undefined || value === null || value === "") {
|
|
139
|
+
return DEFAULT_ROUTING_SCORER;
|
|
140
|
+
}
|
|
141
|
+
if (value === "speed" || value === "discount" || value === "balanced") {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
throw new Error("seller routing scorer must be speed, discount, or balanced");
|
|
145
|
+
}
|
|
146
|
+
function readOptionalString(value) {
|
|
147
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
148
|
+
}
|
|
149
|
+
function readSellerIds(value) {
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
return value
|
|
152
|
+
.filter((entry) => typeof entry === "string")
|
|
153
|
+
.map((entry) => entry.trim())
|
|
154
|
+
.filter((entry, index, all) => entry.length > 0 && all.indexOf(entry) === index);
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === "string") {
|
|
157
|
+
return parseSellerIdList(value);
|
|
158
|
+
}
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
function isObject(value) {
|
|
162
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=seller-routing-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seller-routing-config.js","sourceRoot":"","sources":["../../src/seller-routing-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* seller 路由模式:
|
|
3
|
+
* - `fixed`:强制使用单个 seller(`sellerId`)
|
|
4
|
+
* - `fixedSet`:在指定 seller 集合内挑选
|
|
5
|
+
* - `fullAuto`:根据指标和 scorer 自动排序
|
|
6
|
+
*/
|
|
7
|
+
export type SellerRoutingMode = "fixed" | "fixedSet" | "fullAuto";
|
|
8
|
+
/**
|
|
9
|
+
* 评分器:决定如何把候选的健康/延迟/折扣分折算成总分。
|
|
10
|
+
* - `speed`:TTFT / 推理延迟优先
|
|
11
|
+
* - `discount`:折扣系数优先
|
|
12
|
+
* - `balanced`:三方面加权均衡
|
|
13
|
+
*/
|
|
14
|
+
export type SellerRoutingScorer = "speed" | "discount" | "balanced";
|
|
15
|
+
/**
|
|
16
|
+
* seller 路由策略配置:模式 + 可选的目标 seller 列表 + 可选评分器。
|
|
17
|
+
* 策略层不强制 `scorer` 必填(缺省走 `balanced`);buyer 配置层
|
|
18
|
+
* `BuyerSellerRoutingConfig` 会进一步收紧。
|
|
19
|
+
*/
|
|
20
|
+
export interface SellerRoutingStrategyConfig {
|
|
21
|
+
/** 路由模式 */
|
|
22
|
+
mode: SellerRoutingMode;
|
|
23
|
+
/** `fixed` 模式下唯一允许的 seller ID */
|
|
24
|
+
sellerId?: string;
|
|
25
|
+
/** `fixedSet` 模式下的候选 seller ID 列表(去重) */
|
|
26
|
+
sellerIds?: string[];
|
|
27
|
+
/** 评分器;省略时按 `balanced` 处理 */
|
|
28
|
+
scorer?: SellerRoutingScorer;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 路由规划阶段的中间数据结构:聚合了"兼容性 + 健康画像 + 折扣"。
|
|
32
|
+
* `supportsModel/Protocol/Payment` 由 `sellerSupports*` 系列函数计算,
|
|
33
|
+
* 任一为 `false` 的 candidate 都会被策略层过滤。
|
|
34
|
+
*/
|
|
35
|
+
export interface RoutingCandidate {
|
|
36
|
+
/** seller ID */
|
|
37
|
+
sellerId: string;
|
|
38
|
+
/** 去掉尾部斜杠的 seller URL */
|
|
39
|
+
url: string;
|
|
40
|
+
/** 是否在 seller 的 `models` 列表里 */
|
|
41
|
+
supportsModel: boolean;
|
|
42
|
+
/** 是否在 seller 的 `supportedProtocols` 列表里(含 `messages` alias) */
|
|
43
|
+
supportsProtocol: boolean;
|
|
44
|
+
/** 是否在 seller 的 `paymentMethods` 列表里 */
|
|
45
|
+
supportsPayment: boolean;
|
|
46
|
+
/** 综合健康分 0-100,可选 */
|
|
47
|
+
healthScore?: number;
|
|
48
|
+
/** 平均延迟(毫秒),可选 */
|
|
49
|
+
avgLatencyMs?: number;
|
|
50
|
+
/** health probe 延迟(毫秒),可选 */
|
|
51
|
+
healthProbeLatencyMs?: number;
|
|
52
|
+
/** TTFT(毫秒),可选 */
|
|
53
|
+
ttftMs?: number;
|
|
54
|
+
/** 平均推理延迟(毫秒),可选 */
|
|
55
|
+
avgInferenceMs?: number;
|
|
56
|
+
/** 折扣系数 0-1,可选;缺省视为"无折扣信息" */
|
|
57
|
+
discountRatio?: number;
|
|
58
|
+
/** 上游状态,可选 */
|
|
59
|
+
upstreamStatus?: "healthy" | "degraded" | "unhealthy" | "unknown";
|
|
60
|
+
/** 上游错误类名,可选 */
|
|
61
|
+
upstreamErrorClass?: string;
|
|
62
|
+
/** 在 registry 里的声明顺序(0-based),用于打分平局时的 deterministic 兜底 */
|
|
63
|
+
registryOrder: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `planSellerRoutes()` 的输出:选中的候选列表(已按 scorer 排序)+ 决策溯源。
|
|
67
|
+
*/
|
|
68
|
+
export interface SellerRoutingPlan {
|
|
69
|
+
/** 已按策略排序的候选 seller 列表 */
|
|
70
|
+
routes: RoutingCandidate[];
|
|
71
|
+
/** 路由模式(来自 config) */
|
|
72
|
+
mode: SellerRoutingMode;
|
|
73
|
+
/** 实际生效的评分器(config 缺省时为 `balanced`) */
|
|
74
|
+
scorer: SellerRoutingScorer;
|
|
75
|
+
/** 决策原因,例:`fullAuto:balanced:routes_3`、`fixed_seller_not_compatible` */
|
|
76
|
+
reason: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 单个 candidate 在某 scorer 下的打分拆解:总分 + 各维度分量 + 缺失的输入。
|
|
80
|
+
* 用于路由诊断 / `tb doctor` 面板展示。
|
|
81
|
+
*/
|
|
82
|
+
export interface CandidateScoreBreakdown {
|
|
83
|
+
/** 评分器 */
|
|
84
|
+
scorer: SellerRoutingScorer;
|
|
85
|
+
/** 总分(按 scorer 权重加和) */
|
|
86
|
+
totalScore: number;
|
|
87
|
+
/** 健康分量(仅 `speed` / `balanced` 有意义) */
|
|
88
|
+
healthComponent?: number;
|
|
89
|
+
/** TTFT 分量(仅 `speed` / `balanced` 有意义) */
|
|
90
|
+
ttftComponent?: number;
|
|
91
|
+
/** 平均推理延迟分量(仅 `speed` / `balanced` 有意义) */
|
|
92
|
+
avgInferenceComponent?: number;
|
|
93
|
+
/** 折扣分量(仅 `discount` / `balanced` 有意义) */
|
|
94
|
+
discountComponent?: number;
|
|
95
|
+
/** 打分时缺失的输入项;缺越多则越说明"无依据" */
|
|
96
|
+
missingInputs: Array<"healthScore" | "ttftMs" | "avgInferenceMs" | "discountRatio">;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 根据 `config.mode` 和 `config.scorer` 把候选 seller 过滤、排序并返回路由计划。
|
|
100
|
+
* - `fixed` / `fixedSet` 模式只保留命中的 seller(命中为空时返回空 routes + 决策原因)
|
|
101
|
+
* - `fullAuto` 模式使用所有兼容候选
|
|
102
|
+
* - 排序时按 scorer 计算分数,分数相同时按 `registryOrder` 兜底
|
|
103
|
+
*
|
|
104
|
+
* @param candidates 候选列表(已包含兼容性布尔位)
|
|
105
|
+
* @param config 路由策略配置
|
|
106
|
+
* @returns 路由计划,含 routes / mode / scorer / reason
|
|
107
|
+
*/
|
|
108
|
+
export declare function planSellerRoutes(candidates: RoutingCandidate[], config: SellerRoutingStrategyConfig): SellerRoutingPlan;
|
|
109
|
+
/**
|
|
110
|
+
* 计算单个 candidate 在指定 scorer 下的完整打分拆解(含各维度分量和缺失项)。
|
|
111
|
+
* 不会修改输入 candidate,常用于 doctor 面板和调试日志。
|
|
112
|
+
*
|
|
113
|
+
* @param candidate 待打分的候选
|
|
114
|
+
* @param scorer 评分器:`speed` / `discount` / `balanced`
|
|
115
|
+
* @returns 打分拆解
|
|
116
|
+
*/
|
|
117
|
+
export declare function scoreCandidateBreakdown(candidate: RoutingCandidate, scorer: SellerRoutingScorer): CandidateScoreBreakdown;
|
|
118
|
+
//# sourceMappingURL=seller-routing-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seller-routing-strategy.d.ts","sourceRoot":"","sources":["../../src/seller-routing-strategy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAClE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,WAAW;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wCAAwC;IACxC,eAAe,EAAE,OAAO,CAAC;IACzB,qBAAqB;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,cAAc,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;IAClE,gBAAgB;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,sBAAsB;IACtB,IAAI,EAAE,iBAAiB,CAAC;IACxB,uCAAuC;IACvC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,UAAU;IACV,MAAM,EAAE,mBAAmB,CAAC;IAC5B,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6BAA6B;IAC7B,aAAa,EAAE,KAAK,CAAC,aAAa,GAAG,QAAQ,GAAG,gBAAgB,GAAG,eAAe,CAAC,CAAC;CACrF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,gBAAgB,EAAE,EAC9B,MAAM,EAAE,2BAA2B,GAClC,iBAAiB,CAoBnB;AA8ED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,GAAG,uBAAuB,CAuCzH"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const DEFAULT_SCORER = "balanced";
|
|
2
|
+
/**
|
|
3
|
+
* 根据 `config.mode` 和 `config.scorer` 把候选 seller 过滤、排序并返回路由计划。
|
|
4
|
+
* - `fixed` / `fixedSet` 模式只保留命中的 seller(命中为空时返回空 routes + 决策原因)
|
|
5
|
+
* - `fullAuto` 模式使用所有兼容候选
|
|
6
|
+
* - 排序时按 scorer 计算分数,分数相同时按 `registryOrder` 兜底
|
|
7
|
+
*
|
|
8
|
+
* @param candidates 候选列表(已包含兼容性布尔位)
|
|
9
|
+
* @param config 路由策略配置
|
|
10
|
+
* @returns 路由计划,含 routes / mode / scorer / reason
|
|
11
|
+
*/
|
|
12
|
+
export function planSellerRoutes(candidates, config) {
|
|
13
|
+
const scorer = config.scorer ?? DEFAULT_SCORER;
|
|
14
|
+
const compatible = candidates.filter(isCompatibleCandidate);
|
|
15
|
+
const selected = selectCandidates(compatible, config);
|
|
16
|
+
if (selected.reason !== "ok") {
|
|
17
|
+
return {
|
|
18
|
+
routes: [],
|
|
19
|
+
mode: config.mode,
|
|
20
|
+
scorer,
|
|
21
|
+
reason: selected.reason
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
routes: sortCandidates(selected.candidates, scorer),
|
|
26
|
+
mode: config.mode,
|
|
27
|
+
scorer,
|
|
28
|
+
reason: routeReason(config.mode, scorer, selected.candidates.length)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function selectCandidates(compatible, config) {
|
|
32
|
+
if (config.mode === "fixed") {
|
|
33
|
+
const sellerId = normalizeSellerId(config.sellerId);
|
|
34
|
+
if (!sellerId) {
|
|
35
|
+
return { candidates: [], reason: "fixed_seller_missing" };
|
|
36
|
+
}
|
|
37
|
+
const matched = compatible.filter((candidate) => normalizeSellerId(candidate.sellerId) === sellerId);
|
|
38
|
+
return matched.length > 0
|
|
39
|
+
? { candidates: matched, reason: "ok" }
|
|
40
|
+
: { candidates: [], reason: "fixed_seller_not_compatible" };
|
|
41
|
+
}
|
|
42
|
+
if (config.mode === "fixedSet") {
|
|
43
|
+
const sellerIds = normalizedSellerIdSet(config.sellerIds);
|
|
44
|
+
if (sellerIds.size === 0) {
|
|
45
|
+
return { candidates: [], reason: "fixed_set_empty" };
|
|
46
|
+
}
|
|
47
|
+
const matched = compatible.filter((candidate) => sellerIds.has(normalizeSellerId(candidate.sellerId)));
|
|
48
|
+
return matched.length > 0
|
|
49
|
+
? { candidates: matched, reason: "ok" }
|
|
50
|
+
: { candidates: [], reason: "fixed_set_no_compatible_seller" };
|
|
51
|
+
}
|
|
52
|
+
if (config.mode === "fullAuto") {
|
|
53
|
+
return compatible.length > 0
|
|
54
|
+
? { candidates: compatible, reason: "ok" }
|
|
55
|
+
: { candidates: [], reason: "no_compatible_seller" };
|
|
56
|
+
}
|
|
57
|
+
return { candidates: [], reason: "unsupported_routing_mode" };
|
|
58
|
+
}
|
|
59
|
+
function isCompatibleCandidate(candidate) {
|
|
60
|
+
return candidate.supportsModel && candidate.supportsProtocol && candidate.supportsPayment;
|
|
61
|
+
}
|
|
62
|
+
function normalizedSellerIdSet(ids) {
|
|
63
|
+
return new Set((ids ?? []).map(normalizeSellerId).filter(Boolean));
|
|
64
|
+
}
|
|
65
|
+
function normalizeSellerId(id) {
|
|
66
|
+
return id?.trim().toLowerCase() ?? "";
|
|
67
|
+
}
|
|
68
|
+
function sortCandidates(candidates, scorer) {
|
|
69
|
+
return candidates
|
|
70
|
+
.map((candidate) => ({ ...candidate, score: scoreCandidateBreakdown(candidate, scorer).totalScore }))
|
|
71
|
+
.sort((a, b) => compareCandidates(a, b, scorer))
|
|
72
|
+
.map(({ score: _score, ...candidate }) => candidate);
|
|
73
|
+
}
|
|
74
|
+
function compareCandidates(a, b, scorer) {
|
|
75
|
+
const scoreDiff = b.score - a.score;
|
|
76
|
+
if (scoreDiff !== 0) {
|
|
77
|
+
return scoreDiff;
|
|
78
|
+
}
|
|
79
|
+
if (scorer === "speed") {
|
|
80
|
+
return compareFiniteAsc(effectiveTtftMs(a), effectiveTtftMs(b))
|
|
81
|
+
|| compareFiniteAsc(effectiveAvgInferenceMs(a), effectiveAvgInferenceMs(b))
|
|
82
|
+
|| compareFiniteDesc(a.healthScore, b.healthScore)
|
|
83
|
+
|| compareRegistryOrder(a, b);
|
|
84
|
+
}
|
|
85
|
+
if (scorer === "discount") {
|
|
86
|
+
return compareFiniteAsc(a.discountRatio, b.discountRatio)
|
|
87
|
+
|| compareFiniteDesc(a.healthScore, b.healthScore)
|
|
88
|
+
|| compareRegistryOrder(a, b);
|
|
89
|
+
}
|
|
90
|
+
return compareRegistryOrder(a, b);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 计算单个 candidate 在指定 scorer 下的完整打分拆解(含各维度分量和缺失项)。
|
|
94
|
+
* 不会修改输入 candidate,常用于 doctor 面板和调试日志。
|
|
95
|
+
*
|
|
96
|
+
* @param candidate 待打分的候选
|
|
97
|
+
* @param scorer 评分器:`speed` / `discount` / `balanced`
|
|
98
|
+
* @returns 打分拆解
|
|
99
|
+
*/
|
|
100
|
+
export function scoreCandidateBreakdown(candidate, scorer) {
|
|
101
|
+
const missingInputs = missingScoreInputs(candidate);
|
|
102
|
+
if (scorer === "speed") {
|
|
103
|
+
const ttftComponent = latencyScore(effectiveTtftMs(candidate)) * 0.65;
|
|
104
|
+
const avgInferenceComponent = latencyScore(effectiveAvgInferenceMs(candidate)) * 0.25;
|
|
105
|
+
const healthComponent = finiteOr(candidate.healthScore, 0) * 0.1;
|
|
106
|
+
return {
|
|
107
|
+
scorer,
|
|
108
|
+
totalScore: ttftComponent + avgInferenceComponent + healthComponent,
|
|
109
|
+
healthComponent,
|
|
110
|
+
ttftComponent,
|
|
111
|
+
avgInferenceComponent,
|
|
112
|
+
missingInputs
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (scorer === "discount") {
|
|
116
|
+
const totalScore = -finiteOr(candidate.discountRatio, Number.POSITIVE_INFINITY);
|
|
117
|
+
return {
|
|
118
|
+
scorer,
|
|
119
|
+
totalScore,
|
|
120
|
+
discountComponent: totalScore,
|
|
121
|
+
missingInputs
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const healthComponent = finiteOr(candidate.healthScore, 0) * 0.35;
|
|
125
|
+
const ttftComponent = latencyScore(effectiveTtftMs(candidate)) * 0.2;
|
|
126
|
+
const avgInferenceComponent = latencyScore(effectiveAvgInferenceMs(candidate)) * 0.2;
|
|
127
|
+
const discountComponent = discountScore(candidate.discountRatio) * 0.25;
|
|
128
|
+
return {
|
|
129
|
+
scorer,
|
|
130
|
+
totalScore: healthComponent + ttftComponent + avgInferenceComponent + discountComponent,
|
|
131
|
+
healthComponent,
|
|
132
|
+
ttftComponent,
|
|
133
|
+
avgInferenceComponent,
|
|
134
|
+
discountComponent,
|
|
135
|
+
missingInputs
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function latencyScore(latencyMs) {
|
|
139
|
+
if (!Number.isFinite(latencyMs)) {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
return Math.max(0, 100 - Math.max(0, latencyMs) / 10);
|
|
143
|
+
}
|
|
144
|
+
function discountScore(discountRatio) {
|
|
145
|
+
if (!Number.isFinite(discountRatio)) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
return Math.max(0, 100 * (1 - Math.max(0, discountRatio)));
|
|
149
|
+
}
|
|
150
|
+
function finiteOr(value, fallback) {
|
|
151
|
+
return Number.isFinite(value) ? value : fallback;
|
|
152
|
+
}
|
|
153
|
+
function compareFiniteAsc(a, b) {
|
|
154
|
+
return finiteOr(a, Number.POSITIVE_INFINITY) - finiteOr(b, Number.POSITIVE_INFINITY);
|
|
155
|
+
}
|
|
156
|
+
function compareFiniteDesc(a, b) {
|
|
157
|
+
return finiteOr(b, Number.NEGATIVE_INFINITY) - finiteOr(a, Number.NEGATIVE_INFINITY);
|
|
158
|
+
}
|
|
159
|
+
function effectiveTtftMs(candidate) {
|
|
160
|
+
return candidate.ttftMs ?? candidate.healthProbeLatencyMs ?? candidate.avgLatencyMs;
|
|
161
|
+
}
|
|
162
|
+
function effectiveAvgInferenceMs(candidate) {
|
|
163
|
+
return candidate.avgInferenceMs ?? candidate.avgLatencyMs ?? candidate.healthProbeLatencyMs;
|
|
164
|
+
}
|
|
165
|
+
function compareRegistryOrder(a, b) {
|
|
166
|
+
return a.registryOrder - b.registryOrder;
|
|
167
|
+
}
|
|
168
|
+
function routeReason(mode, scorer, count) {
|
|
169
|
+
return `${mode}:${scorer}:routes_${count}`;
|
|
170
|
+
}
|
|
171
|
+
function missingScoreInputs(candidate) {
|
|
172
|
+
const missing = [];
|
|
173
|
+
if (!Number.isFinite(candidate.healthScore))
|
|
174
|
+
missing.push("healthScore");
|
|
175
|
+
if (!Number.isFinite(candidate.ttftMs))
|
|
176
|
+
missing.push("ttftMs");
|
|
177
|
+
if (!Number.isFinite(candidate.avgInferenceMs))
|
|
178
|
+
missing.push("avgInferenceMs");
|
|
179
|
+
if (!Number.isFinite(candidate.discountRatio))
|
|
180
|
+
missing.push("discountRatio");
|
|
181
|
+
return missing;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=seller-routing-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seller-routing-strategy.js","sourceRoot":"","sources":["../../src/seller-routing-strategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,17 +12,40 @@
|
|
|
12
12
|
* doc) because that would double-charge and would require non-trivial
|
|
13
13
|
* idempotency re-design. v1.2 = abort + retry; v2 may revisit.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* v1.2 §6 / §18.10:stream-failover 策略。
|
|
17
|
+
* buyer 端遵循"abort + client retry"契约:一旦首字节 SSE 写入客户端,上游流失败只能 abrupt close
|
|
18
|
+
* 并附带 `X-TokenBuddy-Retry-Hint: 1` trailer;由 OpenAI / Anthropic SDK 重新发起请求,
|
|
19
|
+
* buyer 切到健康的 seller 重发。
|
|
20
|
+
*
|
|
21
|
+
* 该模块的决策是单向的:buyer 不会把两条流拼接(design doc 里的 option B),
|
|
22
|
+
* 因为会双重计费且需要重做幂等。v1.2 = abort + retry;v2 再考虑。
|
|
23
|
+
*/
|
|
15
24
|
export interface StreamFailoverOptions {
|
|
25
|
+
/** abort 响应中携带的 retry hint 响应头名,默认 `X-TokenBuddy-Retry-Hint` */
|
|
16
26
|
retryHintHeader?: string;
|
|
27
|
+
/** 时间源,默认 `Date.now`;测试可注入 */
|
|
17
28
|
now?: () => number;
|
|
18
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* `StreamFailover.decideOnStreamAbort` 的返回结果。
|
|
32
|
+
*/
|
|
19
33
|
export interface StreamFailoverDecision {
|
|
34
|
+
/** 决策动作:abort_with_retry_hint(已写首字节,abrupt close) / let_stream_complete(路由层可换 seller) */
|
|
20
35
|
action: "abort_with_retry_hint" | "let_stream_complete";
|
|
36
|
+
/** 决策原因(用于日志和稳定事件断言) */
|
|
21
37
|
reason: string;
|
|
38
|
+
/** retry hint 头的取值(`"0"` 或 `"1"`) */
|
|
22
39
|
retryHintValue: string;
|
|
40
|
+
/** 决策时是否已写过首字节(决策的输入状态) */
|
|
23
41
|
firstChunkCommitted: boolean;
|
|
42
|
+
/** 决策时已写入客户端的字节数 */
|
|
24
43
|
bytesFlushed: number;
|
|
25
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* buyer 端 SSE 流失败决策器。
|
|
47
|
+
* 跟踪"首字节是否已写客户端"和"累计写入字节数",在 upstream 中断时决定是 abort+retry 还是切 seller 重试。
|
|
48
|
+
*/
|
|
26
49
|
export declare class StreamFailover {
|
|
27
50
|
private readonly retryHintHeader;
|
|
28
51
|
private readonly now;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-failover.d.ts","sourceRoot":"","sources":["../../src/stream-failover.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,uBAAuB,GAAG,qBAAqB,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,YAAY,CAAK;gBAEb,OAAO,GAAE,qBAA0B;IAK/C;;;;;OAKG;IACH,uBAAuB,IAAI,IAAI;IAO/B;;;OAGG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvC;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB;IAuB3D;;;;OAIG;IACH,QAAQ,IAAI;QAAE,mBAAmB,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAOlE;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAKb;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;CACF;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"stream-failover.d.ts","sourceRoot":"","sources":["../../src/stream-failover.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,MAAM,EAAE,uBAAuB,GAAG,qBAAqB,CAAC;IACxD,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,2BAA2B;IAC3B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,YAAY,CAAK;gBAEb,OAAO,GAAE,qBAA0B;IAK/C;;;;;OAKG;IACH,uBAAuB,IAAI,IAAI;IAO/B;;;OAGG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvC;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB;IAuB3D;;;;OAIG;IACH,QAAQ,IAAI;QAAE,mBAAmB,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAOlE;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAKb;;;;OAIG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;CACF;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,MAAM,CAAC"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createModuleLogger } from "@tokenbuddy/logging";
|
|
2
2
|
const logger = createModuleLogger("tb-proxyd:stream-failover");
|
|
3
|
+
/**
|
|
4
|
+
* buyer 端 SSE 流失败决策器。
|
|
5
|
+
* 跟踪"首字节是否已写客户端"和"累计写入字节数",在 upstream 中断时决定是 abort+retry 还是切 seller 重试。
|
|
6
|
+
*/
|
|
3
7
|
export class StreamFailover {
|
|
4
8
|
retryHintHeader;
|
|
5
9
|
now;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-failover.js","sourceRoot":"","sources":["../../src/stream-failover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,2BAA2B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"stream-failover.js","sourceRoot":"","sources":["../../src/stream-failover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,2BAA2B,CAAC,CAAC;AAgD/D;;;GAGG;AACH,MAAM,OAAO,cAAc;IACR,eAAe,CAAS;IACxB,GAAG,CAAe;IAC3B,mBAAmB,GAAG,KAAK,CAAC;IAC5B,YAAY,GAAG,CAAC,CAAC;IAEzB,YAAY,UAAiC,EAAE;QAC7C,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,yBAAyB,CAAC;QAC5E,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,uBAAuB;QACrB,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAAC,KAAa;QAC9B,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,MAAc;QAChC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9B,OAAO;gBACL,MAAM,EAAE,qBAAqB;gBAC7B,MAAM,EAAE,yBAAyB;gBACjC,cAAc,EAAE,GAAG;gBACnB,mBAAmB,EAAE,KAAK;gBAC1B,YAAY,EAAE,CAAC;aAChB,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,0EAA0E,EAAE;YACjH,MAAM;YACN,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;QACH,OAAO;YACL,MAAM,EAAE,uBAAuB;YAC/B,MAAM;YACN,cAAc,EAAE,GAAG;YACnB,mBAAmB,EAAE,IAAI;YACzB,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,QAAQ;QACN,OAAO;YACL,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;YAC7C,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK;QACH,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;QACjC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACH,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC"}
|
package/dist/src/tb-proxyd.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { TokenbuddyDaemon } from "./daemon.js";
|
|
2
2
|
import { createModuleLogger } from "@tokenbuddy/logging";
|
|
3
3
|
import { resolveBuyerStorePath } from "./buyer-store.js";
|
|
4
|
+
import { normalizeSellerRoutingConfig, parseSellerRoutingEnv } from "./seller-routing-config.js";
|
|
4
5
|
const logger = createModuleLogger("tb-proxyd");
|
|
5
6
|
const dbPath = resolveBuyerStorePath();
|
|
6
7
|
const controlPort = parsePortEnv("TB_PROXYD_CONTROL_PORT", 17820);
|
|
7
8
|
const proxyPort = parsePortEnv("TB_PROXYD_PROXY_PORT", 17821);
|
|
8
9
|
const sellerRegistryUrl = process.env.TB_PROXYD_SELLER_REGISTRY_URL || "https://tb-wallet-bootstrap.fly.dev/registry/sellers";
|
|
9
|
-
const
|
|
10
|
-
const
|
|
10
|
+
const sellerRoutingEnv = parseSellerRoutingEnv();
|
|
11
|
+
const sellerRouting = sellerRoutingEnv ? normalizeSellerRoutingConfig(sellerRoutingEnv) : undefined;
|
|
11
12
|
const daemon = new TokenbuddyDaemon({
|
|
12
13
|
controlPort,
|
|
13
14
|
proxyPort,
|
|
14
15
|
dbPath,
|
|
15
16
|
sellerRegistryUrl,
|
|
16
|
-
|
|
17
|
-
selectedSellerId
|
|
17
|
+
...(sellerRouting ? { sellerRouting } : {})
|
|
18
18
|
});
|
|
19
19
|
logger.info("proxy.process.initializing", "tb-proxyd process initializing", {
|
|
20
20
|
dbPath,
|
|
21
21
|
controlPort,
|
|
22
22
|
proxyPort,
|
|
23
23
|
sellerRegistryUrl,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
sellerRoutingEnvOverride: Boolean(sellerRouting),
|
|
25
|
+
sellerRoutingMode: sellerRouting?.mode,
|
|
26
|
+
sellerRoutingScorer: sellerRouting?.scorer
|
|
26
27
|
});
|
|
27
28
|
daemon.start();
|
|
28
29
|
// Handle graceful stop
|
|
@@ -47,19 +48,4 @@ function parsePortEnv(name, fallback) {
|
|
|
47
48
|
}
|
|
48
49
|
return port;
|
|
49
50
|
}
|
|
50
|
-
function parseSelectionModeEnv() {
|
|
51
|
-
const rawValue = process.env.TB_PROXYD_SELECTION_MODE || "auto";
|
|
52
|
-
if (rawValue === "auto" || rawValue === "manual") {
|
|
53
|
-
return rawValue;
|
|
54
|
-
}
|
|
55
|
-
throw new Error("TB_PROXYD_SELECTION_MODE must be auto or manual");
|
|
56
|
-
}
|
|
57
|
-
function parseSelectedSellerIdEnv() {
|
|
58
|
-
const rawValue = process.env.TB_PROXYD_SELECTED_SELLER_ID;
|
|
59
|
-
if (!rawValue) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
const trimmed = rawValue.trim();
|
|
63
|
-
return trimmed || undefined;
|
|
64
|
-
}
|
|
65
51
|
//# sourceMappingURL=tb-proxyd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tb-proxyd.js","sourceRoot":"","sources":["../../src/tb-proxyd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tb-proxyd.js","sourceRoot":"","sources":["../../src/tb-proxyd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEjG,MAAM,MAAM,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;AAE/C,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;AACvC,MAAM,WAAW,GAAG,YAAY,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;AAClE,MAAM,SAAS,GAAG,YAAY,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;AAC9D,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,IAAI,sDAAsD,CAAC;AAC9H,MAAM,gBAAgB,GAAG,qBAAqB,EAAE,CAAC;AACjD,MAAM,aAAa,GAAG,gBAAgB,CAAC,CAAC,CAAC,4BAA4B,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAEpG,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC;IAClC,WAAW;IACX,SAAS;IACT,MAAM;IACN,iBAAiB;IACjB,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CAC5C,CAAC,CAAC;AAEH,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,gCAAgC,EAAE;IAC1E,MAAM;IACN,WAAW;IACX,SAAS;IACT,iBAAiB;IACjB,wBAAwB,EAAE,OAAO,CAAC,aAAa,CAAC;IAChD,iBAAiB,EAAE,aAAa,EAAE,IAAI;IACtC,mBAAmB,EAAE,aAAa,EAAE,MAAM;CAC3C,CAAC,CAAC;AACH,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,uBAAuB;AACvB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,oCAAoC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3F,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,oCAAoC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1F,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,SAAS,YAAY,CAAC,IAAY,EAAE,QAAgB;IAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,8CAA8C,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|