diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts index f05f3bcddf..8a060b7faa 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts @@ -98,8 +98,17 @@ async function fetchGatewayModels(gateway: Provider) { ); } const endpoints = EndpointsSchema.parse(await endpointsResponse.json()); + const { reasoning, ...modelMetadata } = model; result[model.id] = { - ...model, + ...modelMetadata, + ...(reasoning && { + reasoning: { + mandatory: reasoning.mandatory, + ...(reasoning.supported_efforts && { + supported_efforts: reasoning.supported_efforts, + }), + }, + }), endpoints: endpoints.data.endpoints, }; }) diff --git a/packages/db/src/schema-types.test.ts b/packages/db/src/schema-types.test.ts index 6ed85c58b0..ab4dc4b010 100644 --- a/packages/db/src/schema-types.test.ts +++ b/packages/db/src/schema-types.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from '@jest/globals'; -import { CodeReviewCouncilConfigSchema, OrganizationSettingsSchema } from './schema-types'; +import { + CodeReviewCouncilConfigSchema, + ModelsSchema, + OrganizationSettingsSchema, + StoredModelSchema, +} from './schema-types'; +import type { StoredModel } from './schema-types'; describe('CodeReviewCouncilConfigSchema', () => { const specialist = (id: string) => ({ @@ -66,3 +72,74 @@ describe('OrganizationSettingsSchema org_auto_model', () => { expect(result.success).toBe(false); }); }); + +describe('ModelsSchema', () => { + it('preserves only supported reasoning metadata', () => { + const result = ModelsSchema.parse({ + data: [ + { + id: 'openai/gpt-5.2', + name: 'OpenAI: GPT-5.2', + reasoning: { + mandatory: false, + supported_efforts: ['high', 'medium', 'low', 'none'], + default_enabled: true, + default_effort: 'medium', + }, + }, + ], + }); + + expect(result.data[0].reasoning).toEqual({ + mandatory: false, + supported_efforts: ['high', 'medium', 'low', 'none'], + }); + }); + + it('allows reasoning metadata without supported efforts', () => { + const result = ModelsSchema.parse({ + data: [ + { + id: 'google/gemini-2.5-pro', + name: 'Google: Gemini 2.5 Pro', + reasoning: { mandatory: true }, + }, + ], + }); + + expect(result.data[0].reasoning).toEqual({ mandatory: true }); + }); + + it('drops invalid reasoning metadata', () => { + const result = ModelsSchema.parse({ + data: [ + { + id: 'example/future-effort', + name: 'Future Effort', + reasoning: { + mandatory: false, + supported_efforts: ['future'], + }, + }, + ], + }); + + expect(result.data[0].reasoning).toBeUndefined(); + }); +}); + +describe('StoredModelSchema', () => { + it('includes model reasoning metadata', () => { + const storedModel: StoredModel = { + id: 'openai/gpt-5.2', + name: 'OpenAI: GPT-5.2', + reasoning: { + mandatory: false, + supported_efforts: ['high', 'medium', 'low', 'none'], + }, + endpoints: [], + }; + + expect(StoredModelSchema.parse(storedModel).reasoning).toEqual(storedModel.reasoning); + }); +}); diff --git a/packages/db/src/schema-types.ts b/packages/db/src/schema-types.ts index 3de62fd723..af7ebfdc82 100644 --- a/packages/db/src/schema-types.ts +++ b/packages/db/src/schema-types.ts @@ -2025,6 +2025,13 @@ export const ModelSchema = z.object({ id: z.string(), name: z.string(), type: z.enum(['language', 'embedding', 'image']).optional().catch(undefined), + reasoning: z + .object({ + mandatory: z.boolean(), + supported_efforts: z.array(ReasoningEffortSchema).optional(), + }) + .optional() + .catch(undefined), }); export const ModelsSchema = z.object({ data: z.array(ModelSchema) });