using Morph.AiKit.Agents; using Morph.AiKit.Providers; using Morph.AiKit.Tests.Fakes; using Xunit; namespace Morph.AiKit.Tests; /// /// A companheira obrigatoria de AgentApi.Images/AgentApi.Videos existirem: /// um alias de midia nao vira agente. /// /// /// /// Um registry so e um vocabulario so de alias e a decisao (decisions.md D3) — e /// justamente por isso a recusa precisa existir. Sem ela, um alias de imagem passado por /// engano viraria um agente de chat apontando para um modelo que nao fala chat: a falha /// apareceria no primeiro turno, com uma mensagem do provider que nao diz o que aconteceu. /// /// /// Enum novo sem recusa e uma armadilha nova. Este arquivo e o que impede que ela volte. /// /// public sealed class MediaAliasIsNotAnAgentTests { private const string MediaAliasDocument = """ { "version": "2.0", "defaultAlias": "texto", "aliases": { "texto": { "provider": "openai", "model": "gpt-5.4", "api": "responses", "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 } }, "imagem": { "provider": "openai", "model": "gpt-image-2", "api": "images", "pricingWaiver": "tarifa de imagem cobra por imagem/qualidade, nao por token: ver MediaPricing." }, "video": { "provider": "google", "model": "veo-3.1-fast-generate-preview", "api": "videos", "pricingWaiver": "tarifa de video cobra por segundo de saida: ver MediaPricing." } } } """; private static AgentSpec Spec(string alias, AgentApi? api = null) => new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api }; /// O registry ACEITA images e videos — um vocabulario so de alias. [Theory] [InlineData("imagem", AgentApi.Images)] [InlineData("video", AgentApi.Videos)] public void TheRegistryUnderstandsTheMediaProtocols(string alias, AgentApi expected) { using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory()); Assert.Equal(expected, registry.Resolve(alias).Api); } /// E a fabrica RECUSA, na construcao, com erro nomeado. [Theory] [InlineData("imagem")] [InlineData("video")] public void TheFactoryRefusesAMediaAliasByName(string alias) { using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory()); var factory = new AgentFactory(registry); var ex = Assert.Throws(() => factory.Create(Spec(alias))); Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason); Assert.Contains("EnqueueAsync", ex.Message, StringComparison.Ordinal); } /// /// A recusa tambem cobre o caminho pela SPEC: um alias de conversa com /// Api = Images na spec e a mesma confusao, pela outra porta. /// [Fact] public void TheFactoryRefusesAMediaProtocolDeclaredOnTheSpec() { using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory()); var factory = new AgentFactory(registry); var ex = Assert.Throws(() => factory.Create(Spec("texto", AgentApi.Images))); Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason); } /// Alias de conversa continua passando — a recusa nova nao pode ser um bloqueio geral. [Fact] public void AChatAliasStillBuildsAnAgent() { using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory()); var factory = new AgentFactory(registry); Assert.NotNull(factory.Create(Spec("texto"))); } /// O predicado mora num lugar so — duas copias e como as duas discordam. [Theory] [InlineData(AgentApi.Responses, false)] [InlineData(AgentApi.Chat, false)] [InlineData(AgentApi.Images, true)] [InlineData(AgentApi.Videos, true)] public void IsMediaAnswersForEveryProtocol(AgentApi api, bool expected) => Assert.Equal(expected, api.IsMedia()); /// Um api inventado continua sendo recusado na carga do documento. [Fact] public void AnUnknownApiIsStillRefusedWithTheFullListOfValidValues() { var document = MediaAliasDocument.Replace("\"api\": \"images\"", "\"api\": \"holograma\"", StringComparison.Ordinal); var ex = Assert.Throws( () => ModelRegistry.FromJson(document, new FakeProviderClientFactory())); Assert.Contains("images", ex.Message, StringComparison.Ordinal); Assert.Contains("videos", ex.Message, StringComparison.Ordinal); } }