# OpenSpec Portable Variant ## For hand-typing into target's OpenSpec on the sandbox Type these two files into the target's `openspec/changes/add-sk-chat/` directory. Then run `/opsx:apply` and let the AI implement from the tasks. **Lines to type**: ~25 | **vs code recipe**: ~120 lines | **Compression**: 4.8x --- ## File 1: proposal.md ```markdown # Add SK Chat Endpoint with Tool Calling ## Why Need an AI chat endpoint that streams responses and supports autonomous tool calling for structured data extraction/validation. ## What Changes - Add Semantic Kernel 1.74.0 with OpenAI connector - POST /api/chat endpoint streaming SSE via SK - ExtractionPlugin with [KernelFunction] for field validation - Shared models: ChatRequest, ChatMessage, ExtractedFields, ValidationResult ## Impact - API project: new controller, plugin, DI wiring - Shared project: new models - Config: ResponsesApi section in appsettings.json ``` ## File 2: tasks.md ```markdown ## 1. Shared Models - [ ] 1.1 Create ChatMessage (Role string, Content string, Timestamp DateTime) - [ ] 1.2 Create ChatRequest (Messages List) - [ ] 1.3 Create ExtractedFields with required fields: Client, Project, Hours(decimal?), Rate(decimal?), Currency, Date; optional: Description, PoNumber - [ ] 1.4 Create ValidationResult (IsValid bool, Errors List) ## 2. Extraction Plugin - [ ] 2.1 Create ExtractionPlugin class with [KernelFunction("validate_extracted_fields")] - [ ] 2.2 Accepts fieldsJson string, deserializes to ExtractedFields with PropertyNameCaseInsensitive - [ ] 2.3 Validates required fields non-null/non-empty, decimals > 0 - [ ] 2.4 Returns JSON serialized ValidationResult ## 3. Chat Controller - [ ] 3.1 Create ChatController [ApiController] Route("api/[controller]") injecting Kernel - [ ] 3.2 POST endpoint: set response to text/event-stream, no-cache - [ ] 3.3 Convert request messages to SK ChatHistory (user/assistant roles) - [ ] 3.4 Import ExtractionPlugin per-request via _kernel.ImportPluginFromObject - [ ] 3.5 Use OpenAIPromptExecutionSettings with FunctionChoiceBehavior.Auto() - [ ] 3.6 Stream via GetStreamingChatMessageContentsAsync, emit SSE: data: {"text":"..."}\n\n - [ ] 3.7 Emit data: [DONE]\n\n on completion - [ ] 3.8 Handle HttpRequestException (emit error SSE), TaskCanceledException (silent) ## 4. DI Wiring (Program.cs) - [ ] 4.1 Add using Microsoft.SemanticKernel at top of Program.cs - [ ] 4.2 Read BaseUrl and Model from config "ResponsesApi" section - [ ] 4.3 AddOpenAIChatCompletion(modelId, endpoint with /v1 suffix, apiKey) - [ ] 4.4 AddKernel() - [ ] 4.5 AddSingleton() ## 5. Configuration - [ ] 5.1 Add ResponsesApi section to appsettings.json: BaseUrl "http://localhost:8317/v1", Model "claude-sonnet-4-6" - [ ] 5.2 Add NuGet: Microsoft.SemanticKernel 1.74.0, Microsoft.SemanticKernel.Connectors.OpenAI 1.74.0 ``` --- ## Usage on sandbox 1. Create the change: `openspec new change "add-sk-chat"` 2. Type `proposal.md` into `openspec/changes/add-sk-chat/proposal.md` 3. Type `tasks.md` into `openspec/changes/add-sk-chat/tasks.md` 4. Run `/opsx:apply add-sk-chat` — the AI implements all tasks