routstrd 0.4.10 → 0.4.12

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.
@@ -1,4 +1,11 @@
1
- import { ModelManager, ProviderManager, type SdkStore } from "@routstr/sdk";
1
+ import {
2
+ canonicalIdForModel,
3
+ findModelForId,
4
+ ModelManager,
5
+ ProviderManager,
6
+ type Model,
7
+ type SdkStore,
8
+ } from "@routstr/sdk";
2
9
  import type { ExposedModel } from "./types";
3
10
  import { logger } from "../utils/logger";
4
11
 
@@ -66,13 +73,16 @@ export function createModelService(
66
73
 
67
74
  for (const model of models as PricedModel[]) {
68
75
  if (!model.sats_pricing) continue;
69
- const existing = bestById.get(model.id);
76
+ // Group by canonical id so providers serving the same model under a
77
+ // mapped variant id or alias fold into one aggregate entry.
78
+ const canonicalId = canonicalIdForModel(model as Model);
79
+ const existing = bestById.get(canonicalId);
70
80
  if (
71
81
  !existing ||
72
82
  (model.sats_pricing.completion ?? 0) <
73
83
  (existing.sats_pricing?.completion ?? 0)
74
84
  ) {
75
- bestById.set(model.id, model);
85
+ bestById.set(canonicalId, model);
76
86
  }
77
87
  }
78
88
  }
@@ -155,11 +165,19 @@ export function createModelService(
155
165
  );
156
166
  }
157
167
 
158
- const modelsById = new Map(discoveredModels.map((model) => [model.id, model]));
159
-
168
+ // Resolve each routstr21 id against the aggregated provider models. The
169
+ // aggregation folds providers' mapped variant ids/aliases into one entry
170
+ // per canonical id, but the surviving entry is the provider's raw model,
171
+ // whose `.id` may be the variant (e.g. z-ai-glm-5-3-flash) — while still
172
+ // carrying the full metadata (context_length, architecture, reasoning).
173
+ // A plain id map would miss and degrade the entry to a bare stub, so
174
+ // resolve through the SDK's model mappings (exact native id wins, same as
175
+ // getModelProviders) and always expose the requested canonical id.
160
176
  return routstr21ModelIds.map((modelId) => {
161
- const model = modelsById.get(modelId);
162
- return model || { id: modelId, name: modelId };
177
+ const model = findModelForId(discoveredModels as Model[], modelId);
178
+ return model
179
+ ? { ...model, id: modelId }
180
+ : { id: modelId, name: modelId };
163
181
  });
164
182
  };
165
183
 
@@ -201,18 +219,35 @@ export function createModelService(
201
219
  return null;
202
220
  }
203
221
 
204
- // Get model metadata from first (cheapest) provider
222
+ // Get model metadata. Prefer a provider that serves the requested
223
+ // canonical id natively (nicest name/description); fall back to the
224
+ // cheapest provider matched through the SDK's model mappings, since it
225
+ // may only know the model under a mapped variant id or alias.
205
226
  const cheapest = providers[0]!;
206
227
  const allModels = modelManager.getAllCachedModels();
207
- const firstProvider = allModels[cheapest.baseUrl];
208
- const modelInfo = firstProvider?.find((m: { id: string }) => m.id === modelId);
228
+ let modelInfo: Model | undefined;
229
+ for (const p of providers) {
230
+ const exact = (allModels[p.baseUrl] as Model[] | undefined)?.find(
231
+ (m) => m.id === modelId,
232
+ );
233
+ if (exact) {
234
+ modelInfo = exact;
235
+ break;
236
+ }
237
+ }
238
+ if (!modelInfo) {
239
+ const firstProvider = allModels[cheapest.baseUrl];
240
+ modelInfo = firstProvider
241
+ ? findModelForId(firstProvider as Model[], modelId)
242
+ : undefined;
243
+ }
209
244
 
210
245
  if (!modelInfo) {
211
246
  return null;
212
247
  }
213
248
 
214
249
  return {
215
- id: modelInfo.id,
250
+ id: modelId,
216
251
  name: modelInfo.name,
217
252
  description: modelInfo.description,
218
253
  context_length: modelInfo.context_length,
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Uses applesauce-wallet-connect (same approach as nwc_integration/pay_invoice.mts).
6
6
 
7
- import type { CocodClient } from "./cocod-client";
7
+ import type { WalletClient } from "./wallet-client";
8
8
  import type { WalletConnect } from "applesauce-wallet-connect";
9
9
  import { logger } from "../../utils/logger";
10
10
 
@@ -33,10 +33,19 @@ function isFatalError(message: string): boolean {
33
33
  }
34
34
 
35
35
  export function startAutoRefillLoop(
36
- cocod: CocodClient,
36
+ walletClient: WalletClient,
37
37
  getWallet: () => WalletConnect | undefined,
38
38
  getConfig: () => AutoRefillConfig | undefined,
39
39
  intervalMs: number = 5000,
40
+ payInvoice: (
41
+ invoice: string,
42
+ ) => Promise<{ preimage?: string; fees_paid?: number }> = (invoice) => {
43
+ const wallet = getWallet();
44
+ if (!wallet?.service) {
45
+ return Promise.reject(new Error("NWC not connected"));
46
+ }
47
+ return wallet.payInvoice(invoice);
48
+ },
40
49
  ): () => void {
41
50
  let lastRefillAt = 0;
42
51
  let lastAttemptAt = 0; // tracks last attempt (success or failure) for backoff
@@ -79,7 +88,7 @@ export function startAutoRefillLoop(
79
88
  checkInProgress = true;
80
89
 
81
90
  try {
82
- const balances = await cocod.getBalances();
91
+ const balances = await walletClient.getBalances();
83
92
  const totalBalance = Object.values(balances).reduce<number>(
84
93
  (sum, b) => sum + (typeof b === "number" ? b : 0),
85
94
  0,
@@ -94,17 +103,20 @@ export function startAutoRefillLoop(
94
103
  `[auto-refill] Balance ${totalBalance} sats < threshold ${config.threshold}. Refilling ${config.amount} sats...`,
95
104
  );
96
105
 
97
- const mintUrl = await cocod.getDefaultMint();
106
+ const mintUrl = await walletClient.getDefaultMint();
98
107
  if (!mintUrl) {
99
108
  logger.error("[auto-refill] No default mint configured");
100
109
  return;
101
110
  }
102
111
 
103
- // Step 1: Create a BOLT-11 invoice via cocod to fund the Cashu wallet
112
+ // Step 1: Create a BOLT-11 invoice via the wallet to fund the Cashu wallet
104
113
  logger.log(
105
114
  `[auto-refill] Creating BOLT-11 invoice for ${config.amount} sats via ${mintUrl}...`,
106
115
  );
107
- const { invoice } = await cocod.receiveBolt11(config.amount, mintUrl);
116
+ const { invoice } = await walletClient.receiveBolt11(
117
+ config.amount,
118
+ mintUrl,
119
+ );
108
120
 
109
121
  // Step 2: Pay the invoice via NWC (applesauce)
110
122
  logger.log(`[auto-refill] Paying invoice via NWC...`);
@@ -113,10 +125,10 @@ export function startAutoRefillLoop(
113
125
  logger.log("[auto-refill] Wallet disconnected during refill check");
114
126
  return;
115
127
  }
116
- const payment = await currentWallet.payInvoice(invoice);
128
+ const payment = await payInvoice(invoice);
117
129
 
118
130
  // Step 3: The Cashu mint should automatically detect the paid invoice
119
- // and issue tokens. We don't need to explicitly mint here; cocod
131
+ // and issue tokens. We don't need to explicitly mint here; the wallet
120
132
  // handles this on its end when the mint sees the payment.
121
133
  const preimage = payment.preimage;
122
134
  if (preimage) {
@@ -64,8 +64,8 @@ export interface CleanupSelection<
64
64
  * can have happened before expiry while the daemon was down, leaving no
65
65
  * local observation. Callers that fail quotes automatically at startup must
66
66
  * therefore confirm UNPAID with the mint first (see
67
- * settleExpiredMintQuotes in coco-client.ts); only the explicit,
68
- * user-invoked cleanup command may fail candidates purely locally.
67
+ * failExpiredMintQuoteIfUnpaid in coco-client.ts). Explicit cleanup follows
68
+ * the same rule unless the operator opts into unsafe `--force` behaviour.
69
69
  * - Pending sends are reclaimed (rolled back) only when they are older than
70
70
  * `minAgeMs`, so we never roll back a token that a receiver might still
71
71
  * legitimately claim.
@@ -102,3 +102,17 @@ export function selectCleanupOperations<
102
102
 
103
103
  return { mintsToFail, sendsToReclaim, meltsToCancel };
104
104
  }
105
+
106
+ /** Keep a local-only dry-run preview distinct from mint-confirmed outcomes. */
107
+ export function summarizeMintCleanup(input: {
108
+ dryRun: boolean;
109
+ candidates: number;
110
+ failed: number;
111
+ leftForRecovery: number;
112
+ }) {
113
+ return {
114
+ mintQuoteCandidates: input.candidates,
115
+ failedMintQuotes: input.dryRun ? 0 : input.failed,
116
+ leftForRecovery: input.dryRun ? 0 : input.leftForRecovery,
117
+ };
118
+ }