/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect } from 'vitest';
import { ModelConfigService } from './modelConfigService.js';
describe('ModelConfigService', () => {
    it('should resolve a basic alias to its model and settings', () => {
        const config = {
            aliases: {
                classifier: {
                    modelConfig: {
                        model: 'gemini-1.5-flash-latest',
                        generateContentConfig: {
                            temperature: 0,
                            topP: 0.9,
                        },
                    },
                },
            },
            overrides: [],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'classifier' });
        expect(resolved.model).toBe('gemini-1.5-flash-latest');
        expect(resolved.generateContentConfig).toEqual({
            temperature: 0,
            topP: 0.9,
        });
    });
    it('should apply a simple override on top of an alias', () => {
        const config = {
            aliases: {
                classifier: {
                    modelConfig: {
                        model: 'gemini-1.5-flash-latest',
                        generateContentConfig: {
                            temperature: 0,
                            topP: 0.9,
                        },
                    },
                },
            },
            overrides: [
                {
                    match: { model: 'classifier' },
                    modelConfig: {
                        generateContentConfig: {
                            temperature: 0.5,
                            maxOutputTokens: 1000,
                        },
                    },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'classifier' });
        expect(resolved.model).toBe('gemini-1.5-flash-latest');
        expect(resolved.generateContentConfig).toEqual({
            temperature: 0.5,
            topP: 0.9,
            maxOutputTokens: 1000,
        });
    });
    it('should apply the most specific override rule', () => {
        const config = {
            aliases: {},
            overrides: [
                {
                    match: { model: 'gemini-pro' },
                    modelConfig: { generateContentConfig: { temperature: 0.5 } },
                },
                {
                    match: { model: 'gemini-pro', overrideScope: 'my-agent' },
                    modelConfig: { generateContentConfig: { temperature: 0.1 } },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({
            model: 'gemini-pro',
            overrideScope: 'my-agent',
        });
        expect(resolved.model).toBe('gemini-pro');
        expect(resolved.generateContentConfig).toEqual({ temperature: 0.1 });
    });
    it('should use the last override in case of a tie in specificity', () => {
        const config = {
            aliases: {},
            overrides: [
                {
                    match: { model: 'gemini-pro' },
                    modelConfig: {
                        generateContentConfig: { temperature: 0.5, topP: 0.8 },
                    },
                },
                {
                    match: { model: 'gemini-pro' },
                    modelConfig: { generateContentConfig: { temperature: 0.1 } },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'gemini-pro' });
        expect(resolved.model).toBe('gemini-pro');
        expect(resolved.generateContentConfig).toEqual({
            temperature: 0.1,
            topP: 0.8,
        });
    });
    it('should correctly pass through generation config from an alias', () => {
        const config = {
            aliases: {
                'thinking-alias': {
                    modelConfig: {
                        model: 'gemini-pro',
                        generateContentConfig: {
                            candidateCount: 500,
                        },
                    },
                },
            },
            overrides: [],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'thinking-alias' });
        expect(resolved.generateContentConfig).toEqual({ candidateCount: 500 });
    });
    it('should let an override generation config win over an alias config', () => {
        const config = {
            aliases: {
                'thinking-alias': {
                    modelConfig: {
                        model: 'gemini-pro',
                        generateContentConfig: {
                            candidateCount: 500,
                        },
                    },
                },
            },
            overrides: [
                {
                    match: { model: 'thinking-alias' },
                    modelConfig: {
                        generateContentConfig: {
                            candidateCount: 1000,
                        },
                    },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({ model: 'thinking-alias' });
        expect(resolved.generateContentConfig).toEqual({
            candidateCount: 1000,
        });
    });
    it('should merge settings from global, alias, and multiple matching overrides', () => {
        const config = {
            aliases: {
                'test-alias': {
                    modelConfig: {
                        model: 'gemini-test-model',
                        generateContentConfig: {
                            topP: 0.9,
                            topK: 50,
                        },
                    },
                },
            },
            overrides: [
                {
                    match: { model: 'gemini-test-model' },
                    modelConfig: {
                        generateContentConfig: {
                            topK: 40,
                            maxOutputTokens: 2048,
                        },
                    },
                },
                {
                    match: { overrideScope: 'test-agent' },
                    modelConfig: {
                        generateContentConfig: {
                            maxOutputTokens: 4096,
                        },
                    },
                },
                {
                    match: { model: 'gemini-test-model', overrideScope: 'test-agent' },
                    modelConfig: {
                        generateContentConfig: {
                            temperature: 0.2,
                        },
                    },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({
            model: 'test-alias',
            overrideScope: 'test-agent',
        });
        expect(resolved.model).toBe('gemini-test-model');
        expect(resolved.generateContentConfig).toEqual({
            // From global, overridden by most specific override
            temperature: 0.2,
            // From alias, not overridden
            topP: 0.9,
            // From alias, overridden by less specific override
            topK: 40,
            // From first matching override, overridden by second matching override
            maxOutputTokens: 4096,
        });
    });
    it('should match an agent:core override when agent is undefined', () => {
        const config = {
            aliases: {},
            overrides: [
                {
                    match: { overrideScope: 'core' },
                    modelConfig: {
                        generateContentConfig: {
                            temperature: 0.1,
                        },
                    },
                },
            ],
        };
        const service = new ModelConfigService(config);
        const resolved = service.getResolvedConfig({
            model: 'gemini-pro',
            overrideScope: undefined, // Explicitly undefined
        });
        expect(resolved.model).toBe('gemini-pro');
        expect(resolved.generateContentConfig).toEqual({
            temperature: 0.1,
        });
    });
    describe('alias inheritance', () => {
        it('should resolve a simple "extends" chain', () => {
            const config = {
                aliases: {
                    base: {
                        modelConfig: {
                            model: 'gemini-1.5-pro-latest',
                            generateContentConfig: {
                                temperature: 0.7,
                                topP: 0.9,
                            },
                        },
                    },
                    'flash-variant': {
                        extends: 'base',
                        modelConfig: {
                            model: 'gemini-1.5-flash-latest',
                        },
                    },
                },
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'flash-variant' });
            expect(resolved.model).toBe('gemini-1.5-flash-latest');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.7,
                topP: 0.9,
            });
        });
        it('should override parent properties from child alias', () => {
            const config = {
                aliases: {
                    base: {
                        modelConfig: {
                            model: 'gemini-1.5-pro-latest',
                            generateContentConfig: {
                                temperature: 0.7,
                                topP: 0.9,
                            },
                        },
                    },
                    'flash-variant': {
                        extends: 'base',
                        modelConfig: {
                            model: 'gemini-1.5-flash-latest',
                            generateContentConfig: {
                                temperature: 0.2,
                            },
                        },
                    },
                },
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'flash-variant' });
            expect(resolved.model).toBe('gemini-1.5-flash-latest');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.2,
                topP: 0.9,
            });
        });
        it('should resolve a multi-level "extends" chain', () => {
            const config = {
                aliases: {
                    base: {
                        modelConfig: {
                            model: 'gemini-1.5-pro-latest',
                            generateContentConfig: {
                                temperature: 0.7,
                                topP: 0.9,
                            },
                        },
                    },
                    'base-flash': {
                        extends: 'base',
                        modelConfig: {
                            model: 'gemini-1.5-flash-latest',
                        },
                    },
                    'classifier-flash': {
                        extends: 'base-flash',
                        modelConfig: {
                            generateContentConfig: {
                                temperature: 0,
                            },
                        },
                    },
                },
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({
                model: 'classifier-flash',
            });
            expect(resolved.model).toBe('gemini-1.5-flash-latest');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0,
                topP: 0.9,
            });
        });
        it('should throw an error for circular dependencies', () => {
            const config = {
                aliases: {
                    a: { extends: 'b', modelConfig: {} },
                    b: { extends: 'a', modelConfig: {} },
                },
            };
            const service = new ModelConfigService(config);
            expect(() => service.getResolvedConfig({ model: 'a' })).toThrow('Circular alias dependency: a -> b -> a');
        });
        describe('abstract aliases', () => {
            it('should allow an alias to extend an abstract alias without a model', () => {
                const config = {
                    aliases: {
                        'abstract-base': {
                            modelConfig: {
                                generateContentConfig: {
                                    temperature: 0.1,
                                },
                            },
                        },
                        'concrete-child': {
                            extends: 'abstract-base',
                            modelConfig: {
                                model: 'gemini-1.5-pro-latest',
                                generateContentConfig: {
                                    topP: 0.9,
                                },
                            },
                        },
                    },
                };
                const service = new ModelConfigService(config);
                const resolved = service.getResolvedConfig({ model: 'concrete-child' });
                expect(resolved.model).toBe('gemini-1.5-pro-latest');
                expect(resolved.generateContentConfig).toEqual({
                    temperature: 0.1,
                    topP: 0.9,
                });
            });
            it('should throw an error if a resolved alias chain has no model', () => {
                const config = {
                    aliases: {
                        'abstract-base': {
                            modelConfig: {
                                generateContentConfig: { temperature: 0.7 },
                            },
                        },
                    },
                };
                const service = new ModelConfigService(config);
                expect(() => service.getResolvedConfig({ model: 'abstract-base' })).toThrow('Could not resolve a model name for alias "abstract-base". Please ensure the alias chain or a matching override specifies a model.');
            });
            it('should resolve an abstract alias if an override provides the model', () => {
                const config = {
                    aliases: {
                        'abstract-base': {
                            modelConfig: {
                                generateContentConfig: {
                                    temperature: 0.1,
                                },
                            },
                        },
                    },
                    overrides: [
                        {
                            match: { model: 'abstract-base' },
                            modelConfig: {
                                model: 'gemini-1.5-flash-latest',
                            },
                        },
                    ],
                };
                const service = new ModelConfigService(config);
                const resolved = service.getResolvedConfig({ model: 'abstract-base' });
                expect(resolved.model).toBe('gemini-1.5-flash-latest');
                expect(resolved.generateContentConfig).toEqual({
                    temperature: 0.1,
                });
            });
        });
        it('should throw an error if an extended alias does not exist', () => {
            const config = {
                aliases: {
                    'bad-alias': {
                        extends: 'non-existent',
                        modelConfig: {},
                    },
                },
            };
            const service = new ModelConfigService(config);
            expect(() => service.getResolvedConfig({ model: 'bad-alias' })).toThrow('Alias "non-existent" not found.');
        });
    });
    describe('deep merging', () => {
        it('should deep merge nested config objects from aliases and overrides', () => {
            const config = {
                aliases: {
                    'base-safe': {
                        modelConfig: {
                            model: 'gemini-pro',
                            generateContentConfig: {
                                safetySettings: {
                                    HARM_CATEGORY_HARASSMENT: 'BLOCK_ONLY_HIGH',
                                    HARM_CATEGORY_HATE_SPEECH: 'BLOCK_ONLY_HIGH',
                                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                },
                            },
                        },
                    },
                },
                overrides: [
                    {
                        match: { model: 'base-safe' },
                        modelConfig: {
                            generateContentConfig: {
                                safetySettings: {
                                    HARM_CATEGORY_HATE_SPEECH: 'BLOCK_NONE',
                                    HARM_CATEGORY_SEXUALLY_EXPLICIT: 'BLOCK_MEDIUM_AND_ABOVE',
                                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                },
                            },
                        },
                    },
                ],
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'base-safe' });
            expect(resolved.model).toBe('gemini-pro');
            expect(resolved.generateContentConfig.safetySettings).toEqual({
                // From alias
                HARM_CATEGORY_HARASSMENT: 'BLOCK_ONLY_HIGH',
                // From alias, overridden by override
                HARM_CATEGORY_HATE_SPEECH: 'BLOCK_NONE',
                // From override
                HARM_CATEGORY_SEXUALLY_EXPLICIT: 'BLOCK_MEDIUM_AND_ABOVE',
            });
        });
        it('should not deeply merge merge arrays from aliases and overrides', () => {
            const config = {
                aliases: {
                    base: {
                        modelConfig: {
                            model: 'gemini-pro',
                            generateContentConfig: {
                                stopSequences: ['foo'],
                            },
                        },
                    },
                },
                overrides: [
                    {
                        match: { model: 'base' },
                        modelConfig: {
                            generateContentConfig: {
                                stopSequences: ['overrideFoo'],
                            },
                        },
                    },
                ],
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'base' });
            expect(resolved.model).toBe('gemini-pro');
            expect(resolved.generateContentConfig.stopSequences).toEqual([
                'overrideFoo',
            ]);
        });
    });
    describe('runtime aliases', () => {
        it('should resolve a simple runtime-registered alias', () => {
            const config = {
                aliases: {},
                overrides: [],
            };
            const service = new ModelConfigService(config);
            service.registerRuntimeModelConfig('runtime-alias', {
                modelConfig: {
                    model: 'gemini-runtime-model',
                    generateContentConfig: {
                        temperature: 0.123,
                    },
                },
            });
            const resolved = service.getResolvedConfig({ model: 'runtime-alias' });
            expect(resolved.model).toBe('gemini-runtime-model');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.123,
            });
        });
    });
    describe('custom aliases', () => {
        it('should resolve a custom alias', () => {
            const config = {
                aliases: {},
                customAliases: {
                    'my-custom-alias': {
                        modelConfig: {
                            model: 'gemini-custom',
                            generateContentConfig: {
                                temperature: 0.9,
                            },
                        },
                    },
                },
                overrides: [],
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'my-custom-alias' });
            expect(resolved.model).toBe('gemini-custom');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.9,
            });
        });
        it('should allow custom aliases to override built-in aliases', () => {
            const config = {
                aliases: {
                    'standard-alias': {
                        modelConfig: {
                            model: 'gemini-standard',
                            generateContentConfig: {
                                temperature: 0.5,
                            },
                        },
                    },
                },
                customAliases: {
                    'standard-alias': {
                        modelConfig: {
                            model: 'gemini-custom-override',
                            generateContentConfig: {
                                temperature: 0.1,
                            },
                        },
                    },
                },
                overrides: [],
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({ model: 'standard-alias' });
            expect(resolved.model).toBe('gemini-custom-override');
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.1,
            });
        });
    });
    describe('unrecognized models', () => {
        it('should apply overrides to unrecognized model names', () => {
            const unregisteredModelName = 'my-unregistered-model-v1';
            const config = {
                aliases: {}, // No aliases defined
                overrides: [
                    {
                        match: { model: unregisteredModelName },
                        modelConfig: {
                            generateContentConfig: {
                                temperature: 0.01,
                            },
                        },
                    },
                ],
            };
            const service = new ModelConfigService(config);
            // Request the unregistered model directly
            const resolved = service.getResolvedConfig({
                model: unregisteredModelName,
            });
            // It should preserve the model name and apply the override
            expect(resolved.model).toBe(unregisteredModelName);
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.01,
            });
        });
        it('should apply scoped overrides to unrecognized model names', () => {
            const unregisteredModelName = 'my-unregistered-model-v1';
            const config = {
                aliases: {},
                overrides: [
                    {
                        match: {
                            model: unregisteredModelName,
                            overrideScope: 'special-agent',
                        },
                        modelConfig: {
                            generateContentConfig: {
                                temperature: 0.99,
                            },
                        },
                    },
                ],
            };
            const service = new ModelConfigService(config);
            const resolved = service.getResolvedConfig({
                model: unregisteredModelName,
                overrideScope: 'special-agent',
            });
            expect(resolved.model).toBe(unregisteredModelName);
            expect(resolved.generateContentConfig).toEqual({
                temperature: 0.99,
            });
        });
    });
});
//# sourceMappingURL=modelConfigService.test.js.map