@polymorphism-tech/morph-spec 8.34.2 → 8.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -3
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +16 -17
- package/framework/agents.json +197 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +234 -9
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/e2e-check.js +135 -22
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +26 -5
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +18 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +33 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1483 -839
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +164 -68
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +61 -37
- package/framework/standards/ai-agents/context-providers.md +97 -24
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +119 -18
- package/framework/standards/ai-agents/mcp-tools.md +85 -32
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +39 -5
- package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
- package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
- package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
- package/framework/standards/ai-agents/modalities-vision.md +6 -2
- package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +38 -9
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +174 -39
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +27 -19
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +34 -3
- package/framework/standards/ai-agents/sweet-spot.md +23 -3
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +13 -0
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +3 -3
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +15 -2
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +140 -5
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +8 -1
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/finish.js +333 -21
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +9 -1
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +50 -4
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +410 -33
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +583 -28
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +24 -0
- package/src/lib/worktree/worktree-audit.js +30 -1
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +11 -4
- package/src/utils/hooks-installer.js +29 -26
- package/src/utils/skills-installer.js +36 -29
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
using System.Diagnostics;
|
|
2
|
+
using System.Text.Json;
|
|
3
|
+
using Microsoft.Extensions.AI;
|
|
4
|
+
using Morph.AiKit.Agents;
|
|
5
|
+
using Morph.AiKit.Providers;
|
|
6
|
+
using Morph.AiKit.Tests.Fakes;
|
|
7
|
+
using Xunit;
|
|
8
|
+
|
|
9
|
+
namespace Morph.AiKit.Tests;
|
|
10
|
+
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// Canarios da <see cref="AgentFactory"/> (T10). Duas afirmacoes sao o coracao do arquivo:
|
|
13
|
+
/// <b>a precedencia do protocolo</b> (spec vence o alias quando presente, alias vence quando
|
|
14
|
+
/// a spec cala) e <b>a ordem do pipeline</b>, observada na sequencia real de chamadas, nao
|
|
15
|
+
/// na leitura do codigo.
|
|
16
|
+
/// </summary>
|
|
17
|
+
/// <remarks>
|
|
18
|
+
/// Todo <c>await</c> passa <c>TestContext.Current.CancellationToken</c> — exigencia do
|
|
19
|
+
/// analisador xUnit1051, que sob <c>TreatWarningsAsErrors</c> e build vermelho.
|
|
20
|
+
/// </remarks>
|
|
21
|
+
public sealed class AgentFactoryTests
|
|
22
|
+
{
|
|
23
|
+
// ---------------------------------------------------------------------------------
|
|
24
|
+
// Documentos de registry usados pelos casos
|
|
25
|
+
// ---------------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/// <summary>Um alias Responses e um alias Chat COM o limite do GPT-5.4 declarado.</summary>
|
|
28
|
+
private const string TwoProtocolsDocument = """
|
|
29
|
+
{
|
|
30
|
+
"version": "2.0",
|
|
31
|
+
"defaultAlias": "responses-alias",
|
|
32
|
+
"aliases": {
|
|
33
|
+
"responses-alias": {
|
|
34
|
+
"provider": "openai",
|
|
35
|
+
"model": "gpt-5.4",
|
|
36
|
+
"api": "responses",
|
|
37
|
+
"pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 }
|
|
38
|
+
},
|
|
39
|
+
"chat-alias": {
|
|
40
|
+
"provider": "openai",
|
|
41
|
+
"model": "gpt-5.4",
|
|
42
|
+
"api": "chat",
|
|
43
|
+
"pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 },
|
|
44
|
+
"constraints": {
|
|
45
|
+
"toolCallingRequiresReasoningEffortNone": true,
|
|
46
|
+
"provenance": "Documentacao OpenAI citada na issue #93."
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
""";
|
|
52
|
+
|
|
53
|
+
private static AgentSpec Spec(string alias = "responses-alias", AgentApi? api = null)
|
|
54
|
+
=> new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------------
|
|
57
|
+
// doneCriteria 1 — a precedencia do api:
|
|
58
|
+
// ---------------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/// <summary>
|
|
61
|
+
/// Spec calada: vale o <c>api:</c> do alias. O seletor de cliente e o observatorio —
|
|
62
|
+
/// ele recebe o protocolo <b>efetivo</b>, ja resolvido.
|
|
63
|
+
/// </summary>
|
|
64
|
+
[Fact]
|
|
65
|
+
public void TheAliasApiWinsWhenTheSpecDeclaresNone()
|
|
66
|
+
{
|
|
67
|
+
var seen = new List<AgentApi>();
|
|
68
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
69
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
70
|
+
{
|
|
71
|
+
ChatClientSelector = (_, api) => { seen.Add(api); return new FakeChatClient(); },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
_ = factory.Create(Spec("responses-alias"));
|
|
75
|
+
_ = factory.Create(Spec("chat-alias"));
|
|
76
|
+
|
|
77
|
+
Assert.Equal([AgentApi.Responses, AgentApi.Chat], seen);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// <summary>
|
|
81
|
+
/// Spec preenchida: ela vence, nos dois sentidos. A regra nao e reimplementada aqui —
|
|
82
|
+
/// a fabrica chama <see cref="AgentSpec.ResolveApi"/> (T7), e este teste morre se
|
|
83
|
+
/// alguem inverter a precedencia la ou copia-la para ca.
|
|
84
|
+
/// </summary>
|
|
85
|
+
[Fact]
|
|
86
|
+
public void TheSpecApiWinsOverTheAliasInBothDirections()
|
|
87
|
+
{
|
|
88
|
+
var seen = new List<AgentApi>();
|
|
89
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
90
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
91
|
+
{
|
|
92
|
+
ChatClientSelector = (_, api) => { seen.Add(api); return new FakeChatClient(); },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
_ = factory.Create(Spec("responses-alias", AgentApi.Chat));
|
|
96
|
+
_ = factory.Create(Spec("chat-alias", AgentApi.Responses));
|
|
97
|
+
|
|
98
|
+
Assert.Equal([AgentApi.Chat, AgentApi.Responses], seen);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// <summary>
|
|
102
|
+
/// Sem seletor, "a spec vence" nao pode ser cumprido: o registry memoiza UM cliente por
|
|
103
|
+
/// alias, construido para o protocolo do alias. A fabrica recusa nomeando os dois
|
|
104
|
+
/// protocolos, em vez de devolver um agente que diz falar um e fala outro.
|
|
105
|
+
/// </summary>
|
|
106
|
+
[Fact]
|
|
107
|
+
public void WithoutASelectorADivergentSpecApiIsRefusedNamingBothProtocols()
|
|
108
|
+
{
|
|
109
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
110
|
+
var factory = new AgentFactory(registry);
|
|
111
|
+
|
|
112
|
+
var error = Assert.Throws<AgentFactoryException>(
|
|
113
|
+
() => factory.Create(Spec("responses-alias", AgentApi.Chat)));
|
|
114
|
+
|
|
115
|
+
Assert.Equal(AgentFactoryError.ProtocolWithoutSelector, error.Reason);
|
|
116
|
+
Assert.Contains("Chat", error.Message, StringComparison.Ordinal);
|
|
117
|
+
Assert.Contains("Responses", error.Message, StringComparison.Ordinal);
|
|
118
|
+
Assert.Contains("responses-alias", error.Message, StringComparison.Ordinal);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// <summary>
|
|
122
|
+
/// Sem divergencia e sem seletor, o caminho normal: o cliente memoizado do alias, uma
|
|
123
|
+
/// unica construcao por alias, por mais agentes que se criem.
|
|
124
|
+
/// </summary>
|
|
125
|
+
[Fact]
|
|
126
|
+
public void WithoutASelectorEveryAgentOfAnAliasSharesTheMemoizedClient()
|
|
127
|
+
{
|
|
128
|
+
var providerFactory = new FakeProviderClientFactory();
|
|
129
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, providerFactory);
|
|
130
|
+
var factory = new AgentFactory(registry);
|
|
131
|
+
|
|
132
|
+
_ = factory.Create(Spec("responses-alias"));
|
|
133
|
+
_ = factory.Create(Spec("responses-alias"));
|
|
134
|
+
|
|
135
|
+
Assert.Equal(1, providerFactory.CreateCount);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------------
|
|
139
|
+
// doneCriteria 2 — a ordem do pipeline, observada nas chamadas
|
|
140
|
+
// ---------------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
/// <summary>
|
|
143
|
+
/// A ordem completa, medida numa invocacao com <b>duas</b> voltas de tool calling.
|
|
144
|
+
/// </summary>
|
|
145
|
+
/// <remarks>
|
|
146
|
+
/// O que a sequencia prova, e que nenhuma leitura de codigo prova: o encaixe de custo
|
|
147
|
+
/// entra e sai <b>uma</b> vez envolvendo as duas voltas (esta ACIMA do
|
|
148
|
+
/// <c>FunctionInvokingChatClient</c>), e o encaixe de telemetria entra e sai <b>duas</b>
|
|
149
|
+
/// vezes, uma por chamada ao modelo (esta ABAIXO). Trocar os dois de lugar muda esta
|
|
150
|
+
/// lista, e o teste morre.
|
|
151
|
+
/// </remarks>
|
|
152
|
+
[Fact]
|
|
153
|
+
public async Task ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt()
|
|
154
|
+
{
|
|
155
|
+
var log = new List<string>();
|
|
156
|
+
var leaf = ScriptedToolCallThenAnswer();
|
|
157
|
+
|
|
158
|
+
var agent = BuildAgentWithProbes(leaf, log, spec: Spec() with
|
|
159
|
+
{
|
|
160
|
+
Tools = [AIFunctionFactory.Create(() => "42", "answer")],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
|
|
164
|
+
var response = await agent.RunAsync(
|
|
165
|
+
"qual e a resposta?",
|
|
166
|
+
session,
|
|
167
|
+
cancellationToken: TestContext.Current.CancellationToken);
|
|
168
|
+
|
|
169
|
+
Assert.Equal("a resposta e 42", response.Text);
|
|
170
|
+
Assert.Equal(2, leaf.CallCount);
|
|
171
|
+
Assert.Equal(
|
|
172
|
+
[
|
|
173
|
+
"usage-cost:enter",
|
|
174
|
+
"telemetry:enter",
|
|
175
|
+
"telemetry:exit",
|
|
176
|
+
"telemetry:enter",
|
|
177
|
+
"telemetry:exit",
|
|
178
|
+
"usage-cost:exit",
|
|
179
|
+
],
|
|
180
|
+
log);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// <summary>
|
|
184
|
+
/// A razao de o custo ficar acima do FICC, em numeros: ele ve UMA resposta com o uso
|
|
185
|
+
/// somado das duas voltas (10+30 de entrada, 4+6 de saida). Abaixo do FICC veria duas
|
|
186
|
+
/// respostas parciais — e leria um <c>UsageDetails</c> que o proprio FICC muta para
|
|
187
|
+
/// acumular, o que faria a mesma conta sair diferente conforme a hora de ler.
|
|
188
|
+
/// </summary>
|
|
189
|
+
[Fact]
|
|
190
|
+
public async Task UsageCostAboveFunctionInvocationSeesTheUsageOfEveryToolRoundSummedOnce()
|
|
191
|
+
{
|
|
192
|
+
var log = new List<string>();
|
|
193
|
+
var leaf = ScriptedToolCallThenAnswer();
|
|
194
|
+
var seenUsage = new List<UsageDetails?>();
|
|
195
|
+
|
|
196
|
+
var agent = BuildAgentWithProbes(leaf, log, spec: Spec() with
|
|
197
|
+
{
|
|
198
|
+
Tools = [AIFunctionFactory.Create(() => "42", "answer")],
|
|
199
|
+
}, onUsageCostResponse: r => seenUsage.Add(r.Usage));
|
|
200
|
+
|
|
201
|
+
var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
|
|
202
|
+
_ = await agent.RunAsync(
|
|
203
|
+
"qual e a resposta?",
|
|
204
|
+
session,
|
|
205
|
+
cancellationToken: TestContext.Current.CancellationToken);
|
|
206
|
+
|
|
207
|
+
var usage = Assert.Single(seenUsage);
|
|
208
|
+
Assert.Equal(40, usage!.InputTokenCount);
|
|
209
|
+
Assert.Equal(10, usage.OutputTokenCount);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------------
|
|
213
|
+
// As opcoes de nivel de agente (T7) chegam ao lugar que a T7 declarou
|
|
214
|
+
// ---------------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
/// <summary>
|
|
217
|
+
/// <c>MaxToolIterations</c> e <c>AllowConcurrentToolInvocation</c> tem destino nomeado
|
|
218
|
+
/// no contrato da T7. Aqui eles sao lidos <b>do objeto real</b> do pipeline, via
|
|
219
|
+
/// <c>GetService</c> — nao de um campo que a fabrica tenha guardado.
|
|
220
|
+
/// </summary>
|
|
221
|
+
[Fact]
|
|
222
|
+
public void TheAgentLevelKnobsReachTheFunctionInvokingChatClient()
|
|
223
|
+
{
|
|
224
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
225
|
+
var factory = new AgentFactory(registry);
|
|
226
|
+
|
|
227
|
+
var agent = factory.Create(Spec() with
|
|
228
|
+
{
|
|
229
|
+
Options = new AgentSpecOptions { MaxToolIterations = 3, AllowConcurrentToolInvocation = true },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
var functionInvoking = Assert.IsType<FunctionInvokingChatClient>(
|
|
233
|
+
agent.GetService(typeof(FunctionInvokingChatClient)));
|
|
234
|
+
|
|
235
|
+
Assert.Equal(3, functionInvoking.MaximumIterationsPerRequest);
|
|
236
|
+
Assert.True(functionInvoking.AllowConcurrentInvocation);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// <summary>
|
|
240
|
+
/// <c>TimeoutSeconds</c> vira <see cref="CancellationToken"/> de verdade: um cliente que
|
|
241
|
+
/// nunca responde e cancelado, em vez de pendurar a invocacao.
|
|
242
|
+
/// </summary>
|
|
243
|
+
[Fact]
|
|
244
|
+
public async Task TimeoutSecondsBecomesACancellationTokenThatActuallyCancels()
|
|
245
|
+
{
|
|
246
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
247
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
248
|
+
{
|
|
249
|
+
ChatClientSelector = (_, _) => new NeverAnsweringChatClient(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
var agent = factory.Create(Spec() with
|
|
253
|
+
{
|
|
254
|
+
Options = new AgentSpecOptions { TimeoutSeconds = 1 },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
|
|
258
|
+
|
|
259
|
+
await Assert.ThrowsAnyAsync<OperationCanceledException>(
|
|
260
|
+
() => agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------------
|
|
264
|
+
// A spec inteira chega as ChatOptions do agente
|
|
265
|
+
// ---------------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
/// <summary>
|
|
268
|
+
/// O modelo vem do alias (a spec nunca cita modelo), as instrucoes e o schema vem da
|
|
269
|
+
/// spec, e <see cref="AgentSpecOptions.ApplyTo"/> nao atropela nem as tools nem o
|
|
270
|
+
/// formato de resposta ja definidos.
|
|
271
|
+
/// </summary>
|
|
272
|
+
[Fact]
|
|
273
|
+
public async Task TheSpecReachesTheChatOptionsWithoutClobberingToolsOrSchema()
|
|
274
|
+
{
|
|
275
|
+
using var schemaDocument = JsonDocument.Parse("""{"type":"object"}""");
|
|
276
|
+
var leaf = new FakeChatClient().EnqueueText("ok");
|
|
277
|
+
|
|
278
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
279
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
280
|
+
{
|
|
281
|
+
ChatClientSelector = (_, _) => leaf,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
var agent = factory.Create(Spec() with
|
|
285
|
+
{
|
|
286
|
+
Name = "agente-de-teste",
|
|
287
|
+
Description = "so para o teste",
|
|
288
|
+
Tools = [AIFunctionFactory.Create(() => "42", "answer")],
|
|
289
|
+
OutputSchema = schemaDocument.RootElement.Clone(),
|
|
290
|
+
Options = new AgentSpecOptions { Temperature = 0.2f, MaxOutputTokens = 128 },
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
|
|
294
|
+
_ = await agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken);
|
|
295
|
+
|
|
296
|
+
var received = Assert.Single(leaf.ReceivedOptions);
|
|
297
|
+
Assert.NotNull(received);
|
|
298
|
+
Assert.Equal("voce e um agente de teste", received.Instructions);
|
|
299
|
+
Assert.Equal("gpt-5.4", received.ModelId);
|
|
300
|
+
Assert.Equal(0.2f, received.Temperature);
|
|
301
|
+
Assert.Equal(128, received.MaxOutputTokens);
|
|
302
|
+
Assert.Equal("answer", Assert.Single(received.Tools!).Name);
|
|
303
|
+
Assert.IsType<ChatResponseFormatJson>(received.ResponseFormat);
|
|
304
|
+
Assert.Equal("agente-de-teste", agent.Name);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------------
|
|
308
|
+
// Recusas
|
|
309
|
+
// ---------------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
/// <summary>
|
|
312
|
+
/// Limite de alias violado nao vira agente. A recusa carrega a procedencia escrita no
|
|
313
|
+
/// registry — sem ela, o limite nao teria sequer carregado (T8).
|
|
314
|
+
/// </summary>
|
|
315
|
+
[Fact]
|
|
316
|
+
public void AViolatedAliasConstraintRefusesTheAgentAndCarriesItsProvenance()
|
|
317
|
+
{
|
|
318
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
319
|
+
var factory = new AgentFactory(registry);
|
|
320
|
+
|
|
321
|
+
var error = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec("chat-alias") with
|
|
322
|
+
{
|
|
323
|
+
Tools = [AIFunctionFactory.Create(() => "42", "answer")],
|
|
324
|
+
Options = new AgentSpecOptions { ReasoningEffort = ReasoningEffort.High },
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
Assert.Equal(AgentFactoryError.ConstraintViolated, error.Reason);
|
|
328
|
+
Assert.Contains("issue #93", error.Message, StringComparison.Ordinal);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// <summary>
|
|
332
|
+
/// O MESMO alias, sem tools, nao viola nada: o limite tem quatro fatos, e tres nao
|
|
333
|
+
/// bastam. O canario existe para que a recusa acima nao seja confundida com "modelo de
|
|
334
|
+
/// raciocinio rejeita tool".
|
|
335
|
+
/// </summary>
|
|
336
|
+
[Fact]
|
|
337
|
+
public void TheSameAliasWithoutToolsCreatesTheAgentNormally()
|
|
338
|
+
{
|
|
339
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
340
|
+
var factory = new AgentFactory(registry);
|
|
341
|
+
|
|
342
|
+
var agent = factory.Create(Spec("chat-alias") with
|
|
343
|
+
{
|
|
344
|
+
Options = new AgentSpecOptions { ReasoningEffort = ReasoningEffort.High },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
Assert.NotNull(agent);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/// <summary>Alias desconhecido falha na fabrica com o mesmo erro do registry.</summary>
|
|
351
|
+
[Fact]
|
|
352
|
+
public void AnUnknownAliasFailsAtCreateTime()
|
|
353
|
+
{
|
|
354
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
355
|
+
var factory = new AgentFactory(registry);
|
|
356
|
+
|
|
357
|
+
var error = Assert.Throws<ModelRegistryException>(() => factory.Create(Spec("nao-existe")));
|
|
358
|
+
|
|
359
|
+
Assert.Equal(ModelRegistryError.UnknownAlias, error.Reason);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/// <summary>Seletor que devolve nulo e recusa explicita, nunca agente sem cliente.</summary>
|
|
363
|
+
[Fact]
|
|
364
|
+
public void ASelectorThatReturnsNullIsRefusedInsteadOfProducingAClientlessAgent()
|
|
365
|
+
{
|
|
366
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
367
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
368
|
+
{
|
|
369
|
+
ChatClientSelector = (_, _) => null!,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
var error = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec()));
|
|
373
|
+
|
|
374
|
+
Assert.Equal(AgentFactoryError.NoChatClient, error.Reason);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/// <summary>
|
|
378
|
+
/// Sem nenhum encaixe preenchido, o agente e valido e responde. Um agente sem custo e
|
|
379
|
+
/// sem telemetria nao e um agente pela metade.
|
|
380
|
+
/// </summary>
|
|
381
|
+
[Fact]
|
|
382
|
+
public async Task AnAgentWithNoHooksIsStillAValidAgentThatAnswers()
|
|
383
|
+
{
|
|
384
|
+
var leaf = new FakeChatClient().EnqueueText("pronto");
|
|
385
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
386
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
387
|
+
{
|
|
388
|
+
ChatClientSelector = (_, _) => leaf,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
var agent = factory.Create(Spec());
|
|
392
|
+
var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken);
|
|
393
|
+
var response = await agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken);
|
|
394
|
+
|
|
395
|
+
Assert.Equal("pronto", response.Text);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ---------------------------------------------------------------------------------
|
|
399
|
+
// Apoio dos testes
|
|
400
|
+
// ---------------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
/// <summary>
|
|
403
|
+
/// Roteiro de duas voltas: o modelo pede a tool, a tool responde, o modelo conclui. Os
|
|
404
|
+
/// numeros de uso sao escolhidos para que a soma (40/10) seja distinguivel de qualquer
|
|
405
|
+
/// uma das parcelas.
|
|
406
|
+
/// </summary>
|
|
407
|
+
private static FakeChatClient ScriptedToolCallThenAnswer()
|
|
408
|
+
=> new FakeChatClient()
|
|
409
|
+
.Enqueue(new ChatResponse(new ChatMessage(
|
|
410
|
+
ChatRole.Assistant,
|
|
411
|
+
[new FunctionCallContent("chamada-1", "answer", null)]))
|
|
412
|
+
{
|
|
413
|
+
Usage = new UsageDetails { InputTokenCount = 10, OutputTokenCount = 4, TotalTokenCount = 14 },
|
|
414
|
+
})
|
|
415
|
+
.EnqueueTextWithUsage("a resposta e 42", inputTokens: 30, outputTokens: 6);
|
|
416
|
+
|
|
417
|
+
private static Microsoft.Agents.AI.AIAgent BuildAgentWithProbes(
|
|
418
|
+
FakeChatClient leaf,
|
|
419
|
+
List<string> log,
|
|
420
|
+
AgentSpec spec,
|
|
421
|
+
Action<ChatResponse>? onUsageCostResponse = null)
|
|
422
|
+
{
|
|
423
|
+
using var registry = ModelRegistry.FromJson(TwoProtocolsDocument, new FakeProviderClientFactory());
|
|
424
|
+
var factory = new AgentFactory(registry, new AgentFactoryOptions
|
|
425
|
+
{
|
|
426
|
+
ChatClientSelector = (_, _) => leaf,
|
|
427
|
+
ConfigureUsageCost = (builder, _, _) =>
|
|
428
|
+
builder.Use(inner => new ProbeChatClient(inner, "usage-cost", log, onUsageCostResponse)),
|
|
429
|
+
ConfigureChatTelemetry = (builder, _, _) =>
|
|
430
|
+
builder.Use(inner => new ProbeChatClient(inner, "telemetry", log, onResponse: null)),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
return factory.Create(spec);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/// <summary>
|
|
437
|
+
/// Marca a entrada e a saida de uma posicao do pipeline. E o unico jeito de observar a
|
|
438
|
+
/// ORDEM: ela nao esta em nenhuma propriedade, so na sequencia das chamadas.
|
|
439
|
+
/// </summary>
|
|
440
|
+
private sealed class ProbeChatClient(
|
|
441
|
+
IChatClient inner,
|
|
442
|
+
string tag,
|
|
443
|
+
List<string> log,
|
|
444
|
+
Action<ChatResponse>? onResponse) : DelegatingChatClient(inner)
|
|
445
|
+
{
|
|
446
|
+
public override async Task<ChatResponse> GetResponseAsync(
|
|
447
|
+
IEnumerable<ChatMessage> messages,
|
|
448
|
+
ChatOptions? options = null,
|
|
449
|
+
CancellationToken cancellationToken = default)
|
|
450
|
+
{
|
|
451
|
+
lock (log)
|
|
452
|
+
{
|
|
453
|
+
log.Add(tag + ":enter");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
var response = await base.GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
|
|
457
|
+
onResponse?.Invoke(response);
|
|
458
|
+
|
|
459
|
+
lock (log)
|
|
460
|
+
{
|
|
461
|
+
log.Add(tag + ":exit");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return response;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// <summary>Cliente que nunca responde — existe so para o timeout ter o que cancelar.</summary>
|
|
469
|
+
private sealed class NeverAnsweringChatClient : IChatClient
|
|
470
|
+
{
|
|
471
|
+
public async Task<ChatResponse> GetResponseAsync(
|
|
472
|
+
IEnumerable<ChatMessage> messages,
|
|
473
|
+
ChatOptions? options = null,
|
|
474
|
+
CancellationToken cancellationToken = default)
|
|
475
|
+
{
|
|
476
|
+
await Task.Delay(Timeout.Infinite, cancellationToken).ConfigureAwait(false);
|
|
477
|
+
throw new UnreachableException();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
481
|
+
IEnumerable<ChatMessage> messages,
|
|
482
|
+
ChatOptions? options = null,
|
|
483
|
+
CancellationToken cancellationToken = default)
|
|
484
|
+
=> throw new NotSupportedException();
|
|
485
|
+
|
|
486
|
+
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
487
|
+
=> serviceType == typeof(ChatClientMetadata)
|
|
488
|
+
? new ChatClientMetadata(providerName: "never")
|
|
489
|
+
: null;
|
|
490
|
+
|
|
491
|
+
public void Dispose()
|
|
492
|
+
{
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|