Conversation
Implements step 1 of issue #223: - Add PromptRequest and ImagePromptRequest sealed records - Add PromptRole enum (Summary, ImagePromptDerivation, ImageGeneration) - Add PromptStepOptions sealed record - Add FeedPromptOptions sealed record with GetStep(PromptRole) helper - Add FeedOrchestratorContext sealed record (step 4 prerequisite)
…d FeedOrchestratorContext (#223) - Replace IFeedUrlProvider with FeedOrchestratorContext injection - Replace GetSummaryAsync/GetImagePromptAsync with GenerateTextAsync(PromptRequest) - Replace GenerateImageAsync(string) with GenerateImagesAsync(ImagePromptRequest) - Add FeedOrchestratorContext builder helper (BuildContext) - Add tests for FeedOrchestratorContext injection (two-slot isolation) - Align all existing fan-out, re-summarisation and failure tests to new contracts
…st value objects (issue #223) - Replace GetSummaryAsync/GetImagePromptAsync/GenerateImageAsync string-based calls with GenerateTextAsync(PromptRequest) and GenerateImageAsync(ImagePromptRequest) - Remove short-circuit test that relied on pre-provider length check (now orchestrator concern) - Remove retry-loop test (GetSummaryAsync): loop is preserved in GenerateTextAsync, add corresponding test with PromptRequest.MaxOutputLength - Add BuildPromptRequest / BuildImagePromptRequest test helpers for readability - Cover: 200 success, 429, 5xx, empty choices, null choices, empty/whitespace prompt, malformed JSON, HttpRequestException, retry cap at 3 attempts
…extAsync Replaces obsolete GetSummaryAsync/GetImagePromptAsync call-sites with GenerateTextAsync(PromptRequest, CancellationToken) to align with the new provider contract introduced in issue #223. - BuildService no longer sets prompt-related fields on DeepSeekOptions - All test cases now construct PromptRequest value objects and pass them to GenerateTextAsync - Re-summarisation retry loop tested via MaxOutputLength constraint - CancellationToken propagation tested - Prompt field substitution (InputTextLabel, templates) tested - Options fields removed from tests: SummarySystemPromptTemplate, SummaryUserPromptTemplate, ImagePromptSystemTemplate, ImagePromptUserTemplate
…#223) - Replace GetSummaryAsync / GetImagePromptAsync calls with GenerateTextAsync(PromptRequest) - Remove prompt template fields from PerplexityOptions setup (now owned by orchestrator) - Add BuildPromptRequest helper to reduce duplication - Cover retry-loop and error-path scenarios via PromptRequest.MaxOutputLength
…223) Replace raw string prompt arguments with ImagePromptRequest value objects across all test cases. All calls to GenerateImageAsync now pass a fully constructed ImagePromptRequest with the required InputText, SystemPromptTemplate, and UserPromptTemplate properties, aligned with the new ITextToImageProvider contract introduced in issue #223.
…tRequest value objects Refs #223 - Replace GetSummaryAsync(string, int)/GetImagePromptAsync(string)/GenerateImageAsync(string) calls with GenerateTextAsync(PromptRequest)/GenerateImageAsync(ImagePromptRequest). Verify prompt fields are read from the request object, not from provider options.
…xtKey (#223) - Pass orchestratorContextKey as first constructor argument in all existing tests - Add test: OrchestratorContextKey is set when provided - Add test: OrchestratorContextKey is null when omitted - Add test: two slots with same OrchestratorType carry independent context keys
…t value objects (#223)
…ofile orchestratorContextKey parameter
…enderPlatforms order is irrelevant
…-value-objects feat(models): introduce PromptRequest / ImagePromptRequest value objects (#223)
- Add AiServiceHelper.BuildChatPayload(string text, PromptRequest request, string modelName) - Remove private BuildChatPayload from AzureFoundryService, OpenAiService, DeepSeekService, PerplexityService - Delegate all four providers to AiServiceHelper.BuildChatPayload - Add unit tests for AiServiceHelper.BuildChatPayload covering all acceptance criteria - Update AiServiceHelper XML doc comment
…ithub.com/artcava/XPoster into feature/244-centralize-build-chat-payload
…ithub.com/artcava/XPoster into feature/244-centralize-build-chat-payload
…t-payload Centralize BuildChatPayload in AiServiceHelper
- Add AiModelClass enum (Contracts/Enums) for Text and Image capability keys - Add AiModelCatalog value type (Models/) with TryGet, Supports, GetRequired helpers - Add IAiProviderOptions interface (Contracts/Interfaces) exposing ApiKey, Endpoint, ModelCatalog - Implement IAiProviderOptions on all five provider options classes (OpenAiOptions, AzureFoundryOptions, PerplexityOptions, DeepSeekOptions, FalAiOptions) - ModelCatalog computed from existing concrete properties (backward-compatible) - Add AiProviderValidationHelper internal helper to centralize shared connectivity checks - Refactor all five *OptionsValidator classes to call the shared helper - Add AiProviderOptionsCompositionExtensions with AddAiProviderOptions() single entrypoint - Update Program.cs to use AddAiProviderOptions() instead of five separate calls - Add AiModelCatalogTests (11 tests), AiProviderOptionsAbstractionTests (12 tests), AddAiProviderOptionsTests (9 tests) — 32 new tests, all passing - Full suite: 744/744 passed, 0 warnings
…r-options feat(#248): normalize AI provider options behind shared abstraction
This PR refactors the sender resolution mechanism in `OrchestratorFactory` to leverage .NET 8's keyed dependency injection services instead of resolving concrete sender types via `GetService`. ### Key changes - **Sender registration**: switched from `AddTransient<ConcreteSender>()` to `AddKeyedTransient<ISender, ConcreteSender>(SenderPlatform.XXX)` in `SenderPluginsServiceCollectionExtensions` - **Sender resolution**: replaced the `switch` expression with `GetKeyedService<ISender>(platform)` in `OrchestratorFactory.ResolveSender` - **Test suite**: updated all `OrchestratorFactoryTests` to verify `GetKeyedService` calls instead of `GetService` on concrete types ## Motivation ### Why this is better - **Cleaner design**: the relationship between platform enum and implementation is now explicitly declared at registration time, making the intent clearer - **Better type safety**: `GetKeyedService<ISender>(platform)` returns the correct interface directly, eliminating unsafe casting - **Reduced coupling**: `OrchestratorFactory` no longer needs to know about concrete sender implementations (`XSender`, `InSender`, etc.) - **More maintainable**: adding a new sender platform requires only one line in the extension method — no changes to the factory - **Idiomatic .NET 8**: keyed services are the recommended approach for this kind of platform-based resolution - **Better testability**: tests can mock specific platform senders using `AddKeyedScoped` or verify keyed service resolution ### Comparison | Aspect | Before | After | |--------|--------|-------| | Registration | `AddTransient<XSender>()` | `AddKeyedTransient<ISender, XSender>(SenderPlatform.X)` | | Resolution | `switch` with concrete types | `GetKeyedService<ISender>(platform)` | | Factory knows | All concrete sender types | Only `ISender` interface | | New sender | Modify factory switch | Add one registration line | ## Testing - All 744 existing tests passing (including 737 previously passing + 7 fixed) - Updated sender resolution tests to verify keyed service calls - Verified multi-platform profiles resolve all senders correctly - Confirmed AI provider keyed resolution remains unchanged ## Breaking changes None. This is an internal refactoring that maintains the same public API and behavior. The only changes are to DI registration and internal resolution logic. ## Migration notes If you have custom tests or extensions that rely on resolving senders directly, update them to use keyed service resolution: ```csharp // Before serviceProvider.GetService<XSender>(); // After serviceProvider.GetKeyedService<ISender>(SenderPlatform.X);
…behaviour Refactor: replace transient sender registrations with keyed DI services
…servicecollections Docs updates
chore: bump version to v0.2.0 and promote CHANGELOG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Added
PromptRequestandImagePromptRequestrecords to carry prompt data from orchestrators to providers, making prompt intent a first-class concept decoupled from provider configuration.FeedOrchestrator(#223): addedPromptRole,PromptStepOptions, andFeedPromptOptionsto model an ordered collection of prompt steps (Summary,ImagePromptDerivation,ImageGeneration) resolved by the orchestrator for each phase of the feed pipeline.FeedOrchestratorContext(#223): new value object carryingFeedUrlsandFeedPromptOptionsper scheduled slot, enabling multipleFeedOrchestratorslots to run with independent feed sources and prompt configurations.ScheduledOrchestrationProfile.OrchestratorContextKey(#223): added a logical context key used at runtime to resolve the appropriateFeedOrchestratorContextfor each slot.Changed
SenderPluginDI optimized with Keyed registrationAiProviderOptionsoptimized in a single Extension methodBuildChatPayloadcentralized inAiServiceHelper(#244): eliminated identical privateBuildChatPayloadimplementations acrossAzureFoundryService,OpenAiService,DeepSeekService, andPerplexityService; a singleinternal static object BuildChatPayload(string text, PromptRequest request, string modelName)method now lives inAiServiceHelper, with each provider delegating to it and passing its provider-specific model name.PromptRequest/ImagePromptRequest) instead of reading prompt fields from provider options.FeedOrchestratorrefactored for fan-out with slot context (#223): the orchestrator uses role-keyed prompt steps, generates the base summary once for the widest sender, reuses theSummarystep for re-summarisation of narrower senders, and shares a single image generation across all configured senders in the slot.FeedSlotContexts__{key}sections (#223): feed URLs and prompt options are now defined per logical slot context, replacing the previous global configuration and enabling per-slot tuning.OrchestratorFactoryupdated to resolve keyed context (#223): the factory now selects the correctFeedOrchestratorContextbased onOrchestratorContextKeywhile preserving centralised orchestrator selection and existing reflection-based instantiation.MaskUrlTelemetryInitializerwithMaskUrlTelemetryProcessorto maskaccess_tokenin Facebook Graph API HTTP dependenciesRemoved
FeedOrchestrator(#223): the previous single shared feed URL list and prompt configuration for allFeedOrchestratorslots has been removed in favour of per-slot contexts.Close #169, #223, #244, #248