diff --git a/.changeset/filter-copilot-routers.md b/.changeset/filter-copilot-routers.md new file mode 100644 index 000000000..20de023ce --- /dev/null +++ b/.changeset/filter-copilot-routers.md @@ -0,0 +1,5 @@ +--- +"manifest": patch +--- + +Filter internal Azure routing models from GitHub Copilot provider diff --git a/packages/backend/src/model-discovery/model-discovery.service.spec.ts b/packages/backend/src/model-discovery/model-discovery.service.spec.ts index cc583e173..2969c0847 100644 --- a/packages/backend/src/model-discovery/model-discovery.service.spec.ts +++ b/packages/backend/src/model-discovery/model-discovery.service.spec.ts @@ -523,6 +523,25 @@ describe('ModelDiscoveryService', () => { expect(result[0].contextWindow).toBe(128000); }); + it('should filter out non-chat models from cached results', async () => { + const providers = [ + makeProvider({ + provider: 'copilot', + cached_models: [ + makeModel({ id: 'copilot/claude-opus-4.7', provider: 'copilot' }), + makeModel({ id: 'copilot/accounts/msft/routers/f185i3v4', provider: 'copilot' }), + makeModel({ id: 'copilot/accounts/msft/routers/fmfeto88', provider: 'copilot' }), + ], + }), + ]; + providerRepo.find.mockResolvedValue(providers); + customProviderRepo.find.mockResolvedValue([]); + + const result = await service.getModelsForAgent('agent-1'); + expect(result).toHaveLength(1); + expect(result[0].id).toBe('copilot/claude-opus-4.7'); + }); + it('should deduplicate custom provider models by composite key', async () => { providerRepo.find.mockResolvedValue([]); customProviderRepo.find.mockResolvedValue([ diff --git a/packages/backend/src/model-discovery/model-discovery.service.ts b/packages/backend/src/model-discovery/model-discovery.service.ts index 72aa60661..3de732790 100644 --- a/packages/backend/src/model-discovery/model-discovery.service.ts +++ b/packages/backend/src/model-discovery/model-discovery.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserProvider } from '../entities/user-provider.entity'; import { CustomProvider } from '../entities/custom-provider.entity'; -import { ProviderModelFetcherService } from './provider-model-fetcher.service'; +import { ProviderModelFetcherService, filterNonChatModels } from './provider-model-fetcher.service'; import { ProviderModelRegistryService } from './provider-model-registry.service'; import { DiscoveredModel } from './model-fetcher'; import { decrypt, getEncryptionSecret } from '../common/utils/crypto.util'; @@ -208,8 +208,9 @@ export class ModelDiscoveryService { for (const p of providers) { if (p.provider.startsWith('custom:')) continue; - const cached = p.cached_models; - if (!Array.isArray(cached)) continue; + const rawCached = p.cached_models; + if (!Array.isArray(rawCached)) continue; + const cached = filterNonChatModels(rawCached, p.provider.toLowerCase()); const providerAuthType = p.auth_type === 'subscription' ? 'subscription' : 'api_key'; for (const m of cached) { const effectiveAuthType = m.authType ?? providerAuthType; diff --git a/packages/backend/src/model-discovery/provider-model-fetcher.service.spec.ts b/packages/backend/src/model-discovery/provider-model-fetcher.service.spec.ts index 1f099db55..2006cfb1e 100644 --- a/packages/backend/src/model-discovery/provider-model-fetcher.service.spec.ts +++ b/packages/backend/src/model-discovery/provider-model-fetcher.service.spec.ts @@ -1347,6 +1347,26 @@ describe('ProviderModelFetcherService', () => { const result = await service.fetch('copilot', 'tid=token'); expect(result).toEqual([]); }); + + it('should filter out internal Azure routing models', async () => { + fetchSpy.mockResolvedValue({ + ok: true, + json: async () => ({ + data: [ + { id: 'claude-opus-4.7' }, + { id: 'gpt-4o' }, + { id: 'accounts/msft/routers/f185i3v4' }, + { id: 'accounts/msft/routers/fmfeto88' }, + { id: 'accounts/msft/routers/gdjv4v2v' }, + ], + }), + }); + + const result = await service.fetch('copilot', 'tid=token'); + expect(result).toHaveLength(2); + expect(result[0].id).toBe('copilot/claude-opus-4.7'); + expect(result[1].id).toBe('copilot/gpt-4o'); + }); }); /* ── OpenAI subscription routing ── */ diff --git a/packages/backend/src/model-discovery/provider-model-fetcher.service.ts b/packages/backend/src/model-discovery/provider-model-fetcher.service.ts index 76255cd96..1e1a2a782 100644 --- a/packages/backend/src/model-discovery/provider-model-fetcher.service.ts +++ b/packages/backend/src/model-discovery/provider-model-fetcher.service.ts @@ -116,6 +116,7 @@ export const PROVIDER_NON_CHAT: Record = { mistral: /(?:^mistral-ocr|moderation|voxtral-.*-(?:transcribe|realtime)|^labs-|^mistral-vibe-cli)/i, xai: /(?:imagine|multi-agent)/i, + copilot: /accounts\/[^/]+\/routers\//i, }; /**