mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 07:52:44 +00:00
CLI: dedupe config validate errors and expose allowed values
This commit is contained in:
64
src/agents/context.lookup.test.ts
Normal file
64
src/agents/context.lookup.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("lookupContextTokens", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("returns configured model context window on first lookup", async () => {
|
||||
vi.doMock("../config/config.js", () => ({
|
||||
loadConfig: () => ({
|
||||
models: {
|
||||
providers: {
|
||||
openrouter: {
|
||||
models: [{ id: "openrouter/claude-sonnet", contextWindow: 321_000 }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
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: () => [],
|
||||
})),
|
||||
}));
|
||||
|
||||
const { lookupContextTokens } = await import("./context.js");
|
||||
expect(lookupContextTokens("openrouter/claude-sonnet")).toBe(321_000);
|
||||
});
|
||||
|
||||
it("does not skip eager warmup when --profile is followed by -- terminator", async () => {
|
||||
const loadConfigMock = vi.fn(() => ({ models: {} }));
|
||||
vi.doMock("../config/config.js", () => ({
|
||||
loadConfig: loadConfigMock,
|
||||
}));
|
||||
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: () => [],
|
||||
})),
|
||||
}));
|
||||
|
||||
const argvSnapshot = process.argv;
|
||||
process.argv = ["node", "openclaw", "--profile", "--", "config", "validate"];
|
||||
try {
|
||||
await import("./context.js");
|
||||
expect(loadConfigMock).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
process.argv = argvSnapshot;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js";
|
||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||
import { ensureOpenClawModelsJson } from "./models-config.js";
|
||||
|
||||
@@ -66,55 +67,114 @@ export function applyConfiguredContextWindows(params: {
|
||||
}
|
||||
|
||||
const MODEL_CACHE = new Map<string, number>();
|
||||
const loadPromise = (async () => {
|
||||
let cfg: ReturnType<typeof loadConfig> | undefined;
|
||||
let loadPromise: Promise<void> | null = null;
|
||||
let configuredWindowsPrimed = false;
|
||||
|
||||
function getCommandPathFromArgv(argv: string[]): string[] {
|
||||
const args = argv.slice(2);
|
||||
const tokens: string[] = [];
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (!arg || arg === FLAG_TERMINATOR) {
|
||||
break;
|
||||
}
|
||||
const consumed = consumeRootOptionToken(args, i);
|
||||
if (consumed > 0) {
|
||||
i += consumed - 1;
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("-")) {
|
||||
continue;
|
||||
}
|
||||
tokens.push(arg);
|
||||
if (tokens.length >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function shouldSkipEagerContextWindowWarmup(argv: string[] = process.argv): boolean {
|
||||
const [primary, secondary] = getCommandPathFromArgv(argv);
|
||||
return primary === "config" && secondary === "validate";
|
||||
}
|
||||
|
||||
function primeConfiguredContextWindows(): OpenClawConfig | undefined {
|
||||
if (configuredWindowsPrimed) {
|
||||
return undefined;
|
||||
}
|
||||
configuredWindowsPrimed = true;
|
||||
try {
|
||||
cfg = loadConfig();
|
||||
const cfg = loadConfig();
|
||||
applyConfiguredContextWindows({
|
||||
cache: MODEL_CACHE,
|
||||
modelsConfig: cfg.models as ModelsConfig | undefined,
|
||||
});
|
||||
return cfg;
|
||||
} catch {
|
||||
// If config can't be loaded, leave cache empty.
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureOpenClawModelsJson(cfg);
|
||||
} catch {
|
||||
// Continue with best-effort discovery/overrides.
|
||||
function ensureContextWindowCacheLoaded(): Promise<void> {
|
||||
const cfg = primeConfiguredContextWindows();
|
||||
if (loadPromise) {
|
||||
return loadPromise;
|
||||
}
|
||||
loadPromise = (async () => {
|
||||
if (!cfg) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { discoverAuthStorage, discoverModels } = await import("./pi-model-discovery.js");
|
||||
const agentDir = resolveOpenClawAgentDir();
|
||||
const authStorage = discoverAuthStorage(agentDir);
|
||||
const modelRegistry = discoverModels(authStorage, agentDir) as unknown as ModelRegistryLike;
|
||||
const models =
|
||||
typeof modelRegistry.getAvailable === "function"
|
||||
? modelRegistry.getAvailable()
|
||||
: modelRegistry.getAll();
|
||||
applyDiscoveredContextWindows({
|
||||
try {
|
||||
await ensureOpenClawModelsJson(cfg);
|
||||
} catch {
|
||||
// Continue with best-effort discovery/overrides.
|
||||
}
|
||||
|
||||
try {
|
||||
const { discoverAuthStorage, discoverModels } = await import("./pi-model-discovery.js");
|
||||
const agentDir = resolveOpenClawAgentDir();
|
||||
const authStorage = discoverAuthStorage(agentDir);
|
||||
const modelRegistry = discoverModels(authStorage, agentDir) as unknown as ModelRegistryLike;
|
||||
const models =
|
||||
typeof modelRegistry.getAvailable === "function"
|
||||
? modelRegistry.getAvailable()
|
||||
: modelRegistry.getAll();
|
||||
applyDiscoveredContextWindows({
|
||||
cache: MODEL_CACHE,
|
||||
models,
|
||||
});
|
||||
} catch {
|
||||
// If model discovery fails, continue with config overrides only.
|
||||
}
|
||||
|
||||
applyConfiguredContextWindows({
|
||||
cache: MODEL_CACHE,
|
||||
models,
|
||||
modelsConfig: cfg.models as ModelsConfig | undefined,
|
||||
});
|
||||
} catch {
|
||||
// If model discovery fails, continue with config overrides only.
|
||||
}
|
||||
|
||||
applyConfiguredContextWindows({
|
||||
cache: MODEL_CACHE,
|
||||
modelsConfig: cfg.models as ModelsConfig | undefined,
|
||||
})().catch(() => {
|
||||
// Keep lookup best-effort.
|
||||
});
|
||||
})().catch(() => {
|
||||
// Keep lookup best-effort.
|
||||
});
|
||||
return loadPromise;
|
||||
}
|
||||
|
||||
export function lookupContextTokens(modelId?: string): number | undefined {
|
||||
if (!modelId) {
|
||||
return undefined;
|
||||
}
|
||||
// Best-effort: kick off loading, but don't block.
|
||||
void loadPromise;
|
||||
void ensureContextWindowCacheLoaded();
|
||||
return MODEL_CACHE.get(modelId);
|
||||
}
|
||||
|
||||
if (!shouldSkipEagerContextWindowWarmup()) {
|
||||
// Keep prior behavior where model limits begin loading during startup.
|
||||
// This avoids a cold-start miss on the first context token lookup.
|
||||
void ensureContextWindowCacheLoaded();
|
||||
}
|
||||
|
||||
function resolveConfiguredModelParams(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
provider: string,
|
||||
|
||||
Reference in New Issue
Block a user