routstrd 0.1.5 → 0.1.6
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/bun.lock +198 -2
- package/dist/daemon/index.js +5358 -4083
- package/dist/index.js +4646 -3389
- package/package.json +2 -2
- package/src/cli.ts +54 -2
- package/src/daemon/http/index.ts +19 -0
- package/src/daemon/index.ts +2 -1
- package/src/daemon/models.ts +63 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routstrd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@cashu/cashu-ts": "^3.1.1",
|
|
27
|
-
"@routstr/sdk": "^0.2.
|
|
27
|
+
"@routstr/sdk": "^0.2.9",
|
|
28
28
|
"applesauce-core": "^5.1.0",
|
|
29
29
|
"applesauce-relay": "^5.1.0",
|
|
30
30
|
"commander": "^14.0.2",
|
package/src/cli.ts
CHANGED
|
@@ -365,9 +365,59 @@ program
|
|
|
365
365
|
.command("models")
|
|
366
366
|
.description("List available routstr21 models")
|
|
367
367
|
.option("-r, --refresh", "Force refresh routstr21 models from Nostr", false)
|
|
368
|
-
.
|
|
368
|
+
.option("-m, --model <id>", "Show providers for a specific model")
|
|
369
|
+
.action(async (options: { refresh: boolean; model?: string }) => {
|
|
369
370
|
await ensureDaemonRunning();
|
|
370
371
|
|
|
372
|
+
if (options.model) {
|
|
373
|
+
// Show providers for specific model
|
|
374
|
+
const result = await callDaemon(`/models/${encodeURIComponent(options.model)}/providers`);
|
|
375
|
+
if (result.error) {
|
|
376
|
+
console.log(result.error);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const modelData = result.output as {
|
|
381
|
+
id: string;
|
|
382
|
+
name?: string;
|
|
383
|
+
description?: string;
|
|
384
|
+
context_length?: number;
|
|
385
|
+
providers: Array<{
|
|
386
|
+
baseUrl: string;
|
|
387
|
+
pricing: {
|
|
388
|
+
prompt: number;
|
|
389
|
+
completion: number;
|
|
390
|
+
request: number;
|
|
391
|
+
max_cost: number;
|
|
392
|
+
};
|
|
393
|
+
}>;
|
|
394
|
+
} | undefined;
|
|
395
|
+
|
|
396
|
+
if (!modelData) {
|
|
397
|
+
console.log("Model not found");
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log(`\n${modelData.name || modelData.id}`);
|
|
402
|
+
if (modelData.description) {
|
|
403
|
+
console.log(` ${modelData.description}`);
|
|
404
|
+
}
|
|
405
|
+
if (modelData.context_length) {
|
|
406
|
+
console.log(` Context: ${modelData.context_length.toLocaleString()} tokens`);
|
|
407
|
+
}
|
|
408
|
+
console.log(`\n Providers (${modelData.providers.length}):`);
|
|
409
|
+
for (const provider of modelData.providers) {
|
|
410
|
+
console.log(`\n ${provider.baseUrl}`);
|
|
411
|
+
console.log(` Prompt: ${(provider.pricing.prompt * 1000000).toFixed(2)} sats/M tokens`);
|
|
412
|
+
console.log(` Completion: ${(provider.pricing.completion * 1000000).toFixed(2)} sats/M tokens`);
|
|
413
|
+
console.log(` Request: ${provider.pricing.request.toFixed(2)} sats`);
|
|
414
|
+
console.log(` Max cost: ${provider.pricing.max_cost.toFixed(2)} sats`);
|
|
415
|
+
}
|
|
416
|
+
console.log("");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// List all models with interactive selection
|
|
371
421
|
const result = await callDaemon(
|
|
372
422
|
options.refresh ? "/models?refresh=true" : "/models",
|
|
373
423
|
);
|
|
@@ -386,6 +436,7 @@ program
|
|
|
386
436
|
console.log("No routstr21 models found");
|
|
387
437
|
} else {
|
|
388
438
|
console.log(`\nFound ${models.length} routstr21 models:`);
|
|
439
|
+
console.log("(Use 'routstrd models -m <model_id>' to see providers and pricing)\n");
|
|
389
440
|
models.forEach((model, i) => {
|
|
390
441
|
const details = [
|
|
391
442
|
model.name && model.name !== model.id ? model.name : null,
|
|
@@ -393,8 +444,9 @@ program
|
|
|
393
444
|
]
|
|
394
445
|
.filter(Boolean)
|
|
395
446
|
.join(" - ");
|
|
396
|
-
console.log(
|
|
447
|
+
console.log(` ${String(i + 1).padStart(2)}. ${model.id}${details ? ` (${details})` : ""}`);
|
|
397
448
|
});
|
|
449
|
+
console.log("");
|
|
398
450
|
}
|
|
399
451
|
}
|
|
400
452
|
});
|
package/src/daemon/http/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ type DaemonDeps = {
|
|
|
37
37
|
modelManager: any;
|
|
38
38
|
ensureProvidersBootstrapped: () => Promise<void>;
|
|
39
39
|
getRoutstr21Models: (forceRefresh?: boolean) => Promise<any[]>;
|
|
40
|
+
getModelProviders: (modelId: string) => Promise<any>;
|
|
40
41
|
mode?: ClientMode;
|
|
41
42
|
providerManager: ProviderManager;
|
|
42
43
|
};
|
|
@@ -277,6 +278,7 @@ export function createDaemonRequestHandler(deps: {
|
|
|
277
278
|
modelManager: any;
|
|
278
279
|
ensureProvidersBootstrapped: () => Promise<void>;
|
|
279
280
|
getRoutstr21Models: (forceRefresh?: boolean) => Promise<any[]>;
|
|
281
|
+
getModelProviders: (modelId: string) => Promise<any>;
|
|
280
282
|
mode?: "xcashu" | "apikeys";
|
|
281
283
|
usageTrackingDriver: UsageTrackingDriver;
|
|
282
284
|
providerManager: ProviderManager;
|
|
@@ -423,6 +425,23 @@ export function createDaemonRequestHandler(deps: {
|
|
|
423
425
|
return;
|
|
424
426
|
}
|
|
425
427
|
|
|
428
|
+
// Get providers for a specific model
|
|
429
|
+
const modelProvidersMatch = url.pathname.match(/^\/models\/([^\/]+)\/providers$/);
|
|
430
|
+
if (req.method === "GET" && modelProvidersMatch && modelProvidersMatch[1]) {
|
|
431
|
+
try {
|
|
432
|
+
const modelId = decodeURIComponent(modelProvidersMatch[1]);
|
|
433
|
+
const modelWithProviders = await deps.getModelProviders(modelId);
|
|
434
|
+
if (!modelWithProviders) {
|
|
435
|
+
sendJson(res, 404, { error: `Model '${modelId}' not found` });
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
sendJson(res, 200, { output: modelWithProviders });
|
|
439
|
+
} catch (error) {
|
|
440
|
+
sendJson(res, 500, { error: toErrorMessage(error) });
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
426
445
|
if (req.method === "GET" && url.pathname === "/v1/models") {
|
|
427
446
|
try {
|
|
428
447
|
const forceRefresh =
|
package/src/daemon/index.ts
CHANGED
|
@@ -50,7 +50,7 @@ async function main(): Promise<void> {
|
|
|
50
50
|
const modelManager = new ModelManager(discoveryAdapter);
|
|
51
51
|
// Create shared ProviderManager for consistent failure tracking across all requests
|
|
52
52
|
const providerManager = new ProviderManager(providerRegistry, store);
|
|
53
|
-
const { ensureProvidersBootstrapped, getRoutstr21Models } =
|
|
53
|
+
const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders } =
|
|
54
54
|
createModelService(modelManager);
|
|
55
55
|
|
|
56
56
|
const walletClient = createCocodClient({ cocodPath: config.cocodPath });
|
|
@@ -74,6 +74,7 @@ async function main(): Promise<void> {
|
|
|
74
74
|
modelManager,
|
|
75
75
|
ensureProvidersBootstrapped,
|
|
76
76
|
getRoutstr21Models,
|
|
77
|
+
getModelProviders,
|
|
77
78
|
mode: config.mode || "apikeys",
|
|
78
79
|
usageTrackingDriver,
|
|
79
80
|
providerManager,
|
package/src/daemon/models.ts
CHANGED
|
@@ -2,6 +2,20 @@ import { ModelManager } from "@routstr/sdk";
|
|
|
2
2
|
import type { ExposedModel } from "./types";
|
|
3
3
|
import { logger } from "../utils/logger";
|
|
4
4
|
|
|
5
|
+
export type ModelProviderInfo = {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
pricing: {
|
|
8
|
+
prompt: number;
|
|
9
|
+
completion: number;
|
|
10
|
+
request: number;
|
|
11
|
+
max_cost: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ModelWithProviders = ExposedModel & {
|
|
16
|
+
providers: ModelProviderInfo[];
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
export function createModelService(modelManager: ModelManager) {
|
|
6
20
|
let providerBootstrapPromise: Promise<void> | null = null;
|
|
7
21
|
|
|
@@ -42,8 +56,57 @@ export function createModelService(modelManager: ModelManager) {
|
|
|
42
56
|
});
|
|
43
57
|
};
|
|
44
58
|
|
|
59
|
+
const getModelProviders = async (
|
|
60
|
+
modelId: string,
|
|
61
|
+
): Promise<ModelWithProviders | null> => {
|
|
62
|
+
await ensureProvidersBootstrapped();
|
|
63
|
+
|
|
64
|
+
const allModels = modelManager.getAllCachedModels();
|
|
65
|
+
const providers: ModelProviderInfo[] = [];
|
|
66
|
+
|
|
67
|
+
for (const [baseUrl, models] of Object.entries(allModels)) {
|
|
68
|
+
const model = models.find((m) => m.id === modelId);
|
|
69
|
+
if (model && model.sats_pricing) {
|
|
70
|
+
providers.push({
|
|
71
|
+
baseUrl,
|
|
72
|
+
pricing: {
|
|
73
|
+
prompt: model.sats_pricing.prompt,
|
|
74
|
+
completion: model.sats_pricing.completion,
|
|
75
|
+
request: model.sats_pricing.request,
|
|
76
|
+
max_cost: model.sats_pricing.max_cost,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sort by max_cost (cheapest first)
|
|
83
|
+
providers.sort((a, b) => a.pricing.max_cost - b.pricing.max_cost);
|
|
84
|
+
|
|
85
|
+
if (providers.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get model metadata from first provider that has it
|
|
90
|
+
const cheapest = providers[0]!;
|
|
91
|
+
const firstProvider = allModels[cheapest.baseUrl];
|
|
92
|
+
const modelInfo = firstProvider?.find((m: { id: string }) => m.id === modelId);
|
|
93
|
+
|
|
94
|
+
if (!modelInfo) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
id: modelInfo.id,
|
|
100
|
+
name: modelInfo.name,
|
|
101
|
+
description: modelInfo.description,
|
|
102
|
+
context_length: modelInfo.context_length,
|
|
103
|
+
providers,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
45
107
|
return {
|
|
46
108
|
ensureProvidersBootstrapped,
|
|
47
109
|
getRoutstr21Models,
|
|
110
|
+
getModelProviders,
|
|
48
111
|
};
|
|
49
112
|
}
|