Lodestone

What a tool writes

Set it up once and share the store. Each call names a task, not a model; Lodestone picks the order, the time slices and the fallbacks.

// set up once, and share the store
const ai = createLodestone({
  openrouter: { apiKey: OPENROUTER_KEY,
    allowPaid: true, paidFirst: true,
    paidModels: ['anthropic/claude-haiku-5.5'],
    reasoning: (m) => m.startsWith('anthropic/')
      ? { enabled: false } : { effort: 'low', exclude: true } },
  fallbacks: [
    { provider: requesty(), apiKey: REQUESTY_KEY,
      models: ['bedrock/claude-haiku-5-5'],
      discover: { fill: { maxPromptUsdPerMTok: 1,
                          maxCompletionUsdPerMTok: 5 } } },
    { provider: groq(), apiKey: GROQ_KEY,
      models: ['openai/gpt-oss-20b'], free: true },
  ],
  store, health: createHealth({ store }),
  spend: createSpendLimit({ store, dailyUsd: 2 }),
  rateLimit: { limiter: createRateLimiter({ store, max: 20 }),
               whenOver: 'free-only' },
  trust: { requireNoTraining: true },
});

// each call names a task, not a model
try {
  const { data } = await ai.json({
    prompt, validate: isJob, signal: req.signal,
    caller: userId, private: true });   // a CV: do not keep or train on it
} catch (err) {
  return explain(err, { product: 'MyApp' });
}
  1. Tiers, not model ids. A task maps to fast, strong or vision (picked automatically when a message has an image). Or pass a fixed route to name provider, model and key yourself.
  2. Customer keys stay separate. A caller's own key replaces yours and unlocks the paid model, but your fallback providers and your spend ledger are never touched by that call.
  3. One person cannot empty the budget. Name the caller and each one gets their own allowance (stored as a hash). Over it, they are refused or served from free models only. The spend limit takes a hold per attempt, so a burst cannot overshoot it. How, and what is approximate.
  4. Daily upkeep. checkModels() refreshes every provider's catalogue and reports what has gone or is retiring. cacheKey and peek() reuse answers for repeatable tasks.
  5. Cancelling. Pass a signal; when the person leaves, the call and every model after it stop.
  6. Private by design. onAttempt reports provider, model, outcome and timing only. Prompts and answers are never logged, and a private call is never cached.