From 84d65064604a4cfbea8f8e9b64b192dd016578e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Mon, 16 Mar 2026 03:42:38 +0000 Subject: [PATCH] fix(models): reflect context1m config in models list contextWindow display Wire resolveContextTokensForModel() into toModelRow() so 'oc models list' reflects the context1m config param in the Ctx column. Anthropic Opus/Sonnet 4.x models with context1m: true now display '1M' instead of '195k'/'200k'. Re-submission of #46781 (was auto-closed by bot due to active PR limit). --- src/commands/models/list.registry.test.ts | 99 +++++++++++++++++++++++ src/commands/models/list.registry.ts | 9 ++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/commands/models/list.registry.test.ts diff --git a/src/commands/models/list.registry.test.ts b/src/commands/models/list.registry.test.ts new file mode 100644 index 00000000000..1f0580c4701 --- /dev/null +++ b/src/commands/models/list.registry.test.ts @@ -0,0 +1,99 @@ +import type { Api, Model } from "@mariozechner/pi-ai"; +import { describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; + +const MOCK_1M_TOKENS = 1_048_576; + +const mocks = vi.hoisted(() => ({ + resolveContextTokensForModel: vi.fn(), +})); + +vi.mock("../../agents/context.js", () => ({ + resolveContextTokensForModel: (...args: unknown[]) => mocks.resolveContextTokensForModel(...args), +})); +vi.mock("../../agents/agent-paths.js", () => ({ + resolveOpenClawAgentDir: () => "/tmp/fake-agent-dir", +})); +vi.mock("../../agents/auth-profiles.js", () => ({ + listProfilesForProvider: () => [], +})); +vi.mock("../../agents/model-auth.js", () => ({ + hasUsableCustomProviderApiKey: () => false, + resolveAwsSdkEnvVarName: () => undefined, + resolveEnvApiKey: () => undefined, +})); +vi.mock("../../agents/model-suppression.js", () => ({ + shouldSuppressBuiltInModel: () => false, +})); +vi.mock("../../agents/pi-model-discovery.js", () => ({ + discoverAuthStorage: () => ({}), + discoverModels: () => ({ getAll: () => [], getAvailable: () => [] }), +})); + +const { toModelRow } = await import("./list.registry.js"); + +function makeModel(overrides: Partial> = {}): Model { + return { + provider: "anthropic", + id: "claude-opus-4-6", + api: "anthropic-messages", + name: "Claude Opus 4.6", + input: ["text", "image"], + contextWindow: 200_000, + ...overrides, + } as Model; +} + +describe("toModelRow", () => { + it("reflects context1m-resolved contextWindow from resolveContextTokensForModel", () => { + mocks.resolveContextTokensForModel.mockReturnValue(MOCK_1M_TOKENS); + + const row = toModelRow({ + model: makeModel(), + key: "anthropic/claude-opus-4-6", + tags: [], + cfg: {} as OpenClawConfig, + }); + + expect(row.contextWindow).toBe(MOCK_1M_TOKENS); + expect(mocks.resolveContextTokensForModel).toHaveBeenCalledWith({ + cfg: expect.anything(), + provider: "anthropic", + model: "claude-opus-4-6", + fallbackContextTokens: 200_000, + }); + }); + + it("falls back to registry contextWindow when resolveContextTokensForModel returns undefined", () => { + mocks.resolveContextTokensForModel.mockReturnValue(undefined); + + const row = toModelRow({ + model: makeModel({ contextWindow: 200_000 }), + key: "anthropic/claude-opus-4-6", + tags: [], + cfg: {} as OpenClawConfig, + }); + + expect(row.contextWindow).toBeNull(); + }); + + it("passes provider and model from the Model object, not the key", () => { + mocks.resolveContextTokensForModel.mockReturnValue(128_000); + + const row = toModelRow({ + model: makeModel({ provider: "openai", id: "gpt-5.2", contextWindow: 128_000 }), + key: "openai/gpt-5.2", + tags: [], + cfg: {} as OpenClawConfig, + }); + + expect(row.contextWindow).toBe(128_000); + expect(mocks.resolveContextTokensForModel).toHaveBeenCalledWith( + expect.objectContaining({ + provider: "openai", + model: "gpt-5.2", + fallbackContextTokens: 128_000, + }), + ); + }); +}); diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 0b68d9685e3..8909d0265f7 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -3,6 +3,7 @@ import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; import type { AuthProfileStore } from "../../agents/auth-profiles.js"; import { listProfilesForProvider } from "../../agents/auth-profiles.js"; +import { resolveContextTokensForModel } from "../../agents/context.js"; import { hasUsableCustomProviderApiKey, resolveAwsSdkEnvVarName, @@ -188,7 +189,13 @@ export function toModelRow(params: { key, name: model.name || model.id, input, - contextWindow: model.contextWindow ?? null, + contextWindow: + resolveContextTokensForModel({ + cfg, + provider: model.provider, + model: model.id, + fallbackContextTokens: model.contextWindow, + }) ?? null, local, available, tags: Array.from(mergedTags),