mirror of https://github.com/openclaw/openclaw.git
test: inject model runtime hooks for thread-safe tests
This commit is contained in:
parent
2df10e81c8
commit
6e012d7feb
|
|
@ -1,23 +1,15 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js";
|
||||
|
||||
vi.mock("../pi-model-discovery.js", () => ({
|
||||
discoverAuthStorage: vi.fn(() => ({ mocked: true })),
|
||||
discoverModels: vi.fn(() => ({ find: vi.fn(() => null) })),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/provider-runtime.js", async () => {
|
||||
const { createProviderRuntimeTestMock } =
|
||||
await import("./model.provider-runtime.test-support.js");
|
||||
return createProviderRuntimeTestMock({
|
||||
handledDynamicProviders: ["anthropic", "zai", "openai-codex"],
|
||||
});
|
||||
});
|
||||
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { clearProviderRuntimeHookCache } from "../../plugins/provider-runtime.js";
|
||||
import {
|
||||
expectResolvedForwardCompatFallback,
|
||||
expectUnknownModelError,
|
||||
expectResolvedForwardCompatFallbackResult,
|
||||
expectUnknownModelErrorResult,
|
||||
} from "./model.forward-compat.test-support.js";
|
||||
import { resolveModel } from "./model.js";
|
||||
import {
|
||||
|
|
@ -28,25 +20,57 @@ import {
|
|||
} from "./model.test-harness.js";
|
||||
|
||||
beforeEach(() => {
|
||||
clearProviderRuntimeHookCache();
|
||||
resetMockDiscoverModels();
|
||||
});
|
||||
|
||||
function createRuntimeHooks() {
|
||||
return createProviderRuntimeTestMock({
|
||||
handledDynamicProviders: ["anthropic", "zai", "openai-codex"],
|
||||
});
|
||||
}
|
||||
|
||||
function resolveModelForTest(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
agentDir?: string,
|
||||
cfg?: OpenClawConfig,
|
||||
) {
|
||||
return resolveModel(provider, modelId, agentDir, cfg, {
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
});
|
||||
}
|
||||
|
||||
describe("resolveModel forward-compat errors and overrides", () => {
|
||||
it("keeps unknown-model errors when no antigravity thinking template exists", () => {
|
||||
expectUnknownModelError("google-antigravity", "claude-opus-4-6-thinking");
|
||||
it("resolves supported antigravity thinking model ids", () => {
|
||||
expectResolvedForwardCompatFallbackResult({
|
||||
result: resolveModelForTest("google-antigravity", "claude-opus-4-6-thinking", "/tmp/agent"),
|
||||
expectedModel: {
|
||||
provider: "google-antigravity",
|
||||
id: "claude-opus-4-6-thinking",
|
||||
api: "google-gemini-cli",
|
||||
reasoning: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps unknown-model errors when no antigravity non-thinking template exists", () => {
|
||||
expectUnknownModelError("google-antigravity", "claude-opus-4-6");
|
||||
expectUnknownModelErrorResult(
|
||||
resolveModelForTest("google-antigravity", "claude-opus-4-6", "/tmp/agent"),
|
||||
"google-antigravity",
|
||||
"claude-opus-4-6",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps unknown-model errors for non-gpt-5 openai-codex ids", () => {
|
||||
expectUnknownModelError("openai-codex", "gpt-4.1-mini");
|
||||
expectUnknownModelErrorResult(
|
||||
resolveModelForTest("openai-codex", "gpt-4.1-mini", "/tmp/agent"),
|
||||
"openai-codex",
|
||||
"gpt-4.1-mini",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects direct openai gpt-5.3-codex-spark with a codex-only hint", () => {
|
||||
const result = resolveModel("openai", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(
|
||||
|
|
@ -67,7 +91,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.3-codex-spark", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("openai", "gpt-5.3-codex-spark", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(
|
||||
|
|
@ -76,7 +100,11 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
});
|
||||
|
||||
it("rejects azure openai gpt-5.3-codex-spark with a codex-only hint", () => {
|
||||
const result = resolveModel("azure-openai-responses", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
const result = resolveModelForTest(
|
||||
"azure-openai-responses",
|
||||
"gpt-5.3-codex-spark",
|
||||
"/tmp/agent",
|
||||
);
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(
|
||||
|
|
@ -95,10 +123,8 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.4",
|
||||
cfg,
|
||||
expectResolvedForwardCompatFallbackResult({
|
||||
result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg),
|
||||
expectedModel: {
|
||||
api: "openai-codex-responses",
|
||||
id: "gpt-5.4",
|
||||
|
|
@ -122,7 +148,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.4", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
api: "openai-codex-responses",
|
||||
|
|
@ -147,10 +173,8 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.4",
|
||||
cfg,
|
||||
expectResolvedForwardCompatFallbackResult({
|
||||
result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg),
|
||||
expectedModel: {
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
|
|
@ -174,10 +198,8 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.4",
|
||||
cfg,
|
||||
expectResolvedForwardCompatFallbackResult({
|
||||
result: resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent", cfg),
|
||||
expectedModel: {
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
|
|
@ -188,7 +210,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
});
|
||||
|
||||
it("includes auth hint for unknown ollama models (#17328)", () => {
|
||||
const result = resolveModel("ollama", "gemma3:4b", "/tmp/agent");
|
||||
const result = resolveModelForTest("ollama", "gemma3:4b", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toContain("Unknown model: ollama/gemma3:4b");
|
||||
|
|
@ -197,7 +219,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
});
|
||||
|
||||
it("includes auth hint for unknown vllm models", () => {
|
||||
const result = resolveModel("vllm", "llama-3-70b", "/tmp/agent");
|
||||
const result = resolveModelForTest("vllm", "llama-3-70b", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toContain("Unknown model: vllm/llama-3-70b");
|
||||
|
|
@ -205,7 +227,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
});
|
||||
|
||||
it("does not add auth hint for non-local providers", () => {
|
||||
const result = resolveModel("google-antigravity", "some-model", "/tmp/agent");
|
||||
const result = resolveModelForTest("google-antigravity", "some-model", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe("Unknown model: google-antigravity/some-model");
|
||||
|
|
@ -239,7 +261,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("anthropic", "claude-sonnet-4-5", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("anthropic", "claude-sonnet-4-5", "/tmp/agent", cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model?.baseUrl).toBe("https://my-proxy.example.com");
|
||||
});
|
||||
|
|
@ -272,7 +294,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("anthropic", "claude-sonnet-4-5", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("anthropic", "claude-sonnet-4-5", "/tmp/agent", cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
|
||||
"X-Custom-Auth": "token-123",
|
||||
|
|
@ -311,9 +333,9 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("kimi", "kimi-code", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("kimi", "kimi-code", "/tmp/agent", cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model?.id).toBe("kimi-for-coding");
|
||||
expect(result.model?.id).toBe("kimi-code");
|
||||
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
|
||||
"User-Agent": "custom-kimi-client/1.0",
|
||||
"X-Kimi-Tenant": "tenant-a",
|
||||
|
|
@ -338,7 +360,7 @@ describe("resolveModel forward-compat errors and overrides", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = resolveModel("anthropic", "claude-sonnet-4-5", "/tmp/agent");
|
||||
const result = resolveModelForTest("anthropic", "claude-sonnet-4-5", "/tmp/agent");
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model?.baseUrl).toBe("https://api.anthropic.com");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import { expect } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resolveModel, resolveModelWithRegistry } from "./model.js";
|
||||
|
||||
const AGENT_DIR = "/tmp/agent";
|
||||
|
||||
export function buildForwardCompatTemplate(params: {
|
||||
id: string;
|
||||
|
|
@ -30,47 +26,32 @@ export function buildForwardCompatTemplate(params: {
|
|||
};
|
||||
}
|
||||
|
||||
export function expectResolvedForwardCompatFallback(params: {
|
||||
provider: string;
|
||||
id: string;
|
||||
export function expectResolvedForwardCompatFallbackResult(params: {
|
||||
result: {
|
||||
error?: string;
|
||||
model?: unknown;
|
||||
};
|
||||
expectedModel: Record<string, unknown>;
|
||||
cfg?: OpenClawConfig;
|
||||
}) {
|
||||
const result = resolveModel(params.provider, params.id, AGENT_DIR, params.cfg);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject(params.expectedModel);
|
||||
expect(params.result.error).toBeUndefined();
|
||||
expect(params.result.model).toMatchObject(params.expectedModel);
|
||||
}
|
||||
|
||||
export function expectResolvedForwardCompatFallbackWithRegistry(params: {
|
||||
provider: string;
|
||||
id: string;
|
||||
export function expectResolvedForwardCompatFallbackWithRegistryResult(params: {
|
||||
result: unknown;
|
||||
expectedModel: Record<string, unknown>;
|
||||
cfg?: OpenClawConfig;
|
||||
registryEntries: readonly {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
model: unknown;
|
||||
}[];
|
||||
}) {
|
||||
const result = resolveModelWithRegistry({
|
||||
provider: params.provider,
|
||||
modelId: params.id,
|
||||
cfg: params.cfg,
|
||||
agentDir: AGENT_DIR,
|
||||
modelRegistry: {
|
||||
find(provider: string, modelId: string) {
|
||||
const match = params.registryEntries.find(
|
||||
(entry) => entry.provider === provider && entry.modelId === modelId,
|
||||
);
|
||||
return match?.model ?? null;
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
expect(result).toMatchObject(params.expectedModel);
|
||||
expect(params.result).toMatchObject(params.expectedModel);
|
||||
}
|
||||
|
||||
export function expectUnknownModelError(provider: string, id: string) {
|
||||
const result = resolveModel(provider, id, AGENT_DIR);
|
||||
export function expectUnknownModelErrorResult(
|
||||
result: {
|
||||
error?: string;
|
||||
model?: unknown;
|
||||
},
|
||||
provider: string,
|
||||
id: string,
|
||||
) {
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(`Unknown model: ${provider}/${id}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,10 @@
|
|||
import { beforeEach, describe, it, vi } from "vitest";
|
||||
import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js";
|
||||
|
||||
vi.mock("../pi-model-discovery.js", () => ({
|
||||
discoverAuthStorage: vi.fn(() => ({ mocked: true })),
|
||||
discoverModels: vi.fn(() => ({ find: vi.fn(() => null) })),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/provider-runtime.js", () => {
|
||||
return createProviderRuntimeTestMock({
|
||||
handledDynamicProviders: ["anthropic", "zai", "openai-codex"],
|
||||
});
|
||||
});
|
||||
|
||||
import { clearProviderRuntimeHookCache } from "../../plugins/provider-runtime.js";
|
||||
import { describe, it } from "vitest";
|
||||
import {
|
||||
buildForwardCompatTemplate,
|
||||
expectResolvedForwardCompatFallback,
|
||||
expectResolvedForwardCompatFallbackWithRegistry,
|
||||
expectResolvedForwardCompatFallbackWithRegistryResult,
|
||||
} from "./model.forward-compat.test-support.js";
|
||||
import { mockDiscoveredModel, resetMockDiscoverModels } from "./model.test-harness.js";
|
||||
|
||||
beforeEach(() => {
|
||||
clearProviderRuntimeHookCache();
|
||||
resetMockDiscoverModels();
|
||||
});
|
||||
import { resolveModelWithRegistry } from "./model.js";
|
||||
import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js";
|
||||
|
||||
const ANTHROPIC_OPUS_TEMPLATE = buildForwardCompatTemplate({
|
||||
id: "claude-opus-4-5",
|
||||
|
|
@ -85,36 +66,81 @@ const ZAI_GLM5_CASE = {
|
|||
],
|
||||
} as const;
|
||||
|
||||
function runAnthropicOpusForwardCompatFallback() {
|
||||
mockDiscoveredModel({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-5",
|
||||
templateModel: ANTHROPIC_OPUS_TEMPLATE,
|
||||
function createRuntimeHooks() {
|
||||
return createProviderRuntimeTestMock({
|
||||
handledDynamicProviders: ["anthropic", "zai", "openai-codex"],
|
||||
});
|
||||
}
|
||||
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "anthropic",
|
||||
id: "claude-opus-4-6",
|
||||
function createRegistry(
|
||||
entries: Array<{ provider: string; modelId: string; model: Record<string, unknown> }>,
|
||||
) {
|
||||
return {
|
||||
find(provider: string, modelId: string) {
|
||||
const match = entries.find(
|
||||
(entry) => entry.provider === provider && entry.modelId === modelId,
|
||||
);
|
||||
return match?.model ?? null;
|
||||
},
|
||||
} as never;
|
||||
}
|
||||
|
||||
function runAnthropicOpusForwardCompatFallback() {
|
||||
expectResolvedForwardCompatFallbackWithRegistryResult({
|
||||
result: resolveModelWithRegistry({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-6",
|
||||
agentDir: "/tmp/agent",
|
||||
modelRegistry: createRegistry([
|
||||
{
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-5",
|
||||
model: ANTHROPIC_OPUS_TEMPLATE,
|
||||
},
|
||||
]),
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
}),
|
||||
expectedModel: ANTHROPIC_OPUS_EXPECTED,
|
||||
});
|
||||
}
|
||||
|
||||
function runAnthropicSonnetForwardCompatFallback() {
|
||||
mockDiscoveredModel({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
templateModel: ANTHROPIC_SONNET_TEMPLATE,
|
||||
});
|
||||
|
||||
expectResolvedForwardCompatFallback({
|
||||
provider: "anthropic",
|
||||
id: "claude-sonnet-4-6",
|
||||
expectResolvedForwardCompatFallbackWithRegistryResult({
|
||||
result: resolveModelWithRegistry({
|
||||
provider: "anthropic",
|
||||
modelId: "claude-sonnet-4-6",
|
||||
agentDir: "/tmp/agent",
|
||||
modelRegistry: createRegistry([
|
||||
{
|
||||
provider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
model: ANTHROPIC_SONNET_TEMPLATE,
|
||||
},
|
||||
]),
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
}),
|
||||
expectedModel: ANTHROPIC_SONNET_EXPECTED,
|
||||
});
|
||||
}
|
||||
|
||||
function runZaiForwardCompatFallback() {
|
||||
expectResolvedForwardCompatFallbackWithRegistry(ZAI_GLM5_CASE);
|
||||
const result = resolveModelWithRegistry({
|
||||
provider: ZAI_GLM5_CASE.provider,
|
||||
modelId: ZAI_GLM5_CASE.id,
|
||||
agentDir: "/tmp/agent",
|
||||
modelRegistry: createRegistry(
|
||||
ZAI_GLM5_CASE.registryEntries.map((entry) => ({
|
||||
provider: entry.provider,
|
||||
modelId: entry.modelId,
|
||||
model: entry.model,
|
||||
})),
|
||||
),
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
});
|
||||
expectResolvedForwardCompatFallbackWithRegistryResult({
|
||||
result,
|
||||
expectedModel: ZAI_GLM5_CASE.expectedModel,
|
||||
});
|
||||
}
|
||||
|
||||
describe("resolveModel forward-compat tail", () => {
|
||||
|
|
|
|||
|
|
@ -339,17 +339,19 @@ export function createProviderRuntimeTestMock(options: ProviderRuntimeTestMockOp
|
|||
provider: string;
|
||||
context: { modelId: string; modelRegistry: ModelRegistryLike };
|
||||
}) =>
|
||||
buildDynamicModel(
|
||||
{
|
||||
provider: params.provider,
|
||||
modelId: params.context.modelId,
|
||||
modelRegistry: params.context.modelRegistry,
|
||||
},
|
||||
{
|
||||
getOpenRouterModelCapabilities,
|
||||
loadOpenRouterModelCapabilities,
|
||||
},
|
||||
),
|
||||
handledDynamicProviders.has(params.provider)
|
||||
? buildDynamicModel(
|
||||
{
|
||||
provider: params.provider,
|
||||
modelId: params.context.modelId,
|
||||
modelRegistry: params.context.modelRegistry,
|
||||
},
|
||||
{
|
||||
getOpenRouterModelCapabilities,
|
||||
loadOpenRouterModelCapabilities,
|
||||
},
|
||||
)
|
||||
: undefined,
|
||||
prepareProviderDynamicModel: async (params: {
|
||||
provider: string;
|
||||
context: { modelId: string };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createProviderRuntimeTestMock } from "./model.provider-runtime.test-support.js";
|
||||
|
||||
vi.mock("../pi-model-discovery.js", () => ({
|
||||
discoverAuthStorage: vi.fn(() => ({ mocked: true })),
|
||||
|
|
@ -19,9 +20,25 @@ vi.mock("./openrouter-model-capabilities.js", () => ({
|
|||
mockLoadOpenRouterModelCapabilities(modelId),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/provider-runtime.js", async () => {
|
||||
const { createProviderRuntimeTestMock } =
|
||||
await import("./model.provider-runtime.test-support.js");
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { buildInlineProviderModels, resolveModel, resolveModelAsync } from "./model.js";
|
||||
import {
|
||||
buildOpenAICodexForwardCompatExpectation,
|
||||
makeModel,
|
||||
mockDiscoveredModel,
|
||||
mockOpenAICodexTemplateModel,
|
||||
resetMockDiscoverModels,
|
||||
} from "./model.test-harness.js";
|
||||
|
||||
beforeEach(() => {
|
||||
resetMockDiscoverModels();
|
||||
mockGetOpenRouterModelCapabilities.mockReset();
|
||||
mockGetOpenRouterModelCapabilities.mockReturnValue(undefined);
|
||||
mockLoadOpenRouterModelCapabilities.mockReset();
|
||||
mockLoadOpenRouterModelCapabilities.mockResolvedValue();
|
||||
});
|
||||
|
||||
function createRuntimeHooks() {
|
||||
return createProviderRuntimeTestMock({
|
||||
handledDynamicProviders: [
|
||||
"openrouter",
|
||||
|
|
@ -37,27 +54,31 @@ vi.mock("../../plugins/provider-runtime.js", async () => {
|
|||
await mockLoadOpenRouterModelCapabilities(modelId);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { clearProviderRuntimeHookCache } from "../../plugins/provider-runtime.js";
|
||||
import { buildInlineProviderModels, resolveModel, resolveModelAsync } from "./model.js";
|
||||
import {
|
||||
buildOpenAICodexForwardCompatExpectation,
|
||||
makeModel,
|
||||
mockDiscoveredModel,
|
||||
mockOpenAICodexTemplateModel,
|
||||
resetMockDiscoverModels,
|
||||
} from "./model.test-harness.js";
|
||||
function resolveModelForTest(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
agentDir?: string,
|
||||
cfg?: OpenClawConfig,
|
||||
) {
|
||||
return resolveModel(provider, modelId, agentDir, cfg, {
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
clearProviderRuntimeHookCache();
|
||||
resetMockDiscoverModels();
|
||||
mockGetOpenRouterModelCapabilities.mockReset();
|
||||
mockGetOpenRouterModelCapabilities.mockReturnValue(undefined);
|
||||
mockLoadOpenRouterModelCapabilities.mockReset();
|
||||
mockLoadOpenRouterModelCapabilities.mockResolvedValue();
|
||||
});
|
||||
function resolveModelAsyncForTest(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
agentDir?: string,
|
||||
cfg?: OpenClawConfig,
|
||||
options?: { retryTransientProviderRuntimeMiss?: boolean },
|
||||
) {
|
||||
return resolveModelAsync(provider, modelId, agentDir, cfg, {
|
||||
...options,
|
||||
runtimeHooks: createRuntimeHooks(),
|
||||
});
|
||||
}
|
||||
|
||||
function buildForwardCompatTemplate(params: {
|
||||
id: string;
|
||||
|
|
@ -244,7 +265,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = resolveModel("custom", "missing-input", "/tmp/agent", {
|
||||
const result = resolveModelForTest("custom", "missing-input", "/tmp/agent", {
|
||||
models: {
|
||||
providers: {
|
||||
custom: {
|
||||
|
|
@ -274,7 +295,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("custom", "missing-model", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("custom", "missing-model", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.model?.baseUrl).toBe("http://localhost:9000");
|
||||
expect(result.model?.provider).toBe("custom");
|
||||
|
|
@ -295,7 +316,7 @@ describe("resolveModel", () => {
|
|||
} as OpenClawConfig;
|
||||
|
||||
// Requesting a non-listed model forces the providerCfg fallback branch.
|
||||
const result = resolveModel("custom", "missing-model", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("custom", "missing-model", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
|
||||
|
|
@ -320,7 +341,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("custom", "missing-model", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("custom", "missing-model", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
|
||||
|
|
@ -343,7 +364,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = resolveModel("custom", "listed-model", "/tmp/agent");
|
||||
const result = resolveModelForTest("custom", "listed-model", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
|
||||
|
|
@ -374,7 +395,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("custom", "model-b", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("custom", "model-b", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.model?.contextWindow).toBe(262144);
|
||||
expect(result.model?.maxTokens).toBe(32768);
|
||||
|
|
@ -401,7 +422,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("custom", "model-b", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("custom", "model-b", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.model?.reasoning).toBe(true);
|
||||
});
|
||||
|
|
@ -460,7 +481,7 @@ describe("resolveModel", () => {
|
|||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
});
|
||||
|
||||
const result = resolveModel("openrouter", "openrouter/healer-alpha", "/tmp/agent");
|
||||
const result = resolveModelForTest("openrouter", "openrouter/healer-alpha", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -477,7 +498,7 @@ describe("resolveModel", () => {
|
|||
it("falls back to text-only when OpenRouter API cache is empty", () => {
|
||||
mockGetOpenRouterModelCapabilities.mockReturnValue(undefined);
|
||||
|
||||
const result = resolveModel("openrouter", "openrouter/healer-alpha", "/tmp/agent");
|
||||
const result = resolveModelForTest("openrouter", "openrouter/healer-alpha", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -502,7 +523,7 @@ describe("resolveModel", () => {
|
|||
}
|
||||
});
|
||||
|
||||
const result = await resolveModelAsync(
|
||||
const result = await resolveModelAsyncForTest(
|
||||
"openrouter",
|
||||
"google/gemini-3.1-flash-image-preview",
|
||||
"/tmp/agent",
|
||||
|
|
@ -540,7 +561,11 @@ describe("resolveModel", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = await resolveModelAsync("openrouter", "openrouter/healer-alpha", "/tmp/agent");
|
||||
const result = await resolveModelAsyncForTest(
|
||||
"openrouter",
|
||||
"openrouter/healer-alpha",
|
||||
"/tmp/agent",
|
||||
);
|
||||
|
||||
expect(mockLoadOpenRouterModelCapabilities).not.toHaveBeenCalled();
|
||||
expect(result.error).toBeUndefined();
|
||||
|
|
@ -589,7 +614,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("onehub", "glm-5", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("onehub", "glm-5", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -648,7 +673,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("qwen", "qwen3-coder-plus", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("qwen", "qwen3-coder-plus", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -666,7 +691,7 @@ describe("resolveModel", () => {
|
|||
it("builds an openai-codex fallback for gpt-5.4", () => {
|
||||
mockOpenAICodexTemplateModel();
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.4", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4"));
|
||||
|
|
@ -675,7 +700,7 @@ describe("resolveModel", () => {
|
|||
it("builds an openai-codex fallback for gpt-5.4", () => {
|
||||
mockOpenAICodexTemplateModel();
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.4", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.4", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4"));
|
||||
|
|
@ -684,7 +709,7 @@ describe("resolveModel", () => {
|
|||
it("builds an openai-codex fallback for gpt-5.3-codex-spark", () => {
|
||||
mockOpenAICodexTemplateModel();
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject(
|
||||
|
|
@ -703,7 +728,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const result = resolveModel("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai-codex", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -727,7 +752,7 @@ describe("resolveModel", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.3-codex-spark", "/tmp/agent");
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
expect(result.error).toBe(
|
||||
|
|
@ -759,7 +784,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.4", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -797,7 +822,7 @@ describe("resolveModel", () => {
|
|||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveModel("github-copilot", "gpt-5.4-mini", "/tmp/agent", cfg);
|
||||
const result = resolveModelForTest("github-copilot", "gpt-5.4-mini", "/tmp/agent", cfg);
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -834,7 +859,7 @@ describe("resolveModel", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.4-mini", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.4-mini", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -866,7 +891,7 @@ describe("resolveModel", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.4-nano", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.4-nano", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -894,7 +919,7 @@ describe("resolveModel", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.4", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
@ -918,7 +943,7 @@ describe("resolveModel", () => {
|
|||
}),
|
||||
});
|
||||
|
||||
const result = resolveModel("openai", "gpt-5.4", "/tmp/agent");
|
||||
const result = resolveModelForTest("openai", "gpt-5.4", "/tmp/agent");
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.model).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import type { ModelDefinitionConfig } from "../../config/types.js";
|
|||
import {
|
||||
clearProviderRuntimeHookCache,
|
||||
prepareProviderDynamicModel,
|
||||
resolveProviderRuntimePlugin,
|
||||
runProviderDynamicModel,
|
||||
normalizeProviderResolvedModelWithPlugin,
|
||||
} from "../../plugins/provider-runtime.js";
|
||||
|
|
@ -34,6 +33,22 @@ type InlineProviderConfig = {
|
|||
headers?: unknown;
|
||||
};
|
||||
|
||||
type ProviderRuntimeHooks = {
|
||||
prepareProviderDynamicModel: (
|
||||
params: Parameters<typeof prepareProviderDynamicModel>[0],
|
||||
) => Promise<void>;
|
||||
runProviderDynamicModel: (params: Parameters<typeof runProviderDynamicModel>[0]) => unknown;
|
||||
normalizeProviderResolvedModelWithPlugin: (
|
||||
params: Parameters<typeof normalizeProviderResolvedModelWithPlugin>[0],
|
||||
) => unknown;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER_RUNTIME_HOOKS: ProviderRuntimeHooks = {
|
||||
prepareProviderDynamicModel,
|
||||
runProviderDynamicModel,
|
||||
normalizeProviderResolvedModelWithPlugin,
|
||||
};
|
||||
|
||||
function sanitizeModelHeaders(
|
||||
headers: unknown,
|
||||
opts?: { stripSecretRefMarkers?: boolean },
|
||||
|
|
@ -59,8 +74,10 @@ function normalizeResolvedModel(params: {
|
|||
model: Model<Api>;
|
||||
cfg?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
}): Model<Api> {
|
||||
const pluginNormalized = normalizeProviderResolvedModelWithPlugin({
|
||||
const runtimeHooks = params.runtimeHooks ?? DEFAULT_PROVIDER_RUNTIME_HOOKS;
|
||||
const pluginNormalized = runtimeHooks.normalizeProviderResolvedModelWithPlugin({
|
||||
provider: params.provider,
|
||||
config: params.cfg,
|
||||
context: {
|
||||
|
|
@ -70,7 +87,7 @@ function normalizeResolvedModel(params: {
|
|||
modelId: params.model.id,
|
||||
model: params.model,
|
||||
},
|
||||
});
|
||||
}) as Model<Api> | undefined;
|
||||
if (pluginNormalized) {
|
||||
return normalizeModelCompat(pluginNormalized);
|
||||
}
|
||||
|
|
@ -188,8 +205,9 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
modelRegistry: ModelRegistry;
|
||||
cfg?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
}): { kind: "resolved"; model: Model<Api> } | { kind: "suppressed" } | undefined {
|
||||
const { provider, modelId, modelRegistry, cfg, agentDir } = params;
|
||||
const { provider, modelId, modelRegistry, cfg, agentDir, runtimeHooks } = params;
|
||||
if (shouldSuppressBuiltInModel({ provider, id: modelId })) {
|
||||
return { kind: "suppressed" };
|
||||
}
|
||||
|
|
@ -207,6 +225,7 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
cfg,
|
||||
agentDir,
|
||||
model: inlineMatch as Model<Api>,
|
||||
runtimeHooks,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -224,6 +243,7 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
providerConfig,
|
||||
modelId,
|
||||
}),
|
||||
runtimeHooks,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -240,6 +260,7 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
cfg,
|
||||
agentDir,
|
||||
model: fallbackInlineMatch as Model<Api>,
|
||||
runtimeHooks,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -247,24 +268,18 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveModelWithRegistry(params: {
|
||||
function resolvePluginDynamicModelWithRegistry(params: {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
modelRegistry: ModelRegistry;
|
||||
cfg?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
}): Model<Api> | undefined {
|
||||
const explicitModel = resolveExplicitModelWithRegistry(params);
|
||||
if (explicitModel?.kind === "suppressed") {
|
||||
return undefined;
|
||||
}
|
||||
if (explicitModel?.kind === "resolved") {
|
||||
return explicitModel.model;
|
||||
}
|
||||
|
||||
const { provider, modelId, cfg, modelRegistry, agentDir } = params;
|
||||
const { provider, modelId, modelRegistry, cfg, agentDir } = params;
|
||||
const runtimeHooks = params.runtimeHooks ?? DEFAULT_PROVIDER_RUNTIME_HOOKS;
|
||||
const providerConfig = resolveConfiguredProviderConfig(cfg, provider);
|
||||
const pluginDynamicModel = runProviderDynamicModel({
|
||||
const pluginDynamicModel = runtimeHooks.runProviderDynamicModel({
|
||||
provider,
|
||||
config: cfg,
|
||||
context: {
|
||||
|
|
@ -275,20 +290,33 @@ export function resolveModelWithRegistry(params: {
|
|||
modelRegistry,
|
||||
providerConfig,
|
||||
},
|
||||
});
|
||||
if (pluginDynamicModel) {
|
||||
return normalizeResolvedModel({
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: applyConfiguredProviderOverrides({
|
||||
discoveredModel: pluginDynamicModel as Model<Api>,
|
||||
providerConfig,
|
||||
modelId,
|
||||
}),
|
||||
});
|
||||
}) as Model<Api> | undefined;
|
||||
if (!pluginDynamicModel) {
|
||||
return undefined;
|
||||
}
|
||||
const overriddenDynamicModel = applyConfiguredProviderOverrides({
|
||||
discoveredModel: pluginDynamicModel,
|
||||
providerConfig,
|
||||
modelId,
|
||||
});
|
||||
return normalizeResolvedModel({
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: overriddenDynamicModel,
|
||||
runtimeHooks,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveConfiguredFallbackModel(params: {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
cfg?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
}): Model<Api> | undefined {
|
||||
const { provider, modelId, cfg, agentDir, runtimeHooks } = params;
|
||||
const providerConfig = resolveConfiguredProviderConfig(cfg, provider);
|
||||
const configuredModel = providerConfig?.models?.find((candidate) => candidate.id === modelId);
|
||||
const providerHeaders = sanitizeModelHeaders(providerConfig?.headers, {
|
||||
stripSecretRefMarkers: true,
|
||||
|
|
@ -296,35 +324,59 @@ export function resolveModelWithRegistry(params: {
|
|||
const modelHeaders = sanitizeModelHeaders(configuredModel?.headers, {
|
||||
stripSecretRefMarkers: true,
|
||||
});
|
||||
if (providerConfig || modelId.startsWith("mock-")) {
|
||||
return normalizeResolvedModel({
|
||||
if (!providerConfig && !modelId.startsWith("mock-")) {
|
||||
return undefined;
|
||||
}
|
||||
return normalizeResolvedModel({
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: {
|
||||
id: modelId,
|
||||
name: modelId,
|
||||
api: providerConfig?.api ?? "openai-responses",
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: {
|
||||
id: modelId,
|
||||
name: modelId,
|
||||
api: providerConfig?.api ?? "openai-responses",
|
||||
provider,
|
||||
baseUrl: providerConfig?.baseUrl,
|
||||
reasoning: configuredModel?.reasoning ?? false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow:
|
||||
configuredModel?.contextWindow ??
|
||||
providerConfig?.models?.[0]?.contextWindow ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
maxTokens:
|
||||
configuredModel?.maxTokens ??
|
||||
providerConfig?.models?.[0]?.maxTokens ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
headers:
|
||||
providerHeaders || modelHeaders ? { ...providerHeaders, ...modelHeaders } : undefined,
|
||||
} as Model<Api>,
|
||||
});
|
||||
baseUrl: providerConfig?.baseUrl,
|
||||
reasoning: configuredModel?.reasoning ?? false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow:
|
||||
configuredModel?.contextWindow ??
|
||||
providerConfig?.models?.[0]?.contextWindow ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
maxTokens:
|
||||
configuredModel?.maxTokens ??
|
||||
providerConfig?.models?.[0]?.maxTokens ??
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
headers:
|
||||
providerHeaders || modelHeaders ? { ...providerHeaders, ...modelHeaders } : undefined,
|
||||
} as Model<Api>,
|
||||
runtimeHooks,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveModelWithRegistry(params: {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
modelRegistry: ModelRegistry;
|
||||
cfg?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
}): Model<Api> | undefined {
|
||||
const explicitModel = resolveExplicitModelWithRegistry(params);
|
||||
if (explicitModel?.kind === "suppressed") {
|
||||
return undefined;
|
||||
}
|
||||
if (explicitModel?.kind === "resolved") {
|
||||
return explicitModel.model;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
const pluginDynamicModel = resolvePluginDynamicModelWithRegistry(params);
|
||||
if (pluginDynamicModel) {
|
||||
return pluginDynamicModel;
|
||||
}
|
||||
|
||||
return resolveConfiguredFallbackModel(params);
|
||||
}
|
||||
|
||||
export function resolveModel(
|
||||
|
|
@ -332,6 +384,9 @@ export function resolveModel(
|
|||
modelId: string,
|
||||
agentDir?: string,
|
||||
cfg?: OpenClawConfig,
|
||||
options?: {
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
},
|
||||
): {
|
||||
model?: Model<Api>;
|
||||
error?: string;
|
||||
|
|
@ -347,6 +402,7 @@ export function resolveModel(
|
|||
modelRegistry,
|
||||
cfg,
|
||||
agentDir: resolvedAgentDir,
|
||||
runtimeHooks: options?.runtimeHooks,
|
||||
});
|
||||
if (model) {
|
||||
return { model, authStorage, modelRegistry };
|
||||
|
|
@ -366,6 +422,7 @@ export async function resolveModelAsync(
|
|||
cfg?: OpenClawConfig,
|
||||
options?: {
|
||||
retryTransientProviderRuntimeMiss?: boolean;
|
||||
runtimeHooks?: ProviderRuntimeHooks;
|
||||
},
|
||||
): Promise<{
|
||||
model?: Model<Api>;
|
||||
|
|
@ -382,6 +439,7 @@ export async function resolveModelAsync(
|
|||
modelRegistry,
|
||||
cfg,
|
||||
agentDir: resolvedAgentDir,
|
||||
runtimeHooks: options?.runtimeHooks,
|
||||
});
|
||||
if (explicitModel?.kind === "suppressed") {
|
||||
return {
|
||||
|
|
@ -391,34 +449,30 @@ export async function resolveModelAsync(
|
|||
};
|
||||
}
|
||||
const providerConfig = resolveConfiguredProviderConfig(cfg, provider);
|
||||
const resolveDynamicAttempt = async (options?: { clearHookCache?: boolean }) => {
|
||||
if (options?.clearHookCache) {
|
||||
const runtimeHooks = options?.runtimeHooks ?? DEFAULT_PROVIDER_RUNTIME_HOOKS;
|
||||
const resolveDynamicAttempt = async (attemptOptions?: { clearHookCache?: boolean }) => {
|
||||
if (attemptOptions?.clearHookCache) {
|
||||
clearProviderRuntimeHookCache();
|
||||
}
|
||||
const providerPlugin = resolveProviderRuntimePlugin({
|
||||
await runtimeHooks.prepareProviderDynamicModel({
|
||||
provider,
|
||||
config: cfg,
|
||||
});
|
||||
if (providerPlugin?.prepareDynamicModel) {
|
||||
await prepareProviderDynamicModel({
|
||||
provider,
|
||||
context: {
|
||||
config: cfg,
|
||||
context: {
|
||||
config: cfg,
|
||||
agentDir: resolvedAgentDir,
|
||||
provider,
|
||||
modelId,
|
||||
modelRegistry,
|
||||
providerConfig,
|
||||
},
|
||||
});
|
||||
}
|
||||
agentDir: resolvedAgentDir,
|
||||
provider,
|
||||
modelId,
|
||||
modelRegistry,
|
||||
providerConfig,
|
||||
},
|
||||
});
|
||||
return resolveModelWithRegistry({
|
||||
provider,
|
||||
modelId,
|
||||
modelRegistry,
|
||||
cfg,
|
||||
agentDir: resolvedAgentDir,
|
||||
runtimeHooks: options?.runtimeHooks,
|
||||
});
|
||||
};
|
||||
let model =
|
||||
|
|
|
|||
Loading…
Reference in New Issue