@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.
- package/CLAUDE.md +20 -9
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +10 -9
- package/framework/agents.json +43 -26
- package/framework/docs/CLI.md +7 -5
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/hooks/README.md +1 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
- package/framework/hooks/claude-code/core/worktree-create.js +22 -6
- package/framework/hooks/shared/git-paths.js +38 -5
- package/framework/mcp/registry.json +58 -66
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -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 +59 -25
- 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 +48 -24
- package/framework/skills/core/morph-media/SKILL.md +2 -2
- package/framework/skills/core/morph-plan/SKILL.md +10 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- 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 +7 -6
- package/framework/standards/PROVENANCE.json +4 -4
- package/framework/standards/STANDARDS.json +9 -9
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/dotnet/testing.md +4 -2
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
- package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
- package/framework/standards/frontend/nextjs/testing.md +4 -2
- package/framework/standards/integration/mcp/mcp-tools.md +64 -126
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +12 -6
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/commands/project/worktree.js +60 -4
- package/src/commands/state/gate-check.js +5 -0
- package/src/commands/tasks/dag.js +27 -4
- package/src/commands/validation/verify.js +22 -38
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/risk-detector.js +13 -0
- package/src/lib/tasks/tasks-json-parser.js +21 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/lib/worktree/env-allocator.js +9 -3
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +108 -13
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
|
@@ -2,74 +2,33 @@ using System.Security.Cryptography;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Media;
|
|
4
4
|
|
|
5
|
-
/// <summary>Que tipo de midia o pedido produz. Decide provider, tarifa e limites.</summary>
|
|
6
5
|
public enum MediaKind
|
|
7
6
|
{
|
|
8
|
-
/// <summary>Imagem.</summary>
|
|
9
7
|
Image = 0,
|
|
10
8
|
|
|
11
|
-
/// <summary>Video.</summary>
|
|
12
9
|
Video = 1,
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
/// <summary>
|
|
16
|
-
/// Para que serve a midia pedida.
|
|
17
|
-
/// </summary>
|
|
18
|
-
/// <remarks>
|
|
19
|
-
/// <b>E dado, nao convencao de prompt.</b> A finalidade decide qualidade minima, teto de
|
|
20
|
-
/// custo, se a midia pode ser publicada e se a marcacao visivel e obrigatoria — decisoes que
|
|
21
|
-
/// o codigo precisa tomar. Deixa-las implicitas numa frase do prompt e como uma regra vira
|
|
22
|
-
/// folclore; as duas ultimas sao lidas por <see cref="MediaPurposeExtensions"/> e cobradas em
|
|
23
|
-
/// <see cref="MediaJob.Publish"/>.
|
|
24
|
-
/// </remarks>
|
|
25
12
|
public enum MediaPurpose
|
|
26
13
|
{
|
|
27
|
-
/// <summary>
|
|
28
|
-
/// Foto de catalogo. Revisao humana obrigatoria; qualidade alta na aprovada; marcacao
|
|
29
|
-
/// visivel obrigatoria para publicar — cobrada por <see cref="MediaJob.Publish"/>.
|
|
30
|
-
/// </summary>
|
|
31
14
|
CatalogPhoto = 0,
|
|
32
15
|
|
|
33
|
-
/// <summary>
|
|
34
|
-
/// Peca de marketing. Revisao humana obrigatoria; marcacao visivel obrigatoria — e a
|
|
35
|
-
/// obrigatoriedade e COBRADA por <see cref="MediaJob.Publish"/>, nao prometida aqui.
|
|
36
|
-
/// </summary>
|
|
37
16
|
Marketing = 1,
|
|
38
17
|
|
|
39
|
-
/// <summary>
|
|
40
|
-
/// Rascunho interno. <b>Nunca publicavel</b> — e <see cref="MediaJob.Publish"/> recusa,
|
|
41
|
-
/// em vez de deixar a frase valendo sozinha.
|
|
42
|
-
/// </summary>
|
|
43
18
|
Draft = 2,
|
|
44
19
|
}
|
|
45
20
|
|
|
46
|
-
/// <summary>Qualidade pedida. <c>null</c> em <see cref="MediaOptions"/> = nao envia ao provider.</summary>
|
|
47
21
|
public enum MediaQuality
|
|
48
22
|
{
|
|
49
|
-
/// <summary>Candidatas. A escada de custo comeca aqui, sempre.</summary>
|
|
50
23
|
Low = 0,
|
|
51
24
|
|
|
52
|
-
/// <summary>Meio-termo.</summary>
|
|
53
25
|
Medium = 1,
|
|
54
26
|
|
|
55
|
-
/// <summary>So na vencedora, depois da escolha humana.</summary>
|
|
56
27
|
High = 2,
|
|
57
28
|
}
|
|
58
29
|
|
|
59
|
-
/// <summary>
|
|
60
|
-
/// Uma imagem de referencia do pedido — a foto real do produto que a edicao preserva.
|
|
61
|
-
/// </summary>
|
|
62
|
-
/// <remarks>
|
|
63
|
-
/// <see cref="Sha256Hex"/> e calculado no construtor e entra na chave de idempotencia do
|
|
64
|
-
/// PEDIDO: trocar a foto de referencia <b>e</b> outro pedido, mesmo com o prompt identico.
|
|
65
|
-
/// Por isso o hash de CADA referencia participa, e nao a contagem delas.
|
|
66
|
-
/// </remarks>
|
|
67
30
|
public sealed class MediaReference
|
|
68
31
|
{
|
|
69
|
-
/// <summary>Cria a referencia e ja fixa o hash do conteudo.</summary>
|
|
70
|
-
/// <param name="name">Como o prompt se refere a ela ("image 1 = o sofa").</param>
|
|
71
|
-
/// <param name="mediaType">MIME dos bytes.</param>
|
|
72
|
-
/// <param name="bytes">O conteudo.</param>
|
|
73
32
|
public MediaReference(string name, string mediaType, ReadOnlyMemory<byte> bytes)
|
|
74
33
|
{
|
|
75
34
|
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
|
@@ -81,54 +40,36 @@ public sealed class MediaReference
|
|
|
81
40
|
Sha256Hex = Convert.ToHexStringLower(SHA256.HashData(bytes.Span));
|
|
82
41
|
}
|
|
83
42
|
|
|
84
|
-
/// <summary>Como o prompt indexa esta referencia.</summary>
|
|
85
43
|
public string Name { get; }
|
|
86
44
|
|
|
87
|
-
/// <summary>MIME dos bytes.</summary>
|
|
88
45
|
public string MediaType { get; }
|
|
89
46
|
|
|
90
|
-
/// <summary>O conteudo.</summary>
|
|
91
47
|
public ReadOnlyMemory<byte> Bytes { get; }
|
|
92
48
|
|
|
93
|
-
/// <summary>sha256 do conteudo, em hex minusculo.</summary>
|
|
94
49
|
public string Sha256Hex { get; }
|
|
95
50
|
}
|
|
96
51
|
|
|
97
|
-
|
|
98
|
-
/// Opcoes do pedido. <b><c>null</c> significa "nao envia ao provider"</b> — nunca "manda o
|
|
99
|
-
/// default do kit": inventar default aqui e afirmar sobre o provider o que o kit nao sabe.
|
|
100
|
-
/// </summary>
|
|
52
|
+
// null = nao envia ao provider; inventar default aqui afirmaria sobre o provider o que o kit nao sabe.
|
|
101
53
|
public sealed record MediaOptions
|
|
102
54
|
{
|
|
103
|
-
/// <summary>Nenhuma opcao declarada.</summary>
|
|
104
55
|
public static readonly MediaOptions None = new();
|
|
105
56
|
|
|
106
|
-
/// <summary>Qualidade pedida.</summary>
|
|
107
57
|
public MediaQuality? Quality { get; init; }
|
|
108
58
|
|
|
109
|
-
/// <summary>Quantas candidatas numa chamada. Cobra por unidade.</summary>
|
|
110
59
|
public int? Count { get; init; }
|
|
111
60
|
|
|
112
|
-
/// <summary>Largura em pixels.</summary>
|
|
113
61
|
public int? Width { get; init; }
|
|
114
62
|
|
|
115
|
-
/// <summary>Altura em pixels.</summary>
|
|
116
63
|
public int? Height { get; init; }
|
|
117
64
|
|
|
118
|
-
/// <summary>MIME de saida.</summary>
|
|
119
65
|
public string? MediaType { get; init; }
|
|
120
66
|
|
|
121
|
-
|
|
67
|
+
// O Veo aceita 4, 6 ou 8 s, e exige 8 s com referencias.
|
|
122
68
|
public int? DurationSeconds { get; init; }
|
|
123
69
|
|
|
124
|
-
/// <summary>Proporcao, para video ("16:9", "9:16").</summary>
|
|
125
70
|
public string? AspectRatio { get; init; }
|
|
126
71
|
|
|
127
|
-
|
|
128
|
-
/// Forma canonica das opcoes, para a chave de idempotencia. Ordem fixa e escrita a mao
|
|
129
|
-
/// de proposito: reflexao sobre as propriedades faria a chave mudar quando alguem
|
|
130
|
-
/// reordenasse o tipo, e uma chave que muda sozinha refaz a chamada paga.
|
|
131
|
-
/// </summary>
|
|
72
|
+
// Ordem fixa e escrita a mao: reordenar o tipo nao pode mudar a chave e refazer a chamada paga.
|
|
132
73
|
public string ToCanonicalString()
|
|
133
74
|
=> string.Join('|',
|
|
134
75
|
$"quality={Quality?.ToString() ?? "-"}",
|
|
@@ -140,27 +81,14 @@ public sealed record MediaOptions
|
|
|
140
81
|
$"ar={AspectRatio ?? "-"}");
|
|
141
82
|
}
|
|
142
83
|
|
|
143
|
-
/// <summary>
|
|
144
|
-
/// O pedido de midia: tudo o que decide a chamada paga, e nada mais.
|
|
145
|
-
/// </summary>
|
|
146
|
-
/// <param name="Alias">O apelido do <c>model-registry.v2.json</c>. O pedido nunca cita modelo.</param>
|
|
147
|
-
/// <param name="Prompt">O prompt final, ja montado pelo template versionado.</param>
|
|
148
|
-
/// <param name="Purpose">Para que serve. Ver <see cref="MediaPurpose"/>.</param>
|
|
149
84
|
public sealed record MediaRequest(string Alias, string Prompt, MediaPurpose Purpose)
|
|
150
85
|
{
|
|
151
|
-
/// <summary>Imagem ou video.</summary>
|
|
152
86
|
public MediaKind Kind { get; init; } = MediaKind.Image;
|
|
153
87
|
|
|
154
|
-
/// <summary>As referencias de produto. Vazio = geracao do zero.</summary>
|
|
155
88
|
public IReadOnlyList<MediaReference> References { get; init; } = [];
|
|
156
89
|
|
|
157
|
-
/// <summary>As opcoes. Ver <see cref="MediaOptions"/>.</summary>
|
|
158
90
|
public MediaOptions Options { get; init; } = MediaOptions.None;
|
|
159
91
|
|
|
160
|
-
/// <summary>
|
|
161
|
-
/// Recusa um pedido sem o minimo. Result, e nao excecao: pedido malformado vem de
|
|
162
|
-
/// entrada de usuario, que e caminho previsto.
|
|
163
|
-
/// </summary>
|
|
164
92
|
public MediaResult<MediaRequest> Validate()
|
|
165
93
|
{
|
|
166
94
|
if (string.IsNullOrWhiteSpace(Alias))
|
|
@@ -1,59 +1,24 @@
|
|
|
1
1
|
namespace Morph.AiKit.Media;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// Por que uma operacao de midia recusou. Discriminado por codigo, e nao por tipo de
|
|
5
|
-
/// excecao, porque quem trata precisa do MOTIVO — e porque recusa aqui e caminho previsto,
|
|
6
|
-
/// nunca fluxo de controle por excecao.
|
|
7
|
-
/// </summary>
|
|
8
3
|
public enum MediaErrorCode
|
|
9
4
|
{
|
|
10
|
-
/// <summary>O pedido nao tem o minimo para ser aceito (prompt vazio, alias vazio).</summary>
|
|
11
5
|
InvalidRequest = 0,
|
|
12
6
|
|
|
13
|
-
/// <summary>O teto de custo do tenant/dia recusaria a chamada. Recusa no ENQUEUE.</summary>
|
|
14
7
|
BudgetExceeded = 1,
|
|
15
8
|
|
|
16
|
-
/// <summary>Falha tecnica do provider (429, 5xx, timeout). Retryable.</summary>
|
|
17
9
|
ProviderFailure = 2,
|
|
18
10
|
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Bloqueio de moderacao do provider. <b>Terminal</b>: retentar gasta para receber o
|
|
21
|
-
/// mesmo "nao", e em provider que cobra o bloqueio gasta duas vezes.
|
|
22
|
-
/// </summary>
|
|
23
11
|
PolicyBlocked = 3,
|
|
24
12
|
|
|
25
|
-
/// <summary>A transicao pedida nao existe a partir do estado atual do job.</summary>
|
|
26
13
|
InvalidTransition = 4,
|
|
27
14
|
|
|
28
|
-
/// <summary>O job pedido nao existe no acervo do projeto.</summary>
|
|
29
15
|
NotFound = 5,
|
|
30
16
|
|
|
31
|
-
/// <summary>Nao ha tarifa declarada para o alias — o custo nao pode ser estimado.</summary>
|
|
32
17
|
PricingMissing = 6,
|
|
33
18
|
}
|
|
34
19
|
|
|
35
|
-
/// <summary>Uma recusa, com codigo e frase.</summary>
|
|
36
|
-
/// <param name="Code">O motivo, para quem decide.</param>
|
|
37
|
-
/// <param name="Message">A frase, para quem le o log.</param>
|
|
38
20
|
public sealed record MediaError(MediaErrorCode Code, string Message);
|
|
39
21
|
|
|
40
|
-
/// <summary>
|
|
41
|
-
/// Result do subsistema de midia.
|
|
42
|
-
/// </summary>
|
|
43
|
-
/// <remarks>
|
|
44
|
-
/// <para>
|
|
45
|
-
/// <b>Por que <c>MediaResult</c> e nao <c>Result</c>:</b> este kit nao tem — e nao ganha
|
|
46
|
-
/// aqui — um Result de proposito geral. Introduzir um tipo generico na raiz de
|
|
47
|
-
/// <c>Morph.AiKit</c> e uma decisao transversal, que afeta toda superficie futura do kit e
|
|
48
|
-
/// que uma feature de midia nao deve tomar de passagem. O escopo esta no nome.
|
|
49
|
-
/// </para>
|
|
50
|
-
/// <para>
|
|
51
|
-
/// Excecao continua existindo para <b>falha de composicao</b> (argumento nulo na
|
|
52
|
-
/// montagem), como na <c>AgentFactory</c>. O que nao existe e excecao para dizer "este job
|
|
53
|
-
/// nao pode ser publicado neste estado": isso e informacao, e informacao viaja em Result.
|
|
54
|
-
/// </para>
|
|
55
|
-
/// </remarks>
|
|
56
|
-
/// <typeparam name="T">O valor devolvido no sucesso.</typeparam>
|
|
57
22
|
public sealed record MediaResult<T>
|
|
58
23
|
{
|
|
59
24
|
private readonly T _value;
|
|
@@ -65,28 +30,21 @@ public sealed record MediaResult<T>
|
|
|
65
30
|
Error = error;
|
|
66
31
|
}
|
|
67
32
|
|
|
68
|
-
/// <summary>Se a operacao produziu valor.</summary>
|
|
69
33
|
public bool IsSuccess { get; }
|
|
70
34
|
|
|
71
|
-
/// <summary>A recusa, ou nulo quando <see cref="IsSuccess"/>.</summary>
|
|
72
35
|
public MediaError? Error { get; }
|
|
73
36
|
|
|
74
|
-
/// <summary>O valor. Ler no caminho de falha e erro de programacao, e grita.</summary>
|
|
75
|
-
/// <exception cref="InvalidOperationException">Quando a operacao falhou.</exception>
|
|
76
37
|
public T Value => IsSuccess
|
|
77
38
|
? _value
|
|
78
39
|
: throw new InvalidOperationException(
|
|
79
40
|
$"MediaResult<{typeof(T).Name}> em falha ({Error?.Code}): {Error?.Message}. " +
|
|
80
41
|
"Confira IsSuccess antes de ler Value.");
|
|
81
42
|
|
|
82
|
-
/// <summary>Sucesso com valor.</summary>
|
|
83
43
|
public static MediaResult<T> Ok(T value) => new(true, value, null);
|
|
84
44
|
|
|
85
|
-
/// <summary>Recusa nomeada.</summary>
|
|
86
45
|
public static MediaResult<T> Fail(MediaErrorCode code, string message)
|
|
87
46
|
=> new(false, default!, new MediaError(code, message));
|
|
88
47
|
|
|
89
|
-
/// <summary>Repassa a recusa de outro Result, preservando codigo e frase.</summary>
|
|
90
48
|
public static MediaResult<T> FailLike<TOther>(MediaResult<TOther> other)
|
|
91
49
|
=> other.Error is { } e
|
|
92
50
|
? new MediaResult<T>(false, default!, e)
|
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
namespace Morph.AiKit.Media;
|
|
2
2
|
|
|
3
|
-
/// <summary>O veredito de um humano sobre uma midia gerada.</summary>
|
|
4
3
|
public enum MediaReviewDecision
|
|
5
4
|
{
|
|
6
|
-
/// <summary>Aprovada. Unica decisao que leva a <see cref="MediaJobState.Approved"/>.</summary>
|
|
7
5
|
Approved = 0,
|
|
8
6
|
|
|
9
|
-
/// <summary>Recusada.</summary>
|
|
10
7
|
Rejected = 1,
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/// <summary>
|
|
14
|
-
/// Quem decidiu, o que decidiu, quando, e por que.
|
|
15
|
-
/// </summary>
|
|
16
|
-
/// <remarks>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <b>"Aprovado" sem quem e sem quando nao e revisao, e um campo booleano.</b> Por isso
|
|
19
|
-
/// <see cref="Reviewer"/> e <see cref="DecidedAt"/> sao obrigatorios na construcao, e a
|
|
20
|
-
/// construcao recusa um revisor em branco: um carimbo anonimo nao responde a pergunta que
|
|
21
|
-
/// alguem vai fazer seis meses depois.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// <b>Este tipo nao reimplementa as transicoes.</b> Elas moram no <see cref="MediaJob"/>,
|
|
25
|
-
/// que e o dono do estado; os metodos estaticos daqui sao a forma conveniente de aplicar
|
|
26
|
-
/// uma revisao, e delegam. Uma segunda copia da regra "so Approved publica" e exatamente
|
|
27
|
-
/// como as duas divergem.
|
|
28
|
-
/// </para>
|
|
29
|
-
/// <para>
|
|
30
|
-
/// Um juiz LLM pode preencher <see cref="Note"/> como pre-filtro, mas nao pode ser o
|
|
31
|
-
/// <see cref="Reviewer"/> de um <c>CatalogPhoto</c>: ele economiza o tempo do humano, nao
|
|
32
|
-
/// assume a responsabilidade por um claim visual.
|
|
33
|
-
/// </para>
|
|
34
|
-
/// </remarks>
|
|
35
10
|
public sealed record MediaReview
|
|
36
11
|
{
|
|
37
12
|
private MediaReview(string reviewer, MediaReviewDecision decision, DateTimeOffset decidedAt, string? note)
|
|
@@ -42,52 +17,38 @@ public sealed record MediaReview
|
|
|
42
17
|
Note = note;
|
|
43
18
|
}
|
|
44
19
|
|
|
45
|
-
/// <summary>Identificador do humano que decidiu.</summary>
|
|
46
20
|
public string Reviewer { get; }
|
|
47
21
|
|
|
48
|
-
/// <summary>O veredito.</summary>
|
|
49
22
|
public MediaReviewDecision Decision { get; }
|
|
50
23
|
|
|
51
|
-
/// <summary>Quando.</summary>
|
|
52
24
|
public DateTimeOffset DecidedAt { get; }
|
|
53
25
|
|
|
54
|
-
/// <summary>Observacao livre — inclusive o veredito de um juiz LLM usado como pre-filtro.</summary>
|
|
55
26
|
public string? Note { get; }
|
|
56
27
|
|
|
57
|
-
/// <summary>Uma aprovacao humana.</summary>
|
|
58
|
-
/// <exception cref="ArgumentException">Revisor em branco.</exception>
|
|
59
28
|
public static MediaReview ApprovedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
|
|
60
29
|
{
|
|
61
30
|
ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
|
|
62
31
|
return new MediaReview(reviewer, MediaReviewDecision.Approved, decidedAt, note);
|
|
63
32
|
}
|
|
64
33
|
|
|
65
|
-
/// <summary>Uma recusa humana.</summary>
|
|
66
|
-
/// <exception cref="ArgumentException">Revisor em branco.</exception>
|
|
67
34
|
public static MediaReview RejectedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
|
|
68
35
|
{
|
|
69
36
|
ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
|
|
70
37
|
return new MediaReview(reviewer, MediaReviewDecision.Rejected, decidedAt, note);
|
|
71
38
|
}
|
|
72
39
|
|
|
73
|
-
/// <summary>Aprova o job. Delega a <see cref="MediaJob.Approve"/> — nao reimplementa a regra.</summary>
|
|
74
40
|
public static MediaResult<MediaJob> Approve(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
|
|
75
41
|
{
|
|
76
42
|
ArgumentNullException.ThrowIfNull(job);
|
|
77
43
|
return job.Approve(ApprovedBy(reviewer, decidedAt, note));
|
|
78
44
|
}
|
|
79
45
|
|
|
80
|
-
/// <summary>Recusa o job. Delega a <see cref="MediaJob.Reject"/>.</summary>
|
|
81
46
|
public static MediaResult<MediaJob> Reject(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
|
|
82
47
|
{
|
|
83
48
|
ArgumentNullException.ThrowIfNull(job);
|
|
84
49
|
return job.Reject(RejectedBy(reviewer, decidedAt, note));
|
|
85
50
|
}
|
|
86
51
|
|
|
87
|
-
/// <summary>
|
|
88
|
-
/// Publica o job. Delega a <see cref="MediaJob.Publish"/>, que cobra as tres portas
|
|
89
|
-
/// (estado Approved, finalidade publicavel, marcacao quando exigida).
|
|
90
|
-
/// </summary>
|
|
91
52
|
public static MediaResult<MediaJob> Publish(
|
|
92
53
|
MediaJob job,
|
|
93
54
|
MediaDisclosure? disclosure,
|
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
namespace Morph.AiKit.Media;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// A resolucao de um alias de midia: para quem ir, com que modelo, por qual tarifa.
|
|
5
|
-
/// </summary>
|
|
6
|
-
/// <remarks>
|
|
7
|
-
/// O pedido cita o <b>alias</b>; a chave de idempotencia e a proveniencia citam o
|
|
8
|
-
/// <b>modelo</b>. A diferenca importa: um alias pode ser repontado para outro modelo, e
|
|
9
|
-
/// duas geracoes com o mesmo alias e modelos diferentes nao sao o mesmo pedido.
|
|
10
|
-
/// </remarks>
|
|
11
|
-
/// <param name="Alias">O apelido citado pelo pedido.</param>
|
|
12
|
-
/// <param name="Provider">Familia do SDK.</param>
|
|
13
|
-
/// <param name="Model">O id do modelo no provider.</param>
|
|
14
|
-
/// <param name="Kind">Imagem ou video.</param>
|
|
15
|
-
/// <param name="Pricing">A tarifa, na unidade certa. Ver <see cref="MediaPricing"/>.</param>
|
|
16
3
|
public sealed record MediaAliasBinding(
|
|
17
4
|
string Alias,
|
|
18
5
|
string Provider,
|
|
@@ -20,78 +7,37 @@ public sealed record MediaAliasBinding(
|
|
|
20
7
|
MediaKind Kind,
|
|
21
8
|
MediaPricing Pricing);
|
|
22
9
|
|
|
23
|
-
/// <summary>Resolve um alias de midia. Alias desconhecido e recusa nomeada, nunca nulo.</summary>
|
|
24
10
|
public interface IMediaAliasResolver
|
|
25
11
|
{
|
|
26
|
-
/// <summary>O binding do alias.</summary>
|
|
27
12
|
MediaResult<MediaAliasBinding> Resolve(string alias);
|
|
28
13
|
}
|
|
29
14
|
|
|
30
|
-
/// <summary>
|
|
31
|
-
/// Onde os jobs ficam guardados. <b>A costura entre o kit e o banco do projeto.</b>
|
|
32
|
-
/// </summary>
|
|
33
|
-
/// <remarks>
|
|
34
|
-
/// Sem tipo de infraestrutura na assinatura, pela mesma razao de
|
|
35
|
-
/// <c>IConversationStore</c>: o kit nao conhece banco. No projeto, o handler fala com o
|
|
36
|
-
/// <c>ApplicationDbContext</c> direto — sem camada de service, sem repository generico.
|
|
37
|
-
/// Dentro de um job, <c>IDbContextFactory</c>, como o standard de Hangfire ja exige.
|
|
38
|
-
/// </remarks>
|
|
39
15
|
public interface IMediaJobRepository
|
|
40
16
|
{
|
|
41
|
-
/// <summary>
|
|
42
|
-
/// O job de mesma chave de PEDIDO, se existir. <b>E a consulta que evita pagar duas
|
|
43
|
-
/// vezes</b>, e por isso ela vem antes de qualquer chamada ao provider.
|
|
44
|
-
/// </summary>
|
|
45
17
|
ValueTask<MediaJob?> FindByIdempotencyKeyAsync(MediaIdempotencyKey key, CancellationToken cancellationToken);
|
|
46
18
|
|
|
47
|
-
/// <summary>O job por id, ou nulo.</summary>
|
|
48
19
|
ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken);
|
|
49
20
|
|
|
50
|
-
/// <summary>Grava o estado corrente do job.</summary>
|
|
51
21
|
ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken);
|
|
52
22
|
}
|
|
53
23
|
|
|
54
|
-
/// <summary>
|
|
55
|
-
/// O teto de custo por tenant/dia.
|
|
56
|
-
/// </summary>
|
|
57
|
-
/// <remarks>
|
|
58
|
-
/// <b>A reserva acontece no ENQUEUE</b>, com o custo estimado ANTES da chamada. Um alerta
|
|
59
|
-
/// que chega depois da chamada e contabilidade, nao controle: o dinheiro ja saiu.
|
|
60
|
-
/// </remarks>
|
|
61
24
|
public interface IMediaBudget
|
|
62
25
|
{
|
|
63
|
-
|
|
64
|
-
/// Reserva o custo estimado. <c>false</c> = estourou o teto, e o enqueue recusa com
|
|
65
|
-
/// <see cref="MediaErrorCode.BudgetExceeded"/>.
|
|
66
|
-
/// </summary>
|
|
26
|
+
// Reservado no ENQUEUE, antes da chamada: alerta depois dela e contabilidade, nao controle.
|
|
67
27
|
ValueTask<bool> TryReserveAsync(MediaRequest request, decimal estimatedCostUsd, CancellationToken cancellationToken);
|
|
68
28
|
}
|
|
69
29
|
|
|
70
|
-
/// <summary>
|
|
71
|
-
/// Executa UM job: chama o provider, guarda os bytes, move o estado.
|
|
72
|
-
/// </summary>
|
|
73
|
-
/// <remarks>
|
|
74
|
-
/// A interface e publica para que o <c>BackgroundService</c> do projeto (ou o job Hangfire)
|
|
75
|
-
/// possa dispara-la; a <b>implementacao</b> e <c>internal</c>, porque e ela que segura o
|
|
76
|
-
/// <c>IMediaProvider</c>. Um handler pode pedir "rode este job"; nao pode pedir "gere estes
|
|
77
|
-
/// bytes agora".
|
|
78
|
-
/// </remarks>
|
|
79
30
|
public interface IMediaJobRunner
|
|
80
31
|
{
|
|
81
|
-
/// <summary>Roda o job ate o proximo estado estavel e devolve como ele ficou.</summary>
|
|
82
32
|
Task<MediaResult<MediaJob>> RunAsync(MediaJobId id, CancellationToken cancellationToken = default);
|
|
83
33
|
}
|
|
84
34
|
|
|
85
|
-
/// <summary>Relogio, para que o carimbo de tempo seja testavel sem esperar o mundo girar.</summary>
|
|
86
35
|
public interface IMediaClock
|
|
87
36
|
{
|
|
88
|
-
/// <summary>Agora.</summary>
|
|
89
37
|
DateTimeOffset UtcNow { get; }
|
|
90
38
|
}
|
|
91
39
|
|
|
92
|
-
/// <summary>O relogio do sistema.</summary>
|
|
93
40
|
public sealed class SystemMediaClock : IMediaClock
|
|
94
41
|
{
|
|
95
|
-
/// <inheritdoc />
|
|
96
42
|
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
|
|
97
43
|
}
|
|
@@ -2,98 +2,43 @@ using System.Security.Cryptography;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Media;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
/// O endereco de um conteudo: sha256 dos BYTES.
|
|
7
|
-
/// </summary>
|
|
8
|
-
/// <remarks>
|
|
9
|
-
/// Responde "ja guardamos isto?" — nunca "ja pagamos por isto?", que e a pergunta do
|
|
10
|
-
/// <see cref="MediaIdempotencyKey"/>. Confundir as duas e o defeito caro: dois pedidos
|
|
11
|
-
/// diferentes podem produzir bytes identicos (e devem compartilhar UM arquivo), e o mesmo
|
|
12
|
-
/// pedido nunca deve ser pago duas vezes (mesmo que os bytes saiam diferentes).
|
|
13
|
-
/// </remarks>
|
|
14
|
-
/// <param name="Value">sha256 em hex minusculo.</param>
|
|
5
|
+
// Endereca BYTES ("ja guardamos?"), nunca o pedido ("ja pagamos?"), que e o MediaIdempotencyKey.
|
|
15
6
|
public readonly record struct MediaHash(string Value)
|
|
16
7
|
{
|
|
17
|
-
/// <summary>O hash do conteudo.</summary>
|
|
18
8
|
public static MediaHash Of(ReadOnlySpan<byte> bytes)
|
|
19
9
|
=> new(Convert.ToHexStringLower(SHA256.HashData(bytes)));
|
|
20
10
|
|
|
21
|
-
/// <summary>O hash, para log e para o caminho no blob.</summary>
|
|
22
11
|
public override string ToString() => Value;
|
|
23
12
|
}
|
|
24
13
|
|
|
25
|
-
/// <summary>
|
|
26
|
-
/// Onde os bytes ficam guardados. <b>A costura entre o kit e o blob do projeto</b> — o
|
|
27
|
-
/// unico ponto do <see cref="MediaStore"/> que um projeto precisa implementar.
|
|
28
|
-
/// </summary>
|
|
29
|
-
/// <remarks>
|
|
30
|
-
/// Deliberadamente sem tipo de infraestrutura na assinatura: nenhum <c>BlobClient</c>,
|
|
31
|
-
/// nenhum <c>S3</c>, nenhum caminho absoluto. A implementacao de producao vive no projeto;
|
|
32
|
-
/// a de teste vive nos fakes da suite. O kit nao conhece storage, pela mesma razao pela
|
|
33
|
-
/// qual <c>IConversationStore</c> nao conhece banco.
|
|
34
|
-
/// </remarks>
|
|
35
14
|
public interface IMediaBlobs
|
|
36
15
|
{
|
|
37
|
-
/// <summary>Se o caminho relativo ja existe. Nunca lanca por ausencia.</summary>
|
|
38
16
|
ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken);
|
|
39
17
|
|
|
40
|
-
/// <summary>Grava o conteudo no caminho relativo.</summary>
|
|
41
18
|
ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken);
|
|
42
19
|
}
|
|
43
20
|
|
|
44
|
-
|
|
45
|
-
/// <param name="Hash">sha256 dos bytes.</param>
|
|
46
|
-
/// <param name="RelativePath">
|
|
47
|
-
/// O caminho RELATIVO, que e o que o banco guarda. Caminho absoluto ou URL de provider
|
|
48
|
-
/// amarram a linha ao disco de hoje ou a uma URL que expira — o Veo apaga o arquivo em 2
|
|
49
|
-
/// dias, e todo agregador devolve URL temporaria.
|
|
50
|
-
/// </param>
|
|
51
|
-
/// <param name="MediaType">MIME.</param>
|
|
52
|
-
/// <param name="AlreadyExisted">
|
|
53
|
-
/// Se o conteudo ja estava la. <b>Nao e enfeite:</b> e como o chamador sabe que a gravacao
|
|
54
|
-
/// foi deduplicada em vez de repetida.
|
|
55
|
-
/// </param>
|
|
21
|
+
// Caminho RELATIVO, nunca URL de provider: o Veo apaga o arquivo em 2 dias e agregadores devolvem URL temporaria.
|
|
56
22
|
public sealed record MediaStoreEntry(MediaHash Hash, string RelativePath, string MediaType, bool AlreadyExisted);
|
|
57
23
|
|
|
58
|
-
/// <summary>Storage de midia enderecado por conteudo.</summary>
|
|
59
24
|
public interface IMediaStore
|
|
60
25
|
{
|
|
61
|
-
/// <summary>Guarda os bytes e devolve o endereco. Conteudo repetido NAO e regravado.</summary>
|
|
62
26
|
ValueTask<MediaStoreEntry> PutAsync(
|
|
63
27
|
ReadOnlyMemory<byte> bytes,
|
|
64
28
|
string mediaType,
|
|
65
29
|
CancellationToken cancellationToken);
|
|
66
30
|
}
|
|
67
31
|
|
|
68
|
-
/// <summary>
|
|
69
|
-
/// Storage enderecado por conteudo: o caminho e derivado do sha256, e gravar o mesmo
|
|
70
|
-
/// conteudo duas vezes grava uma.
|
|
71
|
-
/// </summary>
|
|
72
|
-
/// <remarks>
|
|
73
|
-
/// <para>
|
|
74
|
-
/// O caminho e <c>ab/cd/<hash>.<ext></c>. Os dois niveis de prefixo existem
|
|
75
|
-
/// porque um diretorio unico com centenas de milhares de arquivos degrada listagem em
|
|
76
|
-
/// praticamente todo sistema de arquivos e em varios object stores.
|
|
77
|
-
/// </para>
|
|
78
|
-
/// <para>
|
|
79
|
-
/// A extensao vem do MIME e e o unico lugar do subsistema que faz esse mapa. Um MIME
|
|
80
|
-
/// desconhecido vira <c>.bin</c> em vez de recusa: perder a extensao e cosmetico, perder os
|
|
81
|
-
/// bytes pelos quais se pagou nao e.
|
|
82
|
-
/// </para>
|
|
83
|
-
/// </remarks>
|
|
84
32
|
public sealed class MediaStore : IMediaStore
|
|
85
33
|
{
|
|
86
34
|
private readonly IMediaBlobs _blobs;
|
|
87
35
|
|
|
88
|
-
/// <summary>Cria o storage sobre o blob do projeto.</summary>
|
|
89
|
-
/// <exception cref="ArgumentNullException">Blob nulo.</exception>
|
|
90
36
|
public MediaStore(IMediaBlobs blobs)
|
|
91
37
|
{
|
|
92
38
|
ArgumentNullException.ThrowIfNull(blobs);
|
|
93
39
|
_blobs = blobs;
|
|
94
40
|
}
|
|
95
41
|
|
|
96
|
-
/// <inheritdoc />
|
|
97
42
|
public async ValueTask<MediaStoreEntry> PutAsync(
|
|
98
43
|
ReadOnlyMemory<byte> bytes,
|
|
99
44
|
string mediaType,
|
|
@@ -113,14 +58,13 @@ public sealed class MediaStore : IMediaStore
|
|
|
113
58
|
return new MediaStoreEntry(hash, path, mediaType, AlreadyExisted: false);
|
|
114
59
|
}
|
|
115
60
|
|
|
116
|
-
|
|
61
|
+
// Dois niveis de prefixo: um diretorio com centenas de milhares de arquivos degrada a listagem.
|
|
117
62
|
public static string RelativePathFor(MediaHash hash, string mediaType)
|
|
118
63
|
{
|
|
119
64
|
var value = hash.Value;
|
|
120
65
|
return $"{value[..2]}/{value[2..4]}/{value}{ExtensionFor(mediaType)}";
|
|
121
66
|
}
|
|
122
67
|
|
|
123
|
-
/// <summary>MIME para extensao de arquivo. Desconhecido vira <c>.bin</c>, nunca recusa.</summary>
|
|
124
68
|
public static string ExtensionFor(string mediaType) => mediaType.ToLowerInvariant() switch
|
|
125
69
|
{
|
|
126
70
|
"image/png" => ".png",
|
|
@@ -1,65 +1,22 @@
|
|
|
1
1
|
namespace Morph.AiKit.Media;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// As pecas que o projeto precisa fornecer para o subsistema de midia funcionar.
|
|
5
|
-
/// </summary>
|
|
6
|
-
/// <remarks>
|
|
7
|
-
/// Nenhuma delas produz bytes. Repare no que NAO ha aqui: nao ha como o projeto injetar,
|
|
8
|
-
/// substituir ou alcancar um <c>IMediaProvider</c> — os adaptadores sao montados dentro do
|
|
9
|
-
/// assembly, por <see cref="MorphMedia"/>.
|
|
10
|
-
/// </remarks>
|
|
11
3
|
public sealed record MorphMediaOptions
|
|
12
4
|
{
|
|
13
|
-
/// <summary>Resolve alias para provider/modelo/tarifa. Obrigatorio.</summary>
|
|
14
5
|
public required IMediaAliasResolver AliasResolver { get; init; }
|
|
15
6
|
|
|
16
|
-
/// <summary>Onde os jobs vivem. Obrigatorio.</summary>
|
|
17
7
|
public required IMediaJobRepository Jobs { get; init; }
|
|
18
8
|
|
|
19
|
-
/// <summary>Onde os bytes vivem. Obrigatorio.</summary>
|
|
20
9
|
public required IMediaStore Store { get; init; }
|
|
21
10
|
|
|
22
|
-
/// <summary>
|
|
23
|
-
/// Constroi os clientes de SDK. Obrigatorio — e a costura com o provider de verdade.
|
|
24
|
-
/// </summary>
|
|
25
11
|
public required IMediaClientFactory Clients { get; init; }
|
|
26
12
|
|
|
27
|
-
/// <summary>O teto de custo. Nulo = sem teto (aceitavel so em ambiente de desenvolvimento).</summary>
|
|
28
13
|
public IMediaBudget? Budget { get; init; }
|
|
29
14
|
|
|
30
|
-
/// <summary>O relogio. Nulo = relogio do sistema.</summary>
|
|
31
15
|
public IMediaClock? Clock { get; init; }
|
|
32
16
|
}
|
|
33
17
|
|
|
34
|
-
/// <summary>
|
|
35
|
-
/// A raiz de composicao do subsistema de midia.
|
|
36
|
-
/// </summary>
|
|
37
|
-
/// <remarks>
|
|
38
|
-
/// <para>
|
|
39
|
-
/// <b>Por que uma fabrica estatica e nao um <c>AddMorphMedia(IServiceCollection)</c>:</b>
|
|
40
|
-
/// uma extensao de DI exigiria uma <c>PackageReference</c> explicita a
|
|
41
|
-
/// <c>Microsoft.Extensions.DependencyInjection.Abstractions</c>, e neste repositorio as
|
|
42
|
-
/// versoes de pacote sao GERADAS de <c>framework/ai-pin.json</c>. Acrescentar um pacote e
|
|
43
|
-
/// mexer no pin — uma decisao que se toma de proposito, com re-verificacao, e nao de
|
|
44
|
-
/// passagem numa feature de midia. Depender do pacote de forma TRANSITIVA seria pior: uma
|
|
45
|
-
/// dependencia escondida, que some no dia em que a arvore de dependencias mudar.
|
|
46
|
-
/// </para>
|
|
47
|
-
/// <para>
|
|
48
|
-
/// No projeto consumidor, o registro e uma linha, e o kit continua sem conhecer DI:
|
|
49
|
-
/// </para>
|
|
50
|
-
/// <code>
|
|
51
|
-
/// var media = MorphMedia.Create(options);
|
|
52
|
-
/// builder.Services.AddSingleton<IMediaGenerator>(media.Generator);
|
|
53
|
-
/// builder.Services.AddSingleton<IMediaJobRunner>(media.Runner);
|
|
54
|
-
/// </code>
|
|
55
|
-
/// </remarks>
|
|
56
18
|
public static class MorphMedia
|
|
57
19
|
{
|
|
58
|
-
/// <summary>
|
|
59
|
-
/// Monta o subsistema: o gerador publico e o executor de job, ambos sobre os mesmos
|
|
60
|
-
/// adaptadores internos.
|
|
61
|
-
/// </summary>
|
|
62
|
-
/// <exception cref="ArgumentNullException">Opcoes nulas.</exception>
|
|
63
20
|
public static MorphMediaComponents Create(MorphMediaOptions options)
|
|
64
21
|
{
|
|
65
22
|
ArgumentNullException.ThrowIfNull(options);
|
|
@@ -87,7 +44,4 @@ public static class MorphMedia
|
|
|
87
44
|
}
|
|
88
45
|
}
|
|
89
46
|
|
|
90
|
-
/// <summary>O que a raiz de composicao devolve. Duas interfaces, nenhuma delas com bytes.</summary>
|
|
91
|
-
/// <param name="Generator">O ponto de entrada do pedido.</param>
|
|
92
|
-
/// <param name="Runner">O executor de job, para o <c>BackgroundService</c> do projeto.</param>
|
|
93
47
|
public sealed record MorphMediaComponents(IMediaGenerator Generator, IMediaJobRunner Runner);
|