fix(config): prefer context_length from API in model discovery
Updated model discovery to use `context_length` reported by the API when available, falling back to bundled reference data and default. Added `context_length` field to parsed model response and modified context window assignment logic.
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed dynamic forced tool choices from queue hooks being filtered against the active per-turn tool set before provider dispatch. ([#1701](https://github.com/can1357/oh-my-pi/issues/1701))
|
||||
### Fixed
|
||||
- Honor `context_length` reported by OpenAI-compatible proxy `/v1/models` discovery (`discovery: { type: "proxy" }` or `discovery: { type: "openai-models-list" }`), so aggregator-reported windows override the bundled reference when they differ.
|
||||
|
||||
|
||||
## [15.12.4] - 2026-06-13
|
||||
### Fixed
|
||||
|
||||
@@ -393,7 +393,7 @@ export async function discoverOpenAIModelsList(
|
||||
const response = apiKey
|
||||
? await withAuth(apiKey, key => attempt({ ...baseHeaders, Authorization: `Bearer ${key}` }))
|
||||
: await attempt(baseHeaders);
|
||||
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
||||
const payload = (await response.json()) as { data?: Array<{ id: string; context_length?: number }> };
|
||||
const models = payload.data ?? [];
|
||||
const discovered: Model<Api>[] = [];
|
||||
for (const item of models) {
|
||||
@@ -409,7 +409,7 @@ export async function discoverOpenAIModelsList(
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
contextWindow: toPositiveNumberOrUndefined(item.context_length) ?? 128000,
|
||||
maxTokens: discoveryDefaultMaxTokens(providerConfig.api),
|
||||
headers,
|
||||
compat: {
|
||||
@@ -463,7 +463,7 @@ export async function discoverProxyModels(
|
||||
? await withAuth(apiKey, key => attempt({ ...baseHeaders, Authorization: `Bearer ${key}` }))
|
||||
: await attempt(baseHeaders);
|
||||
const payload = (await response.json()) as {
|
||||
data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
|
||||
data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[]; context_length?: number }>;
|
||||
};
|
||||
const items = payload.data ?? [];
|
||||
const discovered: Model<Api>[] = [];
|
||||
@@ -499,7 +499,12 @@ export async function discoverProxyModels(
|
||||
// upstream bundled catalogs, so keep costs local-unknown even when
|
||||
// we successfully recover the upstream model identity.
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: reference?.contextWindow ?? 128000,
|
||||
// Prefer the context_length the API reports for this model; fall
|
||||
// back to the bundled reference, then a sane default.
|
||||
contextWindow:
|
||||
toPositiveNumberOrUndefined(item.context_length) ??
|
||||
reference?.contextWindow ??
|
||||
128000,
|
||||
maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
|
||||
headers,
|
||||
// OpenAI-compat fields are no-ops on anthropic models; the
|
||||
|
||||
@@ -607,4 +607,68 @@ describe("ModelRegistry runtime discovery", () => {
|
||||
expect(llama?.maxTokens).toBe(32_768);
|
||||
expect(llama?.input).toEqual(["text", "image"]);
|
||||
});
|
||||
test("openai-models-list discovery honors API-reported context_length over fallback", async () => {
|
||||
writeRawModelsJson({
|
||||
"openai-test": {
|
||||
baseUrl: "http://127.0.0.1:9999",
|
||||
api: "openai-completions",
|
||||
auth: "none",
|
||||
discovery: { type: "openai-models-list" },
|
||||
},
|
||||
});
|
||||
const fetchMock: FetchImpl = async input => {
|
||||
const url = String(input);
|
||||
if (url === "http://127.0.0.1:9999/v1/models") {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
data: [
|
||||
{ id: "openai-test/contextual-model", context_length: 16385 },
|
||||
{ id: "openai-test/no-context-model" },
|
||||
],
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`);
|
||||
};
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath, { fetch: fetchMock });
|
||||
await registry.refresh();
|
||||
const contextual = registry.getAll().find(m => m.provider === "openai-test" && m.id === "openai-test/contextual-model");
|
||||
expect(contextual?.contextWindow).toBe(16385);
|
||||
const fallback = registry.getAll().find(m => m.provider === "openai-test" && m.id === "openai-test/no-context-model");
|
||||
expect(fallback?.contextWindow).toBe(128000);
|
||||
});
|
||||
|
||||
test("proxy discovery honors API-reported context_length and endpoint routing", async () => {
|
||||
writeRawModelsJson({
|
||||
"proxy-test": {
|
||||
baseUrl: "http://127.0.0.1:9998",
|
||||
auth: "none",
|
||||
discovery: { type: "proxy" },
|
||||
},
|
||||
});
|
||||
const fetchMock: FetchImpl = async input => {
|
||||
const url = String(input);
|
||||
if (url === "http://127.0.0.1:9998/v1/models") {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
data: [
|
||||
{ id: "anthropic-model", supported_endpoint_types: ["anthropic"], context_length: 200000 },
|
||||
{ id: "openai-model", supported_endpoint_types: ["openai"], context_length: 65536 },
|
||||
],
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
throw new Error(`Unexpected URL: ${url}`);
|
||||
};
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath, { fetch: fetchMock });
|
||||
await registry.refresh();
|
||||
const anthropic = registry.getAll().find(m => m.provider === "proxy-test" && m.id === "anthropic-model");
|
||||
expect(anthropic?.api).toBe("anthropic-messages");
|
||||
expect(anthropic?.contextWindow).toBe(200000);
|
||||
const openai = registry.getAll().find(m => m.provider === "proxy-test" && m.id === "openai-model");
|
||||
expect(openai?.api).toBe("openai-completions");
|
||||
expect(openai?.contextWindow).toBe(65536);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user