@tokenbuddy/tokenbuddy 1.0.14 → 1.0.16
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 +23 -0
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +31 -6
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/clawtip-bootstrap.d.ts +23 -0
- package/dist/src/clawtip-bootstrap.d.ts.map +1 -0
- package/dist/src/clawtip-bootstrap.js +47 -0
- package/dist/src/clawtip-bootstrap.js.map +1 -0
- package/dist/src/cli.d.ts +24 -33
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +157 -58
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +79 -1
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +1007 -23
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/model-index.d.ts +1 -1
- package/dist/src/model-index.d.ts.map +1 -1
- package/dist/src/model-index.js +4 -0
- package/dist/src/model-index.js.map +1 -1
- package/dist/src/prewarm-cache.d.ts +4 -0
- package/dist/src/prewarm-cache.d.ts.map +1 -1
- package/dist/src/prewarm-cache.js +2 -1
- package/dist/src/prewarm-cache.js.map +1 -1
- package/dist/src/prewarm-scheduler.d.ts +2 -0
- package/dist/src/prewarm-scheduler.d.ts.map +1 -1
- package/dist/src/prewarm-scheduler.js +4 -2
- package/dist/src/prewarm-scheduler.js.map +1 -1
- package/dist/src/route-failover.d.ts.map +1 -1
- package/dist/src/route-failover.js +10 -0
- package/dist/src/route-failover.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +17 -0
- package/dist/src/seller-catalog.d.ts.map +1 -1
- package/dist/src/seller-catalog.js +15 -1
- package/dist/src/seller-catalog.js.map +1 -1
- package/dist/src/seller-pool.d.ts +12 -1
- package/dist/src/seller-pool.d.ts.map +1 -1
- package/dist/src/seller-pool.js +61 -7
- package/dist/src/seller-pool.js.map +1 -1
- package/dist/src/seller-route-planner.d.ts +11 -1
- package/dist/src/seller-route-planner.d.ts.map +1 -1
- package/dist/src/seller-route-planner.js +21 -9
- package/dist/src/seller-route-planner.js.map +1 -1
- package/dist/src/seller-routing-config.d.ts +2 -0
- package/dist/src/seller-routing-config.d.ts.map +1 -1
- package/dist/src/seller-routing-config.js +11 -1
- package/dist/src/seller-routing-config.js.map +1 -1
- package/package.json +1 -1
- package/src/buyer-store.ts +70 -7
- package/src/clawtip-bootstrap.ts +64 -0
- package/src/cli.ts +201 -76
- package/src/daemon.ts +1159 -25
- package/src/model-index.ts +4 -1
- package/src/prewarm-cache.ts +6 -1
- package/src/prewarm-scheduler.ts +6 -2
- package/src/route-failover.ts +11 -0
- package/src/seller-catalog.ts +24 -1
- package/src/seller-pool.ts +69 -7
- package/src/seller-route-planner.ts +33 -11
- package/src/seller-routing-config.ts +14 -1
- package/static/clawtip/recharge.png +0 -0
- package/static/ui/assets/index-UMiTTeo8.css +1 -0
- package/static/ui/assets/index-YHs-Ca0f.js +206 -0
- package/static/ui/assets/index-YHs-Ca0f.js.map +1 -0
- package/static/ui/icons/apple-touch-icon.png +0 -0
- package/static/ui/icons/tokenbuddy-192.png +0 -0
- package/static/ui/icons/tokenbuddy-512.png +0 -0
- package/static/ui/index.html +21 -0
- package/static/ui/manifest.webmanifest +28 -0
- package/static/ui/sw.js +59 -0
- package/tests/control-plane-ui-endpoints.test.ts +589 -0
- package/tests/daemon-classify.test.ts +9 -0
- package/tests/model-index.test.ts +14 -0
- package/tests/route-failover.test.ts +16 -0
- package/tests/seller-catalog-utilities.test.ts +54 -0
- package/tests/seller-pool.test.ts +56 -0
- package/tests/seller-route-planner.test.ts +40 -0
- package/tests/seller-routing-config.test.ts +13 -0
- package/tests/tokenbuddy.test.ts +200 -7
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBuyerVisibleRegistrySeller } from "./seller-catalog.js";
|
|
1
2
|
import { planSellerRoutes } from "./seller-routing-strategy.js";
|
|
2
3
|
/**
|
|
3
4
|
* 给定 (model, protocol, payment) + 三类数据源,规划出有序的 seller 路由列表。
|
|
@@ -13,7 +14,7 @@ import { planSellerRoutes } from "./seller-routing-strategy.js";
|
|
|
13
14
|
*/
|
|
14
15
|
export function planSellerRouteSet(input) {
|
|
15
16
|
const indexed = indexRegistrySellers(input.registrySellers);
|
|
16
|
-
const metrics = indexMetrics(input.sellerMetrics);
|
|
17
|
+
const metrics = indexMetrics(input.sellerMetrics, input.now ?? Date.now());
|
|
17
18
|
const source = chooseCandidateSource(input, indexed, metrics);
|
|
18
19
|
const strategyPlan = planSellerRoutes(source.candidates, input.routing);
|
|
19
20
|
const routes = strategyPlan.routes.map((candidate) => {
|
|
@@ -27,6 +28,8 @@ export function planSellerRouteSet(input) {
|
|
|
27
28
|
metrics: {
|
|
28
29
|
healthScore: candidate.healthScore,
|
|
29
30
|
avgLatencyMs: candidate.avgLatencyMs,
|
|
31
|
+
ttftMs: candidate.ttftMs,
|
|
32
|
+
avgInferenceMs: candidate.avgInferenceMs,
|
|
30
33
|
discountRatio: candidate.discountRatio,
|
|
31
34
|
registryOrder: candidate.registryOrder
|
|
32
35
|
}
|
|
@@ -51,7 +54,7 @@ function chooseCandidateSource(input, indexed, metrics) {
|
|
|
51
54
|
if (!indexedSeller) {
|
|
52
55
|
return undefined;
|
|
53
56
|
}
|
|
54
|
-
if (metrics.
|
|
57
|
+
if (metrics.blockedSellerIds.has(indexedSeller.seller.id)) {
|
|
55
58
|
return undefined;
|
|
56
59
|
}
|
|
57
60
|
return buildCandidate({
|
|
@@ -77,7 +80,7 @@ function chooseCandidateSource(input, indexed, metrics) {
|
|
|
77
80
|
source: "registry_fallback",
|
|
78
81
|
sourceReason: prewarm.length > 0 ? "prewarm_no_compatible_candidates" : "prewarm_missing",
|
|
79
82
|
candidates: indexed.ordered
|
|
80
|
-
.filter((entry) => !metrics.
|
|
83
|
+
.filter((entry) => !metrics.blockedSellerIds.has(entry.seller.id))
|
|
81
84
|
.map((entry) => buildCandidate({
|
|
82
85
|
seller: entry.seller,
|
|
83
86
|
registryOrder: entry.registryOrder,
|
|
@@ -98,6 +101,8 @@ function buildCandidate(input) {
|
|
|
98
101
|
supportsPayment: sellerSupportsPayment(input.seller, input.paymentMethod),
|
|
99
102
|
healthScore: input.metric?.healthScore,
|
|
100
103
|
avgLatencyMs: input.metric?.avgLatencyMs,
|
|
104
|
+
ttftMs: input.metric?.ttftMs,
|
|
105
|
+
avgInferenceMs: input.metric?.avgInferenceMs,
|
|
101
106
|
discountRatio: input.metric?.discountRatio,
|
|
102
107
|
registryOrder: input.registryOrder
|
|
103
108
|
};
|
|
@@ -108,21 +113,22 @@ function isSelectableCandidate(candidate) {
|
|
|
108
113
|
function indexRegistrySellers(sellers) {
|
|
109
114
|
const ordered = sellers
|
|
110
115
|
.filter((seller) => Boolean(seller?.id && seller.url))
|
|
116
|
+
.filter((seller) => isBuyerVisibleRegistrySeller(seller))
|
|
111
117
|
.map((seller, registryOrder) => ({ seller, registryOrder }));
|
|
112
118
|
return {
|
|
113
119
|
ordered,
|
|
114
120
|
bySellerId: new Map(ordered.map((entry) => [entry.seller.id, entry]))
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
|
-
function indexMetrics(metrics) {
|
|
118
|
-
const
|
|
119
|
-
.filter((metric) => metric.circuit === "open")
|
|
123
|
+
function indexMetrics(metrics, now) {
|
|
124
|
+
const blockedSellerIds = new Set((metrics ?? [])
|
|
125
|
+
.filter((metric) => metric.circuit === "open" || isCapacityBlocked(metric, now))
|
|
120
126
|
.map((metric) => metric.sellerId));
|
|
121
127
|
return {
|
|
122
128
|
bySellerId: new Map((metrics ?? [])
|
|
123
|
-
.filter((metric) => metric.
|
|
129
|
+
.filter((metric) => !blockedSellerIds.has(metric.sellerId))
|
|
124
130
|
.map((metric) => [metric.sellerId, metric])),
|
|
125
|
-
|
|
131
|
+
blockedSellerIds
|
|
126
132
|
};
|
|
127
133
|
}
|
|
128
134
|
function mergeMetric(metric, prewarm) {
|
|
@@ -130,10 +136,16 @@ function mergeMetric(metric, prewarm) {
|
|
|
130
136
|
sellerId: prewarm.sellerId,
|
|
131
137
|
healthScore: prewarm.healthScore ?? metric?.healthScore,
|
|
132
138
|
avgLatencyMs: prewarm.avgLatencyMs ?? metric?.avgLatencyMs,
|
|
139
|
+
ttftMs: metric?.ttftMs,
|
|
140
|
+
avgInferenceMs: metric?.avgInferenceMs,
|
|
133
141
|
discountRatio: metric?.discountRatio,
|
|
134
|
-
circuit: metric?.circuit
|
|
142
|
+
circuit: metric?.circuit,
|
|
143
|
+
capacityBlockedUntil: metric?.capacityBlockedUntil
|
|
135
144
|
};
|
|
136
145
|
}
|
|
146
|
+
function isCapacityBlocked(metric, now) {
|
|
147
|
+
return Number.isFinite(metric.capacityBlockedUntil) && metric.capacityBlockedUntil > now;
|
|
148
|
+
}
|
|
137
149
|
function sellerSupportsModel(seller, modelId) {
|
|
138
150
|
const normalized = normalizeLookupValue(modelId);
|
|
139
151
|
return (seller.models ?? []).some((model) => normalizeLookupValue(model) === normalized);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seller-route-planner.js","sourceRoot":"","sources":["../../src/seller-route-planner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"seller-route-planner.js","sourceRoot":"","sources":["../../src/seller-route-planner.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ export interface BuyerSellerRoutingConfig extends SellerRoutingStrategyConfig {
|
|
|
12
12
|
mode: SellerRoutingMode;
|
|
13
13
|
/** 评分器:`speed` / `discount` / `balanced` */
|
|
14
14
|
scorer: SellerRoutingScorer;
|
|
15
|
+
/** fixed 模式下按模型固定 seller;缺省时回退到全局 sellerId。 */
|
|
16
|
+
fixedByModel?: Record<string, string>;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* 返回 buyer 默认的 seller 路由配置:`fullAuto` + `balanced`。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seller-routing-config.d.ts","sourceRoot":"","sources":["../../src/seller-routing-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,2BAA2B,EAC5B,MAAM,8BAA8B,CAAC;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAC5C,kCAAkC;AAClC,eAAO,MAAM,sBAAsB,EAAE,mBAAgC,CAAC;AAEtE;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,2BAA2B;IAC3E,6CAA6C;IAC7C,IAAI,EAAE,iBAAiB,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"seller-routing-config.d.ts","sourceRoot":"","sources":["../../src/seller-routing-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,2BAA2B,EAC5B,MAAM,8BAA8B,CAAC;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAC5C,kCAAkC;AAClC,eAAO,MAAM,sBAAsB,EAAE,mBAAgC,CAAC;AAEtE;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,2BAA2B;IAC3E,6CAA6C;IAC7C,IAAI,EAAE,iBAAiB,CAAC;IACxB,4CAA4C;IAC5C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,wBAAwB,CAKrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CA0BrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,GAAG,SAAS,CA0BzH;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,OAAO,EACf,QAAQ,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAC3C,wBAAwB,CAS1B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKzD;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,wBAAwB,GAAG,IAAI,CAOhF"}
|
|
@@ -33,6 +33,7 @@ export function normalizeSellerRoutingConfig(value) {
|
|
|
33
33
|
return {
|
|
34
34
|
mode,
|
|
35
35
|
sellerId: readOptionalString(value.sellerId),
|
|
36
|
+
fixedByModel: readFixedByModel(value.fixedByModel),
|
|
36
37
|
scorer
|
|
37
38
|
};
|
|
38
39
|
}
|
|
@@ -121,7 +122,7 @@ export function parseSellerIdList(value) {
|
|
|
121
122
|
* @throws 当 `fixedSet` 模式缺少 `sellerIds` 时
|
|
122
123
|
*/
|
|
123
124
|
export function assertSellerRoutingConfig(config) {
|
|
124
|
-
if (config.mode === "fixed" && !config.sellerId?.trim()) {
|
|
125
|
+
if (config.mode === "fixed" && !config.sellerId?.trim() && Object.keys(config.fixedByModel ?? {}).length === 0) {
|
|
125
126
|
throw new Error("fixed routing requires --seller <sellerId>");
|
|
126
127
|
}
|
|
127
128
|
if (config.mode === "fixedSet" && (!config.sellerIds || config.sellerIds.length === 0)) {
|
|
@@ -158,6 +159,15 @@ function readSellerIds(value) {
|
|
|
158
159
|
}
|
|
159
160
|
return [];
|
|
160
161
|
}
|
|
162
|
+
function readFixedByModel(value) {
|
|
163
|
+
if (!isObject(value)) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const entries = Object.entries(value)
|
|
167
|
+
.map(([modelId, sellerId]) => [modelId.trim(), typeof sellerId === "string" ? sellerId.trim() : ""])
|
|
168
|
+
.filter(([modelId, sellerId]) => modelId.length > 0 && sellerId.length > 0);
|
|
169
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
170
|
+
}
|
|
161
171
|
function isObject(value) {
|
|
162
172
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
163
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seller-routing-config.js","sourceRoot":"","sources":["../../src/seller-routing-config.ts"],"names":[],"mappings":"AAMA,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAC5C,kCAAkC;AAClC,MAAM,CAAC,MAAM,sBAAsB,GAAwB,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"seller-routing-config.js","sourceRoot":"","sources":["../../src/seller-routing-config.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/buyer-store.ts
CHANGED
|
@@ -88,6 +88,15 @@ export interface SafePurchaseLedgerEntry {
|
|
|
88
88
|
/**
|
|
89
89
|
* buyer 端单次推理请求的账本输入。
|
|
90
90
|
* `prompt` / `response` 在落库前会 hash,原始内容永不落盘。
|
|
91
|
+
*
|
|
92
|
+
* v1.2 tb-ui v1 (Iter 4):补 7 字段 — 排查 / 性能 / 路由决策溯源必备。
|
|
93
|
+
* - `ttftMs`:首字延迟(从请求发起到首 byte),`forwardProxyRequest` 计算
|
|
94
|
+
* - `fallbackCount`:本请求实际走过的 seller 数(> 1 即发生 failover)
|
|
95
|
+
* - `routeReason`:`planSellerRouteSet().reason`(如 `fullAuto:balanced:routes_3`)
|
|
96
|
+
* - `falloverChain`:failover 路径上 seller id 数组(空 = 主路径成功)
|
|
97
|
+
* - `upstreamStatus`:seller 透传的上游健康状态(`healthy|degraded|unhealthy|unknown`)
|
|
98
|
+
* - `durationMs`:总耗时(req start → response end 或失败)
|
|
99
|
+
* - `paymentMethod`:本请求使用的支付方式(`mock|clawtip`)
|
|
91
100
|
*/
|
|
92
101
|
export interface InferenceLedgerInput {
|
|
93
102
|
requestId: string;
|
|
@@ -106,6 +115,13 @@ export interface InferenceLedgerInput {
|
|
|
106
115
|
balanceSource?: string;
|
|
107
116
|
prompt?: string;
|
|
108
117
|
response?: string;
|
|
118
|
+
ttftMs?: number;
|
|
119
|
+
fallbackCount?: number;
|
|
120
|
+
routeReason?: string;
|
|
121
|
+
falloverChain?: string[];
|
|
122
|
+
upstreamStatus?: string;
|
|
123
|
+
durationMs?: number;
|
|
124
|
+
paymentMethod?: string;
|
|
109
125
|
}
|
|
110
126
|
|
|
111
127
|
/**
|
|
@@ -130,6 +146,14 @@ export interface SafeInferenceLedgerEntry {
|
|
|
130
146
|
promptHash?: string;
|
|
131
147
|
responseHash?: string;
|
|
132
148
|
createdAt: string;
|
|
149
|
+
// v1.2 tb-ui v1 (Iter 4):新增 7 字段
|
|
150
|
+
ttftMs?: number;
|
|
151
|
+
fallbackCount?: number;
|
|
152
|
+
routeReason?: string;
|
|
153
|
+
falloverChain?: string[];
|
|
154
|
+
upstreamStatus?: string;
|
|
155
|
+
durationMs?: number;
|
|
156
|
+
paymentMethod?: string;
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
/**
|
|
@@ -640,8 +664,10 @@ export class BuyerStore {
|
|
|
640
664
|
request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
641
665
|
completion_tokens, billed_micros, estimated_micros, settled_micros,
|
|
642
666
|
settled_usd_micros, price_version, balance_snapshot_micros, balance_source,
|
|
643
|
-
prompt_hash, response_hash, created_at
|
|
644
|
-
|
|
667
|
+
prompt_hash, response_hash, created_at,
|
|
668
|
+
ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
|
|
669
|
+
duration_ms, payment_method
|
|
670
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
645
671
|
).run(
|
|
646
672
|
input.requestId,
|
|
647
673
|
input.sellerKey,
|
|
@@ -659,7 +685,14 @@ export class BuyerStore {
|
|
|
659
685
|
input.balanceSource || "unknown",
|
|
660
686
|
safeHash(input.prompt) || null,
|
|
661
687
|
safeHash(input.response) || null,
|
|
662
|
-
nowIso()
|
|
688
|
+
nowIso(),
|
|
689
|
+
input.ttftMs ?? null,
|
|
690
|
+
input.fallbackCount ?? null,
|
|
691
|
+
input.routeReason ?? null,
|
|
692
|
+
input.falloverChain ? JSON.stringify(input.falloverChain) : null,
|
|
693
|
+
input.upstreamStatus ?? null,
|
|
694
|
+
input.durationMs ?? null,
|
|
695
|
+
input.paymentMethod ?? null
|
|
663
696
|
);
|
|
664
697
|
}
|
|
665
698
|
|
|
@@ -668,7 +701,9 @@ export class BuyerStore {
|
|
|
668
701
|
`SELECT request_id, seller_key, model_id, endpoint, status, prompt_tokens,
|
|
669
702
|
completion_tokens, billed_micros, estimated_micros, settled_micros,
|
|
670
703
|
settled_usd_micros, price_version, balance_snapshot_micros, balance_source,
|
|
671
|
-
prompt_hash, response_hash, created_at
|
|
704
|
+
prompt_hash, response_hash, created_at,
|
|
705
|
+
ttft_ms, fallback_count, route_reason, fallover_chain_json, upstream_status,
|
|
706
|
+
duration_ms, payment_method
|
|
672
707
|
FROM inference_ledger
|
|
673
708
|
ORDER BY id ASC`
|
|
674
709
|
).all() as Array<{
|
|
@@ -689,6 +724,13 @@ export class BuyerStore {
|
|
|
689
724
|
prompt_hash: string | null;
|
|
690
725
|
response_hash: string | null;
|
|
691
726
|
created_at: string;
|
|
727
|
+
ttft_ms: number | null;
|
|
728
|
+
fallback_count: number | null;
|
|
729
|
+
route_reason: string | null;
|
|
730
|
+
fallover_chain_json: string | null;
|
|
731
|
+
upstream_status: string | null;
|
|
732
|
+
duration_ms: number | null;
|
|
733
|
+
payment_method: string | null;
|
|
692
734
|
}>;
|
|
693
735
|
|
|
694
736
|
return rows.map(row => ({
|
|
@@ -708,7 +750,14 @@ export class BuyerStore {
|
|
|
708
750
|
balanceSource: row.balance_source || undefined,
|
|
709
751
|
promptHash: row.prompt_hash || undefined,
|
|
710
752
|
responseHash: row.response_hash || undefined,
|
|
711
|
-
createdAt: row.created_at
|
|
753
|
+
createdAt: row.created_at,
|
|
754
|
+
ttftMs: row.ttft_ms ?? undefined,
|
|
755
|
+
fallbackCount: row.fallback_count ?? undefined,
|
|
756
|
+
routeReason: row.route_reason ?? undefined,
|
|
757
|
+
falloverChain: row.fallover_chain_json ? JSON.parse(row.fallover_chain_json) as string[] : undefined,
|
|
758
|
+
upstreamStatus: row.upstream_status ?? undefined,
|
|
759
|
+
durationMs: row.duration_ms ?? undefined,
|
|
760
|
+
paymentMethod: row.payment_method ?? undefined
|
|
712
761
|
}));
|
|
713
762
|
}
|
|
714
763
|
|
|
@@ -784,7 +833,14 @@ export class BuyerStore {
|
|
|
784
833
|
balance_source TEXT,
|
|
785
834
|
prompt_hash TEXT,
|
|
786
835
|
response_hash TEXT,
|
|
787
|
-
created_at TEXT NOT NULL
|
|
836
|
+
created_at TEXT NOT NULL,
|
|
837
|
+
ttft_ms INTEGER,
|
|
838
|
+
fallback_count INTEGER,
|
|
839
|
+
route_reason TEXT,
|
|
840
|
+
fallover_chain_json TEXT,
|
|
841
|
+
upstream_status TEXT,
|
|
842
|
+
duration_ms INTEGER,
|
|
843
|
+
payment_method TEXT
|
|
788
844
|
);
|
|
789
845
|
|
|
790
846
|
CREATE TABLE IF NOT EXISTS provider_install_state (
|
|
@@ -821,7 +877,14 @@ export class BuyerStore {
|
|
|
821
877
|
["settled_usd_micros", "INTEGER"],
|
|
822
878
|
["price_version", "TEXT"],
|
|
823
879
|
["balance_snapshot_micros", "INTEGER"],
|
|
824
|
-
["balance_source", "TEXT"]
|
|
880
|
+
["balance_source", "TEXT"],
|
|
881
|
+
["ttft_ms", "INTEGER"],
|
|
882
|
+
["fallback_count", "INTEGER"],
|
|
883
|
+
["route_reason", "TEXT"],
|
|
884
|
+
["fallover_chain_json", "TEXT"],
|
|
885
|
+
["upstream_status", "TEXT"],
|
|
886
|
+
["duration_ms", "INTEGER"],
|
|
887
|
+
["payment_method", "TEXT"]
|
|
825
888
|
]) {
|
|
826
889
|
this.ensureColumn("inference_ledger", column, definition);
|
|
827
890
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO = "bootstrap-pay-to";
|
|
2
|
+
|
|
3
|
+
export interface ClawtipBootstrapResponse {
|
|
4
|
+
activationFeeFen?: number;
|
|
5
|
+
payment?: {
|
|
6
|
+
orderNo?: string;
|
|
7
|
+
amountFen?: number;
|
|
8
|
+
payTo?: string;
|
|
9
|
+
encryptedData?: string;
|
|
10
|
+
indicator?: string;
|
|
11
|
+
slug?: string;
|
|
12
|
+
skillId?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
resourceUrl?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch wallet-bootstrap's ClawTip activation payment payload.
|
|
20
|
+
*/
|
|
21
|
+
export async function fetchClawtipBootstrap(bootstrapUrl: string): Promise<ClawtipBootstrapResponse> {
|
|
22
|
+
const response = await fetch(`${bootstrapUrl.replace(/\/+$/, "")}/payments/clawtip/bootstrap`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ clientTag: "tb-payment-add" })
|
|
26
|
+
});
|
|
27
|
+
const body = await response.json() as ClawtipBootstrapResponse & { error?: string };
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(body.error || `ClawTip bootstrap failed with HTTP ${response.status}`);
|
|
30
|
+
}
|
|
31
|
+
if (!body.payment?.orderNo || !body.payment.indicator || !body.payment.resourceUrl) {
|
|
32
|
+
throw new Error("ClawTip bootstrap response missing payment order fields");
|
|
33
|
+
}
|
|
34
|
+
if ((body.payment.payTo || "").trim() === CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
[
|
|
37
|
+
`ClawTip bootstrap service is misconfigured: payTo is still the placeholder \`${CLAWTIP_BOOTSTRAP_PLACEHOLDER_PAY_TO}\`.`,
|
|
38
|
+
`Bootstrap URL: ${bootstrapUrl}`,
|
|
39
|
+
"Configure the bootstrap service with the real ClawTip merchant pay_to before retrying `tb init`.",
|
|
40
|
+
].join(" ")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
body.payment.resourceUrl = normalizeClawtipBootstrapResourceUrl(bootstrapUrl, body.payment.resourceUrl);
|
|
44
|
+
return body;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize bootstrap resource URLs that accidentally point at the public registry path.
|
|
49
|
+
*/
|
|
50
|
+
export function normalizeClawtipBootstrapResourceUrl(bootstrapUrl: string, resourceUrl: string): string {
|
|
51
|
+
try {
|
|
52
|
+
const bootstrap = new URL(bootstrapUrl);
|
|
53
|
+
const resource = new URL(resourceUrl);
|
|
54
|
+
if (resource.origin === bootstrap.origin && resource.pathname === "/registry/sellers") {
|
|
55
|
+
resource.pathname = bootstrap.pathname.replace(/\/+$/, "") || "/";
|
|
56
|
+
resource.search = "";
|
|
57
|
+
resource.hash = "";
|
|
58
|
+
return resource.toString().replace(/\/$/, "");
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Keep the server-provided value when URL parsing fails.
|
|
62
|
+
}
|
|
63
|
+
return resourceUrl;
|
|
64
|
+
}
|