diff --git a/src/agents/context.lookup.test.ts b/src/agents/context.lookup.test.ts index 428d47759bc..a395f0b3089 100644 --- a/src/agents/context.lookup.test.ts +++ b/src/agents/context.lookup.test.ts @@ -1,8 +1,13 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -function mockContextModuleDeps(loadConfigImpl: () => unknown) { +type DiscoveredModel = { id: string; contextWindow: number }; + +function mockContextDeps(params: { + loadConfig: () => unknown; + discoveredModels?: DiscoveredModel[]; +}) { vi.doMock("../config/config.js", () => ({ - loadConfig: loadConfigImpl, + loadConfig: params.loadConfig, })); vi.doMock("./models-config.js", () => ({ ensureOpenClawModelsJson: vi.fn(async () => {}), @@ -13,29 +18,42 @@ function mockContextModuleDeps(loadConfigImpl: () => unknown) { vi.doMock("./pi-model-discovery.js", () => ({ discoverAuthStorage: vi.fn(() => ({})), discoverModels: vi.fn(() => ({ - getAll: () => [], + getAll: () => params.discoveredModels ?? [], })), })); } +function mockContextModuleDeps(loadConfigImpl: () => unknown) { + mockContextDeps({ loadConfig: loadConfigImpl }); +} + // Shared mock setup used by multiple tests. function mockDiscoveryDeps( - models: Array<{ id: string; contextWindow: number }>, + models: DiscoveredModel[], configModels?: Record }>, ) { - vi.doMock("../config/config.js", () => ({ + mockContextDeps({ loadConfig: () => ({ models: configModels ? { providers: configModels } : {} }), - })); - vi.doMock("./models-config.js", () => ({ - ensureOpenClawModelsJson: vi.fn(async () => {}), - })); - vi.doMock("./agent-paths.js", () => ({ - resolveOpenClawAgentDir: () => "/tmp/openclaw-agent", - })); - vi.doMock("./pi-model-discovery.js", () => ({ - discoverAuthStorage: vi.fn(() => ({})), - discoverModels: vi.fn(() => ({ getAll: () => models })), - })); + discoveredModels: models, + }); +} + +function createContextOverrideConfig(provider: string, model: string, contextWindow: number) { + return { + models: { + providers: { + [provider]: { + models: [{ id: model, contextWindow }], + }, + }, + }, + }; +} + +async function importResolveContextTokensForModel() { + const { resolveContextTokensForModel } = await import("./context.js"); + await new Promise((r) => setTimeout(r, 0)); + return resolveContextTokensForModel; } describe("lookupContextTokens", () => { @@ -150,18 +168,8 @@ describe("lookupContextTokens", () => { { id: "google-gemini-cli/gemini-3.1-pro-preview", contextWindow: 1_048_576 }, ]); - const cfg = { - models: { - providers: { - "google-gemini-cli": { - models: [{ id: "gemini-3.1-pro-preview", contextWindow: 200_000 }], - }, - }, - }, - }; - - const { resolveContextTokensForModel } = await import("./context.js"); - await new Promise((r) => setTimeout(r, 0)); + const cfg = createContextOverrideConfig("google-gemini-cli", "gemini-3.1-pro-preview", 200_000); + const resolveContextTokensForModel = await importResolveContextTokensForModel(); const result = resolveContextTokensForModel({ cfg: cfg as never, @@ -174,18 +182,8 @@ describe("lookupContextTokens", () => { it("resolveContextTokensForModel honors configured overrides when provider keys use mixed case", async () => { mockDiscoveryDeps([{ id: "openrouter/anthropic/claude-sonnet-4-5", contextWindow: 1_048_576 }]); - const cfg = { - models: { - providers: { - " OpenRouter ": { - models: [{ id: "anthropic/claude-sonnet-4-5", contextWindow: 200_000 }], - }, - }, - }, - }; - - const { resolveContextTokensForModel } = await import("./context.js"); - await new Promise((r) => setTimeout(r, 0)); + const cfg = createContextOverrideConfig(" OpenRouter ", "anthropic/claude-sonnet-4-5", 200_000); + const resolveContextTokensForModel = await importResolveContextTokensForModel(); const result = resolveContextTokensForModel({ cfg: cfg as never, @@ -202,16 +200,8 @@ describe("lookupContextTokens", () => { // Real callers (status.summary.ts) always pass cfg when provider is explicit. mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]); - const cfg = { - models: { - providers: { - google: { models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }] }, - }, - }, - }; - - const { resolveContextTokensForModel } = await import("./context.js"); - await new Promise((r) => setTimeout(r, 0)); + const cfg = createContextOverrideConfig("google", "gemini-2.5-pro", 2_000_000); + const resolveContextTokensForModel = await importResolveContextTokensForModel(); // Google with explicit cfg: config direct scan wins before any cache lookup. const googleResult = resolveContextTokensForModel({ @@ -272,16 +262,8 @@ describe("lookupContextTokens", () => { // window and misreport context limits for the OpenRouter session. mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]); - const cfg = { - models: { - providers: { - google: { models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }] }, - }, - }, - }; - - const { resolveContextTokensForModel } = await import("./context.js"); - await new Promise((r) => setTimeout(r, 0)); + const cfg = createContextOverrideConfig("google", "gemini-2.5-pro", 2_000_000); + const resolveContextTokensForModel = await importResolveContextTokensForModel(); // model-only call (no explicit provider) must NOT apply config direct scan. // Falls through to bare cache lookup: "google/gemini-2.5-pro" → 999k ✓.