feat: add React Native AI support#647
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds React Native/Expo support: XHR-based streaming adapters and shared fetch-response utilities, a public ChangesMobile streaming adapters and client entrypoint
React Native/Expo example apps and smoke testing
React Native documentation and quick start guides
Workspace and package configuration
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
🚀 Changeset Version Preview7 package(s) bumped directly, 23 bumped as dependents. 🟥 Major bumps
🟨 Minor bumps
🟩 Patch bumps
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
|
View your CI Pipeline Execution ↗ for commit d8d0d09
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-devtools-core
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
testing/react-native-smoke/scripts/esbuild-smoke.ts (1)
14-20: ⚡ Quick winConsider removing the
@tanstack/airoot alias.Line 15 aliases
@tanstack/aito the root package entry, but the import surface script (assert-import-surface.tslines 245-248) explicitly forbids importing from@tanstack/airoot, requiring@tanstack/ai/clientinstead.Since the import surface validation would fail if any code imports
@tanstack/ai, this alias should never be used. Including it may cause confusion or suggest the import is permitted.Proposed removal
alias: { - '`@tanstack/ai`': resolve(repoRoot, 'packages/ai/src/index.ts'), '`@tanstack/ai/client`': resolve(repoRoot, 'packages/ai/src/client.ts'),🤖 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 `@testing/react-native-smoke/scripts/esbuild-smoke.ts` around lines 14 - 20, Remove the forbidden root alias entry from the esbuild alias map: delete the '`@tanstack/ai`': resolve(repoRoot, 'packages/ai/src/index.ts') line in the alias object in esbuild-smoke.ts so imports must use the allowed scoped entry ('`@tanstack/ai/client`'); this aligns with the import-surface rule that disallows importing from the '`@tanstack/ai`' root.
🤖 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 `@docs/getting-started/quick-start.md`:
- Around line 21-25: The two adjacent blockquotes ("React Native or Expo app?
Use the headless React hooks..." and the "**Tip:** If you'd prefer not to sign
up...") are separated by a blank line which triggers markdownlint MD028; remove
the empty line between them or merge them into one contiguous blockquote so they
form a single continuous blockquote (locate the callout text starting with
"React Native or Expo app?" and the following "**Tip:**" paragraph and either
delete the blank line or combine the content into one blockquote).
In `@examples/ts-react-native-chat/package.json`:
- Around line 1-44: Add the missing packageManager field to the top-level
package.json object in examples/ts-react-native-chat by adding packageManager:
"pnpm@10.17.0" (same JSON object that contains "name", "private", "type", etc.);
ensure the property is a sibling to those keys so the package manifest follows
the **/package.json guideline.
In `@examples/ts-react-native-chat/scripts/dev.mjs`:
- Around line 338-344: The exit handler for child.on('exit') incorrectly treats
any signal-based exit as success; update the callback (the child.on('exit',
(code, signal) => { ... }) block) so that if signal is non-null you call
shutdown with a non-zero code (e.g., shutdown(1) or shutdown with a code derived
from the signal) instead of shutdown(0), and include the signal in the shutdown
invocation or log for context; keep the existing shuttingDown guard and the
existing success path for code === 0 unchanged.
In `@examples/ts-react-native-chat/scripts/smoke-server.ts`:
- Line 169: The content-type assertion in smoke-server.ts compares the entire
header string exactly, which fails when parameters like "; charset=utf-8" are
present; update the assertion that calls response.headers.get('content-type')
(the expression used in the failing assert.equal) to instead check only the
media type portion or to test startsWith/includes (e.g., trim and split on ';'
or use startsWith) and use an appropriate assertion (assert.ok/assert.match) so
the test accepts "application/x-ndjson" with optional parameters.
- Line 145: The assertion constructs a RegExp from LIVE_RECIPE_SERVER_ERROR
which can misinterpret regex metacharacters; change the check to a plain
substring match instead. Replace the call to assert.match(error.message, new
RegExp(LIVE_RECIPE_SERVER_ERROR)) with a string-based assertion such as
assert.include(error.message, LIVE_RECIPE_SERVER_ERROR) or
assert.ok(error.message.includes(LIVE_RECIPE_SERVER_ERROR)) so the test matches
the literal message text; keep references to LIVE_RECIPE_SERVER_ERROR and
error.message in the updated assertion.
In `@examples/ts-react-native-chat/src/server/index.ts`:
- Around line 11-17: The PORT env value is parsed into port using
Number.parseInt and may be NaN, which can break the call to serve; update the
initialization and before calling serve to validate the parsed port (e.g., parse
with Number.parseInt or Number(), then check Number.isFinite/Number.isInteger
and that port > 0 and within valid TCP port range), and if invalid fall back to
the default 8787 (or exit with a clear error); make this change around the
existing port variable and the serve({ fetch: app.fetch, hostname: '0.0.0.0',
port }) invocation so serve always receives a valid numeric port.
In `@pnpm-workspace.yaml`:
- Around line 6-7: Add an inline comment above the trustPolicyExclude entry in
pnpm-workspace.yaml explaining why semver@5.7.2 || 6.3.1 are excluded: state
which transitive packages pull them in (e.g., make-dir@2.1.0, `@babel/`*,
istanbul-lib-instrument), why they must bypass trustPolicy: 'no-downgrade'
(e.g., blocking build or incompatible with newer semver semantics), and note
whether upgrading the parent dependencies to eliminate these versions was
evaluated and is feasible (or why it is not), so the exclusion rationale is
recorded next to the trustPolicyExclude entry.
---
Nitpick comments:
In `@testing/react-native-smoke/scripts/esbuild-smoke.ts`:
- Around line 14-20: Remove the forbidden root alias entry from the esbuild
alias map: delete the '`@tanstack/ai`': resolve(repoRoot,
'packages/ai/src/index.ts') line in the alias object in esbuild-smoke.ts so
imports must use the allowed scoped entry ('`@tanstack/ai/client`'); this aligns
with the import-surface rule that disallows importing from the '`@tanstack/ai`'
root.
🪄 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
Run ID: 60c0cd63-ed29-4403-a4c5-45d65705658a
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (78)
.changeset/react-native-ai-support.mddocs/advanced/tree-shaking.mddocs/api/ai-client.mddocs/api/ai-react.mddocs/chat/connection-adapters.mddocs/config.jsondocs/getting-started/overview.mddocs/getting-started/quick-start-react-native.mddocs/getting-started/quick-start.mddocs/structured-outputs/multi-turn.mdexamples/ts-react-native-chat/.gitignoreexamples/ts-react-native-chat/README.mdexamples/ts-react-native-chat/app.jsonexamples/ts-react-native-chat/index.tsexamples/ts-react-native-chat/metro.config.cjsexamples/ts-react-native-chat/package.jsonexamples/ts-react-native-chat/scripts/dev.mjsexamples/ts-react-native-chat/scripts/dev.test.mjsexamples/ts-react-native-chat/scripts/smoke-server.tsexamples/ts-react-native-chat/scripts/verify-react-resolution.mjsexamples/ts-react-native-chat/src/App.tsxexamples/ts-react-native-chat/src/server/app.tsexamples/ts-react-native-chat/src/server/index.tsexamples/ts-react-native-chat/tsconfig.jsonpackage.jsonpackages/ai-client/src/chat-client.tspackages/ai-client/src/connection-adapters.tspackages/ai-client/src/events.tspackages/ai-client/src/generation-client.tspackages/ai-client/src/generation-types.tspackages/ai-client/src/index.tspackages/ai-client/src/realtime-client.tspackages/ai-client/src/realtime-types.tspackages/ai-client/src/response-stream.tspackages/ai-client/src/sse-parser.tspackages/ai-client/src/sse-utils.tspackages/ai-client/src/tool-types.tspackages/ai-client/src/types.tspackages/ai-client/src/video-generation-client.tspackages/ai-client/tests/chat-client-abort.test.tspackages/ai-client/tests/chat-client.test.tspackages/ai-client/tests/chat-fetcher.test.tspackages/ai-client/tests/connection-adapters-abort.test.tspackages/ai-client/tests/connection-adapters-xhr.test.tspackages/ai-client/tests/connection-adapters.test.tspackages/ai-client/tests/generation-client.test.tspackages/ai-client/tests/infer-chat-messages.test.tspackages/ai-client/tests/test-utils.tspackages/ai-client/tests/tool-types.test.tspackages/ai-client/tests/video-generation-client.test.tspackages/ai-devtools/src/env.d.tspackages/ai-devtools/tsconfig.jsonpackages/ai-preact/src/index.tspackages/ai-react/src/index.tspackages/ai-react/src/types.tspackages/ai-react/src/use-chat.tspackages/ai-solid/src/index.tspackages/ai-svelte/src/index.tspackages/ai-vue/src/index.tspackages/ai/package.jsonpackages/ai/src/client.tspackages/ai/vite.config.tspackages/preact-ai-devtools/src/env.d.tspackages/react-ai-devtools/src/env.d.tspackages/solid-ai-devtools/src/env.d.tspnpm-workspace.yamltesting/react-native-smoke/.gitignoretesting/react-native-smoke/README.mdtesting/react-native-smoke/app.jsontesting/react-native-smoke/index.tstesting/react-native-smoke/metro.config.cjstesting/react-native-smoke/package.jsontesting/react-native-smoke/scripts/assert-bundle-output.tstesting/react-native-smoke/scripts/assert-import-surface.tstesting/react-native-smoke/scripts/esbuild-smoke.tstesting/react-native-smoke/scripts/react-native-runtime-stub.tsxtesting/react-native-smoke/src/App.tsxtesting/react-native-smoke/tsconfig.json
| > **React Native or Expo app?** Use the headless React hooks with an absolute | ||
| > server URL and a mobile-compatible transport. See | ||
| > [Quick Start: React Native](./quick-start-react-native). | ||
|
|
||
| > **Tip:** If you'd prefer not to sign up with individual AI providers, [OpenRouter](../adapters/openrouter) gives you access to 300+ models with a single API key and is the easiest way to get started. |
There was a problem hiding this comment.
Fix adjacent blockquote formatting to satisfy markdownlint (MD028).
The new React Native callout is separated from the next blockquote by a blank line, which triggers no-blanks-blockquote. Remove the blank line or merge the two callouts into one contiguous blockquote.
Suggested diff
> **React Native or Expo app?** Use the headless React hooks with an absolute
> server URL and a mobile-compatible transport. See
> [Quick Start: React Native](./quick-start-react-native).
-
> **Tip:** If you'd prefer not to sign up with individual AI providers, [OpenRouter](../adapters/openrouter) gives you access to 300+ models with a single API key and is the easiest way to get started.As per coding guidelines: "**/*.{ts,tsx,js,jsx,json,md}: Use Prettier for code formatting".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| > **React Native or Expo app?** Use the headless React hooks with an absolute | |
| > server URL and a mobile-compatible transport. See | |
| > [Quick Start: React Native](./quick-start-react-native). | |
| > **Tip:** If you'd prefer not to sign up with individual AI providers, [OpenRouter](../adapters/openrouter) gives you access to 300+ models with a single API key and is the easiest way to get started. | |
| > **React Native or Expo app?** Use the headless React hooks with an absolute | |
| > server URL and a mobile-compatible transport. See | |
| > [Quick Start: React Native](./quick-start-react-native). | |
| > **Tip:** If you'd prefer not to sign up with individual AI providers, [OpenRouter](../adapters/openrouter) gives you access to 300+ models with a single API key and is the easiest way to get started. |
🧰 Tools
🪛 LanguageTool
[style] ~25-~25: Try using a synonym here to strengthen your writing.
Context: ...s, OpenRouter gives you access to 300+ models with a single API...
(GIVE_PROVIDE)
🪛 markdownlint-cli2 (0.22.1)
[warning] 24-24: Blank line inside blockquote
(MD028, no-blanks-blockquote)
🤖 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 `@docs/getting-started/quick-start.md` around lines 21 - 25, The two adjacent
blockquotes ("React Native or Expo app? Use the headless React hooks..." and the
"**Tip:** If you'd prefer not to sign up...") are separated by a blank line
which triggers markdownlint MD028; remove the empty line between them or merge
them into one contiguous blockquote so they form a single continuous blockquote
(locate the callout text starting with "React Native or Expo app?" and the
following "**Tip:**" paragraph and either delete the blank line or combine the
content into one blockquote).
| { | ||
| "name": "ts-react-native-chat", | ||
| "private": true, | ||
| "type": "module", | ||
| "main": "index.ts", | ||
| "expo": { | ||
| "install": { | ||
| "exclude": [ | ||
| "@types/react", | ||
| "react", | ||
| "typescript" | ||
| ] | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "dev": "node scripts/dev.mjs", | ||
| "dev:server": "tsx src/server/index.ts", | ||
| "dev:app": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'start', '--lan', '--clear']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | ||
| "test:dev-script": "node --test scripts/dev.test.mjs", | ||
| "typecheck": "tsc --noEmit", | ||
| "smoke:server": "tsx scripts/smoke-server.ts", | ||
| "smoke:expo": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'export', '--platform', 'ios', '--no-bytecode', '--output-dir', '.expo-example-dist', '--max-workers', '0']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | ||
| "verify:react-resolution": "node scripts/verify-react-resolution.mjs", | ||
| "smoke": "pnpm smoke:server && pnpm smoke:expo" | ||
| }, | ||
| "dependencies": { | ||
| "@hono/node-server": "^1.19.6", | ||
| "@tanstack/ai": "workspace:*", | ||
| "@tanstack/ai-openai": "workspace:*", | ||
| "@tanstack/ai-react": "workspace:*", | ||
| "concurrently": "^9.1.2", | ||
| "dotenv": "^17.2.3", | ||
| "expo": "~54.0.34", | ||
| "hono": "^4.10.6", | ||
| "react": "19.1.0", | ||
| "react-native": "0.81.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^24.10.1", | ||
| "@types/react": "19.1.17", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "5.9.3" | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import json, pathlib
p = pathlib.Path("examples/ts-react-native-chat/package.json")
data = json.loads(p.read_text())
print("packageManager:", data.get("packageManager"))
PYRepository: TanStack/ai
Length of output: 76
Add packageManager to this example manifest.
examples/ts-react-native-chat/package.json is missing packageManager: "pnpm@10.17.0" per the **/package.json coding guideline.
Proposed fix
{
"name": "ts-react-native-chat",
"private": true,
+ "packageManager": "pnpm@10.17.0",
"type": "module",
"main": "index.ts",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| { | |
| "name": "ts-react-native-chat", | |
| "private": true, | |
| "type": "module", | |
| "main": "index.ts", | |
| "expo": { | |
| "install": { | |
| "exclude": [ | |
| "@types/react", | |
| "react", | |
| "typescript" | |
| ] | |
| } | |
| }, | |
| "scripts": { | |
| "dev": "node scripts/dev.mjs", | |
| "dev:server": "tsx src/server/index.ts", | |
| "dev:app": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'start', '--lan', '--clear']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | |
| "test:dev-script": "node --test scripts/dev.test.mjs", | |
| "typecheck": "tsc --noEmit", | |
| "smoke:server": "tsx scripts/smoke-server.ts", | |
| "smoke:expo": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'export', '--platform', 'ios', '--no-bytecode', '--output-dir', '.expo-example-dist', '--max-workers', '0']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | |
| "verify:react-resolution": "node scripts/verify-react-resolution.mjs", | |
| "smoke": "pnpm smoke:server && pnpm smoke:expo" | |
| }, | |
| "dependencies": { | |
| "@hono/node-server": "^1.19.6", | |
| "@tanstack/ai": "workspace:*", | |
| "@tanstack/ai-openai": "workspace:*", | |
| "@tanstack/ai-react": "workspace:*", | |
| "concurrently": "^9.1.2", | |
| "dotenv": "^17.2.3", | |
| "expo": "~54.0.34", | |
| "hono": "^4.10.6", | |
| "react": "19.1.0", | |
| "react-native": "0.81.5" | |
| }, | |
| "devDependencies": { | |
| "@types/node": "^24.10.1", | |
| "@types/react": "19.1.17", | |
| "tsx": "^4.21.0", | |
| "typescript": "5.9.3" | |
| } | |
| } | |
| { | |
| "name": "ts-react-native-chat", | |
| "private": true, | |
| "packageManager": "pnpm@10.17.0", | |
| "type": "module", | |
| "main": "index.ts", | |
| "expo": { | |
| "install": { | |
| "exclude": [ | |
| "`@types/react`", | |
| "react", | |
| "typescript" | |
| ] | |
| } | |
| }, | |
| "scripts": { | |
| "dev": "node scripts/dev.mjs", | |
| "dev:server": "tsx src/server/index.ts", | |
| "dev:app": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'start', '--lan', '--clear']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | |
| "test:dev-script": "node --test scripts/dev.test.mjs", | |
| "typecheck": "tsc --noEmit", | |
| "smoke:server": "tsx scripts/smoke-server.ts", | |
| "smoke:expo": "node -e \"const { spawnSync } = require('node:child_process'); const env = { ...process.env, EXPO_NO_DOTENV: '1' }; delete env.OPENAI_API_KEY; delete env.OPENAI_MODEL; const args = ['exec', 'expo', 'export', '--platform', 'ios', '--no-bytecode', '--output-dir', '.expo-example-dist', '--max-workers', '0']; const result = process.platform === 'win32' ? spawnSync(env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', 'pnpm.cmd', ...args], { stdio: 'inherit', env }) : spawnSync('pnpm', args, { stdio: 'inherit', env }); process.exit(result.status ?? 1)\"", | |
| "verify:react-resolution": "node scripts/verify-react-resolution.mjs", | |
| "smoke": "pnpm smoke:server && pnpm smoke:expo" | |
| }, | |
| "dependencies": { | |
| "`@hono/node-server`": "^1.19.6", | |
| "`@tanstack/ai`": "workspace:*", | |
| "`@tanstack/ai-openai`": "workspace:*", | |
| "`@tanstack/ai-react`": "workspace:*", | |
| "concurrently": "^9.1.2", | |
| "dotenv": "^17.2.3", | |
| "expo": "~54.0.34", | |
| "hono": "^4.10.6", | |
| "react": "19.1.0", | |
| "react-native": "0.81.5" | |
| }, | |
| "devDependencies": { | |
| "`@types/node`": "^24.10.1", | |
| "`@types/react`": "19.1.17", | |
| "tsx": "^4.21.0", | |
| "typescript": "5.9.3" | |
| } | |
| } |
🤖 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 `@examples/ts-react-native-chat/package.json` around lines 1 - 44, Add the
missing packageManager field to the top-level package.json object in
examples/ts-react-native-chat by adding packageManager: "pnpm@10.17.0" (same
JSON object that contains "name", "private", "type", etc.); ensure the property
is a sibling to those keys so the package manifest follows the **/package.json
guideline.
| child.on('exit', (code, signal) => { | ||
| if (shuttingDown) return | ||
| if (code === 0 || signal) { | ||
| shutdown(0) | ||
| } else { | ||
| shutdown(code ?? 1) | ||
| } |
There was a problem hiding this comment.
Don’t treat all signal-based child exits as success.
Line 340 currently maps any signal exit to code 0. If a child is terminated unexpectedly (for example SIGKILL), the runner still exits successfully and masks failure.
Suggested fix
child.on('exit', (code, signal) => {
if (shuttingDown) return
- if (code === 0 || signal) {
+ if (code === 0) {
shutdown(0)
+ } else if (signal === 'SIGINT' || signal === 'SIGTERM') {
+ shutdown(0)
} else {
- shutdown(code ?? 1)
+ shutdown(code ?? 1)
}
})🤖 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 `@examples/ts-react-native-chat/scripts/dev.mjs` around lines 338 - 344, The
exit handler for child.on('exit') incorrectly treats any signal-based exit as
success; update the callback (the child.on('exit', (code, signal) => { ... })
block) so that if signal is non-null you call shutdown with a non-zero code
(e.g., shutdown(1) or shutdown with a code derived from the signal) instead of
shutdown(0), and include the signal in the shutdown invocation or log for
context; keep the existing shuttingDown guard and the existing success path for
code === 0 unchanged.
|
|
||
| assert.equal(response.status, 200) | ||
| assert.ok(error) | ||
| assert.match(error.message, new RegExp(LIVE_RECIPE_SERVER_ERROR)) |
There was a problem hiding this comment.
Use string matching instead of constructing a regex from message text.
Line 145 builds a RegExp from LIVE_RECIPE_SERVER_ERROR; metacharacters in the message can make this assertion flaky.
Suggested fix
- assert.match(error.message, new RegExp(LIVE_RECIPE_SERVER_ERROR))
+ assert.ok(error.message.includes(LIVE_RECIPE_SERVER_ERROR))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert.match(error.message, new RegExp(LIVE_RECIPE_SERVER_ERROR)) | |
| assert.ok(error.message.includes(LIVE_RECIPE_SERVER_ERROR)) |
🤖 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 `@examples/ts-react-native-chat/scripts/smoke-server.ts` at line 145, The
assertion constructs a RegExp from LIVE_RECIPE_SERVER_ERROR which can
misinterpret regex metacharacters; change the check to a plain substring match
instead. Replace the call to assert.match(error.message, new
RegExp(LIVE_RECIPE_SERVER_ERROR)) with a string-based assertion such as
assert.include(error.message, LIVE_RECIPE_SERVER_ERROR) or
assert.ok(error.message.includes(LIVE_RECIPE_SERVER_ERROR)) so the test matches
the literal message text; keep references to LIVE_RECIPE_SERVER_ERROR and
error.message in the updated assertion.
| }) | ||
|
|
||
| assert.equal(response.status, 200) | ||
| assert.equal(response.headers.get('content-type'), 'application/x-ndjson') |
There was a problem hiding this comment.
Make the content-type assertion tolerant of parameters.
Line 169 requires an exact value. Responses commonly include ; charset=utf-8, which would fail this smoke check unnecessarily.
Suggested fix
- assert.equal(response.headers.get('content-type'), 'application/x-ndjson')
+ assert.ok(
+ response.headers.get('content-type')?.startsWith('application/x-ndjson'),
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert.equal(response.headers.get('content-type'), 'application/x-ndjson') | |
| assert.ok( | |
| response.headers.get('content-type')?.startsWith('application/x-ndjson'), | |
| ) |
🤖 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 `@examples/ts-react-native-chat/scripts/smoke-server.ts` at line 169, The
content-type assertion in smoke-server.ts compares the entire header string
exactly, which fails when parameters like "; charset=utf-8" are present; update
the assertion that calls response.headers.get('content-type') (the expression
used in the failing assert.equal) to instead check only the media type portion
or to test startsWith/includes (e.g., trim and split on ';' or use startsWith)
and use an appropriate assertion (assert.ok/assert.match) so the test accepts
"application/x-ndjson" with optional parameters.
| const port = Number.parseInt(process.env.PORT ?? '8787', 10) | ||
|
|
||
| serve({ | ||
| fetch: app.fetch, | ||
| hostname: '0.0.0.0', | ||
| port, | ||
| }) |
There was a problem hiding this comment.
Validate PORT before passing it to serve.
Number.parseInt can return NaN for malformed env input, and passing that through can fail startup.
Proposed fix
-const port = Number.parseInt(process.env.PORT ?? '8787', 10)
+const parsedPort = Number.parseInt(process.env.PORT ?? '8787', 10)
+const port =
+ Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535
+ ? parsedPort
+ : 8787🤖 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 `@examples/ts-react-native-chat/src/server/index.ts` around lines 11 - 17, The
PORT env value is parsed into port using Number.parseInt and may be NaN, which
can break the call to serve; update the initialization and before calling serve
to validate the parsed port (e.g., parse with Number.parseInt or Number(), then
check Number.isFinite/Number.isInteger and that port > 0 and within valid TCP
port range), and if invalid fall back to the default 8787 (or exit with a clear
error); make this change around the existing port variable and the serve({
fetch: app.fetch, hostname: '0.0.0.0', port }) invocation so serve always
receives a valid numeric port.
| trustPolicyExclude: | ||
| - 'semver@5.7.2 || 6.3.1' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Analyze semver dependency tree to understand version conflicts
echo "=== Checking semver dependency tree ==="
pnpm why semver
echo -e "\n=== Checking for packages that depend on old semver versions ==="
pnpm list semver --depth=Infinity --long
echo -e "\n=== Checking if React Native packages are pulling in old semver ==="
rg -n 'semver.*5\.7\.2|semver.*6\.3\.1' --glob '**/package.json' -C2Repository: TanStack/ai
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== pnpm-workspace.yaml (lines around trustPolicy) ==="
nl -ba pnpm-workspace.yaml | sed -n '1,40p'
echo -e "\n=== pnpm why semver@5.7.2 (limit output) ==="
pnpm why semver@5.7.2 --depth 1 || true
echo -e "\n=== pnpm why semver@6.3.1 (limit output) ==="
pnpm why semver@6.3.1 --depth 1 || true
echo -e "\n=== Top-level dependents for semver@5.7.2 ==="
pnpm list --depth 0 semver@5.7.2 || true
echo -e "\n=== Top-level dependents for semver@6.3.1 ==="
pnpm list --depth 0 semver@6.3.1 || true
echo -e "\n=== Find exact entries of trustPolicyExclude in repo ==="
rg -n "trustPolicyExclude|trustPolicy:" -S pnpm-workspace.yaml **/pnpm-workspace.yaml **/*pnpm-workspace*.yaml || trueRepository: TanStack/ai
Length of output: 171
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== pnpm-workspace.yaml (lines around trustPolicy) ==="
cat -n pnpm-workspace.yaml | sed -n '1,80p'
echo -e "\n=== pnpm-workspace.yaml (grep trustPolicy*) ==="
rg -n "trustPolicy" pnpm-workspace.yaml || true
echo -e "\n=== pnpm why semver@5.7.2 (limit output) ==="
pnpm why semver@5.7.2 --depth 1 || true
echo -e "\n=== pnpm why semver@6.3.1 (limit output) ==="
pnpm why semver@6.3.1 --depth 1 || true
echo -e "\n=== Find exact entries of trustPolicyExclude in repo ==="
rg -n "trustPolicyExclude|trustPolicy:" -S pnpm-workspace.yaml **/pnpm-workspace*.yaml **/*pnpm*.yaml || trueRepository: TanStack/ai
Length of output: 2821
🌐 Web query:
pnpm trustPolicy no-downgrade trustPolicyExclude semantics
💡 Result:
The trustPolicy and trustPolicyExclude settings are supply chain security features in pnpm (introduced in v10.21.0 and v10.22.0, respectively) designed to mitigate risks from potentially compromised packages [1][2]. trustPolicy: no-downgrade When set to no-downgrade (default is off), pnpm checks the "trust level" of a package version against its history [1][2]. The trust levels are ordered: Trusted Publisher > Provenance > None [3]. If a package version has a lower trust level than any previously published version, pnpm will fail the installation [1][3]. These checks are based on the publication date, not semantic versioning [1]. Starting in v10.24.0, prerelease versions are ignored during this evaluation to prevent trusted prereleases from blocking stable releases that lack trust evidence [1]. trustPolicyExclude This setting (type: string[]) allows you to provide a list of package selectors to bypass the trustPolicy check [1][2]. This is useful for allowing specific, known-safe versions that do not meet the stricter trust requirements [1][2]. You can specify exact versions (e.g., 'chokidar@4.0.3'), version ranges (e.g., 'webpack@4.47.0 || 5.102.1'), or even exclude all versions of a package by omitting the version [1][4]. In summary, trustPolicy: no-downgrade enforces that trust evidence for a package never regresses, while trustPolicyExclude provides a necessary escape hatch for legitimate cases where downgrading the trust evidence is expected [1][3][2].
Citations:
- 1: https://pnpm.io/settings
- 2: https://pnpm.io/supply-chain-security
- 3: [Feature Request] Support trustPolicy to reject packages with provenance downgrade at install time npm/cli#9242
- 4: trustPolicyIgnoreAfter pnpm/pnpm#10352
Document and justify the trustPolicyExclude exception for semver
pnpm-workspace.yaml sets trustPolicy: 'no-downgrade' and excludes semver@5.7.2 || 6.3.1. Those semver versions come in transitively (semver@5.7.2 via make-dir@2.1.0; semver@6.3.1 via several @babel/* packages and istanbul-lib-instrument). Because no-downgrade is enforced on pnpm’s supply-chain “trust level” regression (not semver ordering), an exclusion can be a legitimate escape hatch—but it materially weakens the policy unless the rationale is recorded.
Add a short comment explaining why these specific versions must bypass no-downgrade (and whether upgrading the pulling deps to avoid them is feasible).
🤖 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 `@pnpm-workspace.yaml` around lines 6 - 7, Add an inline comment above the
trustPolicyExclude entry in pnpm-workspace.yaml explaining why semver@5.7.2 ||
6.3.1 are excluded: state which transitive packages pull them in (e.g.,
make-dir@2.1.0, `@babel/`*, istanbul-lib-instrument), why they must bypass
trustPolicy: 'no-downgrade' (e.g., blocking build or incompatible with newer
semver semantics), and note whether upgrading the parent dependencies to
eliminate these versions was evaluated and is feasible (or why it is not), so
the exclusion rationale is recorded next to the trustPolicyExclude entry.
Summary
ignoreDeprecationsvalue in@tanstack/ai-devtools-core.Local validation
pnpm install --frozen-lockfile(prior worker)pnpm --filter ts-react-native-chat smoke(prior worker)pnpm run test:react-native(prior worker)pnpm test:docs(prior worker)pnpm nx run @tanstack/ai-devtools-core:test:typespnpm nx run @tanstack/ai-devtools-core:buildpnpm run test:prpnpm run build:allpnpm --filter @tanstack/ai-e2e test:e2e(exit 0; 208 passed, 8 flaky retries reported)git diff --checkSummary by CodeRabbit
New Features
Documentation
Tests
Bug Fixes