Skip to content

refactor(chat): give provider message conversion its own module - #3449

Merged
kojiwakayama merged 3 commits into
mainfrom
refactor/provider-message-conversion
Aug 7, 2026
Merged

refactor(chat): give provider message conversion its own module#3449
kojiwakayama merged 3 commits into
mainfrom
refactor/provider-message-conversion

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Finishes the split started in #3439. Zero behaviour change.

src/chat/conversation.ts 993 → 557 lines (1563 → 557 across both increments). What remains is one coherent subject that finally matches the file's name: the persisted conversation format and how UI messages become stored parts.

What moved

Module Lines Owns
provider-message-conversion.ts (new) 447 turning replay history into the ordered message list a provider sees
provider-input-types.ts (new) 34 the ChatProviderModelInput* shapes both conversion and reconciliation need

provider-message-conversion.ts exports exactly one function — convertUiMessagesToProviderModelMessages. The other seven (buildToolNameMap, resolveRawToolResultPart, shouldSkipTransientToolCall, and the four convert*) are private to it.

The sequencing is the design

tool-replay-reconciliation.ts had a deliberate type-only import of ChatProviderModelInputMessage from conversation.ts — the one back-edge left by #3439.

Moving the conversion out without moving those types first would not have removed that edge; it would have inverted it into a genuine two-way dependency between the conversion module and conversation.ts. Strictly worse than the single type-only edge that existed before.

So the types moved first, in their own commit (94b22420c), and that commit's success criterion was a single grep: grep -n "conversation" src/chat/tool-replay-reconciliation.ts must return empty. It does. The back-edge is gone, not relocated.

They went into a new leaf module rather than src/chat/types.ts, because that file is a published deno.json export and putting them there would have widened the public API surface — cleanup, not scope growth.

Resulting graph, every arrow one-way:

part-field-access → message-part-parsing → tool-replay-reconciliation ─┐
                                                                       ▼
                    provider-input-types ────────────► provider-message-conversion
                                                                       │
                                                                       ▼
                                                                 conversation

The conversion module's single edge into conversation.ts is for the source-ID plumbing (getProviderModelMessageSourceId / withProviderModelMessageSourceId), which stays there by design. conversation.ts imports nothing back.

Evidence

  • deno task test:unit: 3811 passed / 27980 steps / 0 failed / 1 ignored — identical before and after every commit. conversation.test.ts (40 cases) and message-prep.test.ts (42 cases) are the fence.
  • All 8 moved functions diffed byte-for-byte against the original, including the 188-line convertAssistantMessage (5282 characters identical).
  • deno task verify:quick exit 0. deno task dupes 232 groups, unchanged.
  • lint:module-boundaries reports 0 cyclic edges; baseline untouched.

Two corrections found by measurement

  • The design assumed isToolCallPart / isToolResultPart / extractTextFromMessage were used by the conversion code. They aren't — none is called anywhere in conversation.ts, including inside the moved functions. They stayed put. (Their real consumers are message-prep.ts and final-step-fallback.ts; extractTextFromMessage has no production consumer at all, only its test.)
  • The consumer list was four, not three: conversation.test.ts imports the entry point directly across 44 assertions. Missing it would have broken the fence test's compilation.

Consumers were repointed directly rather than re-exportedconversation.ts is not a published export and the list is short and known. #3439 already shipped a dead re-export that review caught; not repeating it.

Ceilings

All src/chat/* ceilings re-pinned to real wc -l values, with entries added for both new modules. Two had gone slack and were tightened (tool-replay-reconciliation.ts 294→291, part-field-access.ts 66→65).

The gate was proven to fire, not assumed: appending 20 blank lines to the new module produced

✖ src/chat/provider-message-conversion.ts: 467 LOC exceeds ceiling 447 — split it, don't grow it.
EXIT CODE: 1

and reverting returned exit 0. This matters because modules/server/module-server.ts regrew 1138 → 1806 after its own refactor — past its pre-refactor size — for want of exactly this.

CONTEXT.md records Provider Message Conversion as a domain term.

Summary by CodeRabbit

  • Improvements
    • Improved conversion of chat history for provider processing.
    • Preserved message ordering across system, user, assistant, and tool messages.
    • Improved handling of replayed, temporary, replaced, and completed tool interactions.
    • Preserved tool names and source message references during conversion.
    • Improved mapping for text, files, reasoning, tool calls, and tool results.
    • Consolidated adjacent tool messages for more consistent provider input.

Splits RawToolCallMessagePart, RawToolResultMessagePart,
ChatProviderModelInputToolCallPart, ChatProviderModelInputToolResultPart,
ChatProviderModelInputPart, and ChatProviderModelInputMessage out of
conversation.ts into provider-input-types.ts. This removes the
deliberate type-only back-edge tool-replay-reconciliation.ts had
into conversation.ts, and sets up the next increment to move
provider conversion out of conversation.ts without creating a cycle.

Pure type move, zero behaviour change.
Move convertUiMessagesToProviderModelMessages and its private helpers
(buildToolNameMap, resolveRawToolResultPart, shouldSkipTransientToolCall,
convertSystemMessage, convertUserMessage, convertAssistantMessage,
convertToolMessage, ProviderToolResultContent) out of conversation.ts
into src/chat/provider-message-conversion.ts, verbatim.

conversation.ts keeps the persisted-conversation format and how UI
messages become stored parts; provider-message-conversion.ts is now the
single owner of turning replay history into the ordered message list a
provider sees. Consumers (message-prep.ts, compat.ts,
chat-request.test.ts, conversation.test.ts) repoint directly at the new
module rather than via a re-export from conversation.ts.
conversation.ts shrank from 1018 to 557 lines and split off two new
leaf modules; tighten ceilings to match actual sizes and add entries
for provider-input-types.ts and provider-message-conversion.ts so
regrowth fails CI immediately. Record Provider Message Conversion as
a domain term in CONTEXT.md.
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner August 7, 2026 05:54
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@kojiwakayama
kojiwakayama enabled auto-merge August 7, 2026 05:54
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change extracts provider message conversion and its input types from conversation.ts into dedicated modules. The conversion preserves message ordering and reconciles replayed tool calls and results. Consumers, tests, documentation, and lint ceilings now use the new module boundaries.

Changes

Provider Message Conversion

Layer / File(s) Summary
Provider conversion contracts
src/chat/provider-input-types.ts, src/chat/conversation.ts, src/chat/tool-replay-reconciliation.ts, src/chat/tool-replay-reconciliation.test.ts
Provider input types now live in provider-input-types.ts. conversation.ts no longer exports provider conversion types or conversion logic.
Replay-aware message conversion
src/chat/provider-message-conversion.ts
The new module converts system, user, assistant, and tool messages. It maps content parts, reconciles tool replay occurrences, preserves ordering, merges adjacent tool messages, and propagates source IDs.
Consumer import and project integration
src/chat/compat.ts, src/chat/conversation.test.ts, src/chat/message-prep.ts, src/agent/hosted/chat-request.test.ts, CONTEXT.md, scripts/lint/ban-chat-antipatterns.ts
Consumers and tests import the conversion function and types from their new modules. Documentation and file-size ceilings reflect the extraction.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatHistory
  participant convertUiMessagesToProviderModelMessages
  participant ToolReplayReconciliation
  participant ProviderModelMessages
  ChatHistory->>convertUiMessagesToProviderModelMessages: provide ordered messages
  convertUiMessagesToProviderModelMessages->>ToolReplayReconciliation: reconcile tool occurrences
  ToolReplayReconciliation-->>convertUiMessagesToProviderModelMessages: return authoritative tool matches
  convertUiMessagesToProviderModelMessages->>ProviderModelMessages: emit ordered provider messages
Loading

Possibly related PRs

Suggested reviewers: kwakayama

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 clearly and concisely describes the main refactor: moving provider message conversion into its own module.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/provider-message-conversion

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

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/agent/hosted/chat-request.test.ts (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the internal source alias.

Line 2 crosses a module boundary from src/agent/hosted to src/chat. Replace the relative path with #veryfront/chat/provider-message-conversion.ts.

Proposed fix
-import { convertUiMessagesToProviderModelMessages } from "../../chat/provider-message-conversion.ts";
+import { convertUiMessagesToProviderModelMessages } from "`#veryfront/chat/provider-message-conversion.ts`";

As per coding guidelines, use #veryfront/* for internal source imports.

🤖 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/agent/hosted/chat-request.test.ts` at line 2, Update the import of
convertUiMessagesToProviderModelMessages in the test to use the
`#veryfront/chat/provider-message-conversion.ts` internal source alias instead of
the relative path.

Sources: Coding guidelines, Learnings

🤖 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.

Nitpick comments:
In `@src/agent/hosted/chat-request.test.ts`:
- Line 2: Update the import of convertUiMessagesToProviderModelMessages in the
test to use the `#veryfront/chat/provider-message-conversion.ts` internal source
alias instead of the relative path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bebb0193-d79d-469d-8ad4-8bad9fdd4dcb

📥 Commits

Reviewing files that changed from the base of the PR and between ded1750 and 9dd5faf.

📒 Files selected for processing (11)
  • CONTEXT.md
  • scripts/lint/ban-chat-antipatterns.ts
  • src/agent/hosted/chat-request.test.ts
  • src/chat/compat.ts
  • src/chat/conversation.test.ts
  • src/chat/conversation.ts
  • src/chat/message-prep.ts
  • src/chat/provider-input-types.ts
  • src/chat/provider-message-conversion.ts
  • src/chat/tool-replay-reconciliation.test.ts
  • src/chat/tool-replay-reconciliation.ts

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 234efea Aug 7, 2026
31 checks passed
@kojiwakayama
kojiwakayama deleted the refactor/provider-message-conversion branch August 7, 2026 06:12
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.

1 participant