@tokenbuddy/tokenbuddy 1.0.16 → 1.0.18
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/bin/tb-proxyd.js +2 -0
- package/bin/tb.js +4 -0
- package/dist/src/cli.d.ts +52 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +178 -8
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +8 -0
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +335 -21
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/init-setup.d.ts +25 -0
- package/dist/src/init-setup.d.ts.map +1 -0
- package/dist/src/init-setup.js +125 -0
- package/dist/src/init-setup.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +233 -10
- package/src/daemon.ts +385 -20
- package/src/init-setup.ts +165 -0
- package/static/ui/assets/index-BILQcD8g.js +226 -0
- package/static/ui/assets/index-BILQcD8g.js.map +1 -0
- package/static/ui/assets/index-C5KsebCA.css +1 -0
- package/static/ui/index.html +2 -2
- package/static/ui/manifest.webmanifest +1 -1
- package/static/ui/sw.js +1 -1
- package/tests/control-plane-ui-endpoints.test.ts +193 -1
- package/tests/tokenbuddy.test.ts +209 -0
- package/static/ui/assets/index-UMiTTeo8.css +0 -1
- package/static/ui/assets/index-YHs-Ca0f.js +0 -206
- package/static/ui/assets/index-YHs-Ca0f.js.map +0 -1
package/dist/src/daemon.js
CHANGED
|
@@ -10,7 +10,7 @@ import { fetchClawtipBootstrap } from "./clawtip-bootstrap.js";
|
|
|
10
10
|
import { inspectOpenClawWalletConfig, } from "./init-payment-options.js";
|
|
11
11
|
import { startClawtipWalletBootstrap, waitForClawtipActivationConfirmation, } from "./init-clawtip-activation.js";
|
|
12
12
|
import { applyProviderInstall, detectProviders, previewProviderInstall, rollbackProviderInstall, } from "./provider-install.js";
|
|
13
|
-
import { discoverSellerBackedModels, fetchSellerRegistry, normalizeSellerUrl, RegistryTooLargeError, } from "./seller-catalog.js";
|
|
13
|
+
import { discoverSellerBackedModels, fetchSellerRegistry, isBuyerVisibleRegistrySeller, normalizeSellerUrl, RegistryTooLargeError, } from "./seller-catalog.js";
|
|
14
14
|
import { ModelIndex } from "./model-index.js";
|
|
15
15
|
import { PrewarmCache, prewarmKey } from "./prewarm-cache.js";
|
|
16
16
|
import { CreditTracker } from "./credit-tracker.js";
|
|
@@ -19,6 +19,7 @@ import { RouteFailover } from "./route-failover.js";
|
|
|
19
19
|
import { PrewarmScheduler } from "./prewarm-scheduler.js";
|
|
20
20
|
import { planSellerRouteSet } from "./seller-route-planner.js";
|
|
21
21
|
import { assertSellerRoutingConfig, mergeSellerRoutingConfig, normalizeSellerRoutingConfig, parseSellerIdList, ROUTING_CONFIG_KEY } from "./seller-routing-config.js";
|
|
22
|
+
import { assertInitSetupSteps, buildCompletedInitSetupMarker, INIT_SETUP_CONFIG_KEY, INIT_SETUP_STEPS, isFreshInitMachine, normalizeInitSetupMarker, resolveInitRecommendedModels, } from "./init-setup.js";
|
|
22
23
|
const logger = createModuleLogger("tb-proxyd");
|
|
23
24
|
const FOCUS_SET_CONFIG_KEY = "focus-set";
|
|
24
25
|
const PROXY_JSON_BODY_LIMIT = "10mb";
|
|
@@ -623,6 +624,218 @@ export class TokenbuddyDaemon {
|
|
|
623
624
|
store: this.tokenStore.summary()
|
|
624
625
|
};
|
|
625
626
|
}
|
|
627
|
+
livePayments() {
|
|
628
|
+
return this.tokenStore.listPayments().map((payment) => withLiveClawtipWalletState(payment, this.config.clawtipHomeDir));
|
|
629
|
+
}
|
|
630
|
+
clientToolsSummary() {
|
|
631
|
+
const providerStatuses = detectProviders({ home: this.config.providerHomeDir }).map(clientToolStatusFromProvider);
|
|
632
|
+
const clients = [
|
|
633
|
+
...providerStatuses,
|
|
634
|
+
buildCustomClientToolStatus(this.activeProxyPort()),
|
|
635
|
+
];
|
|
636
|
+
const configuredCount = clients.filter((client) => client.configured).length;
|
|
637
|
+
const detectedCount = clients.filter((client) => client.detected && client.status !== "manual").length;
|
|
638
|
+
return {
|
|
639
|
+
clients,
|
|
640
|
+
summary: {
|
|
641
|
+
configuredCount,
|
|
642
|
+
detectedCount,
|
|
643
|
+
totalCount: clients.length,
|
|
644
|
+
installCommand: "tb init"
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
initRepairStatus(input) {
|
|
649
|
+
if (input.setup.status !== "completed") {
|
|
650
|
+
return { repairMode: false, repairReasons: [] };
|
|
651
|
+
}
|
|
652
|
+
const completedSteps = new Set(input.setup.completedSteps);
|
|
653
|
+
const missingSteps = INIT_SETUP_STEPS.filter((step) => !completedSteps.has(step));
|
|
654
|
+
const repairReasons = [];
|
|
655
|
+
if (missingSteps.length > 0) {
|
|
656
|
+
repairReasons.push(`missing_steps:${missingSteps.join(",")}`);
|
|
657
|
+
}
|
|
658
|
+
if (input.focusSet.length === 0) {
|
|
659
|
+
repairReasons.push("missing_focus_models");
|
|
660
|
+
}
|
|
661
|
+
if (!input.payments.some((payment) => payment.enabled)) {
|
|
662
|
+
repairReasons.push("missing_payment_method");
|
|
663
|
+
}
|
|
664
|
+
if (input.clientsSummary.configuredCount === 0) {
|
|
665
|
+
repairReasons.push("missing_connected_tools");
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
repairMode: repairReasons.length > 0,
|
|
669
|
+
repairReasons,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
initStateSnapshot() {
|
|
673
|
+
const record = this.tokenStore.getDaemonRuntimeConfig(INIT_SETUP_CONFIG_KEY);
|
|
674
|
+
const setup = normalizeInitSetupMarker(record?.config);
|
|
675
|
+
const storedRouting = this.tokenStore.getDaemonRuntimeConfig(ROUTING_CONFIG_KEY)?.config;
|
|
676
|
+
const routingSource = storedRouting !== undefined ? "store" : this.config.sellerRouting ? "config" : "default";
|
|
677
|
+
const clients = this.clientToolsSummary();
|
|
678
|
+
const payments = this.livePayments();
|
|
679
|
+
const focusSet = this.resolveFocusSet();
|
|
680
|
+
const recommendedModels = resolveInitRecommendedModels({
|
|
681
|
+
configuredModels: this.config.initRecommendedModels,
|
|
682
|
+
env: process.env,
|
|
683
|
+
});
|
|
684
|
+
const repair = this.initRepairStatus({
|
|
685
|
+
setup,
|
|
686
|
+
payments,
|
|
687
|
+
clientsSummary: clients.summary,
|
|
688
|
+
focusSet,
|
|
689
|
+
});
|
|
690
|
+
return {
|
|
691
|
+
setup: {
|
|
692
|
+
...setup,
|
|
693
|
+
createdAt: record?.createdAt,
|
|
694
|
+
updatedAt: record?.updatedAt ?? setup.updatedAt,
|
|
695
|
+
},
|
|
696
|
+
freshMachine: isFreshInitMachine(setup),
|
|
697
|
+
repairMode: repair.repairMode,
|
|
698
|
+
repairReasons: repair.repairReasons,
|
|
699
|
+
runtime: this.runtimeSummary(),
|
|
700
|
+
payments,
|
|
701
|
+
clients: clients.clients,
|
|
702
|
+
clientsSummary: clients.summary,
|
|
703
|
+
routing: {
|
|
704
|
+
strategy: this.sellerRouting,
|
|
705
|
+
source: routingSource,
|
|
706
|
+
},
|
|
707
|
+
focusSet,
|
|
708
|
+
recommendedModels,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
async buildInitDoctorReport() {
|
|
712
|
+
const catalog = await this.initDoctorCatalogSnapshot();
|
|
713
|
+
const currentRouting = this.refreshSellerRoutingConfig();
|
|
714
|
+
const payments = this.livePayments().filter((payment) => payment.enabled);
|
|
715
|
+
const clients = this.clientToolsSummary();
|
|
716
|
+
const routeModelId = this.resolveFocusSet()[0] || catalog.models[0]?.id;
|
|
717
|
+
const routingPreview = routeModelId ? this.buildRoutingPreview({ modelId: routeModelId, routing: currentRouting }) : undefined;
|
|
718
|
+
const checks = [
|
|
719
|
+
{
|
|
720
|
+
id: "local_service",
|
|
721
|
+
label: "本地服务",
|
|
722
|
+
status: this.controlServer && this.proxyServer ? "passed" : "failed",
|
|
723
|
+
message: this.controlServer && this.proxyServer
|
|
724
|
+
? "tb-proxyd 正在运行,控制面和代理端口已经打开。"
|
|
725
|
+
: "tb-proxyd 本地服务尚未完全启动。",
|
|
726
|
+
details: [
|
|
727
|
+
`控制面:http://127.0.0.1:${this.activeControlPort()}`,
|
|
728
|
+
`代理:http://127.0.0.1:${this.activeProxyPort()}`
|
|
729
|
+
]
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
id: "proxy_interface",
|
|
733
|
+
label: "代理接口",
|
|
734
|
+
status: this.proxyServer ? "passed" : "failed",
|
|
735
|
+
message: this.proxyServer
|
|
736
|
+
? "OpenAI 和 Anthropic 兼容本地接口已就绪。"
|
|
737
|
+
: "本地代理接口尚未就绪。",
|
|
738
|
+
details: [
|
|
739
|
+
`OpenAI 兼容 Base URL:http://127.0.0.1:${this.activeProxyPort()}/v1`,
|
|
740
|
+
`Anthropic 兼容 Base URL:http://127.0.0.1:${this.activeProxyPort()}`
|
|
741
|
+
]
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
id: "seller_registry",
|
|
745
|
+
label: "供应商注册表",
|
|
746
|
+
status: catalog.available && catalog.sellers.length > 0 ? "passed" : "failed",
|
|
747
|
+
message: catalog.available && catalog.sellers.length > 0
|
|
748
|
+
? `已加载 ${catalog.sellers.length} 个供应商。`
|
|
749
|
+
: "TokenBuddy 还没有可用供应商注册表。",
|
|
750
|
+
details: [
|
|
751
|
+
`来源:${formatInitDoctorCatalogSource(catalog.source)}`,
|
|
752
|
+
...(catalog.errorMessage ? [catalog.errorMessage] : [])
|
|
753
|
+
]
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
id: "model_catalog",
|
|
757
|
+
label: "模型目录",
|
|
758
|
+
status: catalog.available && catalog.models.length > 0 ? "passed" : "failed",
|
|
759
|
+
message: catalog.available && catalog.models.length > 0
|
|
760
|
+
? `已发现 ${new Set(catalog.models.map((model) => model.id)).size} 个可用模型。`
|
|
761
|
+
: "TokenBuddy 还没有加载到供应商支持的模型。",
|
|
762
|
+
details: catalog.models.slice(0, 5).map((model) => model.id)
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: "routing_strategy",
|
|
766
|
+
label: "路由策略",
|
|
767
|
+
status: routingPreview && !("error" in routingPreview.plan) && routingPreview.plan.routes.length > 0 ? "passed" : "failed",
|
|
768
|
+
message: routingPreview && !("error" in routingPreview.plan) && routingPreview.plan.routes.length > 0
|
|
769
|
+
? `当前策略可以为 ${routingPreview.modelId} 找到 ${routingPreview.plan.routes.length} 条供应商路径。`
|
|
770
|
+
: "当前模型和路由策略下没有可用供应商。",
|
|
771
|
+
details: routingPreview
|
|
772
|
+
? ["error" in routingPreview.plan
|
|
773
|
+
? `原因:${routingPreview.plan.error}`
|
|
774
|
+
: `策略:${routingPreview.plan.mode}:${routingPreview.plan.scorer}`]
|
|
775
|
+
: ["没有可用于路由验证的模型。"]
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
id: "payment_method",
|
|
779
|
+
label: "支付方式",
|
|
780
|
+
status: payments.length > 0 ? "passed" : "warning",
|
|
781
|
+
message: payments.length > 0
|
|
782
|
+
? `已启用 ${payments.length} 个支付方式。`
|
|
783
|
+
: "尚未启用支付方式;自动购买会在绑定支付前暂停。",
|
|
784
|
+
details: payments.map((payment) => payment.method)
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
id: "connected_tools",
|
|
788
|
+
label: "已连接工具",
|
|
789
|
+
status: clients.summary.configuredCount > 0 ? "passed" : "warning",
|
|
790
|
+
message: clients.summary.configuredCount > 0
|
|
791
|
+
? `已配置 ${clients.summary.configuredCount} 个 AI 工具。`
|
|
792
|
+
: "尚未检测到已接入 TokenBuddy 的 AI 工具。",
|
|
793
|
+
details: clients.clients
|
|
794
|
+
.filter((client) => client.configured)
|
|
795
|
+
.map((client) => client.name)
|
|
796
|
+
}
|
|
797
|
+
];
|
|
798
|
+
const status = checks.some((check) => check.status === "failed")
|
|
799
|
+
? "failed"
|
|
800
|
+
: checks.some((check) => check.status === "warning" || check.status === "skipped")
|
|
801
|
+
? "warning"
|
|
802
|
+
: "passed";
|
|
803
|
+
return {
|
|
804
|
+
status,
|
|
805
|
+
generatedAt: new Date().toISOString(),
|
|
806
|
+
checks
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
async initDoctorCatalogSnapshot() {
|
|
810
|
+
try {
|
|
811
|
+
const catalog = await this.listSellerBackedModels();
|
|
812
|
+
return {
|
|
813
|
+
available: true,
|
|
814
|
+
models: catalog.models,
|
|
815
|
+
sellers: catalog.sellers,
|
|
816
|
+
source: "live"
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
821
|
+
if (this.lastRegistrySnapshot) {
|
|
822
|
+
const cached = catalogSnapshotFromRegistry(this.lastRegistrySnapshot);
|
|
823
|
+
return {
|
|
824
|
+
...cached,
|
|
825
|
+
available: cached.sellers.length > 0,
|
|
826
|
+
source: "cached",
|
|
827
|
+
errorMessage
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
available: false,
|
|
832
|
+
models: [],
|
|
833
|
+
sellers: [],
|
|
834
|
+
source: "unavailable",
|
|
835
|
+
errorMessage
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
626
839
|
endpointProtocol(endpoint) {
|
|
627
840
|
if (endpoint === "/v1/chat/completions") {
|
|
628
841
|
return "chat_completions";
|
|
@@ -986,6 +1199,7 @@ export class TokenbuddyDaemon {
|
|
|
986
1199
|
}
|
|
987
1200
|
async listSellerBackedModels() {
|
|
988
1201
|
const catalog = await discoverSellerBackedModels(this.config.sellerRegistryUrl);
|
|
1202
|
+
this.lastRegistrySnapshot = sellerCatalogResultToRegistrySnapshot(catalog);
|
|
989
1203
|
return {
|
|
990
1204
|
models: catalog.models,
|
|
991
1205
|
sellers: catalog.sellers
|
|
@@ -1943,9 +2157,74 @@ export class TokenbuddyDaemon {
|
|
|
1943
2157
|
controlApp.get("/payments", (req, res) => {
|
|
1944
2158
|
logger.info("control.payments.requested", "control payments requested", {});
|
|
1945
2159
|
res.status(200).json({
|
|
1946
|
-
payments: this.
|
|
2160
|
+
payments: this.livePayments()
|
|
1947
2161
|
});
|
|
1948
2162
|
});
|
|
2163
|
+
controlApp.get("/init/state", (req, res) => {
|
|
2164
|
+
try {
|
|
2165
|
+
const state = this.initStateSnapshot();
|
|
2166
|
+
logger.info("init.state.requested", "init setup state requested", {
|
|
2167
|
+
setupStatus: state.setup.status,
|
|
2168
|
+
freshMachine: state.freshMachine,
|
|
2169
|
+
configuredClientCount: state.clientsSummary.configuredCount,
|
|
2170
|
+
paymentCount: state.payments.length
|
|
2171
|
+
});
|
|
2172
|
+
res.status(200).json(state);
|
|
2173
|
+
}
|
|
2174
|
+
catch (error) {
|
|
2175
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2176
|
+
logger.warn("init.state.failed", "init setup state failed", { errorMessage });
|
|
2177
|
+
res.status(500).json({
|
|
2178
|
+
error: {
|
|
2179
|
+
code: "init_state_failed",
|
|
2180
|
+
message: errorMessage
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
controlApp.post("/init/complete", (req, res) => {
|
|
2186
|
+
try {
|
|
2187
|
+
const completedSteps = assertInitSetupSteps(req.body?.completedSteps);
|
|
2188
|
+
const setup = buildCompletedInitSetupMarker(completedSteps);
|
|
2189
|
+
this.tokenStore.saveDaemonRuntimeConfig(INIT_SETUP_CONFIG_KEY, setup);
|
|
2190
|
+
const state = this.initStateSnapshot();
|
|
2191
|
+
logger.info("init.setup.completed", "init setup marker completed", {
|
|
2192
|
+
completedStepCount: setup.completedSteps.length
|
|
2193
|
+
});
|
|
2194
|
+
res.status(200).json(state);
|
|
2195
|
+
}
|
|
2196
|
+
catch (error) {
|
|
2197
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2198
|
+
logger.warn("init.setup.complete_failed", "init setup complete failed", { errorMessage });
|
|
2199
|
+
res.status(400).json({
|
|
2200
|
+
error: {
|
|
2201
|
+
code: "init_setup_complete_failed",
|
|
2202
|
+
message: errorMessage
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
controlApp.post("/init/doctor/run", async (_req, res) => {
|
|
2208
|
+
try {
|
|
2209
|
+
const report = await this.buildInitDoctorReport();
|
|
2210
|
+
logger.info("init.doctor.completed", "init doctor completed", {
|
|
2211
|
+
status: report.status,
|
|
2212
|
+
failedCount: report.checks.filter((check) => check.status === "failed").length,
|
|
2213
|
+
warningCount: report.checks.filter((check) => check.status === "warning").length
|
|
2214
|
+
});
|
|
2215
|
+
res.status(200).json(report);
|
|
2216
|
+
}
|
|
2217
|
+
catch (error) {
|
|
2218
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2219
|
+
logger.warn("init.doctor.failed", "init doctor failed", { errorMessage });
|
|
2220
|
+
res.status(500).json({
|
|
2221
|
+
error: {
|
|
2222
|
+
code: "init_doctor_failed",
|
|
2223
|
+
message: errorMessage
|
|
2224
|
+
}
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
1949
2228
|
controlApp.post("/payments/clawtip/activate", async (req, res) => {
|
|
1950
2229
|
try {
|
|
1951
2230
|
const qr = await this.startClawtipActivationQr();
|
|
@@ -2118,27 +2397,13 @@ export class TokenbuddyDaemon {
|
|
|
2118
2397
|
});
|
|
2119
2398
|
controlApp.get("/providers/status", (_req, res) => {
|
|
2120
2399
|
try {
|
|
2121
|
-
const
|
|
2122
|
-
const clients = [
|
|
2123
|
-
...providerStatuses,
|
|
2124
|
-
buildCustomClientToolStatus(this.activeProxyPort()),
|
|
2125
|
-
];
|
|
2126
|
-
const configuredCount = clients.filter((client) => client.configured).length;
|
|
2127
|
-
const detectedCount = clients.filter((client) => client.detected && client.status !== "manual").length;
|
|
2400
|
+
const status = this.clientToolsSummary();
|
|
2128
2401
|
logger.info("provider.status.requested", "provider status requested", {
|
|
2129
|
-
clientCount: clients.length,
|
|
2130
|
-
configuredCount,
|
|
2131
|
-
detectedCount
|
|
2132
|
-
});
|
|
2133
|
-
res.status(200).json({
|
|
2134
|
-
clients,
|
|
2135
|
-
summary: {
|
|
2136
|
-
configuredCount,
|
|
2137
|
-
detectedCount,
|
|
2138
|
-
totalCount: clients.length,
|
|
2139
|
-
installCommand: "tb init"
|
|
2140
|
-
}
|
|
2402
|
+
clientCount: status.clients.length,
|
|
2403
|
+
configuredCount: status.summary.configuredCount,
|
|
2404
|
+
detectedCount: status.summary.detectedCount
|
|
2141
2405
|
});
|
|
2406
|
+
res.status(200).json(status);
|
|
2142
2407
|
}
|
|
2143
2408
|
catch (error) {
|
|
2144
2409
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -2670,6 +2935,55 @@ function routingKey(routing) {
|
|
|
2670
2935
|
...fixedByModel
|
|
2671
2936
|
].join("\u0001");
|
|
2672
2937
|
}
|
|
2938
|
+
function formatInitDoctorCatalogSource(source) {
|
|
2939
|
+
if (source === "live") {
|
|
2940
|
+
return "实时注册表";
|
|
2941
|
+
}
|
|
2942
|
+
if (source === "cached") {
|
|
2943
|
+
return "本机缓存";
|
|
2944
|
+
}
|
|
2945
|
+
return "不可用";
|
|
2946
|
+
}
|
|
2947
|
+
function sellerCatalogResultToRegistrySnapshot(catalog) {
|
|
2948
|
+
return {
|
|
2949
|
+
version: catalog.version,
|
|
2950
|
+
defaultSeller: catalog.defaultSeller,
|
|
2951
|
+
sellers: catalog.sellers
|
|
2952
|
+
.filter((seller) => seller.status === "ok")
|
|
2953
|
+
.map((seller) => ({
|
|
2954
|
+
id: seller.id,
|
|
2955
|
+
name: seller.name,
|
|
2956
|
+
status: "active",
|
|
2957
|
+
url: seller.url,
|
|
2958
|
+
supportedProtocols: seller.supportedProtocols,
|
|
2959
|
+
paymentMethods: seller.paymentMethods,
|
|
2960
|
+
models: catalog.models
|
|
2961
|
+
.filter((model) => model.sellerId === seller.id)
|
|
2962
|
+
.map((model) => model.id)
|
|
2963
|
+
}))
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
function catalogSnapshotFromRegistry(registry) {
|
|
2967
|
+
const visibleSellers = registry.sellers.filter(isBuyerVisibleRegistrySeller);
|
|
2968
|
+
return {
|
|
2969
|
+
available: visibleSellers.length > 0,
|
|
2970
|
+
source: "cached",
|
|
2971
|
+
models: visibleSellers.flatMap((seller) => (seller.models ?? []).map((modelId) => ({
|
|
2972
|
+
id: modelId,
|
|
2973
|
+
sellerId: seller.id,
|
|
2974
|
+
sellerName: seller.name,
|
|
2975
|
+
sellerUrl: seller.url,
|
|
2976
|
+
supportedProtocols: seller.supportedProtocols ?? [],
|
|
2977
|
+
paymentMethods: seller.paymentMethods ?? []
|
|
2978
|
+
}))),
|
|
2979
|
+
sellers: visibleSellers.map((seller) => ({
|
|
2980
|
+
id: seller.id,
|
|
2981
|
+
name: seller.name,
|
|
2982
|
+
url: seller.url,
|
|
2983
|
+
status: seller.status ?? "active"
|
|
2984
|
+
}))
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2673
2987
|
/**
|
|
2674
2988
|
* 从 query string 构造 `BuyerSellerRoutingConfig` override(用于 GET /routing/preview)。
|
|
2675
2989
|
* 接受 `mode` / `scorer` / `sellerId` / `sellerIds`(逗号分隔)。
|