mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 04:37:40 +00:00
refactor(agent): dedupe harness and command workflows
This commit is contained in:
@@ -6,7 +6,6 @@ vi.mock("../pi-model-discovery.js", () => ({
|
||||
}));
|
||||
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { discoverModels } from "../pi-model-discovery.js";
|
||||
import { buildInlineProviderModels, resolveModel } from "./model.js";
|
||||
import {
|
||||
makeModel,
|
||||
@@ -19,6 +18,48 @@ beforeEach(() => {
|
||||
resetMockDiscoverModels();
|
||||
});
|
||||
|
||||
function buildForwardCompatTemplate(params: {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
api: "anthropic-messages" | "google-gemini-cli" | "openai-completions";
|
||||
baseUrl: string;
|
||||
input?: readonly ["text"] | readonly ["text", "image"];
|
||||
cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
||||
contextWindow?: number;
|
||||
maxTokens?: number;
|
||||
}) {
|
||||
return {
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
provider: params.provider,
|
||||
api: params.api,
|
||||
baseUrl: params.baseUrl,
|
||||
reasoning: true,
|
||||
input: params.input ?? (["text", "image"] as const),
|
||||
cost: params.cost ?? { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
contextWindow: params.contextWindow ?? 200000,
|
||||
maxTokens: params.maxTokens ?? 64000,
|
||||
};
|
||||
}
|
||||
|
||||
function expectResolvedForwardCompatFallback(params: {
|
||||
provider: string;
|
||||
id: string;
|
||||
expectedModel: Record<string, unknown>;
|
||||
cfg?: OpenClawConfig;
|
||||
}) {
|
||||
const result = resolveModel(params.provider, params.id, "/tmp/agent", params.cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject(params.expectedModel);
|
||||
}
|
||||
|
||||
function expectUnknownModelError(provider: string, id: string) {
|
||||
const result = resolveModel(provider, id, "/tmp/agent");
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(`Unknown model: ${provider}/${id}`);
|
||||
}
|
||||
|
||||
describe("buildInlineProviderModels", () => {
|
||||
it("attaches provider ids to inline models", () => {
|
||||
const providers = {
|
||||
@@ -151,175 +192,126 @@ describe("resolveModel", () => {
|
||||
});
|
||||
|
||||
it("builds an anthropic forward-compat fallback for claude-opus-4-6", () => {
|
||||
const templateModel = {
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
mockDiscoveredModel({
|
||||
provider: "anthropic",
|
||||
api: "anthropic-messages",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
input: ["text", "image"] as const,
|
||||
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
};
|
||||
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn((provider: string, modelId: string) => {
|
||||
if (provider === "anthropic" && modelId === "claude-opus-4-5") {
|
||||
return templateModel;
|
||||
}
|
||||
return null;
|
||||
modelId: "claude-opus-4-5",
|
||||
templateModel: buildForwardCompatTemplate({
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
provider: "anthropic",
|
||||
api: "anthropic-messages",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
}),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
});
|
||||
|
||||
const result = resolveModel("anthropic", "claude-opus-4-6", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-6",
|
||||
api: "anthropic-messages",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
expectedModel: {
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-6",
|
||||
api: "anthropic-messages",
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
reasoning: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds an antigravity forward-compat fallback for claude-opus-4-6-thinking", () => {
|
||||
const templateModel = {
|
||||
id: "claude-opus-4-5-thinking",
|
||||
name: "Claude Opus 4.5 Thinking",
|
||||
mockDiscoveredModel({
|
||||
provider: "google-antigravity",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
input: ["text", "image"] as const,
|
||||
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
};
|
||||
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn((provider: string, modelId: string) => {
|
||||
if (provider === "google-antigravity" && modelId === "claude-opus-4-5-thinking") {
|
||||
return templateModel;
|
||||
}
|
||||
return null;
|
||||
modelId: "claude-opus-4-5-thinking",
|
||||
templateModel: buildForwardCompatTemplate({
|
||||
id: "claude-opus-4-5-thinking",
|
||||
name: "Claude Opus 4.5 Thinking",
|
||||
provider: "google-antigravity",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
}),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
});
|
||||
|
||||
const result = resolveModel("google-antigravity", "claude-opus-4-6-thinking", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "google-antigravity",
|
||||
id: "claude-opus-4-6-thinking",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
expectedModel: {
|
||||
provider: "google-antigravity",
|
||||
id: "claude-opus-4-6-thinking",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds an antigravity forward-compat fallback for claude-opus-4-6", () => {
|
||||
const templateModel = {
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
mockDiscoveredModel({
|
||||
provider: "google-antigravity",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
input: ["text", "image"] as const,
|
||||
cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
};
|
||||
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn((provider: string, modelId: string) => {
|
||||
if (provider === "google-antigravity" && modelId === "claude-opus-4-5") {
|
||||
return templateModel;
|
||||
}
|
||||
return null;
|
||||
modelId: "claude-opus-4-5",
|
||||
templateModel: buildForwardCompatTemplate({
|
||||
id: "claude-opus-4-5",
|
||||
name: "Claude Opus 4.5",
|
||||
provider: "google-antigravity",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
}),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
});
|
||||
|
||||
const result = resolveModel("google-antigravity", "claude-opus-4-6", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "google-antigravity",
|
||||
id: "claude-opus-4-6",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
expectedModel: {
|
||||
provider: "google-antigravity",
|
||||
id: "claude-opus-4-6",
|
||||
api: "google-gemini-cli",
|
||||
baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com",
|
||||
reasoning: true,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a zai forward-compat fallback for glm-5", () => {
|
||||
const templateModel = {
|
||||
id: "glm-4.7",
|
||||
name: "GLM-4.7",
|
||||
mockDiscoveredModel({
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
input: ["text"] as const,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 131072,
|
||||
};
|
||||
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn((provider: string, modelId: string) => {
|
||||
if (provider === "zai" && modelId === "glm-4.7") {
|
||||
return templateModel;
|
||||
}
|
||||
return null;
|
||||
modelId: "glm-4.7",
|
||||
templateModel: buildForwardCompatTemplate({
|
||||
id: "glm-4.7",
|
||||
name: "GLM-4.7",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
maxTokens: 131072,
|
||||
}),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
});
|
||||
|
||||
const result = resolveModel("zai", "glm-5", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "zai",
|
||||
id: "glm-5",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
expectedModel: {
|
||||
provider: "zai",
|
||||
id: "glm-5",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps unknown-model errors when no antigravity thinking template exists", () => {
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn(() => null),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
|
||||
const result = resolveModel("google-antigravity", "claude-opus-4-6-thinking", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe("Unknown model: google-antigravity/claude-opus-4-6-thinking");
|
||||
expectUnknownModelError("google-antigravity", "claude-opus-4-6-thinking");
|
||||
});
|
||||
|
||||
it("keeps unknown-model errors when no antigravity non-thinking template exists", () => {
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn(() => null),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
|
||||
const result = resolveModel("google-antigravity", "claude-opus-4-6", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe("Unknown model: google-antigravity/claude-opus-4-6");
|
||||
expectUnknownModelError("google-antigravity", "claude-opus-4-6");
|
||||
});
|
||||
|
||||
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
|
||||
const result = resolveModel("openai-codex", "gpt-4.1-mini", "/tmp/agent");
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe("Unknown model: openai-codex/gpt-4.1-mini");
|
||||
expectUnknownModelError("openai-codex", "gpt-4.1-mini");
|
||||
});
|
||||
|
||||
it("uses codex fallback even when openai-codex provider is configured", () => {
|
||||
@@ -337,15 +329,15 @@ describe("resolveModel", () => {
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
vi.mocked(discoverModels).mockReturnValue({
|
||||
find: vi.fn(() => null),
|
||||
} as unknown as ReturnType<typeof discoverModels>);
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.3-codex", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model?.api).toBe("openai-codex-responses");
|
||||
expect(result.model?.id).toBe("gpt-5.3-codex");
|
||||
expect(result.model?.provider).toBe("openai-codex");
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.3-codex",
|
||||
cfg,
|
||||
expectedModel: {
|
||||
api: "openai-codex-responses",
|
||||
id: "gpt-5.3-codex",
|
||||
provider: "openai-codex",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user