mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
committed by
GitHub
parent
c4fc33480c
commit
454eb75b31
@@ -26,6 +26,7 @@ _In development — bullets added per PR; finalized at release._
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **fix(api): resolve `GET /v1/models/{id}` case-insensitively** — clients that normalise the model id (e.g. OpenCode requesting `minimax/minimax-m3` for the canonical catalog entry `minimax/MiniMax-M3`) missed the single-model lookup, which is case-sensitive, and fell back to advertising `context_length: 0`. `findModelById` now prefers an exact-case match and falls back to a case-insensitive match, so the real entry (and its context window) is returned regardless of casing. ([#5082](https://github.com/diegosouzapw/OmniRoute/issues/5082))
|
||||
- **fix(services): embed WS proxy honours `LIVE_WS_HOST`; reject empty `messages` early** — two headless/Docker deployment fixes (#5110). The embed WebSocket proxy (`:20131`) only read `EMBED_WS_PROXY_HOST`, so behind a reverse proxy/tunnel it stayed bound to `127.0.0.1` even with `LIVE_WS_HOST=0.0.0.0` set and the Live dashboard showed "WebSocket disconnected"; it now falls back to `LIVE_WS_HOST` (default still loopback). Separately, a request with an explicitly empty `messages: []` array was forwarded upstream and bounced back as a confusing raw `400/502`; `handleChat` now rejects it up front with a clear `messages: at least one message is required` (Responses-API `input` requests are unaffected). ([#5110](https://github.com/diegosouzapw/OmniRoute/issues/5110))
|
||||
- **fix(proxy): repair one-click Deno & Cloudflare relay deployments** — the `/api/settings/proxy/test` endpoint only recognized the `vercel` relay type, so testing a deployed Deno or Cloudflare relay returned `proxy.type must be http, https, or socks5` and never reached the relay; it now routes all relay types through `isRelayType()`. On installs with `STORAGE_ENCRYPTION_KEY` the relay-auth token is read via `extractRelayAuth` (encrypted `relayAuthEnc` form), fixing the silent `401` that left `publicIp` null. The Cloudflare Worker upload now sends the script part as `application/javascript` (the API rejects `application/javascript+module`; ES-module semantics come from `main_module`), and the proxy-registry schema accepts the `deno`/`cloudflare` types + `deno-relay`/`cloudflare-relay` sources so editing a deployed relay no longer 400s. ([#5128](https://github.com/diegosouzapw/OmniRoute/issues/5128))
|
||||
- **fix(quota): hydrate the in-memory quota cache from snapshots + scope auto-combo candidates** — after a restart the quota cache was empty, so a known-exhausted connection looked healthy until re-queried; `isAccountQuotaExhausted` now lazily hydrates from persisted `quota_snapshots`. Auto-combo candidate expansion is also scoped to the connections each combo target actually allows, instead of pulling in every connection for the provider. ([#5015](https://github.com/diegosouzapw/OmniRoute/pull/5015) — thanks @JxnLexn)
|
||||
|
||||
@@ -12,13 +12,23 @@ import { CORS_HEADERS } from "@/shared/utils/cors";
|
||||
|
||||
type CatalogModel = { id?: unknown } & Record<string, unknown>;
|
||||
|
||||
/** Find a model entry in the unified catalog `data` array by its exact id. */
|
||||
/**
|
||||
* Find a model entry in the unified catalog `data` array by id.
|
||||
*
|
||||
* Exact-case matches win; failing that we fall back to a case-insensitive match
|
||||
* so clients that normalise the model id (#5082 — OpenCode requesting
|
||||
* `minimax/minimax-m3` for the canonical `minimax/MiniMax-M3`) still resolve the
|
||||
* real entry — and its `context_length` — instead of falling back to 0.
|
||||
*/
|
||||
export function findModelById(
|
||||
data: CatalogModel[] | null | undefined,
|
||||
requestedId: string
|
||||
): CatalogModel | null {
|
||||
if (!Array.isArray(data)) return null;
|
||||
return data.find((m) => typeof m?.id === "string" && m.id === requestedId) ?? null;
|
||||
const exact = data.find((m) => typeof m?.id === "string" && m.id === requestedId);
|
||||
if (exact) return exact;
|
||||
const lower = requestedId.toLowerCase();
|
||||
return data.find((m) => typeof m?.id === "string" && m.id.toLowerCase() === lower) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,28 @@ test("findModelById returns null for an unknown model", () => {
|
||||
assert.equal(findModelById(CATALOG, "does-not-exist"), null);
|
||||
});
|
||||
|
||||
// #5082 — OpenCode (and other @ai-sdk/openai-compatible clients) may request a
|
||||
// model id with different casing than the canonical catalog entry
|
||||
// (e.g. `minimax/minimax-m3` vs the registered `minimax/MiniMax-M3`). A
|
||||
// case-sensitive lookup misses, the client falls back to `context_length: 0`.
|
||||
// The single-model lookup must resolve case-insensitively so the real entry
|
||||
// (with its context window) is returned.
|
||||
test("findModelById resolves a differently-cased id (case-insensitive)", () => {
|
||||
const found = findModelById(CATALOG, "CLAUDE/Claude-Sonnet-4-6");
|
||||
assert.ok(found, "lowercase/uppercase variants must resolve to the canonical entry");
|
||||
assert.equal(found.id, "claude/claude-sonnet-4-6");
|
||||
});
|
||||
|
||||
test("findModelById prefers an exact-case match over a case-insensitive one", () => {
|
||||
const data = [
|
||||
{ id: "Model-X", object: "model", context_length: 111 },
|
||||
{ id: "model-x", object: "model", context_length: 222 },
|
||||
];
|
||||
// Exact case must win when present, not the first case-insensitive hit.
|
||||
assert.equal(findModelById(data, "model-x")?.context_length, 222);
|
||||
assert.equal(findModelById(data, "Model-X")?.context_length, 111);
|
||||
});
|
||||
|
||||
test("findModelById tolerates a non-array catalog", () => {
|
||||
assert.equal(findModelById(undefined, "gpt-5"), null);
|
||||
assert.equal(findModelById(null, "gpt-5"), null);
|
||||
|
||||
Reference in New Issue
Block a user