@polymorphism-tech/morph-spec 8.38.0 → 8.41.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.
Files changed (242) hide show
  1. package/CLAUDE.md +20 -9
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +4 -3
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +43 -26
  10. package/framework/docs/CLI.md +7 -5
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/hooks/README.md +1 -1
  14. package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
  15. package/framework/hooks/claude-code/core/worktree-create.js +22 -6
  16. package/framework/hooks/shared/git-paths.js +38 -5
  17. package/framework/mcp/registry.json +58 -66
  18. package/framework/rules/infrastructure-standards.md +0 -1
  19. package/framework/rules/morph-workflow.md +9 -4
  20. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  21. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  22. package/framework/skills/core/morph-apply/SKILL.md +59 -25
  23. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  24. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  25. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  26. package/framework/skills/core/morph-implement/SKILL.md +48 -24
  27. package/framework/skills/core/morph-media/SKILL.md +2 -2
  28. package/framework/skills/core/morph-plan/SKILL.md +10 -3
  29. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  30. package/framework/skills/core/morph-review/SKILL.md +1 -1
  31. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  32. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  33. package/framework/skills/core/morph-uiux/SKILL.md +7 -6
  34. package/framework/standards/PROVENANCE.json +4 -4
  35. package/framework/standards/STANDARDS.json +9 -9
  36. package/framework/standards/ai-agents/context-providers.md +16 -26
  37. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  38. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  39. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  40. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  41. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  42. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  43. package/framework/standards/architecture/code-comments.md +52 -16
  44. package/framework/standards/backend/dotnet/testing.md +4 -2
  45. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  46. package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
  47. package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
  48. package/framework/standards/frontend/nextjs/testing.md +4 -2
  49. package/framework/standards/integration/mcp/mcp-tools.md +64 -126
  50. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  51. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  52. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  53. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  54. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  55. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  56. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  57. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  58. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  59. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  60. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  61. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  62. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  63. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  64. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  65. package/framework/templates/code/dotnet/test.cs +2 -87
  66. package/framework/templates/code/typescript/contracts.ts +140 -167
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  142. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  143. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  144. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  145. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  146. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  147. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  150. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  151. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  152. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  153. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  154. package/framework/templates/integrations/asaas-client.cs +332 -387
  155. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  156. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  157. package/framework/templates/project/validate.js +97 -120
  158. package/package.json +2 -2
  159. package/src/commands/dashboard/dashboard.js +80 -53
  160. package/src/commands/mcp/mcp-setup.js +37 -9
  161. package/src/commands/project/doctor.js +12 -6
  162. package/src/commands/project/init.js +6 -3
  163. package/src/commands/project/install-plugin-cmd.js +1 -1
  164. package/src/commands/project/update.js +5 -0
  165. package/src/commands/project/worktree.js +60 -4
  166. package/src/commands/state/gate-check.js +5 -0
  167. package/src/commands/tasks/dag.js +27 -4
  168. package/src/commands/validation/verify.js +22 -38
  169. package/src/dashboard/app/App.jsx +80 -16
  170. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  171. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  172. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  173. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  174. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  175. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  176. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  177. package/src/dashboard/app/components/Markdown.jsx +47 -0
  178. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  179. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  180. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  181. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  182. package/src/dashboard/app/hooks/usePref.js +38 -0
  183. package/src/dashboard/app/lib/format.js +69 -0
  184. package/src/dashboard/app/lib/phases.js +56 -0
  185. package/src/dashboard/app/lib/project.js +50 -0
  186. package/src/dashboard/app/pages/Activity.jsx +24 -0
  187. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  188. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  189. package/src/dashboard/app/pages/Features.jsx +400 -0
  190. package/src/dashboard/app/pages/Guide.jsx +146 -0
  191. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  192. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  193. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  194. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  195. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  196. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  197. package/src/dashboard/app/pages/Standards.jsx +407 -332
  198. package/src/dashboard/app/styles/index.css +187 -47
  199. package/src/dashboard/index.html +14 -12
  200. package/src/dashboard/package.json +32 -24
  201. package/src/dashboard/scanner/activity-scanner.js +351 -0
  202. package/src/dashboard/scanner/agents-scanner.js +59 -1
  203. package/src/dashboard/scanner/docs-scanner.js +59 -0
  204. package/src/dashboard/scanner/feature-status.js +10 -0
  205. package/src/dashboard/scanner/features-scanner.js +360 -0
  206. package/src/dashboard/scanner/index.js +14 -7
  207. package/src/dashboard/scanner/phases-scanner.js +14 -15
  208. package/src/dashboard/scanner/portfolio.js +153 -0
  209. package/src/dashboard/scanner/project-api.js +195 -0
  210. package/src/dashboard/scanner/rules-scanner.js +42 -37
  211. package/src/dashboard/scanner/skills-scanner.js +105 -43
  212. package/src/dashboard/scanner/standards-scanner.js +2 -0
  213. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  214. package/src/lib/detectors/claude-config-detector.js +2 -7
  215. package/src/lib/detectors/index.js +5 -1
  216. package/src/lib/evaluator-report.js +22 -0
  217. package/src/lib/generators/settings-generator.js +208 -209
  218. package/src/lib/installers/mcp-installer.js +724 -719
  219. package/src/lib/installers/memory-installer.js +1 -2
  220. package/src/lib/projects/registry.js +154 -0
  221. package/src/lib/risk-detector.js +13 -0
  222. package/src/lib/tasks/tasks-json-parser.js +21 -0
  223. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  224. package/src/lib/worktree/env-allocator.js +9 -3
  225. package/src/scripts/install-plugin.js +1 -1
  226. package/src/scripts/setup-infra.js +2 -4
  227. package/src/utils/agents-installer.js +108 -13
  228. package/src/utils/banner.js +1 -1
  229. package/src/commands/index.js +0 -13
  230. package/src/commands/project/checkpoint.js +0 -209
  231. package/src/core/state/phase-state-machine.js +0 -197
  232. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  233. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  234. package/src/dashboard/scanner/squads-scanner.js +0 -13
  235. package/src/dashboard/scanner/state-scanner.js +0 -85
  236. package/src/lib/generators/index.js +0 -10
  237. package/src/lib/hooks/hook-executor.js +0 -167
  238. package/src/lib/validators/checkpoint-persistence.js +0 -39
  239. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  240. package/src/lib/validators/shared/index.js +0 -12
  241. package/src/lib/validators/shared/issue-counter.js +0 -14
  242. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -2,24 +2,7 @@ using Google.GenAI.Types;
2
2
 
3
3
  namespace Morph.AiKit.Media.Providers;
4
4
 
5
- /// <summary>
6
- /// Adaptador de imagem do Google (familia Nano Banana). <b><c>internal</c></b> — ver
7
- /// <see cref="IMediaProvider"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// <b>Nao ha adaptador MEAI oficial para o Google</b> (medido em 2026-09-08: o unico
12
- /// <c>AsIImageGenerator()</c> publicado e o de OpenAI/Azure OpenAI). O caminho e
13
- /// <c>Google.GenAI</c> direto — <c>Models.GenerateContentAsync</c> com
14
- /// <c>ResponseModalities</c> incluindo <c>"IMAGE"</c>, lendo os bytes de
15
- /// <c>Candidates[0].Content.Parts[..].InlineData</c>.
16
- /// </para>
17
- /// <para>
18
- /// <c>GenerateImagesAsync</c> do SDK <b>nao serve</b>: ela e a API do Imagen, cuja familia
19
- /// <c>imagen-4.0-*</c> esta na tabela de deprecacoes de
20
- /// <c>ai-agents/modalities-image-gen.md</c>.
21
- /// </para>
22
- /// </remarks>
5
+ // GenerateImagesAsync e a API do Imagen (familia deprecada); Nano Banana vai por GenerateContentAsync.
23
6
  internal sealed class GoogleImageProvider : IMediaProvider
24
7
  {
25
8
  private readonly IMediaClientFactory _clients;
@@ -30,13 +13,10 @@ internal sealed class GoogleImageProvider : IMediaProvider
30
13
  _clients = clients;
31
14
  }
32
15
 
33
- /// <inheritdoc />
34
16
  public string Provider => "google";
35
17
 
36
- /// <inheritdoc />
37
18
  public bool Supports(MediaKind kind) => kind == MediaKind.Image;
38
19
 
39
- /// <inheritdoc />
40
20
  public async Task<MediaResult<MediaArtifact>> ProduceAsync(
41
21
  MediaRequest request,
42
22
  MediaAliasBinding binding,
@@ -55,9 +35,7 @@ internal sealed class GoogleImageProvider : IMediaProvider
55
35
 
56
36
  var parts = new List<Part> { new() { Text = request.Prompt } };
57
37
 
58
- // Cada referencia entra como InlineData, na ORDEM em que o prompt as indexa
59
- // ("image 1 = o sofa"). Referencia nao indexada e referencia cujo uso o modelo
60
- // escolhe sozinho.
38
+ // A ordem das referencias e a que o prompt indexa ("image 1 = o sofa").
61
39
  foreach (var reference in request.References)
62
40
  {
63
41
  parts.Add(new Part
@@ -91,8 +69,6 @@ internal sealed class GoogleImageProvider : IMediaProvider
91
69
 
92
70
  if (blob is null)
93
71
  {
94
- // Sem bytes e sem excecao: o caso tipico e filtro de conteudo. Terminal, e nao
95
- // um erro tecnico que um retry resolveria.
96
72
  return MediaResult<MediaArtifact>.Fail(
97
73
  MediaErrorCode.PolicyBlocked,
98
74
  $"alias '{binding.Alias}': a resposta nao trouxe imagem. O caso tipico e filtro de " +
@@ -105,8 +81,7 @@ internal sealed class GoogleImageProvider : IMediaProvider
105
81
  blob.Data,
106
82
  RevisedPrompt: null,
107
83
  ResponseId: response.ResponseId,
108
- // SynthID vem embutido em toda imagem Gemini, e os bytes vao para o blob como
109
- // chegaram: nenhum re-encode entre a resposta e o storage.
84
+ // SynthID vem embutido e os bytes nao sao re-encodados ate o storage.
110
85
  C2paPreserved: true));
111
86
  }
112
87
  }
@@ -3,32 +3,8 @@ using OpenAI.Images;
3
3
 
4
4
  namespace Morph.AiKit.Media.Providers;
5
5
 
6
- /// <summary>
7
- /// Adaptador de imagem da OpenAI. <b><c>internal</c></b> — ver <see cref="IMediaProvider"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// A chamada passa pela abstracao <see cref="IImageGenerator"/> de
12
- /// <c>Microsoft.Extensions.AI</c>, e nao pelo <c>ImageClient</c> cru, para que o pipeline
13
- /// nao fique acoplado ao SDK de um provider. A ponte oficial
14
- /// (<c>Microsoft.Extensions.AI.OpenAI.AsIImageGenerator()</c>) <b>nao esta no pin deste
15
- /// kit</b>, entao o adaptador <see cref="OpenAIImageGenerator"/> vive aqui, escrito a mao
16
- /// sobre o <c>ImageClient</c>. Trocar por <c>AsIImageGenerator()</c> e uma linha, no dia em
17
- /// que o pacote entrar em <c>framework/ai-pin.json</c>.
18
- /// </para>
19
- /// <para>
20
- /// <b>Medido contra o pin em 2026-09-08, nao suposto:</b>
21
- /// <c>Microsoft.Extensions.AI.IImageGenerator</c> carrega <c>[Experimental("MEAI001")]</c>
22
- /// e herda <c>IDisposable</c>; <c>ImageGenerationRequest</c> tem <c>Prompt</c> e
23
- /// <c>OriginalImages</c> (<c>IEnumerable&lt;AIContent&gt;</c>), e tambem e experimental;
24
- /// <c>ImageGenerationOptions</c> <b>nao</b> tem propriedade tipada para
25
- /// <c>quality</c>/<c>background</c>/<c>input_fidelity</c> — por isso a qualidade viaja em
26
- /// <c>AdditionalProperties</c> e e este adaptador que a traduz para o SDK.
27
- /// </para>
28
- /// </remarks>
29
6
  internal sealed class OpenAIImageProvider : IMediaProvider
30
7
  {
31
- /// <summary>A chave sob a qual a qualidade viaja em <c>AdditionalProperties</c>.</summary>
32
8
  internal const string QualityKey = "quality";
33
9
 
34
10
  private readonly IMediaClientFactory _clients;
@@ -39,13 +15,10 @@ internal sealed class OpenAIImageProvider : IMediaProvider
39
15
  _clients = clients;
40
16
  }
41
17
 
42
- /// <inheritdoc />
43
18
  public string Provider => "openai";
44
19
 
45
- /// <inheritdoc />
46
20
  public bool Supports(MediaKind kind) => kind == MediaKind.Image;
47
21
 
48
- /// <inheritdoc />
49
22
  public async Task<MediaResult<MediaArtifact>> ProduceAsync(
50
23
  MediaRequest request,
51
24
  MediaAliasBinding binding,
@@ -69,9 +42,6 @@ internal sealed class OpenAIImageProvider : IMediaProvider
69
42
 
70
43
  if (request.References.Count > 0)
71
44
  {
72
- // OriginalImages e o gancho de EDICAO: preserva o produto real em vez de
73
- // recria-lo. Gerar do zero quando a foto existe inventa material, medida ou cor
74
- // que o produto nao tem — o claim visual falso que o AI Act art. 50 mira.
75
45
  generationRequest.OriginalImages =
76
46
  [.. request.References.Select(r => new DataContent(r.Bytes, r.MediaType))];
77
47
  }
@@ -90,8 +60,7 @@ internal sealed class OpenAIImageProvider : IMediaProvider
90
60
 
91
61
  if (request.Options.Quality is { } quality)
92
62
  {
93
- // Nao ha propriedade tipada na 10.9.0 — medido. Vai por AdditionalProperties, e
94
- // o adaptador acima e quem traduz para o SDK.
63
+ // MEAI 10.9.0 nao tem propriedade tipada para qualidade.
95
64
  options.AdditionalProperties = new AdditionalPropertiesDictionary
96
65
  {
97
66
  [QualityKey] = quality.ToString(),
@@ -119,16 +88,13 @@ internal sealed class OpenAIImageProvider : IMediaProvider
119
88
  data.Data,
120
89
  RevisedPrompt: (response.RawRepresentation as GeneratedImage)?.RevisedPrompt,
121
90
  ResponseId: null,
122
- // Os bytes chegam como o provider os produziu: nenhum re-encode entre a
123
- // resposta e o blob. E o que mantem a credencial C2PA viva.
91
+ // Nenhum re-encode entre a resposta e o blob: e o que mantem a credencial C2PA viva.
124
92
  C2paPreserved: true));
125
93
  }
126
94
  #pragma warning restore MEAI001
127
95
  catch (System.ClientModel.ClientResultException ex)
128
96
  {
129
- // Bloqueio de moderacao e resposta DEFINITIVA, nunca erro tecnico: retentar
130
- // gasta para receber o mesmo "nao". A distincao mora aqui, no unico ponto que
131
- // ve o codigo HTTP.
97
+ // A OpenAI devolve bloqueio de moderacao como HTTP 400: terminal, nunca retry.
132
98
  return ex.Status == 400
133
99
  ? MediaResult<MediaArtifact>.Fail(
134
100
  MediaErrorCode.PolicyBlocked,
@@ -140,20 +106,8 @@ internal sealed class OpenAIImageProvider : IMediaProvider
140
106
  }
141
107
  }
142
108
 
143
- /// <summary>
144
- /// <see cref="IImageGenerator"/> escrito a mao sobre o <c>ImageClient</c> do SDK da OpenAI.
145
- /// </summary>
146
- /// <remarks>
147
- /// Existe porque <c>Microsoft.Extensions.AI.OpenAI</c> — que traz
148
- /// <c>AsIImageGenerator()</c> — nao esta em <c>framework/ai-pin.json</c>, e acrescentar
149
- /// pacote e mexer no pin. O contrato imitado e o mesmo: <c>OriginalImages</c> vazio vira
150
- /// <c>GenerateImageAsync</c>; com referencia, vira <c>GenerateImageEditAsync</c>, que e o
151
- /// caminho que PRESERVA o produto real.
152
- /// </remarks>
153
- // OPENAI001 entra junto, e nao por preguica: GeneratedImageQuality.{Low,Medium,High}Quality
154
- // e ImageEditOptions.Quality carregam o diagnostico de "evaluation only" no OpenAI 2.13.0 —
155
- // medido pelo compilador, nao suposto. Suprimir os dois com o motivo escrito e o item 3 do
156
- // checklist de ai-agents/modalities-image-gen.md.
109
+ // Escrito a mao porque AsIImageGenerator() (Microsoft.Extensions.AI.OpenAI) nao esta no pin.
110
+ // OPENAI001: GeneratedImageQuality e ImageEditOptions.Quality sao "evaluation only" no OpenAI 2.13.0.
157
111
  #pragma warning disable MEAI001, OPENAI001
158
112
  internal sealed class OpenAIImageGenerator : IImageGenerator
159
113
  {
@@ -211,9 +165,7 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
211
165
 
212
166
  public void Dispose()
213
167
  {
214
- // O ImageClient nao e IDisposable e o seu tempo de vida e do projeto: a fabrica de
215
- // clientes cria, o projeto descarta. Descartar aqui seria uma segunda dona do mesmo
216
- // recurso — foi assim que um cliente vazou de um teste, uma vez.
168
+ // O ImageClient pertence a fabrica de clientes; descartar aqui seria uma segunda dona.
217
169
  }
218
170
 
219
171
  private static OpenAI.Images.ImageGenerationOptions BuildGenerationOptions(
@@ -221,8 +173,6 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
221
173
  {
222
174
  var sdk = new OpenAI.Images.ImageGenerationOptions
223
175
  {
224
- // Bytes, sempre. Uma URL de provider expira, e um caminho quebrado depois e um
225
- // arquivo pelo qual se pagou e que nao existe mais.
226
176
  ResponseFormat = GeneratedImageFormat.Bytes,
227
177
  };
228
178
 
@@ -256,10 +206,6 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
256
206
  return sdk;
257
207
  }
258
208
 
259
- /// <summary>
260
- /// Traduz a qualidade que viajou em <c>AdditionalProperties</c>. Chave ausente devolve
261
- /// nulo — e nulo significa "nao envia ao provider", nunca "manda o default do kit".
262
- /// </summary>
263
209
  private static GeneratedImageQuality? QualityOf(Microsoft.Extensions.AI.ImageGenerationOptions? options)
264
210
  {
265
211
  if (options?.AdditionalProperties is not { } extra
@@ -285,4 +231,4 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
285
231
  _ => "reference.png",
286
232
  };
287
233
  }
288
- #pragma warning restore MEAI001
234
+ #pragma warning restore MEAI001, OPENAI001
@@ -2,40 +2,11 @@ using Google.GenAI.Types;
2
2
 
3
3
  namespace Morph.AiKit.Media.Providers;
4
4
 
5
- /// <summary>
6
- /// Adaptador de video do Veo. <b><c>internal</c></b> — ver <see cref="IMediaProvider"/>.
7
- /// </summary>
8
- /// <remarks>
9
- /// <para>
10
- /// <b>E o unico caminho de video com SDK .NET oficial</b> depois do desligamento da Sora
11
- /// (data em <c>ai-agents/media-video.md</c>, secao Deprecacoes). Seedance, Kling e Runway
12
- /// exigem <c>HttpClient</c> tipado com DTOs proprios, e por isso nao tem adaptador aqui.
13
- /// </para>
14
- /// <para>
15
- /// <b>Assinatura medida contra Google.GenAI 1.21.0, nao lida em blog:</b>
16
- /// <c>Models.GenerateVideosAsync(string model, GenerateVideosSource source,
17
- /// GenerateVideosConfig config, CancellationToken)</c> devolve
18
- /// <c>GenerateVideosOperation</c> — o prompt vai DENTRO de <c>GenerateVideosSource</c>, e
19
- /// nao como parametro solto. O polling e
20
- /// <c>Operations.GetAsync(operation, config, ct)</c>, e o veredito e
21
- /// <c>operation.Done</c>.
22
- /// </para>
23
- /// <para>
24
- /// <b>Os bytes sao baixados AQUI.</b> O Veo retem o arquivo por 2 dias; devolver a URL e
25
- /// entregar um link que morre. Quando a resposta so traz <c>Uri</c>, este adaptador recusa
26
- /// com motivo em vez de guardar a URL fingindo que e permanente.
27
- /// </para>
28
- /// </remarks>
29
5
  internal sealed class VeoVideoProvider : IMediaProvider
30
6
  {
31
- /// <summary>Intervalo entre voltas do polling. ~10 s e o intervalo que a doc do Veo usa.</summary>
32
7
  internal static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(10);
33
8
 
34
- /// <summary>
35
- /// Teto de voltas. Existe porque <c>Done</c> que nunca vira <c>true</c> e um caso real,
36
- /// e um laco sem saida e indistinguivel de um worker travado. 60 voltas cobrem os 6 min
37
- /// documentados como pior caso, com folga.
38
- /// </summary>
9
+ // Cobre os 6 min documentados como pior caso do Veo; Done pode nunca virar true.
39
10
  internal const int MaxPolls = 60;
40
11
 
41
12
  private readonly IMediaClientFactory _clients;
@@ -48,13 +19,10 @@ internal sealed class VeoVideoProvider : IMediaProvider
48
19
  _delay = delay ?? Task.Delay;
49
20
  }
50
21
 
51
- /// <inheritdoc />
52
22
  public string Provider => "google";
53
23
 
54
- /// <inheritdoc />
55
24
  public bool Supports(MediaKind kind) => kind == MediaKind.Video;
56
25
 
57
- /// <inheritdoc />
58
26
  public async Task<MediaResult<MediaArtifact>> ProduceAsync(
59
27
  MediaRequest request,
60
28
  MediaAliasBinding binding,
@@ -80,8 +48,7 @@ internal sealed class VeoVideoProvider : IMediaProvider
80
48
 
81
49
  if (request.References.Count > 0)
82
50
  {
83
- // O Veo exige 8 s quando ha imagens de referencia. Pedir 4 s COM referencia e
84
- // requisicao invalida, e descobrir isso em producao custa uma rodada de suporte.
51
+ // O Veo exige 8 s quando ha imagens de referencia.
85
52
  config.ReferenceImages =
86
53
  [
87
54
  .. request.References.Select(r => new VideoGenerationReferenceImage
@@ -127,8 +94,6 @@ internal sealed class VeoVideoProvider : IMediaProvider
127
94
 
128
95
  if (response?.RaiMediaFilteredCount is > 0)
129
96
  {
130
- // Bloqueio de moderacao. No Veo ele NAO cobra — e nao vira retry, porque
131
- // retentar recebe o mesmo "nao".
132
97
  return MediaResult<MediaArtifact>.Fail(
133
98
  MediaErrorCode.PolicyBlocked,
134
99
  $"alias '{binding.Alias}': {response.RaiMediaFilteredCount} saida(s) bloqueada(s) por politica: " +
@@ -1,29 +1,7 @@
1
1
  namespace Morph.AiKit.Middleware;
2
2
 
3
- /// <summary>
4
- /// Para onde vai o custo de cada volta ao modelo. <b>A costura de saida do
5
- /// <see cref="UsageCostMiddleware"/></b>: o kit calcula, o projeto decide onde guardar
6
- /// (tabela, metrica, log estruturado, fila).
7
- /// </summary>
8
- /// <remarks>
9
- /// <para>
10
- /// <b>Por que uma interface de um metodo so e nao um evento nem um logger:</b> custo e
11
- /// dado de negocio, nao diagnostico. Um projeto multi-tenant precisa somar por conta e por
12
- /// mes; um <c>ILogger</c> nao devolve soma. A interface e minima de proposito — quem quiser
13
- /// um logger implementa um sink de duas linhas.
14
- /// </para>
15
- /// <para>
16
- /// <b>Contrato de execucao:</b> <see cref="Record"/> e chamado no caminho da resposta,
17
- /// <b>sincronamente</b>, uma vez por volta ao modelo. Uma implementacao que faca I/O
18
- /// bloqueante aqui paga esse custo na latencia de cada resposta — enfileire e persista fora
19
- /// do caminho quente. E ela pode ser chamada de varias threads ao mesmo tempo (um mesmo
20
- /// pipeline atende varias conversas): a implementacao e responsavel pela propria seguranca
21
- /// sob concorrencia.
22
- /// </para>
23
- /// </remarks>
24
3
  public interface IUsageCostSink
25
4
  {
26
- /// <summary>Publica o custo de uma volta ao modelo.</summary>
27
- /// <param name="record">O registro ja calculado. Nunca nulo.</param>
5
+ // Chamado sincronamente no caminho da resposta e de varias threads: I/O aqui vira latencia.
28
6
  void Record(UsageCostRecord record);
29
7
  }
@@ -4,68 +4,12 @@ using Morph.AiKit.Providers;
4
4
 
5
5
  namespace Morph.AiKit.Middleware;
6
6
 
7
- /// <summary>
8
- /// Middleware de <see cref="IChatClient"/> que le o <c>Usage</c> da resposta, aplica a
9
- /// tarifa do alias e publica uma linha de custo em <see cref="IUsageCostSink"/>.
10
- /// </summary>
11
- /// <remarks>
12
- /// <para>
13
- /// <b>Onde ele entra no pipeline:</b> <b>ACIMA</b> da invocacao de funcoes, com a
14
- /// telemetria de chat abaixo dela (<c>usage-cost -&gt; function-invocation -&gt;
15
- /// OpenTelemetry -&gt; cliente do provider</c>). Na pratica: a
16
- /// <c>AgentFactory.BuildChatPipeline</c> registra o encaixe de custo <b>antes</b> de
17
- /// <c>UseFunctionInvocation</c>, e no <c>ChatClientBuilder</c> o primeiro registrado e o
18
- /// mais <b>externo</b>. A ordem nao e estetica, e o motivo e o
19
- /// <c>FunctionInvokingChatClient</c> (FICC): acima dele o middleware ve <b>uma</b> resposta
20
- /// cujo <c>Usage</c> ja vem com o total de todas as voltas de tool (medido: duas voltas de
21
- /// 10/4 e 30/6 chegam como <c>in=40, out=10</c>) e publica <b>uma</b> linha por chamada.
22
- /// Abaixo dele veria as respostas parciais — e leria o mesmo objeto <c>UsageDetails</c> que
23
- /// o proprio FICC <b>muta</b> para acumular, o que faria a mesma conta dar resultado
24
- /// diferente conforme a hora de ler. Isso e medido, nao argumentado, em
25
- /// <c>AgentFactoryTests.ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt</c>
26
- /// (sequencia <c>usage-cost:enter -&gt; telemetry:enter/exit x2 -&gt; usage-cost:exit</c>) e
27
- /// em <c>AgentFactoryTests.UsageCostAboveFunctionInvocationSeesTheUsageOfEveryToolRoundSummedOnce</c>.
28
- /// A montagem do pipeline e da <c>AgentFactory</c> (T10); daqui so depende a extensao
29
- /// <see cref="UsageCostChatClientBuilderExtensions.UseUsageCost"/>.
30
- /// </para>
31
- /// <para>
32
- /// <b>Nota de errata:</b> o <c>spec.md</c> sec. 3.1 desta feature prescrevia a seta
33
- /// literal <c>function-invocation -&gt; usage-cost -&gt; OpenTelemetry</c>, que se le ao
34
- /// contrario do que esta implementado aqui. A divergencia foi medida na T10 sec. 3.2 e esta
35
- /// registrada como <b>Errata E9</b> no proprio <c>spec.md</c> — o corpo do plano e
36
- /// append-only e continua com a seta antiga; a verdade e esta.
37
- /// </para>
38
- /// <para>
39
- /// <b>A conta e a do <see cref="ModelPricing.ComputeCostUsd"/>, nao uma segunda.</b> Este
40
- /// tipo nao contem aritmetica de tarifa: ele converte <c>Usage</c> em argumentos, chama a
41
- /// conta que ja existe e carimba o resultado. Reescrever a formula aqui daria dois numeros
42
- /// para o mesmo custo — que e literalmente a dor no nome desta epica (em ProspectPRO a
43
- /// conta esta repetida em cinco agentes).
44
- /// </para>
45
- /// <para>
46
- /// <b>Nunca lanca por causa de tarifa.</b> Alias sem tarifa e caminho previsto: sai
47
- /// <c>CostUsd = 0</c> com <see cref="UsageCostStatus.Waived"/> e o motivo escrito no
48
- /// record. Inventar tarifa e pior que nao ter. E resposta sem <c>Usage</c> sai como
49
- /// <see cref="UsageCostStatus.UsageMissing"/> — visivel, em vez de silenciosa.
50
- /// </para>
51
- /// </remarks>
52
7
  public sealed class UsageCostMiddleware : DelegatingChatClient
53
8
  {
54
9
  private readonly ModelAlias _alias;
55
10
  private readonly IUsageCostSink _sink;
56
11
  private readonly TimeProvider _timeProvider;
57
12
 
58
- /// <summary>Monta o middleware sobre um cliente interno.</summary>
59
- /// <param name="innerClient">O proximo elo do pipeline.</param>
60
- /// <param name="alias">
61
- /// O alias que atende este cliente — de onde vem a tarifa e o nome que vai no record.
62
- /// </param>
63
- /// <param name="sink">Para onde as linhas de custo vao.</param>
64
- /// <param name="timeProvider">
65
- /// Relogio. Nulo usa <see cref="TimeProvider.System"/>. Injetavel porque o standard
66
- /// <c>backend-dotnet-testing</c> proibe <c>DateTimeOffset.UtcNow</c> direto num tipo
67
- /// testavel — sem isso, o instante do record e intestavel.
68
- /// </param>
69
13
  public UsageCostMiddleware(
70
14
  IChatClient innerClient,
71
15
  ModelAlias alias,
@@ -81,7 +25,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
81
25
  _timeProvider = timeProvider ?? TimeProvider.System;
82
26
  }
83
27
 
84
- /// <inheritdoc />
85
28
  public override async Task<ChatResponse> GetResponseAsync(
86
29
  IEnumerable<ChatMessage> messages,
87
30
  ChatOptions? options = null,
@@ -95,13 +38,7 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
95
38
  return response;
96
39
  }
97
40
 
98
- /// <inheritdoc />
99
- /// <remarks>
100
- /// No streaming o <c>Usage</c> chega como <see cref="UsageContent"/> avulso, tipicamente
101
- /// no ultimo update — e pode chegar em mais de um pedaco. Os pedacos sao somados e a
102
- /// linha de custo sai <b>uma unica vez</b>, ao fim da enumeracao: emitir por update daria
103
- /// N linhas para uma volta so.
104
- /// </remarks>
41
+ // No streaming o Usage pode chegar em mais de um UsageContent: soma e publica uma linha so, ao fim.
105
42
  public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
106
43
  IEnumerable<ChatMessage> messages,
107
44
  ChatOptions? options = null,
@@ -132,8 +69,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
132
69
 
133
70
  if (usage is null || (usage.InputTokenCount is null && usage.OutputTokenCount is null))
134
71
  {
135
- // Sem relato de uso nao ha o que tarifar. A linha sai assim mesmo, marcada: um
136
- // custo que ninguem consegue medir precisa aparecer, nao sumir.
137
72
  _sink.Record(new UsageCostRecord(_alias.Name, 0, 0, null, 0m, at)
138
73
  {
139
74
  Status = UsageCostStatus.UsageMissing,
@@ -147,8 +82,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
147
82
 
148
83
  if (_alias.Pricing is not { } pricing)
149
84
  {
150
- // Caminho previsto, nunca excecao: o alias declarou por escrito que nao tem
151
- // tarifa (o registry recusa a carga de um alias sem tarifa E sem dispensa).
152
85
  _sink.Record(new UsageCostRecord(_alias.Name, input, output, cached, 0m, at)
153
86
  {
154
87
  Status = UsageCostStatus.Waived,
@@ -157,9 +90,7 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
157
90
  return;
158
91
  }
159
92
 
160
- // Um provider que reporte 'cached' maior que 'input' esta se contradizendo; a conta
161
- // do T8 recusa esse par. Aqui o dado inconsistente vira lacuna declarada, e nao uma
162
- // excecao que derruba a resposta do usuario por causa de um contador.
93
+ // ComputeCostUsd recusa esse par; um contador incoerente do provider nao pode derrubar a resposta.
163
94
  if (cached > input)
164
95
  {
165
96
  _sink.Record(new UsageCostRecord(_alias.Name, input, output, cached, 0m, at)
@@ -182,19 +113,8 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
182
113
  }
183
114
  }
184
115
 
185
- /// <summary>
186
- /// Registro do <see cref="UsageCostMiddleware"/> no pipeline de <see cref="IChatClient"/>.
187
- /// </summary>
188
116
  public static class UsageCostChatClientBuilderExtensions
189
117
  {
190
- /// <summary>
191
- /// Insere a contabilizacao de custo no pipeline, com a tarifa do alias dado.
192
- /// </summary>
193
- /// <param name="builder">O builder do pipeline.</param>
194
- /// <param name="alias">Alias que atende este cliente.</param>
195
- /// <param name="sink">Destino das linhas de custo.</param>
196
- /// <param name="timeProvider">Relogio; nulo usa <see cref="TimeProvider.System"/>.</param>
197
- /// <returns>O proprio builder, para encadear.</returns>
198
118
  public static ChatClientBuilder UseUsageCost(
199
119
  this ChatClientBuilder builder,
200
120
  ModelAlias alias,
@@ -2,74 +2,16 @@ using Morph.AiKit.Providers;
2
2
 
3
3
  namespace Morph.AiKit.Middleware;
4
4
 
5
- /// <summary>
6
- /// Como o custo de uma volta ao modelo ficou conhecido. Existe para que
7
- /// <see cref="UsageCostRecord.CostUsd"/> possa ser um <see cref="decimal"/> nao-anulavel
8
- /// <b>sem que zero passe a significar tres coisas diferentes</b>.
9
- /// </summary>
10
- /// <remarks>
11
- /// A dor que esta feature nomeia e <c>cost_usd</c> saindo nulo. Trocar o nulo por um zero
12
- /// mudo seria trocar um defeito por outro pior — zero soma, nulo nao. Aqui o valor e sempre
13
- /// um numero e o status diz se aquele numero e uma medicao, uma decisao ou uma lacuna.
14
- /// </remarks>
15
5
  public enum UsageCostStatus
16
6
  {
17
- /// <summary>
18
- /// Tarifa do alias aplicada sobre uso reportado pelo provider. O unico status em que
19
- /// <see cref="UsageCostRecord.CostUsd"/> e uma <b>medicao</b> — e o unico que pode
20
- /// entrar numa soma de fatura sem ressalva.
21
- /// </summary>
22
7
  Computed = 0,
23
8
 
24
- /// <summary>
25
- /// O alias nao tem tarifa e <b>tem dispensa escrita</b>
26
- /// (<c>ModelAlias.PricingWaiver</c>). Custo zero por decisao registrada — modelo local,
27
- /// por exemplo, onde a conta e a maquina e nao o token. O motivo viaja em
28
- /// <see cref="UsageCostRecord.PricingWaiver"/>: um zero aqui nunca e anonimo.
29
- /// </summary>
30
9
  Waived = 1,
31
10
 
32
- /// <summary>
33
- /// O provider <b>nao reportou</b> <c>Usage</c> nesta volta. Os tokens sao desconhecidos,
34
- /// logo o custo tambem e: o zero em <see cref="UsageCostRecord.CostUsd"/> e ausencia de
35
- /// medicao, <b>nao</b> gratuidade. Some com <see cref="Computed"/> apenas ciente disso.
36
- /// </summary>
11
+ // O zero de CostUsd aqui e ausencia de medicao, nao gratuidade.
37
12
  UsageMissing = 2,
38
13
  }
39
14
 
40
- /// <summary>
41
- /// O custo de uma volta ao modelo, ja calculado. <b>Uma linha por resposta</b>, pronta para
42
- /// virar registro de banco, metrica ou linha de fatura.
43
- /// </summary>
44
- /// <remarks>
45
- /// <para>
46
- /// <b><see cref="CostUsd"/> nunca e nulo — por construcao do tipo.</b> Esse e o ponto
47
- /// inteiro: a dor catalogada na issue e um <c>cost_usd</c> nulo atravessando tres camadas
48
- /// e aparecendo como buraco no relatorio. A ausencia de tarifa deixou de poder chegar ate
49
- /// aqui por esquecimento: o <c>ModelRegistry</c> (T8) <b>recusa a carga</b> de um alias que
50
- /// nao declare <c>pricing</c> nem <c>pricingWaiver</c>. Logo, quando este record sai com
51
- /// <see cref="UsageCostStatus.Waived"/>, existe necessariamente um motivo escrito em
52
- /// <see cref="PricingWaiver"/>.
53
- /// </para>
54
- /// <para>
55
- /// <b>A conta nao mora aqui.</b> Ela mora em <see cref="ModelPricing.ComputeCostUsd"/>
56
- /// (T8), e o <see cref="UsageCostMiddleware"/> apenas a chama. Uma segunda aritmetica do
57
- /// mesmo custo e o defeito que esta epica tem no nome: em ProspectPRO ela esta repetida
58
- /// literalmente em cinco agentes, e duas copias da mesma conta divergem — a divergencia
59
- /// aparece na fatura.
60
- /// </para>
61
- /// </remarks>
62
- /// <param name="Alias">O alias do <c>ModelRegistry</c> que atendeu a volta.</param>
63
- /// <param name="InputTokens">Tokens de entrada reportados. Zero quando nao houve relato.</param>
64
- /// <param name="OutputTokens">Tokens de saida reportados. Zero quando nao houve relato.</param>
65
- /// <param name="CachedInputTokens">
66
- /// Tokens de entrada servidos do cache do provider, ou nulo quando o provider nao reporta.
67
- /// E <b>subconjunto</b> de <paramref name="InputTokens"/>, nunca uma parcela a mais.
68
- /// </param>
69
- /// <param name="CostUsd">
70
- /// Custo em USD. Nunca nulo; leia <see cref="Status"/> antes de somar.
71
- /// </param>
72
- /// <param name="At">Instante da volta, vindo do <see cref="TimeProvider"/> injetado.</param>
73
15
  public sealed record UsageCostRecord(
74
16
  string Alias,
75
17
  long InputTokens,
@@ -78,18 +20,9 @@ public sealed record UsageCostRecord(
78
20
  decimal CostUsd,
79
21
  DateTimeOffset At)
80
22
  {
81
- /// <summary>Como o custo ficou conhecido. Ver <see cref="UsageCostStatus"/>.</summary>
82
23
  public required UsageCostStatus Status { get; init; }
83
24
 
84
- /// <summary>
85
- /// O motivo escrito de o alias nao ter tarifa, copiado de
86
- /// <c>ModelAlias.PricingWaiver</c>. Preenchido <b>somente</b> com
87
- /// <see cref="UsageCostStatus.Waived"/>.
88
- /// </summary>
89
25
  public string? PricingWaiver { get; init; }
90
26
 
91
- /// <summary>
92
- /// Se <see cref="CostUsd"/> e uma medicao — o unico caso em que somar e honesto.
93
- /// </summary>
94
27
  public bool IsMeasuredCost => Status == UsageCostStatus.Computed;
95
28
  }
@@ -4,48 +4,8 @@ using Morph.AiKit.Agents;
4
4
 
5
5
  namespace Morph.AiKit.Observability;
6
6
 
7
- /// <summary>
8
- /// Liga as GenAI semantic conventions nos dois niveis do kit — agente e chat client — com
9
- /// <b>um guard contra span duplicado em cada um</b>.
10
- /// </summary>
11
- /// <remarks>
12
- /// <para>
13
- /// <b>O que foi medido no pin (Microsoft.Agents.AI 1.20.0), e nao suposto:</b>
14
- /// </para>
15
- /// <list type="bullet">
16
- /// <item>
17
- /// Uma invocacao instrumentada produz <c>invoke_agent</c> (raiz) e <c>chat</c> (filho).
18
- /// Duas camadas de instrumentacao no MESMO nivel produzem o span daquele nivel duas
19
- /// vezes: dois <c>OpenTelemetryChatClient</c> dao dois <c>chat</c>; dois
20
- /// <c>OpenTelemetryAgent</c> dao dois <c>invoke_agent</c>.
21
- /// </item>
22
- /// <item>
23
- /// <c>GetService</c> atravessa a cadeia de delegates nos dois niveis — e por isso que
24
- /// "ja existe instrumentacao aqui embaixo?" e uma pergunta respondivel, e nao um
25
- /// palpite. E o predicado dos dois guards.
26
- /// </item>
27
- /// <item>
28
- /// O MAF 1.20.0 tem um guard proprio, mas ele so alcanca o pipeline que o
29
- /// <c>ChatClientAgent</c> monta sozinho. A <c>AgentFactory</c> monta o proprio
30
- /// (<c>UseProvidedChatClientAsIs = true</c>) para poder posicionar o custo e ajustar o
31
- /// tool calling — e nesse arranjo o slot do MAF nao existe. Medido: agente
32
- /// instrumentado + pipeline proprio sem estas extensoes = <b>zero</b> spans de chat.
33
- /// O guard daqui nao duplica o do MAF; ele cobre o pipeline que o MAF nao ve.
34
- /// </item>
35
- /// </list>
36
- /// </remarks>
37
7
  public static class MorphOpenTelemetryExtensions
38
8
  {
39
- /// <summary>
40
- /// Instrumenta o pipeline de chat. Aplicado pela <c>AgentFactory</c> ABAIXO do
41
- /// <c>FunctionInvokingChatClient</c>, que e onde o MAF 1.20.0 documenta que a
42
- /// instrumentacao tem de ficar para o span de chat fechar antes das tools.
43
- /// </summary>
44
- /// <remarks>
45
- /// O guard vive na decisao de <see cref="ShouldInstrument"/>: com
46
- /// <see cref="MorphTelemetryOptions.InstrumentChatClient"/> nulo (o default), um
47
- /// pipeline que ja carrega um <see cref="OpenTelemetryChatClient"/> e devolvido intacto.
48
- /// </remarks>
49
9
  public static ChatClientBuilder UseMorphTelemetry(this ChatClientBuilder builder, MorphTelemetryOptions options)
50
10
  {
51
11
  ArgumentNullException.ThrowIfNull(builder);
@@ -59,10 +19,6 @@ public static class MorphOpenTelemetryExtensions
59
19
  : inner);
60
20
  }
61
21
 
62
- /// <summary>
63
- /// Instrumenta o agente — o span <c>invoke_agent</c>, o mais externo, que cobre a
64
- /// invocacao inteira. Um agente que ja e instrumentado e devolvido intacto.
65
- /// </summary>
66
22
  public static AIAgentBuilder UseMorphTelemetry(this AIAgentBuilder builder, MorphTelemetryOptions options)
67
23
  {
68
24
  ArgumentNullException.ThrowIfNull(builder);
@@ -76,20 +32,6 @@ public static class MorphOpenTelemetryExtensions
76
32
  });
77
33
  }
78
34
 
79
- /// <summary>
80
- /// Preenche os dois encaixes de telemetria da <see cref="AgentFactoryOptions"/> de uma
81
- /// vez, respeitando <see cref="AgentSpecOptions.EnableSensitiveTelemetry"/> quando a
82
- /// spec opinou.
83
- /// </summary>
84
- /// <remarks>
85
- /// A fabrica e dona da POSICAO de cada encaixe; esta extensao so diz o que ocupa cada
86
- /// um. E por aqui que a T10 e a T12 se encontram sem uma referenciar a outra.
87
- /// <para>
88
- /// <c>EnableSensitiveTelemetry</c> nulo na spec significa "nao opinei" e mantem o valor
89
- /// destas opcoes; <c>false</c> significa "decidi que nao" e vence. A diferenca entre os
90
- /// dois e preservada de proposito desde a T7.
91
- /// </para>
92
- /// </remarks>
93
35
  public static AgentFactoryOptions WithMorphTelemetry(
94
36
  this AgentFactoryOptions factoryOptions,
95
37
  MorphTelemetryOptions options)
@@ -109,10 +51,7 @@ public static class MorphOpenTelemetryExtensions
109
51
  ? options with { EnableSensitiveData = sensitive }
110
52
  : options;
111
53
 
112
- /// <summary>
113
- /// <b>O guard.</b> Nulo = instrumenta so o que ainda nao esta instrumentado;
114
- /// <c>true</c> = instrumenta sempre (e duplica, quando ja havia); <c>false</c> = nunca.
115
- /// </summary>
54
+ // GetService atravessa a cadeia de delegates (MAF 1.20.0), entao detecta instrumentacao ja aplicada abaixo.
116
55
  private static bool ShouldInstrument(IChatClient inner, MorphTelemetryOptions options)
117
56
  => options.InstrumentChatClient
118
57
  ?? inner.GetService(typeof(OpenTelemetryChatClient)) is null;