Skip to content

feat(ai-client): accept a per-call body via SendMessageOptions - #1027

Open
boring91 wants to merge 2 commits into
TanStack:mainfrom
boring91:feat/send-message-options-body
Open

feat(ai-client): accept a per-call body via SendMessageOptions#1027
boring91 wants to merge 2 commits into
TanStack:mainfrom
boring91:feat/send-message-options-body

Conversation

@boring91

@boring91 boring91 commented Jul 31, 2026

Copy link
Copy Markdown

🎯 Changes

ChatClient.sendMessage(content, body?, sendOptions?) accepts a per-call body merged into the request's forwardedProps at the highest priority — but every framework hook (useChat, injectChat, createChat, …) exposes sendMessage(content, options) and hardcodes undefined for the positional argument (e.g. ai-angular/src/inject-chat.ts, ai-react/src/use-chat.ts), and none of them expose the underlying ChatClient.

That leaves no race-free way to send per-message data through a hook. The workaround — updating a reactive chat-level body/forwardedProps option right before sending — is racy: reactive option changes sync to the client asynchronously (e.g. via an Angular effect), so "set option, then send" can flush after the request is built. Real-world case: attaching upload ids to the specific message that references them.

The fix: SendMessageOptions gains an optional body. ChatClient.sendMessage resolves body ?? sendOptions.body (the positional argument wins — it predates the option and existing callers may pass both), so every framework wrapper inherits the capability with zero wrapper code changes — they already forward sendOptions. Queued sends preserve the per-call body exactly like the positional form (same enqueueMessage/deliverMessage path).

// Now works through every hook:
await sendMessage("Summarize the attached files", {
  body: { attachmentIds: ["att_1", "att_2"] },
});

Also included:

  • Docs: "Per-call body" section in docs/chat/connection-adapters.md; updated sendMessage JSDoc (the "(body must still be the 2nd arg on ChatClient)" note is no longer true) and the useMcpAppBridge example, which can now forward the bridge's body instead of dropping it.
  • Non-breaking: purely additive; no existing signature or behavior changes.

Test plan

  • Unit — ai-client (chat-client.test.ts, chat-client-queue.test.ts): sendOptions.body merges over chat-level body; positional body wins over (replaces, not merges with) sendOptions.body; queued sends drain with their own per-call sendOptions.body.
  • Unit — wrappers (ai-react/tests/use-chat.test.ts, ai-angular/tests/inject-chat.test.ts): hook-level sendMessage(content, { body }) reaches the connection adapter's data, merged with the chat-level body option.
  • E2E (testing/e2e/tests/per-call-body.spec.ts + fixture + a [per-call-body] branch in the chat page): drives the browser UI and asserts the /api/chat POST body carries the per-call marker under forwardedProps, merged with — not replacing — the chat-level keys. A regression that drops options.body fails the marker assertion; one that replaces the merge fails the provider/feature assertions.
  • Verified locally: pnpm run test:pr (sherif, knip, docs links, kiira doc-snippet typecheck, oxlint, unit, types, build — all green) and the e2e subset per-call-body + chat.spec.ts + queue.spec.ts (27 passed).

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset (@tanstack/ai-client minor).
  • This change is docs/CI/dev-only (no release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for supplying request body data per message when sending chat messages.
    • Per-message values merge with chat-level settings and take precedence.
    • Per-call body data works with framework hooks, direct chat clients, streaming, and queued messages.
    • Direct chat clients may continue using the positional body argument.
  • Bug Fixes

    • Improved streaming behavior when the page is hidden.
  • Documentation

    • Updated guidance and examples for per-message request bodies.
  • Tests

    • Added coverage for merging, precedence, streaming, queued messages, and end-to-end request forwarding.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a106f52-8dbe-433b-bf5f-6d6f087183f6

📥 Commits

Reviewing files that changed from the base of the PR and between a420768 and aa007b8.

📒 Files selected for processing (1)
  • packages/ai-client/src/chat-client.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Adds per-call body support to sendMessage, merging values into forwardedProps with per-call precedence while preserving positional arguments and queued sends. Framework tests, documentation, bridge examples, and end-to-end coverage validate the behavior. It also changes stream yielding for hidden pages.

Changes

Per-call message bodies

Layer / File(s) Summary
Body contract and delivery
packages/ai-client/src/types.ts, packages/ai-client/src/chat-client.ts, packages/ai-client/tests/chat-client*.test.ts
Adds SendMessageOptions.body, resolves positional-body precedence, forwards the resolved body for immediate sends, and preserves it for queued sends.
Framework hook integration
packages/ai-angular/tests/*, packages/ai-react/*, packages/ai-solid/src/types.ts, packages/ai-preact/src/use-mcp-app-bridge.ts, docs/chat/connection-adapters.md
Documents and tests per-call body merging through framework hooks and updates bridge examples to forward the body argument.
End-to-end validation and release documentation
testing/e2e/..., .changeset/spotty-donuts-repeat.md
Adds an end-to-end scenario that verifies merged forwardedProps and records the new behavior in the release changeset.

Visible-page stream scheduling

Layer / File(s) Summary
Visible-page chunk yielding
packages/ai-client/src/chat-client.ts
Skips the per-chunk macrotask yield when the document is undefined or the page is hidden.

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

Merge Risk: 🔵 Low · up to aa007

This change adds per-message request data, but queued sends may drop earlier messages’ fields when multiple bodies are combined into one request. The PR is mergeable with explicit owner awareness and follow-up to confirm the intended behavior and cover this case with a test.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ChatClient
  participant ConnectionAdapter
  participant ChatAPI
  Caller->>ChatClient: sendMessage(content, { body })
  ChatClient->>ChatClient: merge per-call body with chat-level body
  ChatClient->>ConnectionAdapter: send request with forwardedProps
  ConnectionAdapter->>ChatAPI: POST chat request
  ChatAPI-->>Caller: assistant response
Loading

Suggested reviewers: alemtuzlak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 12 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding per-call body support to SendMessageOptions.
Description check ✅ Passed The description covers the changes, rationale, testing, documentation, and release impact, with only minor checklist omissions.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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 @.changeset/spotty-donuts-repeat.md:
- Line 9: Correct the direct ChatClient usage in the changeset: reserve
sendMessage(content, { body: { ... } }) for framework hooks, and document
ChatClient calls as sendMessage(content, { ... }) with the marker fields
directly, or as options passed in the third argument.

In `@packages/ai-client/tests/chat-client.test.ts`:
- Around line 3400-3448: Move the added ChatClient body-precedence tests from
packages/ai-client/tests/chat-client.test.ts#L3400-L3448 into a source-adjacent
*.test.ts file covering ChatClient, move the queue-body test from
packages/ai-client/tests/chat-client-queue.test.ts#L688-L722 into its
source-adjacent test file, move the injectChat test from
packages/ai-angular/tests/inject-chat.test.ts#L126-L147 into its source-adjacent
test file, and move the useChat test from
packages/ai-react/tests/use-chat.test.ts#L292-L319 into its source-adjacent test
file; preserve the existing test behavior and assertions.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81377b9c-09ab-4682-a091-3342b2d01c25

📥 Commits

Reviewing files that changed from the base of the PR and between 05280a5 and 741b713.

📒 Files selected for processing (15)
  • .changeset/spotty-donuts-repeat.md
  • docs/chat/connection-adapters.md
  • packages/ai-angular/tests/inject-chat.test.ts
  • packages/ai-client/src/chat-client.ts
  • packages/ai-client/src/types.ts
  • packages/ai-client/tests/chat-client-queue.test.ts
  • packages/ai-client/tests/chat-client.test.ts
  • packages/ai-preact/src/use-mcp-app-bridge.ts
  • packages/ai-react/src/types.ts
  • packages/ai-react/src/use-mcp-app-bridge.ts
  • packages/ai-react/tests/use-chat.test.ts
  • packages/ai-solid/src/types.ts
  • testing/e2e/fixtures/chat/basic.json
  • testing/e2e/src/routes/$provider/$feature.tsx
  • testing/e2e/tests/per-call-body.spec.ts

Comment thread .changeset/spotty-donuts-repeat.md Outdated
Comment thread packages/ai-client/tests/chat-client.test.ts
@gaetansnl

Copy link
Copy Markdown

thanks for this, I have the same issue!

@tombeckenham
tombeckenham force-pushed the feat/send-message-options-body branch from a3f47d9 to 9ee3d19 Compare August 10, 2026 09:06
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit aa007b8

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 35s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 25s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-21 21:24:04 UTC

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR, @boring91! 🙌 @jherr will take a look.

Automated pre-review checks

  • ⚠️ CI failing — worth a look before review
  • ✅ No merge conflicts
  • ✅ Changeset present
  • ✅ E2E test changes included

Automated triage — a human review follows.

@github-actions github-actions Bot added the waiting-on: author Waiting for the author to respond or update label Aug 13, 2026
@tombeckenham
tombeckenham force-pushed the feat/send-message-options-body branch 2 times, most recently from a34529b to 115ec26 Compare August 20, 2026 10:40
@pkg-pr-new

pkg-pr-new Bot commented Aug 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1027

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1027

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1027

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1027

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1027

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1027

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1027

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1027

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1027

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1027

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1027

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1027

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1027

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1027

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1027

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1027

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1027

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1027

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1027

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1027

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1027

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1027

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1027

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1027

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1027

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1027

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/@tanstack/ai-llmgateway@1027

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1027

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1027

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1027

@tanstack/ai-octane

npm i https://pkg.pr.new/@tanstack/ai-octane@1027

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1027

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1027

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1027

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1027

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1027

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1027

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1027

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1027

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@1027

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1027

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1027

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1027

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1027

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1027

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1027

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1027

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1027

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@1027

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1027

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1027

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1027

@tanstack/ai-vertex

npm i https://pkg.pr.new/@tanstack/ai-vertex@1027

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1027

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@1027

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1027

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1027

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1027

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1027

commit: aa007b8

@tombeckenham
tombeckenham force-pushed the feat/send-message-options-body branch from 115ec26 to a420768 Compare August 21, 2026 03:26
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

boring91 and others added 2 commits August 22, 2026 07:07
ChatClient.sendMessage already took a per-call body as its positional
second argument, but every framework hook (useChat, injectChat,
createChat, ...) exposes sendMessage(content, options) and forwards
undefined for it — leaving no race-free way to send per-message data
(e.g. attachment ids) through a hook: updating a reactive chat-level
body/forwardedProps option right before sending can flush after the
send.

SendMessageOptions gains an optional body that ChatClient resolves as a
fallback to the positional argument (positional wins), so every wrapper
inherits the capability with no wrapper code changes. Queued sends
preserve it exactly like the positional form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On ChatClient the second positional argument is the raw body, so the
{ body } options form belongs to the hooks; direct ChatClient callers
pass it as the third argument.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tombeckenham
tombeckenham force-pushed the feat/send-message-options-body branch from a420768 to aa007b8 Compare August 21, 2026 21:07
@github-actions github-actions Bot added waiting-on: maintainer The ball is in the maintainers’ court merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update and removed waiting-on: author Waiting for the author to respond or update waiting-on: maintainer The ball is in the maintainers’ court merge-conflicts Conflicts with the base branch — needs a rebase labels Aug 21, 2026
@github-actions github-actions Bot added merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update and removed waiting-on: author Waiting for the author to respond or update merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: maintainer The ball is in the maintainers’ court labels Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants