myapikey 0.27.0 → 0.29.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myapikey",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  import { Hono, type MiddlewareHandler } from "hono";
2
2
  import { newProviderId, newApiKey, trimBase } from "../shared/config";
3
- import type { ChainSlot, Format, FormatEntry, ModelEntry, Provider, RouteKey } from "../shared/types";
3
+ import type { ChainSlot, Format, FormatEntry, LogEntry, ModelEntry, Provider, RouteKey } from "../shared/types";
4
4
  import type { Store } from "./store";
5
5
  import { shortError, anthropicAuthHeaders, upstreamTarget, upstreamHeaders } from "./proxy";
6
6
  import { networkInterfaces } from "node:os";
@@ -377,7 +377,46 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
377
377
  app.get("/models", (c) => {
378
378
  const d = store.get();
379
379
  const byId = new Map(d.providers.map((p) => [p.id, p]));
380
- const models = Object.entries(d.models).map(([name, e]) => projectModel(name, e, byId));
380
+ // Where each model's traffic ACTUALLY landed most recently: the latest
381
+ // successful call per (model, format) from the log tail (newest first,
382
+ // so the first hit wins). The UI shows this as the model's in-use slot —
383
+ // after a failover the chip follows the source that really served.
384
+ // Companion map for FAILURES (latest 4xx/5xx per model+format) so the
385
+ // chain popover can flag the slots that recently errored.
386
+ // Legacy rows carry only the provider NAME; resolve it to the stable id
387
+ // so they can still match a slot.
388
+ const byName = new Map(d.providers.map((p) => [p.name, p.id]));
389
+ const okLog = new Map<string, Map<string, LogEntry>>();
390
+ const badLog = new Map<string, Map<string, LogEntry>>();
391
+ for (const row of store.getLogs()) {
392
+ const pid = row.providerId ?? (row.provider ? byName.get(row.provider) : undefined);
393
+ if (!pid) continue;
394
+ const target = row.status >= 200 && row.status < 400 ? okLog : badLog;
395
+ let perFmt = target.get(row.model);
396
+ if (!perFmt) target.set(row.model, (perFmt = new Map()));
397
+ if (!perFmt.has(row.format)) perFmt.set(row.format, row);
398
+ }
399
+ const lastEntries = (src: Map<string, Map<string, LogEntry>>, name: string) =>
400
+ [...src.get(name)?.entries() ?? []];
401
+ const models = Object.entries(d.models).map(([name, e]) => ({
402
+ ...projectModel(name, e, byId),
403
+ lastRoute: lastEntries(okLog, name).map(([format, row]) => ({
404
+ format,
405
+ providerId: row.providerId ?? byName.get(row.provider) ?? "",
406
+ // The upstream name that was actually forwarded (a per-slot rewrite,
407
+ // or the public name sent verbatim).
408
+ model: row.upstreamModel ?? row.model,
409
+ ts: row.ts,
410
+ })),
411
+ lastFail: lastEntries(badLog, name).map(([format, row]) => ({
412
+ format,
413
+ providerId: row.providerId ?? byName.get(row.provider) ?? "",
414
+ model: row.upstreamModel ?? row.model,
415
+ status: row.status,
416
+ error: row.error,
417
+ ts: row.ts,
418
+ })),
419
+ }));
381
420
  return c.json({ models });
382
421
  });
383
422