Skip to content

Ocean/pipeline integration tests d552 - #21

Closed
Ocean82 wants to merge 2 commits into
mainfrom
ocean/pipeline-integration-tests-d552
Closed

Ocean/pipeline integration tests d552#21
Ocean82 wants to merge 2 commits into
mainfrom
ocean/pipeline-integration-tests-d552

Conversation

@Ocean82

@Ocean82 Ocean82 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary by Sourcery

Replace legacy BrainDispatcher stage with separate DeterministicDispatcher and LLMGateway in the chat pipeline, and propagate LLM provider metadata from server responses through to chat messages and dev UI.

Enhancements:

  • Update chat service and pipeline integration/smoke tests to route deterministic intents through DeterministicDispatcher and LLM requests through LLMGateway.
  • Resolve analysis targets inside DeterministicDispatcher when not explicitly provided, using pipeline context and prior insights.
  • Refine server-side LLM selection to track BYOK vs built-in providers and emit normalized provider/model metadata in responses.
  • Extend agent client SSE handling to parse complete events (including provider meta) and include provider metadata when converting server responses to chat messages.
  • Display provider/model details for assistant messages in the chat panel in development builds.

Tests:

  • Expand pipeline integration and smoke tests with richer AI dependency mocks and new expectations for DeterministicDispatcher and LLMGateway behavior.
  • Add unit tests for parsing complete SSE events and propagating provider metadata into ChatMessage objects.

Summary by CodeRabbit

  • New Features

    • Development builds now show the provider and model used for assistant replies.
    • Chat responses retain provider details across streaming and message conversion.
    • Deterministic analysis and LLM explanations are handled through dedicated processing stages.
  • Bug Fixes

    • Improved streaming response parsing for completed, malformed, and unexpected events.
    • Added fallback handling when supported chat operations encounter errors.
  • Tests

    • Expanded integration coverage for deterministic analysis, explanations, fallback behavior, metadata preservation, and streaming responses.

cursoragent and others added 2 commits August 9, 2026 16:08
Parse server meta through SSE/complete responses into ChatMessage,
show provider details in DEV chat UI, and label BYOK by actual
provider/host instead of hardcoding openrouter.

Co-authored-by: Ocean82 <Ocean82@users.noreply.github.com>
Replace brainDispatcher/processMessage mocks with deterministic-dispatcher
and llm-gateway stages, mocking agentClient, intent/mode, and analysis deps.

Co-authored-by: Ocean82 <Ocean82@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 9, 2026 17:27
@sourcery-ai

sourcery-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR refactors the AI chat routing pipeline to split deterministic spreadsheet skills from LLM handling, wires provider identity metadata end-to-end from server to UI, and updates integration/smoke tests plus deterministic dispatcher to operate on resolved analysis targets instead of test-only stubs.

Sequence diagram for providerMeta propagation from server to UI

sequenceDiagram
  actor User
  participant ChatPanel
  participant ChatService as processChatMessage
  participant Pipeline as LLMGatewayStage
  participant Server as runLlmChat

  User->>ChatPanel: submit message
  ChatPanel->>ChatService: processChatMessage(input)
  ChatService->>Pipeline: router.process(pipelineContext)
  Pipeline->>Server: runLlmChat(params)
  Server-->>Pipeline: ServerChatResponse{ meta }
  Pipeline-->>ChatService: StageResult{ metadata.providerMeta }
  ChatService-->>ChatPanel: ChatMessage{ providerMeta }
  ChatPanel-->>User: render providerMeta in dev details
Loading

Flow diagram for updated chat pipeline stages

flowchart LR
  subgraph client_pipeline[Client chat pipeline]
    AP[AgentParserStage]
    TR[TemplateResolverStage]
    IC[IntentClassifierStage]
    DD[DeterministicDispatcherStage]
    LG[LLMGatewayStage]

    AP --> TR --> IC --> DD --> LG
  end

  U[User input] --> AP
  LG --> S[Server runLlmChat]
  S --> LG
  LG --> R[Final assistant reply]
Loading

File-Level Changes

Change Details Files
Refactor pipeline integration and smoke tests to use DeterministicDispatcher + LLMGateway and richer mocks for intent, mode, analysis, and context.
  • Update test descriptions and expectations to reference DeterministicDispatcher and LLMGateway instead of BrainDispatcher.
  • Inject new mocks for agentClient streaming, intent parsing, mode classification, budget analysis, analysis target resolution, sheet profiling, reporting, cleaning, query engine, comparison, response building, outlier handling, mode flags, auditor, and contextual suggestions.
  • Ensure tests drive routing with parseUserIntent + classifyMode and assert correct stageName and LLM usage/non-usage.
  • Provide defaultIntent helper for tests to avoid repeating generic intent objects.
  • Update smoke tests to verify deterministic vs LLM paths and ensure downstream stages are or are not invoked as expected.
src/ai/pipeline/__tests__/pipeline.integration.test.ts
src/ai/pipeline/__tests__/smokeTest.integration.test.ts
Extend agentClient streaming parsing to preserve provider metadata from SSE complete events and propagate it onto ChatMessage.
  • Introduce ServerChatResponse.meta typed with ProviderMeta and a parseCompleteSseEvent helper to safely parse SSE complete events.
  • Refactor chatWithAgentServerStream SSE loop to differentiate token vs complete events, using parseCompleteSseEvent for the latter.
  • Update serverResponseToChatMessage to copy response.meta into ChatMessage.providerMeta.
  • Add tests verifying parseCompleteSseEvent behavior and providerMeta propagation.
src/ai/agentClient.ts
src/ai/agentClient.test.ts
Make DeterministicDispatcher resolve its own AnalysisTarget from PipelineContext when not provided, and use workbook/priorInsights from either explicit parameters or context.
  • Change createDeterministicDispatcherStage signature to accept optional target and workbookName, resolving them from PipelineContext via resolveAnalysisTarget when omitted.
  • Derive profile and insights from the resolved target rather than external test scaffolding.
  • Use context.priorInsights as default when priorInsights param is undefined.
  • Thread resolved workbookName and target into data-awareness, cleaning, reporting, comparison, and query dispatch paths.
  • Update imports to bring in resolveAnalysisTarget alongside AnalysisTarget type.
src/ai/pipeline/stages/deterministicDispatcher.ts
Add provider metadata plumbing to server LLM chat, brain dispatcher, LLMGateway, ToolResult, response builder, chat service, and ChatPanel UI.
  • Introduce ProviderMeta type and providerMeta field on ChatMessage and ToolResult.
  • In server runLlmChat, compute providerMeta for BYOK and server providers, track byokSucceeded separately from usedProvider, and ensure meta is returned consistently including retry path.
  • Update brain.processMessage to attach serverResult.meta onto ToolResult.providerMeta.
  • Extend LLMGateway stage to copy serverResult.meta onto StageResult.metadata.providerMeta.
  • Update responseBuilder.toolResultToChatMessage to pass ToolResult.providerMeta onto ChatMessage.providerMeta.
  • Adjust chatService.stageResultToChatMessage to read providerMeta from stage metadata and include it in the ToolResult-like object, and to treat deterministic-dispatcher and llm-gateway like brain-dispatcher for rendering and fallback.
  • Render providerMeta in ChatPanel in dev builds via an expandable details block.
  • Export new dispatcher/gateway creators from pipeline stage index and main pipeline module, keeping createBrainDispatcherStage as deprecated for transitional use.
server/src/index.ts
src/ai/brain.ts
src/ai/pipeline/stages/llmGateway.ts
src/ai/responseBuilder.ts
src/ai/types.ts
src/services/chatService.ts
src/components/ChatPanel.tsx
src/ai/pipeline/stages/index.ts
src/ai/pipeline/index.ts
src/types/api.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change separates deterministic and LLM pipeline stages, tracks BYOK and server-provider metadata, validates complete SSE events, propagates provider metadata through chat results, and displays it in development builds. Integration tests now cover the new routing and fallback behavior.

Changes

LLM pipeline and provider metadata

Layer / File(s) Summary
Provider metadata and response parsing
server/src/index.ts, src/ai/agentClient.ts, src/types/api.ts, src/ai/types.ts, src/ai/brain.ts, src/ai/agentClient.test.ts
Provider and model metadata now follows successful BYOK or server-provider responses. Complete SSE events use shared validation and preserve metadata.
Deterministic and LLM stage routing
src/ai/pipeline/*, src/services/chatService.ts, src/ai/pipeline/__tests__/*
The pipeline uses DeterministicDispatcher followed by LLMGateway. Deterministic stages resolve runtime context values. Integration tests cover routing, short circuits, explanations, fallbacks, and recovery.
Result conversion and development display
src/ai/pipeline/stages/llmGateway.ts, src/ai/responseBuilder.ts, src/services/chatService.ts, src/components/ChatPanel.tsx
Provider metadata is copied into tool results and chat messages. Development builds show expandable provider details.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatService
  participant DeterministicDispatcher
  participant LLMGateway
  participant AgentClient
  participant AIServer
  ChatService->>DeterministicDispatcher: process classified request
  DeterministicDispatcher-->>ChatService: claim or pass
  ChatService->>LLMGateway: process unclaimed request
  LLMGateway->>AgentClient: stream chat request
  AgentClient->>AIServer: send request
  AIServer-->>AgentClient: complete SSE with provider metadata
  AgentClient-->>LLMGateway: parsed server response
  LLMGateway-->>ChatService: result with providerMeta
Loading

Possibly related PRs

  • Ocean82/smartshit#1: Introduced related streaming and provider failover handling in the same server and client paths.
  • Ocean82/smartshit#3: Added related authenticated LLM request and provider-fallback behavior.
  • Ocean82/smartshit#6: Changed related provider ordering and Groq model configuration.

Poem

A rabbit sees the providers glow,
As metadata hops from flow to flow.
Deterministic paths lead the way,
LLMs answer when they may.
SSE carrots parse just right—
“Provider details!” shines in sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title identifies the pipeline integration test changes, which are part of the pull request, but it does not describe the broader pipeline and provider metadata updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new defaultIntent() helper is duplicated in both pipeline.integration.test.ts and smokeTest.integration.test.ts; consider extracting this to a shared test utility to avoid divergence and keep future intent shape changes in one place.
  • The large block of Vitest mocks for context/analysis (buildContext, analysisTarget, sheetProfile, budget analysis, etc.) is now duplicated across the two integration test files; factoring these into a shared test setup would reduce maintenance overhead and help keep the mocked behavior consistent.
  • In chatWithAgentServerStream, each SSE event JSON is parsed twice (once inline for token events and again in parseCompleteSseEvent); you could pass an already-parsed object into a helper or branch on type in a single parse to avoid redundant JSON parsing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `defaultIntent()` helper is duplicated in both `pipeline.integration.test.ts` and `smokeTest.integration.test.ts`; consider extracting this to a shared test utility to avoid divergence and keep future intent shape changes in one place.
- The large block of Vitest mocks for context/analysis (buildContext, analysisTarget, sheetProfile, budget analysis, etc.) is now duplicated across the two integration test files; factoring these into a shared test setup would reduce maintenance overhead and help keep the mocked behavior consistent.
- In `chatWithAgentServerStream`, each SSE event JSON is parsed twice (once inline for token events and again in `parseCompleteSseEvent`); you could pass an already-parsed object into a helper or branch on `type` in a single parse to avoid redundant JSON parsing.

## Individual Comments

### Comment 1
<location path="src/ai/agentClient.ts" line_range="25-34" />
<code_context>
+}
+
+/** Parse an SSE `data:` JSON payload into a ServerChatResponse when type=complete. */
+export function parseCompleteSseEvent(jsonStr: string): ServerChatResponse | null {
+  try {
+    const event = JSON.parse(jsonStr) as {
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid double JSON.parse for SSE events to reduce overhead and inconsistency risk.

Within the stream loop, `jsonStr` is parsed twice: once to inspect `type`, then again in `parseCompleteSseEvent`. This adds per-event overhead and creates two separate parsing paths (a minimal `{ type?: string; content?: string }` shape vs. the full event type). Refactor so the event is parsed only once—either by passing the already-parsed object into `parseCompleteSseEvent`, or by making `parseCompleteSseEvent` the single parsing entry point for SSE chunks.

Suggested implementation:

```typescript
/** Parse a parsed SSE `data:` JSON payload into a ServerChatResponse when type=complete. */
export function parseCompleteSseEvent(event: {
  type?: string
  message?: string
  actions?: ServerAgentAction[]
  source?: string
  reasoning?: string
  suggestions?: string[]
  meta?: ProviderMeta
}): ServerChatResponse | null {
  try {
    if (event.type !== 'complete' || typeof event.message !== 'string') return null
    return {

```

To fully avoid double JSON.parse:
1. In the SSE stream loop where `jsonStr` is read, keep a single `const event = JSON.parse(jsonStr)` and pass `event` into `parseCompleteSseEvent(event)` instead of `parseCompleteSseEvent(jsonStr)`.
2. Remove any other callers that still pass a string and ensure they now pass the already-parsed event object.
3. If there is a separate lightweight parse for `{ type?: string; content?: string }`, you can reuse that parsed object by widening its type (or re-parsing once into the richer shape), then forwarding it into `parseCompleteSseEvent`.
</issue_to_address>

### Comment 2
<location path="server/src/index.ts" line_range="241-244" />
<code_context>
+      } catch {
+        // keep custom
+      }
+      providerMeta = {
+        provider: byok.provider?.trim() || byokHost || 'byok',
+        model: byok.model?.trim() || byokHost,
+      }
+      byokSucceeded = true
</code_context>
<issue_to_address>
**suggestion:** Defaulting BYOK model to the host name may be misleading or confusing.

Here `providerMeta.model` falls back to `byokHost` when `byok.model` is empty, so the "model" field may show a hostname (e.g. `api.openai.com`) instead of a model identifier. To avoid confusion when debugging or inspecting replies, consider using an explicit placeholder (e.g. `'unknown-model'` or `'custom'`) or omitting the model rather than reusing the host value.

```suggestion
      providerMeta = {
        provider: byok.provider?.trim() || byokHost || 'byok',
        model: byok.model?.trim() || 'unknown-model',
      }
```
</issue_to_address>

Fix all in Cursor


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/ai/agentClient.ts
Comment on lines +25 to +34
export function parseCompleteSseEvent(jsonStr: string): ServerChatResponse | null {
try {
const event = JSON.parse(jsonStr) as {
type?: string
message?: string
actions?: ServerAgentAction[]
source?: string
reasoning?: string
suggestions?: string[]
meta?: ProviderMeta

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Avoid double JSON.parse for SSE events to reduce overhead and inconsistency risk.

Within the stream loop, jsonStr is parsed twice: once to inspect type, then again in parseCompleteSseEvent. This adds per-event overhead and creates two separate parsing paths (a minimal { type?: string; content?: string } shape vs. the full event type). Refactor so the event is parsed only once—either by passing the already-parsed object into parseCompleteSseEvent, or by making parseCompleteSseEvent the single parsing entry point for SSE chunks.

Suggested implementation:

/** Parse a parsed SSE `data:` JSON payload into a ServerChatResponse when type=complete. */
export function parseCompleteSseEvent(event: {
  type?: string
  message?: string
  actions?: ServerAgentAction[]
  source?: string
  reasoning?: string
  suggestions?: string[]
  meta?: ProviderMeta
}): ServerChatResponse | null {
  try {
    if (event.type !== 'complete' || typeof event.message !== 'string') return null
    return {

To fully avoid double JSON.parse:

  1. In the SSE stream loop where jsonStr is read, keep a single const event = JSON.parse(jsonStr) and pass event into parseCompleteSseEvent(event) instead of parseCompleteSseEvent(jsonStr).
  2. Remove any other callers that still pass a string and ensure they now pass the already-parsed event object.
  3. If there is a separate lightweight parse for { type?: string; content?: string }, you can reuse that parsed object by widening its type (or re-parsing once into the richer shape), then forwarding it into parseCompleteSseEvent.

Fix in Cursor

Comment thread server/src/index.ts
Comment on lines +241 to +244
providerMeta = {
provider: byok.provider?.trim() || byokHost || 'byok',
model: byok.model?.trim() || byokHost,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Defaulting BYOK model to the host name may be misleading or confusing.

Here providerMeta.model falls back to byokHost when byok.model is empty, so the "model" field may show a hostname (e.g. api.openai.com) instead of a model identifier. To avoid confusion when debugging or inspecting replies, consider using an explicit placeholder (e.g. 'unknown-model' or 'custom') or omitting the model rather than reusing the host value.

Suggested change
providerMeta = {
provider: byok.provider?.trim() || byokHost || 'byok',
model: byok.model?.trim() || byokHost,
}
providerMeta = {
provider: byok.provider?.trim() || byokHost || 'byok',
model: byok.model?.trim() || 'unknown-model',
}

Fix in Cursor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ai/agentClient.ts`:
- Around line 39-43: Validate optional completion fields in the event-mapping
logic of agentClient: ensure source matches the allowed enum, suggestions is an
array of strings, and meta contains valid ProviderMeta string fields before
forwarding them. Reject or normalize malformed values so ChatPanel receives only
valid data, and add tests covering each malformed field.
- Around line 136-142: Update the streaming read loop around JSON.parse,
parseCompleteSseEvent, and onToken to retain an undecoded-line buffer across
chunks. Process only newline-terminated SSE frames, append each decoded chunk to
the buffer, and after the stream ends flush the decoder and process any
remaining buffered line so token and complete events are preserved.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 50ff659b-0f11-4f6f-82b3-4f560801205e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e27abb and f35dac0.

📒 Files selected for processing (15)
  • server/src/index.ts
  • src/ai/agentClient.test.ts
  • src/ai/agentClient.ts
  • src/ai/brain.ts
  • src/ai/pipeline/__tests__/pipeline.integration.test.ts
  • src/ai/pipeline/__tests__/smokeTest.integration.test.ts
  • src/ai/pipeline/index.ts
  • src/ai/pipeline/stages/deterministicDispatcher.ts
  • src/ai/pipeline/stages/index.ts
  • src/ai/pipeline/stages/llmGateway.ts
  • src/ai/responseBuilder.ts
  • src/ai/types.ts
  • src/components/ChatPanel.tsx
  • src/services/chatService.ts
  • src/types/api.ts

Comment thread src/ai/agentClient.ts
Comment on lines +39 to +43
actions: Array.isArray(event.actions) ? event.actions : [],
source: (event.source as ServerChatResponse['source']) ?? 'llm',
reasoning: event.reasoning,
suggestions: event.suggestions,
meta: event.meta,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate all optional completion fields before forwarding them.

JSON.parse plus a type assertion does not validate source, suggestions, or meta. For example, a completion event with suggestions: "text" reaches ChatPanel, where the assistant-message renderer calls .map() and throws. Validate the source enum, string-array suggestions, and both ProviderMeta strings. Reject or normalize invalid values before returning the response. Add malformed-field tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ai/agentClient.ts` around lines 39 - 43, Validate optional completion
fields in the event-mapping logic of agentClient: ensure source matches the
allowed enum, suggestions is an array of strings, and meta contains valid
ProviderMeta string fields before forwarding them. Reject or normalize malformed
values so ChatPanel receives only valid data, and add tests covering each
malformed field.

Comment thread src/ai/agentClient.ts
Comment on lines +136 to +142
const parsed = JSON.parse(jsonStr) as { type?: string; content?: string }
if (parsed.type === 'token' && typeof parsed.content === 'string') {
onToken(parsed.content)
continue
}
const complete = parseCompleteSseEvent(jsonStr)
if (complete) finalResponse = complete

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve SSE frames that span stream chunks.

A ReadableStream chunk can end inside a data: line. text.split('\n') discards that partial line, and the next chunk no longer starts with data: . This can drop token events or the only complete event and return null after streamed text. Keep an undecoded-line buffer between reads, process only newline-terminated frames, and flush the decoder and buffer when the stream ends.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ai/agentClient.ts` around lines 136 - 142, Update the streaming read loop
around JSON.parse, parseCompleteSseEvent, and onToken to retain an
undecoded-line buffer across chunks. Process only newline-terminated SSE frames,
append each decoded chunk to the buffer, and after the stream ends flush the
decoder and process any remaining buffered line so token and complete events are
preserved.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the chat/AI routing pipeline by splitting the legacy “BrainDispatcher” responsibilities into a deterministic stage and a server-LLM gateway stage, while also propagating normalized LLM provider/model metadata from server responses through the client and into the dev UI.

Changes:

  • Replace BrainDispatcher usage in the chat pipeline with DeterministicDispatcher (local skills) + LLMGateway (terminal server LLM stage), and update integration/smoke tests accordingly.
  • Add provider/model metadata plumbing end-to-end: server emits meta, client parses complete SSE events with meta, and assistant ChatMessage can carry providerMeta.
  • Show provider details for assistant messages in dev builds.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types/api.ts Introduces ProviderMeta and attaches it to ChatMessage.
src/services/chatService.ts Updates pipeline stages and maps stage metadata into rendered chat messages.
src/components/ChatPanel.tsx Adds a dev-only <details> UI for provider/model display on assistant messages.
src/ai/types.ts Extends ToolResult to carry provider metadata.
src/ai/responseBuilder.ts Propagates providerMeta when converting ToolResultChatMessage.
src/ai/pipeline/stages/llmGateway.ts Emits provider metadata into StageResult.metadata from server responses.
src/ai/pipeline/stages/index.ts Exports deterministic + gateway stages and deprecates brain dispatcher export.
src/ai/pipeline/stages/deterministicDispatcher.ts Resolves analysis targets from PipelineContext when not explicitly provided.
src/ai/pipeline/index.ts Re-exports the new stages from the pipeline module.
src/ai/pipeline/tests/smokeTest.integration.test.ts Updates smoke tests to assert deterministic vs LLM routing with new stages.
src/ai/pipeline/tests/pipeline.integration.test.ts Updates integration routing tests for new stage split and mocking.
src/ai/brain.ts Adds providerMeta to legacy brain path ToolResult (transitional compatibility).
src/ai/agentClient.ts Parses complete SSE events (including meta) and copies metadata into ChatMessage.
src/ai/agentClient.test.ts Adds unit coverage for parsing complete SSE events and metadata propagation.
server/src/index.ts Normalizes provider/model metadata (BYOK vs server providers) and returns it as meta.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

toolUsed: result.metadata?.toolUsed as string | undefined,
reasoning: result.metadata?.reasoning as string | undefined,
suggestions: result.suggestions,
providerMeta: result.metadata?.providerMeta as { provider: string; model: string } | undefined,
Comment thread src/ai/types.ts
chartConfig?: ChartConfig
toolUsed?: string
reasoning?: string
providerMeta?: { provider: string; model: string }
Comment thread src/ai/agentClient.ts
Comment on lines +36 to +44
if (event.type !== 'complete' || typeof event.message !== 'string') return null
return {
message: event.message,
actions: Array.isArray(event.actions) ? event.actions : [],
source: (event.source as ServerChatResponse['source']) ?? 'llm',
reasoning: event.reasoning,
suggestions: event.suggestions,
meta: event.meta,
}
@Ocean82

Ocean82 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #20, which includes these commits plus the remaining priority residuals (pipeline cutover, macros, ONNX Path B, BYOK). CI blockers are being fixed on #20.

@Ocean82 Ocean82 closed this Aug 9, 2026
@Ocean82
Ocean82 deleted the ocean/pipeline-integration-tests-d552 branch August 9, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants