import { beforeEach, describe, expect, it, vi } from
"vitest" ;
import { discoverModels } from
"../pi-model-discovery.js" ;
import { createProviderRuntimeTestMock } from
"./model.provider-runtime.test-support.js" ;
vi.mock(
"../model-suppression.js" , () => ({
shouldSuppressBuiltInModel: ({ provider, id }: { provider?: string; id?: string }) =>
(provider ===
"openai" ||
provider ===
"azure-openai-responses" ||
provider ===
"openai-codex" ) &&
id?.trim().toLowerCase() ===
"gpt-5.3-codex-spark" ,
buildSuppressedBuiltInModelError: ({ provider, id }: { provider?: string; id?: string }) => {
if (
(provider !==
"openai" &&
provider !==
"azure-openai-responses" &&
provider !==
"openai-codex" ) ||
id?.trim().toLowerCase() !==
"gpt-5.3-codex-spark"
) {
return undefined;
}
return `Unknown model: ${provider}/gpt-
5 .
3 -codex-spark. gpt-
5 .
3 -codex-spark is no longe
r exposed by the OpenAI or Codex catalogs. Use openai/gpt-5 .5 .`;
},
}));
vi.mock("../pi-model-discovery.js" , () => ({
discoverAuthStorage: vi.fn(() => ({ mocked: true })),
discoverModels: vi.fn(() => ({ find: vi.fn(() => null ) })),
}));
import type { OpenRouterModelCapabilities } from "./openrouter-model-capabilities.js" ;
const mockGetOpenRouterModelCapabilities = vi.fn<
(modelId: string) => OpenRouterModelCapabilities | undefined
>(() => undefined);
const mockLoadOpenRouterModelCapabilities = vi.fn<(modelId: string) => Promise<void >>(
async () => {},
);
vi.mock("./openrouter-model-capabilities.js" , () => ({
getOpenRouterModelCapabilities: (modelId: string) => mockGetOpenRouterModelCapabilities(modelId),
loadOpenRouterModelCapabilities: (modelId: string) =>
mockLoadOpenRouterModelCapabilities(modelId),
}));
import type { OpenClawConfig } from "../../config/config.js" ;
import { buildForwardCompatTemplate } from "./model.forward-compat.test-support.js" ;
import { buildInlineProviderModels, resolveModel, resolveModelAsync } from "./model.js" ;
import {
buildOpenAICodexForwardCompatExpectation,
makeModel,
mockDiscoveredModel,
OPENAI_CODEX_TEMPLATE_MODEL,
mockOpenAICodexTemplateModel,
resetMockDiscoverModels,
} from "./model.test-harness.js" ;
beforeEach(() => {
resetMockDiscoverModels(discoverModels);
mockGetOpenRouterModelCapabilities.mockReset();
mockGetOpenRouterModelCapabilities.mockReturnValue(undefined);
mockLoadOpenRouterModelCapabilities.mockReset();
mockLoadOpenRouterModelCapabilities.mockResolvedValue();
});
function createRuntimeHooks() {
return createProviderRuntimeTestMock({
handledDynamicProviders: [
"openrouter" ,
"github-copilot" ,
"openai-codex" ,
"openai" ,
"anthropic" ,
"zai" ,
],
getOpenRouterModelCapabilities: (modelId: string) =>
mockGetOpenRouterModelCapabilities(modelId),
loadOpenRouterModelCapabilities: async (modelId: string) => {
await mockLoadOpenRouterModelCapabilities(modelId);
},
});
}
function resolveModelForTest(
provider: string,
modelId: string,
agentDir?: string,
cfg?: OpenClawConfig,
) {
const resolvedAgentDir = agentDir ?? "/tmp/agent" ;
return resolveModel(provider, modelId, agentDir, cfg, {
authStorage: { mocked: true } as never,
modelRegistry: discoverModels({ mocked: true } as never, resolvedAgentDir),
runtimeHooks: createRuntimeHooks(),
});
}
function resolveModelAsyncForTest(
provider: string,
modelId: string,
agentDir?: string,
cfg?: OpenClawConfig,
options?: { retryTransientProviderRuntimeMiss?: boolean },
) {
const resolvedAgentDir = agentDir ?? "/tmp/agent" ;
return resolveModelAsync(provider, modelId, agentDir, cfg, {
authStorage: { mocked: true } as never,
modelRegistry: discoverModels({ mocked: true } as never, resolvedAgentDir),
...options,
runtimeHooks: createRuntimeHooks(),
});
}
describe("resolveModel" , () => {
it("defaults model input to text when discovery omits input" , () => {
mockDiscoveredModel(discoverModels, {
provider: "custom" ,
modelId: "missing-input" ,
templateModel: {
id: "missing-input" ,
name: "missing-input" ,
api: "openai-completions" ,
provider: "custom" ,
baseUrl: "http://localhost:9999 ",
reasoning: false ,
// NOTE: deliberately omit input to simulate buggy/custom catalogs.
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 8192 ,
maxTokens: 1024 ,
},
});
const result = resolveModelForTest("custom" , "missing-input" , "/tmp/agent" , {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9999 ",
api: "openai-completions" ,
// Intentionally keep this minimal — the discovered model provides the rest.
models: [{ id: "missing-input" , name: "missing-input" }],
},
},
},
} as unknown as OpenClawConfig);
expect(result.error).toBeUndefined();
expect(Array.isArray(result.model?.input)).toBe(true );
expect(result.model?.input).toEqual(["text" ]);
});
it("includes provider baseUrl in fallback model" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
models: [],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom" , "missing-model" , "/tmp/agent" , cfg);
expect(result.model?.baseUrl).toBe("http://localhost:9000 ");
expect(result.model?.provider).toBe("custom" );
expect(result.model?.id).toBe("missing-model" );
});
it("normalizes Google fallback baseUrls for custom providers" , () => {
const cfg = {
models: {
providers: {
"google-paid" : {
baseUrl: "https://generativelanguage.googleapis.com ",
api: "google-generative-ai" ,
models: [],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("google-paid" , "missing-model" , "/tmp/agent" , cfg);
expect(result.model?.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta ");
});
it("normalizes configured Google override baseUrls when provider api is omitted" , () => {
mockDiscoveredModel(discoverModels, {
provider: "google" ,
modelId: "gemini-2.5-pro" ,
templateModel: {
...makeModel("gemini-2.5-pro" ),
provider: "google" ,
api: "google-generative-ai" ,
baseUrl: "https://generativelanguage.googleapis.com/v1beta ",
},
});
const cfg = {
models: {
providers: {
google: {
baseUrl: "https://generativelanguage.googleapis.com ",
models: [{ id: "gemini-2.5-pro" , name: "gemini-2.5-pro" }],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("google" , "gemini-2.5-pro" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model?.api).toBe("google-generative-ai" );
expect(result.model?.baseUrl).toBe("https://generativelanguage.googleapis.com/v1beta ");
});
it("normalizes custom api.openai.com providers to responses transport" , () => {
const cfg = {
models: {
providers: {
"custom-openai" : {
baseUrl: "https://api.openai.com/v1 ",
api: "openai-completions" ,
models: [
{
...makeModel("gpt-5.4" ),
provider: "custom-openai" ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom-openai" , "gpt-5.4" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "custom-openai" ,
id: "gpt-5.4" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
});
});
it("normalizes custom api.x.ai providers to responses transport" , () => {
const cfg = {
models: {
providers: {
"custom-xai" : {
baseUrl: "https://api.x.ai/v1 ",
api: "openai-completions" ,
models: [
{
...makeModel("grok-4.1-fast" ),
provider: "custom-xai" ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom-xai" , "grok-4.1-fast" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "custom-xai" ,
id: "grok-4.1-fast" ,
api: "openai-responses" ,
baseUrl: "https://api.x.ai/v1 ",
});
});
it("includes provider headers in provider fallback model" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
headers: { "X-Custom-Auth" : "token-123" },
models: [makeModel("listed-model" )],
},
},
},
} as unknown as OpenClawConfig;
// Requesting a non-listed model forces the providerCfg fallback branch.
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({
"X-Custom-Auth" : "token-123" ,
});
});
it("drops SecretRef marker provider headers in fallback models" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
headers: {
Authorization: "secretref-env:OPENAI_HEADER_TOKEN" ,
"X-Managed" : "secretref-managed" ,
"X-Custom-Auth" : "token-123" ,
},
models: [makeModel("listed-model" )],
},
},
},
} as unknown as OpenClawConfig;
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({
"X-Custom-Auth" : "token-123" ,
});
});
it("drops marker headers from discovered models.json entries" , () => {
mockDiscoveredModel(discoverModels, {
provider: "custom" ,
modelId: "listed-model" ,
templateModel: {
...makeModel("listed-model" ),
provider: "custom" ,
headers: {
Authorization: "secretref-env:OPENAI_HEADER_TOKEN" ,
"X-Managed" : "secretref-managed" ,
"X-Static" : "tenant-a" ,
},
},
});
const result = resolveModelForTest("custom" , "listed-model" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toEqual({
"X-Static" : "tenant-a" ,
});
});
it("prefers matching configured model metadata for fallback token limits" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
models: [
{
...makeModel("model-a" ),
contextWindow: 4096 ,
maxTokens: 1024 ,
},
{
...makeModel("model-b" ),
contextWindow: 262144 ,
maxTokens: 32768 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom" , "model-b" , "/tmp/agent" , cfg);
expect(result.model?.contextWindow).toBe(262144 );
expect(result.model?.maxTokens).toBe(32768 );
});
it("propagates reasoning from matching configured fallback model" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
models: [
{
...makeModel("model-a" ),
reasoning: false ,
},
{
...makeModel("model-b" ),
reasoning: true ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom" , "model-b" , "/tmp/agent" , cfg);
expect(result.model?.reasoning).toBe(true );
});
it("propagates image input capability from matching configured fallback model" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
models: [
{
...makeModel("model-a" ),
input: ["text" ],
},
{
...makeModel("model-b" ),
input: ["text" , "image" ],
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom" , "model-b" , "/tmp/agent" , cfg);
expect(result.model?.input).toEqual(["text" , "image" ]);
});
it("keeps unknown fallback models text-only instead of borrowing image input from another configured model" , () => {
const cfg = {
models: {
providers: {
custom: {
baseUrl: "http://localhost:9000 ",
models: [
{
...makeModel("model-a" ),
input: ["text" , "image" ],
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("custom" , "typoed-model" , "/tmp/agent" , cfg);
expect(result.model?.id).toBe("typoed-model" );
expect(result.model?.input).toEqual(["text" ]);
});
it("repairs stale text-only Foundry fallback rows for GPT-family models" , () => {
const cfg = {
models: {
providers: {
"microsoft-foundry" : {
baseUrl: "https://example.services.ai.azure.com/openai/v1 ",
api: "azure-openai-responses" ,
models: [
{
...makeModel("gpt-5.4" ),
name: "gpt-5.4" ,
api: "azure-openai-responses" ,
input: ["text" ],
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("microsoft-foundry" , "gpt-5.4" , "/tmp/agent" , cfg);
expect(result.model?.input).toEqual(["text" , "image" ]);
});
it("repairs stale text-only Foundry discovered rows for GPT-family models" , () => {
const cfg = {
models: {
providers: {
"microsoft-foundry" : {
baseUrl: "https://example.services.ai.azure.com/openai/v1 ",
api: "azure-openai-responses" ,
models: [
{
...makeModel("gpt-5.4" ),
name: "gpt-5.4" ,
api: "azure-openai-responses" ,
input: ["text" ],
},
],
},
},
},
} as unknown as OpenClawConfig;
mockDiscoveredModel(discoverModels, {
provider: "microsoft-foundry" ,
modelId: "gpt-5.4" ,
templateModel: {
id: "gpt-5.4" ,
name: "gpt-5.4" ,
provider: "microsoft-foundry" ,
baseUrl: "https://example.services.ai.azure.com/openai/v1 ",
api: "azure-openai-responses" ,
reasoning: false ,
input: ["text" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 128000 ,
maxTokens: 16384 ,
},
});
const result = resolveModelForTest("microsoft-foundry" , "gpt-5.4" , "/tmp/agent" , cfg);
expect(result.model?.input).toEqual(["text" , "image" ]);
});
it("repairs stale text-only Foundry discovered rows without config overrides" , () => {
mockDiscoveredModel(discoverModels, {
provider: "microsoft-foundry" ,
modelId: "gpt-5.4" ,
templateModel: {
id: "gpt-5.4" ,
name: "gpt-5.4" ,
provider: "microsoft-foundry" ,
baseUrl: "https://example.services.ai.azure.com/openai/v1 ",
api: "azure-openai-responses" ,
reasoning: false ,
input: ["text" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 128000 ,
maxTokens: 16384 ,
},
});
const result = resolveModelForTest("microsoft-foundry" , "gpt-5.4" , "/tmp/agent" );
expect(result.model?.input).toEqual(["text" , "image" ]);
});
it("matches prefixed OpenRouter native ids in configured fallback models" , () => {
const cfg = {
models: {
providers: {
openrouter: {
baseUrl: "https://openrouter.ai/api/v1 ",
api: "openai-completions" ,
models: [
{
...makeModel("openrouter/healer-alpha" ),
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 262144 ,
maxTokens: 65536 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const models = buildInlineProviderModels(cfg.models?.providers ?? {});
expect(models).toEqual(
expect.arrayContaining([
expect.objectContaining({
provider: "openrouter" ,
id: "openrouter/healer-alpha" ,
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 262144 ,
maxTokens: 65536 ,
}),
]),
);
expect(models.find((model) => model.id === "openrouter/healer-alpha" )).toMatchObject({
provider: "openrouter" ,
id: "openrouter/healer-alpha" ,
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 262144 ,
maxTokens: 65536 ,
});
});
it("uses OpenRouter API capabilities for unknown models when cache is populated" , () => {
mockGetOpenRouterModelCapabilities.mockReturnValue({
name: "Healer Alpha" ,
input: ["text" , "image" ],
reasoning: true ,
contextWindow: 262144 ,
maxTokens: 65536 ,
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
});
const result = resolveModelForTest("openrouter" , "openrouter/healer-alpha" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openrouter" ,
id: "openrouter/healer-alpha" ,
name: "Healer Alpha" ,
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 262144 ,
maxTokens: 65536 ,
});
});
it("falls back to text-only when OpenRouter API cache is empty" , () => {
mockGetOpenRouterModelCapabilities.mockReturnValue(undefined);
const result = resolveModelForTest("openrouter" , "openrouter/healer-alpha" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openrouter" ,
id: "openrouter/healer-alpha" ,
reasoning: false ,
input: ["text" ],
});
});
it("matches prefixed Hugging Face ids against discovered registry models" , () => {
mockDiscoveredModel(discoverModels, {
provider: "huggingface" ,
modelId: "deepseek-ai/DeepSeek-R1" ,
templateModel: {
...makeModel("deepseek-ai/DeepSeek-R1" ),
provider: "huggingface" ,
baseUrl: "https://router.huggingface.co/v1 ",
reasoning: true ,
input: ["text" ],
},
});
const result = resolveModelForTest(
"huggingface" ,
"huggingface/deepseek-ai/DeepSeek-R1" ,
"/tmp/agent" ,
);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "huggingface" ,
id: "deepseek-ai/DeepSeek-R1" ,
reasoning: true ,
input: ["text" ],
});
});
it("preloads OpenRouter capabilities before first async resolve of an unknown model" , async () => {
mockLoadOpenRouterModelCapabilities.mockImplementation(async (modelId) => {
if (modelId === "google/gemini-3.1-flash-image-preview" ) {
mockGetOpenRouterModelCapabilities.mockReturnValue({
name: "Google: Nano Banana 2 (Gemini 3.1 Flash Image Preview)" ,
input: ["text" , "image" ],
reasoning: true ,
contextWindow: 65536 ,
maxTokens: 65536 ,
cost: { input: 0 .5 , output: 3 , cacheRead: 0 , cacheWrite: 0 },
});
}
});
const result = await resolveModelAsyncForTest(
"openrouter" ,
"google/gemini-3.1-flash-image-preview" ,
"/tmp/agent" ,
);
expect(mockLoadOpenRouterModelCapabilities).toHaveBeenCalledWith(
"google/gemini-3.1-flash-image-preview" ,
);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openrouter" ,
id: "google/gemini-3.1-flash-image-preview" ,
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 65536 ,
maxTokens: 65536 ,
});
});
it("skips OpenRouter preload for models already present in the registry" , async () => {
mockDiscoveredModel(discoverModels, {
provider: "openrouter" ,
modelId: "openrouter/healer-alpha" ,
templateModel: {
id: "openrouter/healer-alpha" ,
name: "Healer Alpha" ,
api: "openai-completions" ,
provider: "openrouter" ,
baseUrl: "https://openrouter.ai/api/v1 ",
reasoning: true ,
input: ["text" , "image" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 262144 ,
maxTokens: 65536 ,
},
});
const result = await resolveModelAsyncForTest(
"openrouter" ,
"openrouter/healer-alpha" ,
"/tmp/agent" ,
);
expect(mockLoadOpenRouterModelCapabilities).not.toHaveBeenCalled();
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openrouter" ,
id: "openrouter/healer-alpha" ,
input: ["text" , "image" ],
});
});
it("prefers configured provider api metadata over discovered registry model" , () => {
mockDiscoveredModel(discoverModels, {
provider: "onehub" ,
modelId: "glm-5" ,
templateModel: {
id: "glm-5" ,
name: "GLM-5 (cached)" ,
provider: "onehub" ,
api: "anthropic-messages" ,
baseUrl: "https://old-provider.example.com/v1 ",
reasoning: false ,
input: ["text" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 8192 ,
maxTokens: 2048 ,
},
});
const cfg = {
models: {
providers: {
onehub: {
baseUrl: "http://new-provider.example.com/v1 ",
api: "openai-completions" ,
models: [
{
...makeModel("glm-5" ),
api: "openai-completions" ,
reasoning: true ,
contextWindow: 198000 ,
maxTokens: 16000 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("onehub" , "glm-5" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "onehub" ,
id: "glm-5" ,
api: "openai-completions" ,
baseUrl: "http://new-provider.example.com/v1 ",
reasoning: true ,
contextWindow: 198000 ,
maxTokens: 16000 ,
});
});
it("prefers exact provider config over normalized alias match when both keys exist" , () => {
mockDiscoveredModel(discoverModels, {
provider: "bedrock" ,
modelId: "bedrock-alias-exact-test" ,
templateModel: {
id: "bedrock-alias-exact-test" ,
name: "Bedrock alias test" ,
provider: "bedrock" ,
api: "openai-completions" ,
baseUrl: "https://default-provider.example.com/v1 ",
reasoning: false ,
input: ["text" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 8192 ,
maxTokens: 2048 ,
},
});
const cfg = {
models: {
providers: {
"amazon-bedrock" : {
baseUrl: "https://canonical-bedrock.example.com/v1 ",
api: "openai-completions" ,
headers: { "X-Provider" : "canonical" },
models: [{ ...makeModel("bedrock-alias-exact-test" ), reasoning: false }],
},
bedrock: {
baseUrl: "https://alias-bedrock.example.com/v1 ",
api: "anthropic-messages" ,
headers: { "X-Provider" : "alias" },
models: [
{
...makeModel("bedrock-alias-exact-test" ),
api: "anthropic-messages" ,
reasoning: true ,
contextWindow: 262144 ,
maxTokens: 32768 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("bedrock" , "bedrock-alias-exact-test" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "bedrock" ,
id: "bedrock-alias-exact-test" ,
api: "anthropic-messages" ,
baseUrl: "https://alias-bedrock.example.com ",
reasoning: true ,
contextWindow: 262144 ,
maxTokens: 32768 ,
headers: { "X-Provider" : "alias" },
});
});
it("builds an openai-codex fallback for gpt-5.4" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const result = resolveModelForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4" ));
});
it("upgrades stale exact openai-codex gpt-5.4 registry metadata via forward-compat" , () => {
vi.mocked(discoverModels).mockReturnValue({
find: vi.fn((provider: string, modelId: string) => {
if (provider !== "openai-codex" ) {
return null ;
}
if (modelId === "gpt-5.4" ) {
return {
...OPENAI_CODEX_TEMPLATE_MODEL,
id: "gpt-5.4" ,
name: "GPT-5.4" ,
contextWindow: 272000 ,
};
}
if (modelId === "gpt-5.3-codex" ) {
return {
...OPENAI_CODEX_TEMPLATE_MODEL,
id: "gpt-5.3-codex" ,
name: "GPT-5.3 Codex" ,
};
}
return null ;
}),
} as unknown as ReturnType<typeof discoverModels>);
const result = resolveModelForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
contextWindow: 1 _050 _000 ,
maxTokens: 128000 ,
});
});
it("does not downgrade exact openai-codex gpt-5.3-codex registry metadata" , () => {
vi.mocked(discoverModels).mockReturnValue({
find: vi.fn((provider: string, modelId: string) => {
if (provider !== "openai-codex" ) {
return null ;
}
if (modelId === "gpt-5.3-codex" ) {
return {
...OPENAI_CODEX_TEMPLATE_MODEL,
id: "gpt-5.3-codex" ,
name: "GPT-5.3 Codex" ,
contextWindow: 272000 ,
};
}
return null ;
}),
} as unknown as ReturnType<typeof discoverModels>);
const result = resolveModelForTest("openai-codex" , "gpt-5.3-codex" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.3-codex" ,
contextWindow: 272000 ,
maxTokens: 128000 ,
});
});
it("canonicalizes the legacy openai-codex gpt-5.4-codex alias at runtime" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const result = resolveModelForTest("openai-codex" , "gpt-5.4-codex" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4" ));
expect(result.model?.id).toBe("gpt-5.4" );
expect(result.model?.name).toBe("gpt-5.4" );
});
it("applies canonical openai-codex overrides when resolving the gpt-5.4-codex alias" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const cfg = {
models: {
providers: {
"openai-codex" : {
baseUrl: "https://proxy.example.com/backend-api ",
api: "openai-codex-responses" ,
models: [
{
...makeModel("gpt-5.4" ),
contextWindow: 123456 ,
contextTokens: 65432 ,
maxTokens: 7777 ,
reasoning: false ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai-codex" , "gpt-5.4-codex" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
api: "openai-codex-responses" ,
baseUrl: "https://proxy.example.com/backend-api ",
contextWindow: 123456 ,
contextTokens: 65432 ,
maxTokens: 7777 ,
reasoning: false ,
});
});
it("prefers alias-specific overrides over canonical ones for gpt-5.4-codex" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const cfg = {
models: {
providers: {
"openai-codex" : {
api: "openai-codex-responses" ,
models: [
{
...makeModel("gpt-5.4" ),
contextWindow: 222222 ,
maxTokens: 22222 ,
},
{
...makeModel("gpt-5.4-codex" ),
contextWindow: 111111 ,
maxTokens: 11111 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai-codex" , "gpt-5.4-codex" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
contextWindow: 111111 ,
maxTokens: 11111 ,
});
});
it("builds an openai-codex fallback for gpt-5.4-mini" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const result = resolveModelForTest("openai-codex" , "gpt-5.4-mini" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject(buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini" ));
});
it("does not build an openai-codex fallback for removed gpt-5.3-codex-spark" , () => {
mockOpenAICodexTemplateModel(discoverModels);
const result = resolveModelForTest("openai-codex" , "gpt-5.3-codex-spark" , "/tmp/agent" );
expect(result.model).toBeUndefined();
expect(result.error).toBe(
"Unknown model: openai-codex/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5." ,
);
});
it("rejects stale openai-codex gpt-5.3-codex-spark discovery rows" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.3-codex-spark" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.3-codex-spark" ),
name: "GPT-5.3 Codex Spark" ,
input: ["text" ],
},
});
const result = resolveModelForTest("openai-codex" , "gpt-5.3-codex-spark" , "/tmp/agent" );
expect(result.model).toBeUndefined();
expect(result.error).toBe(
"Unknown model: openai-codex/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5." ,
);
});
it("prefers runtime-resolved openai-codex gpt-5.4 metadata when it has a larger context window" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
contextWindow: 128 _000 ,
contextTokens: 32 _000 ,
input: ["text" ],
},
});
const result = resolveModelForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
contextWindow: 1 _050 _000 ,
contextTokens: 272 _000 ,
});
});
it("lets official openai-codex metadata override stale configured model rows" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
},
});
const cfg = {
models: {
providers: {
"openai-codex" : {
baseUrl: "https://chatgpt.com/backend-api ",
api: "openai-codex-responses" ,
models: [
{
...makeModel("gpt-5.5-pro" ),
api: "openai-codex-responses" ,
reasoning: false ,
input: ["text" ],
cost: { input: 5 , output: 30 , cacheRead: 0 .5 , cacheWrite: 0 },
contextWindow: 400 _000 ,
contextTokens: 64 _000 ,
maxTokens: 32 _000 ,
metadataSource: "models-add" ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai-codex" , "gpt-5.5-pro" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.5-pro" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
reasoning: true ,
input: ["text" , "image" ],
cost: { input: 30 , output: 180 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 1 _000 _000 ,
contextTokens: 272 _000 ,
maxTokens: 128 _000 ,
});
});
it("lets official openai-codex metadata override legacy unmarked models-add rows" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.5" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.5" ),
name: "GPT-5.5" ,
cost: { input: 5 , output: 30 , cacheRead: 0 .5 , cacheWrite: 0 },
contextWindow: 400 _000 ,
},
});
const cfg = {
models: {
providers: {
"openai-codex" : {
baseUrl: "https://chatgpt.com/backend-api ",
api: "openai-codex-responses" ,
models: [
{
...makeModel("gpt-5.5" ),
api: "openai-codex-responses" ,
reasoning: true ,
input: ["text" , "image" ],
cost: { input: 5 , output: 30 , cacheRead: 0 .5 , cacheWrite: 0 },
contextWindow: 400 _000 ,
contextTokens: 272 _000 ,
maxTokens: 128 _000 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai-codex" , "gpt-5.5" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.5" ,
cost: { input: 5 , output: 30 , cacheRead: 0 .5 , cacheWrite: 0 },
contextWindow: 400 _000 ,
maxTokens: 128 _000 ,
});
});
it("resolves openai-codex gpt-5.5 even when discovery omits the OAuth catalog row" , () => {
const result = resolveModelForTest("openai-codex" , "gpt-5.5" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.5" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 400 _000 ,
contextTokens: 272 _000 ,
maxTokens: 128 _000 ,
});
});
it("preserves unmarked manual openai-codex metadata overrides" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.5" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.5" ),
name: "GPT-5.5" ,
cost: { input: 5 , output: 30 , cacheRead: 0 .5 , cacheWrite: 0 },
contextWindow: 400 _000 ,
},
});
const cfg = {
models: {
providers: {
"openai-codex" : {
baseUrl: "https://chatgpt.com/backend-api ",
api: "openai-codex-responses" ,
models: [
{
...makeModel("gpt-5.5" ),
api: "openai-codex-responses" ,
reasoning: true ,
input: ["text" , "image" ],
cost: { input: 9 , output: 99 , cacheRead: 0 .9 , cacheWrite: 0 },
contextWindow: 555 _555 ,
contextTokens: 111 _111 ,
maxTokens: 22 _222 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai-codex" , "gpt-5.5" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.5" ,
cost: { input: 9 , output: 99 , cacheRead: 0 .9 , cacheWrite: 0 },
contextWindow: 555 _555 ,
contextTokens: 111 _111 ,
maxTokens: 22 _222 ,
});
});
it("prefers runtime-resolved openai-codex gpt-5.4 metadata during async resolution too" , async () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
contextWindow: 128 _000 ,
contextTokens: 32 _000 ,
},
});
const result = await resolveModelAsyncForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
contextWindow: 1 _050 _000 ,
contextTokens: 272 _000 ,
});
});
it("normalizes stale discovered openai-codex /backend-api/v1 metadata" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
baseUrl: "https://chatgpt.com/backend-api/v1 ",
},
});
const result = resolveModelForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
});
});
it("normalizes stale discovered openrouter /v1 metadata" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openrouter" ,
modelId: "openai/gpt-5.4" ,
templateModel: {
provider: "openrouter" ,
id: "openai/gpt-5.4" ,
name: "GPT-5.4" ,
api: "openai-completions" ,
baseUrl: "https://openrouter.ai/v1 ",
reasoning: true ,
input: ["text" , "image" ],
cost: { input: 0 , output: 0 , cacheRead: 0 , cacheWrite: 0 },
contextWindow: 200 _000 ,
maxTokens: 8 _192 ,
},
});
const result = resolveModelForTest("openrouter" , "openai/gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openrouter" ,
id: "openai/gpt-5.4" ,
api: "openai-completions" ,
baseUrl: "https://openrouter.ai/api/v1 ",
});
});
it("normalizes discovered openai-codex metadata when api is missing" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
api: undefined,
},
});
const result = resolveModelForTest("openai-codex" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
});
});
it("passes configured workspaceDir to runtime preference hooks" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
contextWindow: 128 _000 ,
contextTokens: 32 _000 ,
},
});
const shouldPreferRuntimeResolvedModel = vi.fn(
(params: { workspaceDir?: string; context: { agentDir?: string } }) =>
params.workspaceDir === "/tmp/workspace" && params.context.agentDir === "/tmp/agent-state" ,
);
const runProviderDynamicModel = vi.fn(
(params: { workspaceDir?: string; context: { provider: string; modelId: string } }) =>
params.workspaceDir === "/tmp/workspace" &&
params.context.provider === "openai-codex" &&
params.context.modelId === "gpt-5.4"
? ({
...buildOpenAICodexForwardCompatExpectation("gpt-5.4" ),
name: "GPT-5.4" ,
} as ReturnType<typeof buildOpenAICodexForwardCompatExpectation>)
: undefined,
);
const runtimeHooks = {
...createRuntimeHooks(),
shouldPreferProviderRuntimeResolvedModel: shouldPreferRuntimeResolvedModel,
runProviderDynamicModel,
};
const cfg = {
agents: {
defaults: {
workspace: "/tmp/workspace" ,
},
},
} as OpenClawConfig;
const result = resolveModel("openai-codex" , "gpt-5.4" , "/tmp/agent-state" , cfg, {
authStorage: { mocked: true } as never,
modelRegistry: discoverModels({ mocked: true } as never, "/tmp/agent-state" ),
runtimeHooks,
});
expect(shouldPreferRuntimeResolvedModel).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai-codex" ,
workspaceDir: "/tmp/workspace" ,
context: expect.objectContaining({
agentDir: "/tmp/agent-state" ,
workspaceDir: "/tmp/workspace" ,
}),
}),
);
expect(runProviderDynamicModel).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai-codex" ,
workspaceDir: "/tmp/workspace" ,
context: expect.objectContaining({
agentDir: "/tmp/agent-state" ,
modelId: "gpt-5.4" ,
provider: "openai-codex" ,
}),
}),
);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4" ,
contextWindow: 1 _050 _000 ,
contextTokens: 272 _000 ,
});
});
it("keeps exact discovered metadata for other openai-codex models" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai-codex" ,
modelId: "gpt-5.4-mini" ,
templateModel: {
...buildOpenAICodexForwardCompatExpectation("gpt-5.4-mini" ),
name: "GPT-5.4 Mini" ,
contextWindow: 64 _000 ,
input: ["text" ],
},
});
const result = resolveModelForTest("openai-codex" , "gpt-5.4-mini" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai-codex" ,
id: "gpt-5.4-mini" ,
api: "openai-codex-responses" ,
baseUrl: "https://chatgpt.com/backend-api ",
contextWindow: 64 _000 ,
input: ["text" ],
});
});
it("rejects stale direct openai gpt-5.3-codex-spark discovery rows" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.3-codex-spark" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.3-codex-spark" ,
name: "GPT-5.3 Codex Spark" ,
provider: "openai" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
}),
});
const result = resolveModelForTest("openai" , "gpt-5.3-codex-spark" , "/tmp/agent" );
expect(result.model).toBeUndefined();
expect(result.error).toBe(
"Unknown model: openai/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5." ,
);
});
it("applies provider overrides to openai gpt-5.4 forward-compat models" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.4" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.4" ,
name: "GPT-5.2" ,
provider: "openai" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
}),
});
const cfg = {
models: {
providers: {
openai: {
baseUrl: "https://proxy.example.com/v1 ",
headers: { "X-Proxy-Auth" : "token-123" },
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("openai" , "gpt-5.4" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai" ,
id: "gpt-5.4" ,
api: "openai-responses" ,
baseUrl: "https://proxy.example.com/v1 ",
});
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toMatchObject(
{
"X-Proxy-Auth" : "token-123" ,
},
);
});
it("applies configured overrides to github-copilot dynamic models" , () => {
const cfg = {
models: {
providers: {
"github-copilot" : {
baseUrl: "https://proxy.example.com/v1 ",
api: "openai-completions" ,
headers: { "X-Proxy-Auth" : "token-123" },
models: [
{
...makeModel("gpt-5.4-mini" ),
reasoning: true ,
input: ["text" ],
contextWindow: 256000 ,
maxTokens: 32000 ,
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = resolveModelForTest("github-copilot" , "gpt-5.4-mini" , "/tmp/agent" , cfg);
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "github-copilot" ,
id: "gpt-5.4-mini" ,
api: "openai-completions" ,
baseUrl: "https://proxy.example.com/v1 ",
reasoning: true ,
input: ["text" ],
contextWindow: 256000 ,
maxTokens: 32000 ,
});
expect((result.model as unknown as { headers?: Record<string, string> }).headers).toMatchObject(
{
"X-Proxy-Auth" : "token-123" ,
},
);
});
it("resolves github-copilot Claude dynamic models to anthropic-messages by default" , () => {
const result = resolveModelForTest("github-copilot" , "claude-sonnet-4.6" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "github-copilot" ,
id: "claude-sonnet-4.6" ,
api: "anthropic-messages" ,
});
});
it("builds an openai fallback for gpt-5.4 mini from the gpt-5.4-mini template" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.4-mini" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.4-mini" ,
name: "GPT-5 mini" ,
provider: "openai" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 400 _000 ,
maxTokens: 128 _000 ,
}),
});
const result = resolveModelForTest("openai" , "gpt-5.4-mini" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai" ,
id: "gpt-5.4-mini" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 400 _000 ,
maxTokens: 128 _000 ,
});
});
it("builds an openai fallback for gpt-5.4 nano from the gpt-5.4-nano template" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.4-nano" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.4-nano" ,
name: "GPT-5 nano" ,
provider: "openai" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 400 _000 ,
maxTokens: 128 _000 ,
}),
});
const result = resolveModelForTest("openai" , "gpt-5.4-nano" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai" ,
id: "gpt-5.4-nano" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
reasoning: true ,
input: ["text" , "image" ],
contextWindow: 400 _000 ,
maxTokens: 128 _000 ,
});
});
it("normalizes stale native openai gpt-5.4 completions transport to responses" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.4" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.4" ,
name: "GPT-5.4" ,
provider: "openai" ,
api: "openai-completions" ,
baseUrl: "https://api.openai.com/v1 ",
}),
});
const result = resolveModelForTest("openai" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai" ,
id: "gpt-5.4" ,
api: "openai-responses" ,
baseUrl: "https://api.openai.com/v1 ",
});
});
it("keeps proxied openai completions transport untouched" , () => {
mockDiscoveredModel(discoverModels, {
provider: "openai" ,
modelId: "gpt-5.4" ,
templateModel: buildForwardCompatTemplate({
id: "gpt-5.4" ,
name: "GPT-5.4" ,
provider: "openai" ,
api: "openai-completions" ,
baseUrl: "https://proxy.example.com/v1 ",
}),
});
const result = resolveModelForTest("openai" , "gpt-5.4" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "openai" ,
id: "gpt-5.4" ,
api: "openai-completions" ,
baseUrl: "https://proxy.example.com/v1 ",
});
});
it("normalizes stale native xai completions transport to responses" , () => {
mockDiscoveredModel(discoverModels, {
provider: "xai" ,
modelId: "grok-4.20-beta-latest-reasoning" ,
templateModel: buildForwardCompatTemplate({
id: "grok-4.20-beta-latest-reasoning" ,
name: "Grok 4.20 Beta Latest (Reasoning)" ,
provider: "xai" ,
api: "openai-completions" ,
baseUrl: "https://api.x.ai/v1 ",
}),
});
const result = resolveModelForTest("xai" , "grok-4.20-beta-latest-reasoning" , "/tmp/agent" );
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "xai" ,
id: "grok-4.20-beta-latest-reasoning" ,
api: "openai-responses" ,
baseUrl: "https://api.x.ai/v1 ",
});
});
it("normalizes stale native xai completions transport after plugin model normalization" , () => {
mockDiscoveredModel(discoverModels, {
provider: "xai" ,
modelId: "grok-4.20-beta-latest-reasoning" ,
templateModel: buildForwardCompatTemplate({
id: "grok-4.20-beta-latest-reasoning" ,
name: "Grok 4.20 Beta Latest (Reasoning)" ,
provider: "xai" ,
api: "openai-completions" ,
baseUrl: "https://api.x.ai/v1 ",
}),
});
const result = resolveModel("xai" , "grok-4.20-beta-latest-reasoning" , "/tmp/agent" , undefined, {
authStorage: { mocked: true } as never,
modelRegistry: discoverModels({ mocked: true } as never, "/tmp/agent" ),
runtimeHooks: {
applyProviderResolvedModelCompatWithPlugins: () => undefined,
buildProviderUnknownModelHintWithPlugin: () => undefined,
clearProviderRuntimeHookCache: () => {},
prepareProviderDynamicModel: async () => {},
runProviderDynamicModel: () => undefined,
applyProviderResolvedTransportWithPlugin: ({ provider, context }) =>
provider === "xai" &&
context.model.api === "openai-completions" &&
context.model.baseUrl === "https://api.x.ai/v1 "
? {
...context.model,
api: "openai-responses" ,
}
: undefined,
normalizeProviderResolvedModelWithPlugin: ({ provider, context }) =>
provider === "xai" ? (context.model as never) : undefined,
normalizeProviderTransportWithPlugin: () => undefined,
},
});
expect(result.error).toBeUndefined();
expect(result.model).toMatchObject({
provider: "xai" ,
id: "grok-4.20-beta-latest-reasoning" ,
api: "openai-responses" ,
baseUrl: "https://api.x.ai/v1 ",
});
});
});
Messung V0.5 in Prozent C=94 H=91 G=92
¤ Dauer der Verarbeitung: 0.45 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland