myapikey 0.15.0 → 0.16.1

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.15.0",
3
+ "version": "0.16.1",
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": [
@@ -356,6 +356,28 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
356
356
  return c.json({ ok: true }, 201);
357
357
  });
358
358
 
359
+ // Rename a model - changes the ROUTING KEY, the name clients request. The
360
+ // config entry (enabled slots, chains, upstream mappings) moves as-is, so
361
+ // an unmapped slot now forwards the NEW name upstream (that's the point of
362
+ // renaming); slots with an explicit upstream mapping keep it. Renaming to
363
+ // an existing name is rejected; key order in data.json is preserved.
364
+ app.post("/models/:name/rename", async (c) => {
365
+ const name = c.req.param("name");
366
+ const body = await readJson<{ name?: string }>(c.req.raw);
367
+ const next = body?.name?.trim();
368
+ if (!next) return c.json({ error: { message: "name is required" } }, 400);
369
+ if (next === name) return c.json({ error: { message: "new name is the same as the current one" } }, 400);
370
+ const cfg = store.get();
371
+ if (!cfg.models[name]) return c.json({ error: { message: "model not found" } }, 404);
372
+ if (cfg.models[next]) return c.json({ error: { message: `model already exists: ${next}` } }, 409);
373
+ await store.update((d) => {
374
+ const rebuilt: typeof d.models = {};
375
+ for (const [k, v] of Object.entries(d.models)) rebuilt[k === name ? next : k] = v;
376
+ d.models = rebuilt;
377
+ });
378
+ return c.json({ ok: true });
379
+ });
380
+
359
381
  app.post("/models/:name/providers", async (c) => {
360
382
  const name = c.req.param("name");
361
383
  const body = await readJson<{ format?: RouteKey; providerId?: string; model?: string }>(c.req.raw);
@@ -4,8 +4,10 @@ import type { Format, Provider, RouteKey, Usage } from "../shared/types";
4
4
  import type { Store } from "./store";
5
5
  import { UsageCollector } from "./tokens";
6
6
 
7
- /** HTTP statuses that should trigger failover to the next provider. */
8
- const RETRYABLE = new Set([408, 425, 429, 500, 502, 503, 504]);
7
+ /** HTTP statuses that should trigger failover to the next provider. 401/403
8
+ * included: a banned/invalid credential (e.g. "User has been banned") is dead
9
+ * for THIS source only - the same request may be fine on the next one. */
10
+ const RETRYABLE = new Set([401, 403, 408, 425, 429, 500, 502, 503, 504]);
9
11
 
10
12
  /** Headers copied from upstream back to the client. */
11
13
  const COPY_DOWN = ["content-type", "cache-control", "x-request-id", "openai-organization", "anthropic-ratelimit-requests-reset"];