fix: sort model providers by per-token rate and show [Disabled] annotation

getModelProviders was manually iterating getAllCachedModels() and sorting
by max_cost (a per-request ceiling), which didn't match the SDK's actual
routing order (prompt+completion per million tokens). Switch to
providerManager.getProviderPriceRankingForModel({ includeDisabled: true })
so the display order matches real routing. Cross-reference the store's
disabledProviders list to set a per-provider 'disabled' flag, and print
'[Disabled]' next to the URL in the CLI.
This commit is contained in:
redshift
2026-07-29 14:44:00 +01:00
parent 04f8c22085
commit 44846e9728
4 changed files with 34 additions and 23 deletions
+3
View File
@@ -32,5 +32,8 @@ temp/
.worktrees/
.routstrd/
# Graphify output
graphify-out/
# Standalone prototype scripts
index.ts
+4 -1
View File
@@ -751,6 +751,7 @@ program
context_length?: number;
providers: Array<{
baseUrl: string;
disabled?: boolean;
pricing: {
prompt: number;
completion: number;
@@ -777,7 +778,9 @@ program
}
console.log(`\n Providers (${modelData.providers.length}):`);
for (const provider of modelData.providers) {
console.log(`\n ${provider.baseUrl}`);
console.log(
`\n ${provider.baseUrl}${provider.disabled ? " [Disabled]" : ""}`,
);
console.log(
` Prompt: ${(provider.pricing.prompt * 1000000).toFixed(2)} sats/M tokens`,
);
+1 -1
View File
@@ -113,7 +113,7 @@ async function main(): Promise<void> {
// Create shared ProviderManager for consistent failure tracking across all requests
const providerManager = new ProviderManager(discoveryAdapter, store, daemonSdkLogger);
const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders, refreshProvidersAndModels } =
createModelService(modelManager, store);
createModelService(modelManager, providerManager, store);
const walletClient = await createCocoClient();
+26 -21
View File
@@ -1,9 +1,10 @@
import { ModelManager, type SdkStore } from "@routstr/sdk";
import { ModelManager, ProviderManager, type SdkStore } from "@routstr/sdk";
import type { ExposedModel } from "./types";
import { logger } from "../utils/logger";
export type ModelProviderInfo = {
baseUrl: string;
disabled: boolean;
pricing: {
prompt: number;
completion: number;
@@ -16,7 +17,11 @@ export type ModelWithProviders = ExposedModel & {
providers: ModelProviderInfo[];
};
export function createModelService(modelManager: ModelManager, store: SdkStore) {
export function createModelService(
modelManager: ModelManager,
providerManager: ProviderManager,
store: SdkStore,
) {
let providerBootstrapPromise: Promise<void> | null = null;
const ensureProvidersBootstrapped = (): Promise<void> => {
@@ -77,33 +82,33 @@ export function createModelService(modelManager: ModelManager, store: SdkStore)
): Promise<ModelWithProviders | null> => {
await ensureProvidersBootstrapped();
const allModels = modelManager.getAllCachedModels();
const providers: ModelProviderInfo[] = [];
const disabledSet = new Set<string>(store.getState().disabledProviders || []);
for (const [baseUrl, models] of Object.entries(allModels)) {
const model = models.find((m) => m.id === modelId);
if (model && model.sats_pricing) {
providers.push({
baseUrl,
pricing: {
prompt: model.sats_pricing.prompt,
completion: model.sats_pricing.completion,
request: model.sats_pricing.request,
max_cost: model.sats_pricing.max_cost,
},
});
}
}
// Use the SDK ranking (sorted by prompt+completion per million tokens)
// so the display order matches real routing. includeDisabled keeps
// disabled providers visible so we can annotate them.
const ranking = providerManager.getProviderPriceRankingForModel(modelId, {
includeDisabled: true,
});
// Sort by max_cost (cheapest first)
providers.sort((a, b) => a.pricing.max_cost - b.pricing.max_cost);
const providers: ModelProviderInfo[] = ranking.map((entry: any) => ({
baseUrl: entry.baseUrl,
disabled: disabledSet.has(entry.baseUrl),
pricing: {
prompt: entry.promptPerMillion / 1_000_000,
completion: entry.completionPerMillion / 1_000_000,
request: entry.model.sats_pricing?.request ?? 0,
max_cost: entry.model.sats_pricing?.max_cost ?? 0,
},
}));
if (providers.length === 0) {
return null;
}
// Get model metadata from first provider that has it
// Get model metadata from first (cheapest) provider
const cheapest = providers[0]!;
const allModels = modelManager.getAllCachedModels();
const firstProvider = allModels[cheapest.baseUrl];
const modelInfo = firstProvider?.find((m: { id: string }) => m.id === modelId);