Skip to content

📐 refactor: Normalize Stream Events Behind a ChatTransport Type - #16335

Merged
berry-13 merged 3 commits into
canaryfrom
berry-13/chat-transport-types
Sep 25, 2026
Merged

berry-13 merged 3 commits into
canaryfrom
berry-13/chat-transport-types

Conversation

@berry-13

Copy link
Copy Markdown
Collaborator

Summary

The stream hooks tell server frames apart by probing JSON keys (final, created, event, sync, type, message) plus the named attachment and error events, and nothing names that protocol. This first link of a stack, following #16288, #16289 and #16290, adds types only to librechat-data-provider.

It adds the wire frames as the server writes them, a ChatEvent union discriminated by type like TMessageContentParts, and a ChatTransport interface. JSDoc notes the AI SDK equivalents (UIMessageChunk, sendMessages, reconnectToStream) without adopting their names. Nothing consumes them yet.

Type of change

  • Refactor

Testing

Tested environments/configuration:

  • Types only; no runtime path changes.

Automated tests:

  • cd packages/data-provider && npx tsc --noEmit: clean
  • cd packages/data-provider && npx jest: 51 suites, 2142 passed, 1 skipped
  • npx eslint, npx prettier --check, node scripts/sort-imports.mts --check on both touched files: clean

Screenshots / recordings

No user-facing change

Risk / compatibility

None. New exported types only.

Checklist

  • I reviewed my own changes
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • Required documentation PR: N/A

Add the ChatTransport interface and a ChatEvent union discriminated by
type, covering every frame the client handles today: the created, sync
and final control frames, the step and side-channel { event, data }
frames, legacy content and text frames, attachments, errors, open and
abort. Types only; nothing consumes them yet.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T23:11:46.152874Z 26c063b New commits
🔒 Security Review ✅ Completed 2026-09-24T22:25:05.095817Z 0980b3b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0980b3b404

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

terminalStatus?: 'complete' | 'error' | 'aborted';
generationCreatedAt?: number;
generationProtocolVersion?: number;
requestMessage?: TMessage | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allow partial messages in terminal frames

When an Assistants run completes, both chatV1.js and chatV2.js emit a requestMessage containing only parentMessageId and thread_id, but TMessage requires fields including messageId, conversationId, text, and isCreatedByUser. Consequently, a transport cannot represent these valid terminal frames as ChatFinalFrame without an assertion, and consumers are falsely told those required fields exist; type this field as Partial<TMessage> | null (as the sync frame already does).

Useful? React with 👍 / 👎.

@berry-13 berry-13 Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 30e25a6. Assistants final frames send only parentMessageId and thread_id (chatV1.js:719, chatV2.js:557); requestMessage/responseMessage are now Partial | null.

Comment on lines +141 to +143
export type ChatErrorData = {
text?: string;
message?: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include the actual SSE error payload variants

When packages/api/src/utils/events.ts::handleError is called with a string, the error event parses to a primitive string, while the resumable agent route also emits objects shaped like { error, generationProtocolVersion }; neither value is assignable to this object type. A normalizing transport therefore cannot emit a correctly typed ChatEvent for common server errors without discarding data or casting, so ChatErrorData needs to cover the primitive and resumable error forms in addition to the existing failed-turn object.

Useful? React with 👍 / 👎.

@berry-13 berry-13 Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 30e25a6. ChatErrorData now admits the bare string from handleError and { error, generationProtocolVersion } from the agents stream route.

| ChatSyncFrame
| ChatFinalFrame
| ChatEventFrame
| TContentData

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept assistant content frames without userMessageId

When Assistants streams a non-text content part, both AssistantService.addContentData and StreamRunManager.addContentData emit index, type, the content part, messageId, thread_id, and conversationId, but neither emits userMessageId. Since TContentData requires that field, including it directly here excludes these existing wire frames from ChatFrame; use a wire-specific content type with userMessageId optional (or update every producer before exposing this contract).

Useful? React with 👍 / 👎.

@berry-13 berry-13 Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 30e25a6. Added ChatContentFrame with optional userMessageId, since AssistantService.addContentData omits it; ChatFrame and the content event use it.

responseMessage?: TMessage | null;
conversation?: Partial<TConversation> | null;
runMessages?: TMessage[];
title?: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Permit null titles on aborted terminal frames

When abortMiddleware.js aborts a conversation object whose title is still empty, it deliberately emits title: null, but this contract permits only a string or omission. That valid abort frame therefore cannot be represented as ChatFinalFrame, and a consumer checking only for undefined can incorrectly treat the runtime value as a string; declare the wire field as title?: string | null.

Useful? React with 👍 / 👎.

@berry-13 berry-13 Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 30e25a6. title is now string | null, matching abortMiddleware.js:119.

Assistants final frames carry a partial requestMessage, aborts can send
title: null, Assistants content frames omit userMessageId, and the error
event body can be a bare string or { error, generationProtocolVersion }.
Widen the frame types to admit each.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30e25a6c54

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

/** `{ event, data }` for the agent graph's step events, dispatched to the step handler. */
export type ChatStepFrame =
| { event: StepEvents.ON_RUN_STEP; data: Agents.RunStep }
| { event: StepEvents.ON_AGENT_UPDATE; data: Agents.AgentUpdate }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cover the legacy agent-update payload

When hide_sequential_outputs is enabled for an intermediate non-tool step, api/server/controllers/agents/callbacks.js emits on_agent_update with data: { runId, message }, while Agents.AgentUpdate requires the unrelated { type, agent_update } content-part shape. This valid wire frame therefore cannot be represented as ChatStepFrame without a cast or lossy conversion; add the legacy payload shape to this event variant or change the producer before publishing this contract.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26c063b. on_agent_update now admits ChatAgentStatusUpdate ({ runId, message }) from callbacks.js:550.

| {
text?: string;
message?: string;
error?: string | { message?: string };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Admit boolean error payloads

For an error raised after SSE headers have been sent, api/server/middleware/error.js::sendError constructs an error body with error: true and passes that object directly to handleError, but this union permits only a string or object in the error property. Fresh evidence in the current head is the unconditional handleError(res, errorMessage) path for that boolean-marked object, so the earlier expansion still leaves a common server error frame unrepresentable as ChatErrorData without a cast.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26c063b. ChatErrorData.error now admits true, as sendError sets once headers are sent (error.js:44).

export type ChatEventFrame =
| ChatTitleFrame
| ChatStepFrame
| { event: 'attachment'; data: TAttachment }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept plain file attachment payloads

When an agent publishes a run artifact, packages/api/src/agents/files/binding.ts calls emitAttachment(file) with a plain TFile; those published files do not carry the messageId and toolCallId required by TAttachmentMetadata. Both the named attachment SSE path and its resumable { event: 'attachment' } equivalent can therefore carry a valid payload excluded by this variant, so the wire attachment type needs to include plain TFile publications rather than requiring TAttachment universally.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26c063b. Attachments are ChatAttachment = TAttachment | TFile, covering plain run-artifact publications.

| { event: StepEvents.ON_MESSAGE_DELTA; data: Agents.MessageDeltaEvent }
| { event: StepEvents.ON_REASONING_DELTA; data: Agents.ReasoningDeltaEvent }
| { event: StepEvents.ON_RUN_STEP_DELTA; data: Agents.RunStepDeltaEvent }
| { event: StepEvents.ON_RUN_STEP_COMPLETED; data: { result: Agents.ToolEndEvent } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Permit completed-step frames without a result

When on_run_step_completed arrives without data.result, api/server/controllers/agents/callbacks.js still forwards the frame if it belongs to the final agent or sequential outputs are visible. Requiring result here excludes those explicitly emitted frames and forces a transport to assert a value that consumers cannot safely dereference; model result as optional or nullable, or stop emitting the result-less variant.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 26c063b. on_run_step_completed data is { result?: Agents.ToolEndEvent | null }.

on_agent_update also carries a { runId, message } status line when
sequential outputs are hidden, on_run_step_completed can arrive without
a result, a published run artifact is a plain TFile, and sendError marks
its body with error: true. The frame types now admit each.
@berry-13
berry-13 added this pull request to stack #16346 September 24, 2026 23:16
@danny-avila danny-avila added the 🗺️ Shared Types codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) label Sep 25, 2026
@berry-13
berry-13 merged commit 2f8a207 into canary Sep 25, 2026
39 checks passed
@berry-13
berry-13 deleted the berry-13/chat-transport-types branch September 25, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🗺️ Shared Types codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants