@polymorphism-tech/morph-spec 8.33.0 → 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 +22 -2
- package/README.md +2 -2
- package/bin/morph-spec.js +43 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +20 -22
- package/framework/agents.json +198 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +238 -10
- 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/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/rules/infrastructure-standards.md +3 -1
- 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 +28 -7
- 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 +20 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +38 -4
- 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 +1504 -830
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +256 -160
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +217 -193
- package/framework/standards/ai-agents/context-providers.md +261 -188
- 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 +351 -250
- package/framework/standards/ai-agents/mcp-tools.md +250 -197
- 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 +241 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
- package/framework/standards/ai-agents/modalities-vision.md +205 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +260 -231
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +293 -158
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +143 -135
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +280 -249
- package/framework/standards/ai-agents/sweet-spot.md +129 -109
- 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 +90 -0
- package/framework/templates/REGISTRY.json +14 -1
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- 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/infrastructure/docker/docker-compose.template.yml +24 -14
- package/package.json +3 -3
- package/src/commands/e2e/e2e.js +91 -40
- package/src/commands/project/doctor.js +143 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +274 -53
- 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/delete.js +8 -0
- package/src/commands/state/finish.js +360 -53
- 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/state-manager.js +9 -1
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- 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/e2e/compose-env.js +55 -14
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/installers/effects.js +193 -0
- 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/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +431 -37
- package/src/lib/validators/verify-runner.js +590 -28
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +69 -11
- package/src/lib/worktree/worktree-audit.js +70 -3
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/file-copier.js +28 -16
- package/src/utils/hooks-installer.js +75 -29
- package/src/utils/skills-installer.js +194 -187
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
using Microsoft.Extensions.AI;
|
|
2
|
+
using OpenAI.Images;
|
|
3
|
+
|
|
4
|
+
namespace Morph.AiKit.Media.Providers;
|
|
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<AIContent></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
|
+
internal sealed class OpenAIImageProvider : IMediaProvider
|
|
30
|
+
{
|
|
31
|
+
/// <summary>A chave sob a qual a qualidade viaja em <c>AdditionalProperties</c>.</summary>
|
|
32
|
+
internal const string QualityKey = "quality";
|
|
33
|
+
|
|
34
|
+
private readonly IMediaClientFactory _clients;
|
|
35
|
+
|
|
36
|
+
public OpenAIImageProvider(IMediaClientFactory clients)
|
|
37
|
+
{
|
|
38
|
+
ArgumentNullException.ThrowIfNull(clients);
|
|
39
|
+
_clients = clients;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// <inheritdoc />
|
|
43
|
+
public string Provider => "openai";
|
|
44
|
+
|
|
45
|
+
/// <inheritdoc />
|
|
46
|
+
public bool Supports(MediaKind kind) => kind == MediaKind.Image;
|
|
47
|
+
|
|
48
|
+
/// <inheritdoc />
|
|
49
|
+
public async Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
50
|
+
MediaRequest request,
|
|
51
|
+
MediaAliasBinding binding,
|
|
52
|
+
CancellationToken cancellationToken)
|
|
53
|
+
{
|
|
54
|
+
ArgumentNullException.ThrowIfNull(request);
|
|
55
|
+
ArgumentNullException.ThrowIfNull(binding);
|
|
56
|
+
|
|
57
|
+
var client = _clients.CreateOpenAiImageClient(binding);
|
|
58
|
+
if (client is null)
|
|
59
|
+
{
|
|
60
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
61
|
+
MediaErrorCode.ProviderFailure,
|
|
62
|
+
$"alias '{binding.Alias}': a fabrica de clientes nao construiu um ImageClient para o provider 'openai'.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#pragma warning disable MEAI001 // IImageGenerator/ImageGenerationRequest sao experimentais: a superficie pode mudar entre menores. Risco assumido por escrito.
|
|
66
|
+
using IImageGenerator generator = new OpenAIImageGenerator(client);
|
|
67
|
+
|
|
68
|
+
var generationRequest = new ImageGenerationRequest { Prompt = request.Prompt };
|
|
69
|
+
|
|
70
|
+
if (request.References.Count > 0)
|
|
71
|
+
{
|
|
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
|
+
generationRequest.OriginalImages =
|
|
76
|
+
[.. request.References.Select(r => new DataContent(r.Bytes, r.MediaType))];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var options = new Microsoft.Extensions.AI.ImageGenerationOptions
|
|
80
|
+
{
|
|
81
|
+
ModelId = binding.Model,
|
|
82
|
+
MediaType = request.Options.MediaType,
|
|
83
|
+
Count = request.Options.Count,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (request.Options is { Width: { } width, Height: { } height })
|
|
87
|
+
{
|
|
88
|
+
options.ImageSize = new System.Drawing.Size(width, height);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (request.Options.Quality is { } quality)
|
|
92
|
+
{
|
|
93
|
+
// Nao ha propriedade tipada na 10.9.0 — medido. Vai por AdditionalProperties, e
|
|
94
|
+
// o adaptador acima e quem traduz para o SDK.
|
|
95
|
+
options.AdditionalProperties = new AdditionalPropertiesDictionary
|
|
96
|
+
{
|
|
97
|
+
[QualityKey] = quality.ToString(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
var response = await generator
|
|
104
|
+
.GenerateAsync(generationRequest, options, cancellationToken)
|
|
105
|
+
.ConfigureAwait(false);
|
|
106
|
+
|
|
107
|
+
var data = response.Contents.OfType<DataContent>().FirstOrDefault();
|
|
108
|
+
if (data is null)
|
|
109
|
+
{
|
|
110
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
111
|
+
MediaErrorCode.ProviderFailure,
|
|
112
|
+
$"alias '{binding.Alias}': a resposta nao trouxe DataContent. Uma resposta so com URL " +
|
|
113
|
+
"seria um arquivo que expira, e este pipeline guarda BYTES.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return MediaResult<MediaArtifact>.Ok(new MediaArtifact(
|
|
117
|
+
MediaKind.Image,
|
|
118
|
+
data.MediaType,
|
|
119
|
+
data.Data,
|
|
120
|
+
RevisedPrompt: (response.RawRepresentation as GeneratedImage)?.RevisedPrompt,
|
|
121
|
+
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.
|
|
124
|
+
C2paPreserved: true));
|
|
125
|
+
}
|
|
126
|
+
#pragma warning restore MEAI001
|
|
127
|
+
catch (System.ClientModel.ClientResultException ex)
|
|
128
|
+
{
|
|
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.
|
|
132
|
+
return ex.Status == 400
|
|
133
|
+
? MediaResult<MediaArtifact>.Fail(
|
|
134
|
+
MediaErrorCode.PolicyBlocked,
|
|
135
|
+
$"alias '{binding.Alias}': o provider recusou o pedido (HTTP 400): {ex.Message}")
|
|
136
|
+
: MediaResult<MediaArtifact>.Fail(
|
|
137
|
+
MediaErrorCode.ProviderFailure,
|
|
138
|
+
$"alias '{binding.Alias}': falha tecnica (HTTP {ex.Status}): {ex.Message}");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
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.
|
|
157
|
+
#pragma warning disable MEAI001, OPENAI001
|
|
158
|
+
internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
159
|
+
{
|
|
160
|
+
private readonly ImageClient _client;
|
|
161
|
+
|
|
162
|
+
public OpenAIImageGenerator(ImageClient client)
|
|
163
|
+
{
|
|
164
|
+
ArgumentNullException.ThrowIfNull(client);
|
|
165
|
+
_client = client;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public async Task<ImageGenerationResponse> GenerateAsync(
|
|
169
|
+
ImageGenerationRequest request,
|
|
170
|
+
Microsoft.Extensions.AI.ImageGenerationOptions? options,
|
|
171
|
+
CancellationToken cancellationToken = default)
|
|
172
|
+
{
|
|
173
|
+
ArgumentNullException.ThrowIfNull(request);
|
|
174
|
+
|
|
175
|
+
var reference = request.OriginalImages?.OfType<DataContent>().FirstOrDefault();
|
|
176
|
+
|
|
177
|
+
GeneratedImage image = reference is null
|
|
178
|
+
? (await _client
|
|
179
|
+
.GenerateImageAsync(request.Prompt ?? string.Empty, BuildGenerationOptions(options), cancellationToken)
|
|
180
|
+
.ConfigureAwait(false)).Value
|
|
181
|
+
: (await _client
|
|
182
|
+
.GenerateImageEditAsync(
|
|
183
|
+
new MemoryStream(reference.Data.ToArray()),
|
|
184
|
+
FileNameFor(reference.MediaType),
|
|
185
|
+
request.Prompt ?? string.Empty,
|
|
186
|
+
BuildEditOptions(options),
|
|
187
|
+
cancellationToken)
|
|
188
|
+
.ConfigureAwait(false)).Value;
|
|
189
|
+
|
|
190
|
+
var mediaType = options?.MediaType ?? "image/png";
|
|
191
|
+
|
|
192
|
+
return new ImageGenerationResponse([new DataContent(image.ImageBytes.ToMemory(), mediaType)])
|
|
193
|
+
{
|
|
194
|
+
RawRepresentation = image,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
199
|
+
{
|
|
200
|
+
ArgumentNullException.ThrowIfNull(serviceType);
|
|
201
|
+
|
|
202
|
+
if (serviceKey is not null)
|
|
203
|
+
{
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return serviceType.IsInstanceOfType(_client) ? _client
|
|
208
|
+
: serviceType.IsInstanceOfType(this) ? this
|
|
209
|
+
: null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public void Dispose()
|
|
213
|
+
{
|
|
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.
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private static OpenAI.Images.ImageGenerationOptions BuildGenerationOptions(
|
|
220
|
+
Microsoft.Extensions.AI.ImageGenerationOptions? options)
|
|
221
|
+
{
|
|
222
|
+
var sdk = new OpenAI.Images.ImageGenerationOptions
|
|
223
|
+
{
|
|
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
|
+
ResponseFormat = GeneratedImageFormat.Bytes,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (options?.ImageSize is { } size)
|
|
230
|
+
{
|
|
231
|
+
sdk.Size = new GeneratedImageSize(size.Width, size.Height);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (QualityOf(options) is { } quality)
|
|
235
|
+
{
|
|
236
|
+
sdk.Quality = quality;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return sdk;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private static ImageEditOptions BuildEditOptions(Microsoft.Extensions.AI.ImageGenerationOptions? options)
|
|
243
|
+
{
|
|
244
|
+
var sdk = new ImageEditOptions { ResponseFormat = GeneratedImageFormat.Bytes };
|
|
245
|
+
|
|
246
|
+
if (options?.ImageSize is { } size)
|
|
247
|
+
{
|
|
248
|
+
sdk.Size = new GeneratedImageSize(size.Width, size.Height);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (QualityOf(options) is { } quality)
|
|
252
|
+
{
|
|
253
|
+
sdk.Quality = quality;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return sdk;
|
|
257
|
+
}
|
|
258
|
+
|
|
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
|
+
private static GeneratedImageQuality? QualityOf(Microsoft.Extensions.AI.ImageGenerationOptions? options)
|
|
264
|
+
{
|
|
265
|
+
if (options?.AdditionalProperties is not { } extra
|
|
266
|
+
|| !extra.TryGetValue(OpenAIImageProvider.QualityKey, out var raw)
|
|
267
|
+
|| raw is null)
|
|
268
|
+
{
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return raw.ToString() switch
|
|
273
|
+
{
|
|
274
|
+
nameof(MediaQuality.Low) => GeneratedImageQuality.LowQuality,
|
|
275
|
+
nameof(MediaQuality.Medium) => GeneratedImageQuality.MediumQuality,
|
|
276
|
+
nameof(MediaQuality.High) => GeneratedImageQuality.HighQuality,
|
|
277
|
+
_ => null,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private static string FileNameFor(string? mediaType) => mediaType switch
|
|
282
|
+
{
|
|
283
|
+
"image/jpeg" or "image/jpg" => "reference.jpg",
|
|
284
|
+
"image/webp" => "reference.webp",
|
|
285
|
+
_ => "reference.png",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
#pragma warning restore MEAI001
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
using Google.GenAI.Types;
|
|
2
|
+
|
|
3
|
+
namespace Morph.AiKit.Media.Providers;
|
|
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
|
+
internal sealed class VeoVideoProvider : IMediaProvider
|
|
30
|
+
{
|
|
31
|
+
/// <summary>Intervalo entre voltas do polling. ~10 s e o intervalo que a doc do Veo usa.</summary>
|
|
32
|
+
internal static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(10);
|
|
33
|
+
|
|
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>
|
|
39
|
+
internal const int MaxPolls = 60;
|
|
40
|
+
|
|
41
|
+
private readonly IMediaClientFactory _clients;
|
|
42
|
+
private readonly Func<TimeSpan, CancellationToken, Task> _delay;
|
|
43
|
+
|
|
44
|
+
public VeoVideoProvider(IMediaClientFactory clients, Func<TimeSpan, CancellationToken, Task>? delay = null)
|
|
45
|
+
{
|
|
46
|
+
ArgumentNullException.ThrowIfNull(clients);
|
|
47
|
+
_clients = clients;
|
|
48
|
+
_delay = delay ?? Task.Delay;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// <inheritdoc />
|
|
52
|
+
public string Provider => "google";
|
|
53
|
+
|
|
54
|
+
/// <inheritdoc />
|
|
55
|
+
public bool Supports(MediaKind kind) => kind == MediaKind.Video;
|
|
56
|
+
|
|
57
|
+
/// <inheritdoc />
|
|
58
|
+
public async Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
59
|
+
MediaRequest request,
|
|
60
|
+
MediaAliasBinding binding,
|
|
61
|
+
CancellationToken cancellationToken)
|
|
62
|
+
{
|
|
63
|
+
ArgumentNullException.ThrowIfNull(request);
|
|
64
|
+
ArgumentNullException.ThrowIfNull(binding);
|
|
65
|
+
|
|
66
|
+
var client = _clients.CreateGoogleClient(binding);
|
|
67
|
+
if (client is null)
|
|
68
|
+
{
|
|
69
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
70
|
+
MediaErrorCode.ProviderFailure,
|
|
71
|
+
$"alias '{binding.Alias}': a fabrica de clientes nao construiu um Google.GenAI.Client.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var config = new GenerateVideosConfig
|
|
75
|
+
{
|
|
76
|
+
DurationSeconds = request.Options.DurationSeconds,
|
|
77
|
+
AspectRatio = request.Options.AspectRatio,
|
|
78
|
+
NumberOfVideos = request.Options.Count,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (request.References.Count > 0)
|
|
82
|
+
{
|
|
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.
|
|
85
|
+
config.ReferenceImages =
|
|
86
|
+
[
|
|
87
|
+
.. request.References.Select(r => new VideoGenerationReferenceImage
|
|
88
|
+
{
|
|
89
|
+
Image = new Image { ImageBytes = r.Bytes.ToArray(), MimeType = r.MediaType },
|
|
90
|
+
}),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var operation = await client.Models
|
|
95
|
+
.GenerateVideosAsync(
|
|
96
|
+
binding.Model,
|
|
97
|
+
new GenerateVideosSource { Prompt = request.Prompt },
|
|
98
|
+
config,
|
|
99
|
+
cancellationToken)
|
|
100
|
+
.ConfigureAwait(false);
|
|
101
|
+
|
|
102
|
+
for (var poll = 0; operation.Done != true && poll < MaxPolls; poll++)
|
|
103
|
+
{
|
|
104
|
+
await _delay(PollInterval, cancellationToken).ConfigureAwait(false);
|
|
105
|
+
operation = await client.Operations
|
|
106
|
+
.GetAsync(operation, new GetOperationConfig(), cancellationToken)
|
|
107
|
+
.ConfigureAwait(false);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (operation.Done != true)
|
|
111
|
+
{
|
|
112
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
113
|
+
MediaErrorCode.ProviderFailure,
|
|
114
|
+
$"alias '{binding.Alias}': a operacao '{operation.Name}' nao concluiu em {MaxPolls} voltas. " +
|
|
115
|
+
"O id ficou gravado no job: retomar o polling nao paga de novo.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (operation.Error is { Count: > 0 })
|
|
119
|
+
{
|
|
120
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
121
|
+
MediaErrorCode.ProviderFailure,
|
|
122
|
+
$"alias '{binding.Alias}': a operacao terminou com erro: " +
|
|
123
|
+
string.Join("; ", operation.Error.Select(e => $"{e.Key}={e.Value}")));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var response = operation.Response;
|
|
127
|
+
|
|
128
|
+
if (response?.RaiMediaFilteredCount is > 0)
|
|
129
|
+
{
|
|
130
|
+
// Bloqueio de moderacao. No Veo ele NAO cobra — e nao vira retry, porque
|
|
131
|
+
// retentar recebe o mesmo "nao".
|
|
132
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
133
|
+
MediaErrorCode.PolicyBlocked,
|
|
134
|
+
$"alias '{binding.Alias}': {response.RaiMediaFilteredCount} saida(s) bloqueada(s) por politica: " +
|
|
135
|
+
string.Join("; ", response.RaiMediaFilteredReasons ?? []));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var video = response?.GeneratedVideos?.Select(g => g.Video).FirstOrDefault(v => v is not null);
|
|
139
|
+
|
|
140
|
+
if (video?.VideoBytes is not { Length: > 0 } bytes)
|
|
141
|
+
{
|
|
142
|
+
return MediaResult<MediaArtifact>.Fail(
|
|
143
|
+
MediaErrorCode.ProviderFailure,
|
|
144
|
+
$"alias '{binding.Alias}': a operacao concluiu sem bytes de video" +
|
|
145
|
+
(video?.Uri is { Length: > 0 } uri
|
|
146
|
+
? $" (so a URI '{uri}', que o Veo apaga em 2 dias). Baixe os bytes antes de marcar Generated."
|
|
147
|
+
: "."));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return MediaResult<MediaArtifact>.Ok(new MediaArtifact(
|
|
151
|
+
MediaKind.Video,
|
|
152
|
+
video.MimeType ?? "video/mp4",
|
|
153
|
+
bytes,
|
|
154
|
+
RevisedPrompt: null,
|
|
155
|
+
ResponseId: operation.Name,
|
|
156
|
+
C2paPreserved: true));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
namespace Morph.AiKit.Middleware;
|
|
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
|
+
public interface IUsageCostSink
|
|
25
|
+
{
|
|
26
|
+
/// <summary>Publica o custo de uma volta ao modelo.</summary>
|
|
27
|
+
/// <param name="record">O registro ja calculado. Nunca nulo.</param>
|
|
28
|
+
void Record(UsageCostRecord record);
|
|
29
|
+
}
|