mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
normalizeDiscoveredModels only copied record.inputTokenLimit, but OpenRouter returns the window as context_length / top_provider.context_length, so every synced model fell back to the 128K default. Read context_length (and top_provider.max_completion_tokens for output) as a fallback. Co-authored-by: pulyankote <pulyankote@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d3422c1c4d
commit
9bc2c89924
@@ -14,6 +14,21 @@ function toNonEmptyString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a positive integer token limit from a list of candidate values.
|
||||
* Used to fall back across the differently-named context/output fields that
|
||||
* upstream catalogs expose (e.g. OpenRouter uses `context_length` /
|
||||
* `top_provider.context_length` instead of `inputTokenLimit`). See #3202.
|
||||
*/
|
||||
function firstPositiveNumber(...candidates: unknown[]): number | undefined {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate > 0) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isAutoFetchModelsEnabled(providerSpecificData: unknown): boolean {
|
||||
return asRecord(providerSpecificData).autoFetchModels !== false;
|
||||
}
|
||||
@@ -45,6 +60,23 @@ export function normalizeDiscoveredModels(models: unknown): SyncedAvailableModel
|
||||
).sort()
|
||||
: undefined;
|
||||
|
||||
const topProvider = asRecord(record.top_provider);
|
||||
|
||||
// OpenRouter (and similar passthrough catalogs) report the context window as
|
||||
// `context_length` / `top_provider.context_length`, not `inputTokenLimit`.
|
||||
// Fall back across those names so synced models carry a real window instead
|
||||
// of the provider default (128K). Explicit `inputTokenLimit` still wins. #3202
|
||||
const inputTokenLimit = firstPositiveNumber(
|
||||
record.inputTokenLimit,
|
||||
record.context_length,
|
||||
record.contextLength,
|
||||
topProvider.context_length
|
||||
);
|
||||
const outputTokenLimit = firstPositiveNumber(
|
||||
record.outputTokenLimit,
|
||||
topProvider.max_completion_tokens
|
||||
);
|
||||
|
||||
deduped.set(id, {
|
||||
id,
|
||||
name,
|
||||
@@ -53,12 +85,8 @@ export function normalizeDiscoveredModels(models: unknown): SyncedAvailableModel
|
||||
? { apiFormat: toNonEmptyString(record.apiFormat)! }
|
||||
: {}),
|
||||
...(supportedEndpoints && supportedEndpoints.length > 0 ? { supportedEndpoints } : {}),
|
||||
...(typeof record.inputTokenLimit === "number"
|
||||
? { inputTokenLimit: record.inputTokenLimit }
|
||||
: {}),
|
||||
...(typeof record.outputTokenLimit === "number"
|
||||
? { outputTokenLimit: record.outputTokenLimit }
|
||||
: {}),
|
||||
...(typeof inputTokenLimit === "number" ? { inputTokenLimit } : {}),
|
||||
...(typeof outputTokenLimit === "number" ? { outputTokenLimit } : {}),
|
||||
...(typeof record.description === "string" ? { description: record.description } : {}),
|
||||
...(record.supportsThinking === true ? { supportsThinking: true } : {}),
|
||||
});
|
||||
|
||||
58
tests/unit/openrouter-context-length-3202.test.ts
Normal file
58
tests/unit/openrouter-context-length-3202.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { normalizeDiscoveredModels } from "@/lib/providerModels/modelDiscovery";
|
||||
|
||||
// Regression guard for #3202.
|
||||
//
|
||||
// OpenRouter's /api/v1/models returns the context window as `context_length`
|
||||
// (and `top_provider.context_length`), NOT `inputTokenLimit`. The provider
|
||||
// discovery path (`parseResponse: (data) => data.data || []`) passes these raw
|
||||
// records straight into `normalizeDiscoveredModels`, so before the fix synced
|
||||
// OpenRouter models never carried `inputTokenLimit` and `/v1/models` fell back
|
||||
// to the 128K provider default for every model.
|
||||
|
||||
test("#3202 maps OpenRouter context_length into inputTokenLimit", () => {
|
||||
const [model] = normalizeDiscoveredModels([
|
||||
{ id: "deepseek/deepseek-v4", context_length: 1048576 },
|
||||
]);
|
||||
|
||||
assert.equal(model.id, "deepseek/deepseek-v4");
|
||||
assert.equal(model.inputTokenLimit, 1048576);
|
||||
});
|
||||
|
||||
test("#3202 preserves an explicit inputTokenLimit when already present", () => {
|
||||
const [model] = normalizeDiscoveredModels([
|
||||
{ id: "vendor/with-explicit", inputTokenLimit: 200000, context_length: 999999 },
|
||||
]);
|
||||
|
||||
// Explicit inputTokenLimit wins over the context_length fallback.
|
||||
assert.equal(model.inputTokenLimit, 200000);
|
||||
});
|
||||
|
||||
test("#3202 falls back to top_provider.context_length", () => {
|
||||
const [model] = normalizeDiscoveredModels([
|
||||
{ id: "vendor/top-provider-window", top_provider: { context_length: 262144 } },
|
||||
]);
|
||||
|
||||
assert.equal(model.inputTokenLimit, 262144);
|
||||
});
|
||||
|
||||
test("#3202 maps OpenRouter output cap (top_provider.max_completion_tokens)", () => {
|
||||
const [model] = normalizeDiscoveredModels([
|
||||
{
|
||||
id: "vendor/with-output-cap",
|
||||
context_length: 131072,
|
||||
top_provider: { max_completion_tokens: 32768 },
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(model.inputTokenLimit, 131072);
|
||||
assert.equal(model.outputTokenLimit, 32768);
|
||||
});
|
||||
|
||||
test("#3202 leaves inputTokenLimit unset when no window field is present", () => {
|
||||
const [model] = normalizeDiscoveredModels([{ id: "vendor/no-window" }]);
|
||||
|
||||
assert.equal(model.inputTokenLimit, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user