Skip to content

chore: monorepo setup audit (build, tsconfig, types, docs)#572

Merged
AlemTuzlak merged 14 commits into
mainfrom
chore/monorepo-setup-audit
May 19, 2026
Merged

chore: monorepo setup audit (build, tsconfig, types, docs)#572
AlemTuzlak merged 14 commits into
mainfrom
chore/monorepo-setup-audit

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

@AlemTuzlak AlemTuzlak commented May 18, 2026

Summary

End-to-end setup audit of the monorepo, focused on making the repo easier to contribute to without changing user-facing runtime behaviour (one intentional removal, called out below).

Seven commits, grouped by theme:

  1. chore(monorepo)testing/** excluded from build/CI scripts (was only excluding examples/**); defensive ignore list of every in-workspace private package in .changeset/config.json; vite bumped ^7.2.7 → ^7.3.3 across every declaring package.json to resolve a transitive version skew; engines: { node: ">=24", pnpm: ">=11.0.0" } on the root.
  2. chore(tsconfig) — new tsconfig.base.json at the root holding the shared compilerOptions; every package tsconfig migrated to extend it with consistent include: ["src", "tests"] and only package-unique overrides. Tests are now included in tsc everywhere (previously several packages silently excluded them).
  3. refactor(ai-gemini)GeminiThinkingOptions and GeminiThinkingAdvancedOptions merged into a single interface with all fields optional. The old shape's intersection couldn't cleanly express the runtime contract.
  4. refactor(ai-anthropic)'system' removed from validKeys; modelOptions.system is no longer accepted. System prompts now flow through the systemPrompts public API as designed. See the note below.
  5. fix(ai) — re-export Logger from the @tanstack/ai/adapter-internals subpath (was an export interface not re-exported on the public path).
  6. test — fix the ~200 pre-existing type errors that the typecheck standardization surfaced (stale model names, EventType enum drift, missing required fields on result mocks, etc.) and clean up unsafe casts (10 as any + 8 as unknown as removed, 5 remaining at trust boundaries with justification comments).
  7. docs(contributing)CONTRIBUTING.md, .editorconfig, .vscode/extensions.json. Fixes the PR template's broken link to a previously-missing CONTRIBUTING.md.

Behaviour change to flag (commit 4)

modelOptions.system is no longer accepted by the Anthropic adapter. Anyone constructing system prompts that way (via a cast — the public type never exposed it) needs to move to systemPrompts.

Regression to track: systemPrompts: Array<string> doesn't currently accept cache_control for prompt-caching. A follow-up will extend systemPrompts to Array<string | { content, metadata }> with provider-typed metadata. Design doc drafted separately.

Changesets

Not added — maintainers may want different bump levels. My read:

  • @tanstack/ai-anthropic — at least a minor (silent removal of an internal-but-accessible-via-cast escape hatch). Could argue major.
  • @tanstack/ai-gemini — patch (the public type loosens; nothing tightens).
  • @tanstack/ai — patch (additive Logger re-export).

Test plan

  • pnpm test:ci is green on this branch (verified locally: 33 projects × 8 targets all pass).
  • CI on this PR matches the local result.
  • Review the Anthropic system-prompt change — is removing the cast escape hatch what we want, or do you prefer a different shape for the follow-up?
  • Spot-check CONTRIBUTING.md for accuracy on commands and conventions.
  • Decide on changeset bump levels and add them.

Summary by CodeRabbit

  • Documentation

    • Added a comprehensive CONTRIBUTING guide
    • Removed outdated Node.js "Requirements" from READMEs
  • Bug Fixes

    • Clarified Anthropic system-prompt handling (use systemPrompts; unknown modelOptions keys are now logged)
    • Simplified Gemini thinking-related options and typing
  • Chores

    • Bumped dev tooling versions across packages/examples
    • Consolidated TypeScript configs to a shared base
    • Added EditorConfig and VS Code extension recommendations
    • Added package manager engine constraint and updated changeset ignore list

Review Change Stack

… bump vite

- Exclude testing/** alongside examples/** in build, build:all, test:ci, test:pr,
  test:lib, test:types, test:eslint, test:build, test:coverage. Internal test
  harnesses (testing/e2e, testing/panel) had build scripts but no exclude,
  so they were getting built on every release pipeline.
- Add an `ignore` list to .changeset/config.json covering every in-workspace
  private package. Defends against accidental publication if `"private": true`
  is ever dropped from a package by mistake.
- Bump vite from ^7.2.7 to ^7.3.3 across every declaring package.json. Resolves
  a transitive version skew (some dev-only plugins pull vite@7.3.x) that was
  breaking typechecking of vite.config.ts files.
- Add `engines` to the root package.json: `node >=24`, `pnpm >=11.0.0`.
…ests

- Introduce tsconfig.base.json at the repo root with the shared compilerOptions.
  Root tsconfig.json now extends it for the small set of root-level files
  (scripts, config files) we still typecheck.
- Migrate every package tsconfig to extend tsconfig.base.json with a consistent
  shape: only the package's unique compilerOptions overrides (outDir, rootDir
  where needed, JSX runtime, framework lib) plus a uniform
  `include: ["src", "tests"]` / `exclude: ["node_modules", "dist"]`.
- Drop brittle one-off patterns: per-package duplicated compilerOptions
  (ai-devtools, ai-isolate-cloudflare), explicit single-test-file includes
  (ai-anthropic, ai-gemini), and `**/*.config.ts` excludes that were silently
  hiding test files from `tsc`.
- Config files (vite.config.ts, vitest.config.ts) are no longer in the include
  set. They're typechecked at build time by vite/vitest themselves and the
  cross-tool type matrix makes them brittle to check via `tsc`.

Net effect: `pnpm test:types` now actually typechecks the tests in every
package. Prior to this commit several packages were silently excluding tests
from the typecheck pipeline.
…interface

The old shape kept thinking config in two separate interfaces — one with
required `includeThoughts` + optional `thinkingBudget`, the other with
`thinkingLevel` — and intersected both into ExternalTextProviderOptions.
The intersection produced a `thinkingConfig` shape where neither
`includeThoughts`+`thinkingBudget` nor `thinkingLevel` could be passed
cleanly, even though the adapter reads both at runtime
(see src/adapters/text.ts mapCommonOptionsToGemini).

Merge into a single GeminiThinkingOptions with all three fields optional.
Drop GeminiThinkingAdvancedOptions entirely. Update model-meta intersections
and the sync-provider-models template that references the type.

GeminiThinkingOptions is the only thinking type exported from
@tanstack/ai-gemini, so no public-API breakage. GeminiThinkingAdvancedOptions
was never exported.
`system` was sneaking into modelOptions via `validKeys` and getting spread
over the system block constructed from systemPrompts. That was a leaky
abstraction: it bypassed the cross-provider `systemPrompts` API and forced
users to construct Anthropic's TextBlockParam shape manually to attach
features like cache_control.

- Remove `'system'` from validKeys in adapters/text.ts. The adapter no longer
  accepts a system override from modelOptions.
- Keep `system?: string | Array<TextBlockParam>` on InternalTextProviderOptions
  (the adapter still constructs it internally from `options.systemPrompts`).
  Mark it explicitly as internal in the JSDoc.
- Delete the test that exercised the override behaviour and prune the
  `system` field + `& { system: string }` satisfies intersection from the
  one other test that was using modelOptions.system.

Regression to be aware of: there is currently no public way to attach
Anthropic `cache_control` to system prompts. A follow-up will extend
`systemPrompts` to accept `{ content, metadata }` with provider-specific
metadata (cache_control for Anthropic, others later).
`Logger` was declared as an `export interface` in src/logger/types.ts but
wasn't re-exported from src/adapter-internals.ts. Provider adapter packages
consume internals only via the `@tanstack/ai/adapter-internals` subpath, so
without this re-export they couldn't reach the `Logger` type without an
ambient import that would break the encapsulation boundary.

Surfaced by the openai-base tests once `tsc` started checking tests.
…tion

Including tests in `tsc` (see the tsconfig commit) surfaced ~200 pre-existing
type errors across the test suite that had silently rotted as source types
evolved. This commit fixes them and tightens the remaining type assertions.

Patterns fixed:

- Stale model name literals (`claude-3-7-sonnet-20250219` →
  `claude-3-7-sonnet`, `grok-4-0709` → `grok-4`, `chatgpt-4.0` →
  `chatgpt-4o-latest`, etc.).
- EventType string literals replaced with the enum values from
  `@tanstack/ai`/`@ag-ui/core` in groq/grok/openai-base/openai tests.
- Removed/renamed provider option fields (`generationConfig.*` flattened
  onto top-level Gemini options; `system` removed from anthropic
  modelOptions tests).
- Required fields added to mock generation result objects (`id` on
  image/speech/transcription mocks, `id` + `usage` on summarize mocks)
  to satisfy GenerationFetcher result types.
- `MakeInputModalitiesTypes<['text', 'image', ...]>` wrappers and
  provider-typed content parts (`AnthropicTextPart`, `OpenAITextPart`,
  `GeminiImagePart`, etc.) plumbed into the model-meta tests.
- `tools` array entries replaced with typed `AnyClientTool` factories
  for the elevenlabs realtime tests.
- Cloudflare worker tests: 7 `as any` casts collapsed into a single
  `readJson<T>` helper with one trust-boundary cast and `Extract<>` types
  per response status arm.
- ai-react/ai-solid `useChat({ onToolCall })` tests: `onToolCall` is not
  a public UseChatOptions field — it's set internally by ChatClient via
  the `tools` array. Deleted one test of nonexistent behaviour and
  removed dead `onToolCall` options from two `addToolResult` tests.
- ai-react/ai-solid `useGeneration` tests: explicit generic arguments
  instead of `onResult ... as any`; `as const` on inline chunk arrays so
  StreamChunk literals infer with EventType enum values intact.
- ai-svelte `create-generation` tests: EventType enum + required fields
  on AGUIEvent shapes (`threadId` on RUN_STARTED/RUN_FINISHED, `message`
  on RUN_ERROR).
- openai-base tests now build a real `new OpenAI({ apiKey })` and
  monkey-patch a typed `MockChatCompletionCreate` / `MockResponsesCreate`
  signature instead of `as unknown as OpenAI`.

Remaining `as`-style casts in the touched files: 5 total, all at trust
boundaries with justification comments (Response.json() → typed shape;
discriminated-union narrowing for the schemaless useChat branch; AGUI
RUN_ERROR carrying both spec-shape `message` and legacy `error.message`).

`@ts-expect-error` is used in one place (audio-adapter.test.ts) for the
"unsupported model name is rejected" test, replacing an `as never` cast.

No production code changes other than:
- A new export of an existing `Logger` interface (separate commit).
- The anthropic modelOptions.system removal (separate commit).
…ndations

- CONTRIBUTING.md covering prereqs (Node 24, pnpm 11), initial setup,
  repo layout, day-to-day commands, the per-package tsconfig pattern,
  where to add unit tests, the (mandatory) E2E coverage matrix,
  changesets, PR flow, and how to add a new provider adapter. Documents
  the known gaps: .vue/.svelte SFCs are not linted today, and build
  configs are not in the `tsc` pass.
- .editorconfig with the repo's existing conventions (LF, utf-8,
  2-space, final newline).
- .vscode/extensions.json recommending eslint, prettier, vitest
  explorer, nx, svelte, vue, editorconfig.

The PR template already linked to a missing CONTRIBUTING.md; this fixes
that broken link.
@AlemTuzlak AlemTuzlak requested a review from a team as a code owner May 18, 2026 11:39
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5898cdd3-96f7-4390-8de1-5600557fd04d

📥 Commits

Reviewing files that changed from the base of the PR and between c284260 and 1031edd.

📒 Files selected for processing (4)
  • examples/ts-svelte-chat/package.json
  • examples/ts-vue-chat/package.json
  • packages/typescript/ai-ollama/package.json
  • packages/typescript/ai-react/tests/use-chat-structured-output.test.ts
✅ Files skipped from review due to trivial changes (1)
  • examples/ts-vue-chat/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/typescript/ai-ollama/package.json
  • examples/ts-svelte-chat/package.json

📝 Walkthrough

Walkthrough

Adds a shared TypeScript base; updates package tsconfigs to extend it; bumps Vite across packages; tightens root scripts and pnpm engine; populates Changesets ignore; removes Node “Requirements” docs; updates provider option/types (Anthropic, Gemini, OpenAI); and refactors many tests for stricter typing and EventType usage.

Changes

Monorepo config + providers/tests refactor

Layer / File(s) Summary
Unified repository update sweep
.changeset/config.json, .editorconfig, .vscode/extensions.json, tsconfig.base.json, tsconfig.json, package.json, scripts/sync-provider-models.ts, CONTRIBUTING.md, README.md, packages/**, examples/**, testing/**
Adds tsconfig.base.json and updates package tsconfigs to extend it; bumps Vite devDependencies across examples/packages; adds engines.pnpm and narrows Nx scripts; populates Changesets ignore; removes Node “Requirements” notes from READMEs; adjusts provider option types/mappings (Anthropic, Gemini, OpenAI); updates sync-provider script; and refactors tests for stronger typing, non-null assertions, and EventType enum usage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hopped through configs, ears held high,
Tsconfigs unified beneath one sky.
Vite bumps and scripts trimmed with care,
Providers typed and tests made fair.
A rabbit cheers: tidy repo, carrot share. 🥕

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/monorepo-setup-audit

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

🚀 Changeset Version Preview

2 package(s) bumped directly, 0 bumped as dependents.

🟨 Minor bumps

Package Version Reason
@tanstack/ai-anthropic 0.8.8 → 0.9.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-react 0.10.0 → 0.10.1 Changeset

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 18, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​vite@​7.3.3961008298100

View full report

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 18, 2026

View your CI Pipeline Execution ↗ for commit c284260

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-19 09:05:32 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 18, 2026

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 1031edd

… isolated-vm)

The previous "Node 24+" claim was overreaching:

- Only `@tanstack/ai-isolate-node` (via `isolated-vm`) has a Node version
  floor. Everything else in the workspace runs fine on older Node.
- `isolated-vm`'s actual `engines.node` is `>=22.0.0` (see
  https://github.com/laverdet/isolated-vm/blob/main/package.json), not 24.

Changes:
- Root package.json: drop the `node: ">=24"` entry from engines (keep the
  pnpm constraint). No global Node floor for development.
- packages/typescript/ai-isolate-node/package.json: bump `engines.node`
  from `>=18` to `>=22` to match upstream.
- packages/typescript/ai-isolate-node/README.md: same — say `>=22`, and
  add the `--no-node-snapshot` flag note for Node 20+ runtime usage.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typescript/ai-gemini/tests/model-meta.test.ts (1)

243-249: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore a thinking-specific assertion in this test.

Line 243 says this test validates thinking support, but Line 248 only checks stopSequences (already covered elsewhere). Please assert thinkingConfig so this test still guards the intended contract.

✅ Suggested fix
-  it('thinking models should allow thinkingConfig in generationConfig', () => {
+  it('thinking models should allow thinkingConfig', () => {
     type Options = GeminiChatModelProviderOptionsByName['gemini-2.5-pro']

-    // The generationConfig should include thinkingConfig from GeminiCommonConfigOptions
-    // which intersects with GeminiThinkingOptions
-    expectTypeOf<Options>().toHaveProperty('stopSequences')
+    expectTypeOf<Options>().toHaveProperty('thinkingConfig')
   })
🤖 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 `@packages/typescript/ai-gemini/tests/model-meta.test.ts` around lines 243 -
249, The test currently asserts only stopSequences but should assert the
presence of the thinking-specific config: update the assertion in the test block
"thinking models should allow thinkingConfig in generationConfig" to check that
the resolved Options type (alias Options from
GeminiChatModelProviderOptionsByName['gemini-2.5-pro']) has a thinkingConfig
property (e.g., replace or add to
expectTypeOf<Options>().toHaveProperty('thinkingConfig') so the test verifies
thinkingConfig is present).
🧹 Nitpick comments (2)
packages/typescript/ai-openai/tests/openai-adapter.test.ts (1)

85-131: ⚡ Quick win

Assert systemPrompts propagation in this mapping test.

Line 85 adds systemPrompts, but Lines 115-131 never verify it appears in the outbound payload. That leaves a regression gap in an option-mapping test.

Proposed test hardening
     expect(payload).toMatchObject({
       model: 'gpt-4o-mini',
       temperature: 0.25,
       top_p: 0.6,
       max_output_tokens: 1024, // Responses API uses max_output_tokens instead of max_tokens
       stream: true,
       tool_choice: 'required', // From modelOptions
     })
+    expect(JSON.stringify(payload)).toContain('Stay concise')
🤖 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 `@packages/typescript/ai-openai/tests/openai-adapter.test.ts` around lines 85 -
131, The test adds systemPrompts but never asserts they are sent; update the
mapping test that inspects responsesCreate's payload (the payload variable from
responsesCreate.mock.calls[0]) to verify systemPrompts are propagated into the
outbound payload input—e.g. assert payload.input.system_prompts (or
payload.input.systemPrompts depending on mapping) includes ['Stay concise'] so
the test ensures systemPrompts passed into the adapter are present in the
Responses API payload.
packages/typescript/ai-vue-ui/tsconfig.json (1)

11-11: ⚡ Quick win

Include tests in this tsconfig to keep repo-wide typecheck coverage consistent.

Line 11 only includes src, so test type errors in this package can be missed compared to the rest of the monorepo policy.

Proposed diff
-  "include": ["src"],
+  "include": ["src", "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 `@packages/typescript/ai-vue-ui/tsconfig.json` at line 11, The tsconfig.json
currently sets the "include" array to only ["src"], which omits tests from
project-wide typechecks; update the "include" property in this file to also
cover test files (e.g., add patterns like "test", "tests", "**/*.spec.ts",
"**/*.test.ts", or similar used across the repo) so that test files in the
ai-vue-ui package are type-checked alongside src; modify the "include" array in
tsconfig.json to add the appropriate test globs.
🤖 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 `@package.json`:
- Around line 10-12: The engines.pnpm constraint currently set to ">=11.0.0"
conflicts with the monorepo policy; update the engines.pnpm entry (the "engines"
object and its "pnpm" field) to allow the required pnpm version by changing the
version string to ">=10.17.0" (or to the exact policy version "10.17.0" if
pinning is preferred) so installs using pnpm@10.17.0 are accepted.

In `@packages/typescript/ai-isolate-node/README.md`:
- Around line 7-9: The README.md has a contradictory version note: update the
bullet that currently says "On Node.js 20.x and later, pass `--no-node-snapshot`
when running `node`" to reference Node.js 22 instead so it matches the stated
minimum "Node.js >= 22"; edit the README.md bullet mentioning
`--no-node-snapshot` to read "On Node.js 22.x and later, pass
`--no-node-snapshot` when running `node`" to keep the document consistent.

---

Outside diff comments:
In `@packages/typescript/ai-gemini/tests/model-meta.test.ts`:
- Around line 243-249: The test currently asserts only stopSequences but should
assert the presence of the thinking-specific config: update the assertion in the
test block "thinking models should allow thinkingConfig in generationConfig" to
check that the resolved Options type (alias Options from
GeminiChatModelProviderOptionsByName['gemini-2.5-pro']) has a thinkingConfig
property (e.g., replace or add to
expectTypeOf<Options>().toHaveProperty('thinkingConfig') so the test verifies
thinkingConfig is present).

---

Nitpick comments:
In `@packages/typescript/ai-openai/tests/openai-adapter.test.ts`:
- Around line 85-131: The test adds systemPrompts but never asserts they are
sent; update the mapping test that inspects responsesCreate's payload (the
payload variable from responsesCreate.mock.calls[0]) to verify systemPrompts are
propagated into the outbound payload input—e.g. assert
payload.input.system_prompts (or payload.input.systemPrompts depending on
mapping) includes ['Stay concise'] so the test ensures systemPrompts passed into
the adapter are present in the Responses API payload.

In `@packages/typescript/ai-vue-ui/tsconfig.json`:
- Line 11: The tsconfig.json currently sets the "include" array to only ["src"],
which omits tests from project-wide typechecks; update the "include" property in
this file to also cover test files (e.g., add patterns like "test", "tests",
"**/*.spec.ts", "**/*.test.ts", or similar used across the repo) so that test
files in the ai-vue-ui package are type-checked alongside src; modify the
"include" array in tsconfig.json to add the appropriate test globs.
🪄 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: ee6e3601-e003-4b01-8f01-939472427154

📥 Commits

Reviewing files that changed from the base of the PR and between 6a23e80 and 8323a3c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (118)
  • .changeset/config.json
  • .editorconfig
  • .vscode/extensions.json
  • CONTRIBUTING.md
  • README.md
  • examples/ts-code-mode-web/package.json
  • examples/ts-group-chat/package.json
  • examples/ts-react-chat/package.json
  • examples/ts-react-media/package.json
  • examples/ts-react-search/package.json
  • examples/ts-solid-chat/package.json
  • examples/ts-svelte-chat/package.json
  • examples/ts-vue-chat/package.json
  • examples/vanilla-chat/package.json
  • package.json
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • packages/typescript/ai-anthropic/src/text/text-provider-options.ts
  • packages/typescript/ai-anthropic/tests/anthropic-adapter.test.ts
  • packages/typescript/ai-anthropic/tests/model-meta.test.ts
  • packages/typescript/ai-anthropic/tsconfig.json
  • packages/typescript/ai-client/README.md
  • packages/typescript/ai-client/package.json
  • packages/typescript/ai-client/tests/test-utils.ts
  • packages/typescript/ai-client/tsconfig.json
  • packages/typescript/ai-code-mode-skills/tsconfig.json
  • packages/typescript/ai-code-mode/tests/create-code-mode-tool.test.ts
  • packages/typescript/ai-code-mode/tsconfig.json
  • packages/typescript/ai-devtools/README.md
  • packages/typescript/ai-devtools/package.json
  • packages/typescript/ai-devtools/tsconfig.json
  • packages/typescript/ai-elevenlabs/tests/audio-adapter.test.ts
  • packages/typescript/ai-elevenlabs/tests/realtime-adapter.test.ts
  • packages/typescript/ai-elevenlabs/tsconfig.json
  • packages/typescript/ai-event-client/tsconfig.json
  • packages/typescript/ai-fal/package.json
  • packages/typescript/ai-fal/tsconfig.json
  • packages/typescript/ai-gemini/README.md
  • packages/typescript/ai-gemini/package.json
  • packages/typescript/ai-gemini/src/model-meta.ts
  • packages/typescript/ai-gemini/src/text/text-provider-options.ts
  • packages/typescript/ai-gemini/tests/gemini-adapter.test.ts
  • packages/typescript/ai-gemini/tests/image-adapter.test.ts
  • packages/typescript/ai-gemini/tests/model-meta.test.ts
  • packages/typescript/ai-gemini/tsconfig.json
  • packages/typescript/ai-grok/package.json
  • packages/typescript/ai-grok/tests/grok-adapter.test.ts
  • packages/typescript/ai-grok/tsconfig.json
  • packages/typescript/ai-groq/package.json
  • packages/typescript/ai-groq/tests/groq-adapter.test.ts
  • packages/typescript/ai-groq/tsconfig.json
  • packages/typescript/ai-isolate-cloudflare/tests/isolate-driver.test.ts
  • packages/typescript/ai-isolate-cloudflare/tests/worker.test.ts
  • packages/typescript/ai-isolate-cloudflare/tsconfig.json
  • packages/typescript/ai-isolate-node/README.md
  • packages/typescript/ai-isolate-node/package.json
  • packages/typescript/ai-isolate-node/tsconfig.json
  • packages/typescript/ai-isolate-quickjs/tsconfig.json
  • packages/typescript/ai-ollama/README.md
  • packages/typescript/ai-ollama/package.json
  • packages/typescript/ai-ollama/tsconfig.json
  • packages/typescript/ai-openai/README.md
  • packages/typescript/ai-openai/package.json
  • packages/typescript/ai-openai/tests/image-adapter.test.ts
  • packages/typescript/ai-openai/tests/model-meta.test.ts
  • packages/typescript/ai-openai/tests/openai-adapter.test.ts
  • packages/typescript/ai-openai/tsconfig.json
  • packages/typescript/ai-openrouter/package.json
  • packages/typescript/ai-openrouter/tsconfig.json
  • packages/typescript/ai-preact/package.json
  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-react-ui/README.md
  • packages/typescript/ai-react-ui/package.json
  • packages/typescript/ai-react-ui/tsconfig.json
  • packages/typescript/ai-react/README.md
  • packages/typescript/ai-react/package.json
  • packages/typescript/ai-react/tests/test-utils.ts
  • packages/typescript/ai-react/tests/use-chat-structured-output.test.ts
  • packages/typescript/ai-react/tests/use-chat.test.ts
  • packages/typescript/ai-react/tests/use-generation.test.ts
  • packages/typescript/ai-react/tsconfig.json
  • packages/typescript/ai-solid-ui/package.json
  • packages/typescript/ai-solid-ui/tsconfig.json
  • packages/typescript/ai-solid/tests/test-utils.ts
  • packages/typescript/ai-solid/tests/use-chat.test.ts
  • packages/typescript/ai-solid/tests/use-generation.test.ts
  • packages/typescript/ai-solid/tsconfig.json
  • packages/typescript/ai-svelte/package.json
  • packages/typescript/ai-svelte/tests/create-generation.test.ts
  • packages/typescript/ai-svelte/tests/use-chat.test.ts
  • packages/typescript/ai-svelte/tsconfig.json
  • packages/typescript/ai-utils/package.json
  • packages/typescript/ai-utils/tsconfig.json
  • packages/typescript/ai-vue-ui/package.json
  • packages/typescript/ai-vue-ui/tsconfig.json
  • packages/typescript/ai-vue/tsconfig.json
  • packages/typescript/ai/README.md
  • packages/typescript/ai/src/adapter-internals.ts
  • packages/typescript/ai/tsconfig.json
  • packages/typescript/openai-base/package.json
  • packages/typescript/openai-base/tests/chat-completions-structured-output-stream.test.ts
  • packages/typescript/openai-base/tests/chat-completions-text.test.ts
  • packages/typescript/openai-base/tests/responses-structured-output-stream.test.ts
  • packages/typescript/openai-base/tests/responses-text.test.ts
  • packages/typescript/openai-base/tsconfig.json
  • packages/typescript/preact-ai-devtools/README.md
  • packages/typescript/preact-ai-devtools/package.json
  • packages/typescript/preact-ai-devtools/tsconfig.json
  • packages/typescript/react-ai-devtools/README.md
  • packages/typescript/react-ai-devtools/package.json
  • packages/typescript/react-ai-devtools/tsconfig.json
  • packages/typescript/solid-ai-devtools/README.md
  • packages/typescript/solid-ai-devtools/package.json
  • packages/typescript/solid-ai-devtools/tsconfig.json
  • scripts/sync-provider-models.ts
  • testing/e2e/package.json
  • testing/panel/package.json
  • tsconfig.base.json
  • tsconfig.json
💤 Files with no reviewable changes (13)
  • packages/typescript/ai-openai/README.md
  • README.md
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • packages/typescript/ai/README.md
  • packages/typescript/ai-gemini/README.md
  • packages/typescript/ai-client/README.md
  • packages/typescript/ai-react-ui/README.md
  • packages/typescript/ai-ollama/README.md
  • packages/typescript/ai-devtools/README.md
  • packages/typescript/ai-react/README.md
  • packages/typescript/solid-ai-devtools/README.md
  • packages/typescript/preact-ai-devtools/README.md
  • packages/typescript/react-ai-devtools/README.md

Comment thread package.json
Comment thread packages/typescript/ai-isolate-node/README.md Outdated
- package.json: relax engines.pnpm from `>=11.0.0` to `>=10.17.0` to match
  CONTRIBUTING.md ("pnpm 10.17.0 or newer"). packageManager still pins
  pnpm@11.1.1 for corepack users.
- packages/typescript/ai-isolate-node/README.md: rewrite the
  `--no-node-snapshot` bullet so it no longer says "Node.js 20.x and later"
  alongside the "Node.js >= 22" minimum stated two bullets above. The flag
  is required by isolated-vm on every Node version we support.
- packages/typescript/ai-gemini/tests/model-meta.test.ts: the
  "thinking models should allow thinkingConfig" type-test was asserting
  `stopSequences` (a placeholder swapped in during the typecheck rot
  cleanup). Restore the original intent: assert that `thinkingConfig` is
  present on `GeminiChatModelProviderOptionsByName['gemini-2.5-pro']`.
- packages/typescript/ai-openai/tests/openai-adapter.test.ts: the test
  passes `systemPrompts: ['Stay concise']` but never checked that it
  arrived on the outbound payload. Add the missing
  `instructions: 'Stay concise'` assertion (the Responses API field the
  adapter writes `systemPrompts.join('\n')` into).

Skipped:
- packages/typescript/ai-vue-ui/tsconfig.json (and sibling -ui packages):
  CodeRabbit suggested adding `"tests"` to `include`. The *-ui packages
  have no test files on disk and use `composite: true` with `rootDir: src`,
  so widening include would point at a non-existent directory.
Unknown `modelOptions` keys passed to the Anthropic text adapter (reachable
by casting around the public type, as the prior `modelOptions.system`
escape hatch did) were silently filtered out by the `validKeys` allowlist.
That meant callers losing `cache_control` on system blocks would see no
signal — just a higher token bill.

`mapCommonOptionsToAnthropic` now diffs the supplied keys against the
allowlist and emits a `logger.errors` line naming the dropped keys, with
a hint pointing at `systemPrompts` when `system` is among them. A
regression test pins the behavior: `systemPrompts` wins, bogus keys
don't leak into the payload, and the error log fires with the right
metadata.

Adds a minor-bump changeset for `@tanstack/ai-anthropic` documenting the
breaking removal of `modelOptions.system` (from commit 66737a5), the
prompt-caching regression, and the planned follow-up to widen
`systemPrompts` to carry per-prompt provider metadata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/typescript/ai-anthropic/src/adapters/text.ts (1)

308-308: 💤 Low value

Consider creating the Set once as a module constant.

The Set is created on every request. While the performance impact is negligible with only 9 keys, creating it once would be more efficient:

const VALID_KEY_SET = new Set<keyof InternalTextProviderOptions>(validKeys)

Then use VALID_KEY_SET.has(key) in the filter.

🤖 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 `@packages/typescript/ai-anthropic/src/adapters/text.ts` at line 308, Extract
the per-request Set creation into a module-level constant to avoid rebuilding it
each call: create a constant like VALID_KEY_SET = new Set<keyof
InternalTextProviderOptions>(validKeys) at module scope and replace the
in-function instantiation (currently `const validKeySet = new
Set<string>(validKeys)`) with using VALID_KEY_SET.has(key) where the code
filters option keys; keep the same type parametering to match
InternalTextProviderOptions and ensure all references to `validKeySet` are
updated to `VALID_KEY_SET`.
🤖 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 `@packages/typescript/ai-anthropic/tests/anthropic-adapter.test.ts`:
- Around line 144-185: The test's logger mock uses logger.error but the
implementation (in text.ts, called via chat) calls options.logger.errors(), so
update the mock used in anthropic-adapter.test.ts to match the InternalLogger
shape: replace or augment the mock logger to include an errors: vi.fn() method
(and keep debug/info/warn as needed), and update any assertions that reference
logger.error to check logger.errors instead; ensure the error call lookup uses
logger.errors.mock.calls and verifies the droppedKeys and hint as before.

---

Nitpick comments:
In `@packages/typescript/ai-anthropic/src/adapters/text.ts`:
- Line 308: Extract the per-request Set creation into a module-level constant to
avoid rebuilding it each call: create a constant like VALID_KEY_SET = new
Set<keyof InternalTextProviderOptions>(validKeys) at module scope and replace
the in-function instantiation (currently `const validKeySet = new
Set<string>(validKeys)`) with using VALID_KEY_SET.has(key) where the code
filters option keys; keep the same type parametering to match
InternalTextProviderOptions and ensure all references to `validKeySet` are
updated to `VALID_KEY_SET`.
🪄 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: e30369bf-1221-4cb8-92e4-25ed8a465e91

📥 Commits

Reviewing files that changed from the base of the PR and between 05e65f1 and c284260.

📒 Files selected for processing (3)
  • .changeset/anthropic-drop-model-options-system.md
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • packages/typescript/ai-anthropic/tests/anthropic-adapter.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/anthropic-drop-model-options-system.md

Comment on lines +144 to 185
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}

const chunks: StreamChunk[] = []
for await (const chunk of chat({
adapter,
messages: [{ role: 'user', content: 'Hi' }],
systemPrompts: ['This should be overridden'],
systemPrompts: ['real system prompt'],
modelOptions: {
system: [
{
type: 'text',
text: 'You are a helpful assistant.',
cache_control: { type: 'ephemeral' },
},
],
},
system: 'ignored escape hatch',
bogus_key: 'also ignored',
} as unknown as AnthropicTextProviderOptions,
debug: { logger, errors: true },
})) {
chunks.push(chunk)
}

const [payload] = mocks.betaMessagesCreate.mock.calls[0]
const [payload] = mocks.betaMessagesCreate.mock.calls[0]!

// modelOptions.system should take precedence over systemPrompts
// systemPrompts wins; modelOptions.system was dropped.
expect(payload.system).toEqual([
{
type: 'text',
text: 'You are a helpful assistant.',
cache_control: { type: 'ephemeral' },
},
{ type: 'text', text: 'real system prompt' },
])
// bogus_key did not leak into the request either.
expect(payload).not.toHaveProperty('bogus_key')

// The drop is loud — error fired with both keys named and a hint for `system`.
expect(logger.error).toHaveBeenCalled()
const errorCall = logger.error.mock.calls.find((call) =>
String(call[0]).includes('dropped unknown modelOptions key'),
)
expect(errorCall).toBeDefined()
const [, meta] = errorCall!
expect((meta as { droppedKeys: Array<string> }).droppedKeys).toEqual(
expect.arrayContaining(['system', 'bogus_key']),
)
expect((meta as { hint?: string }).hint).toMatch(/systemPrompts/)
})
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Run the specific test to verify it passes
cd packages/typescript/ai-anthropic
pnpm test -- --run anthropic-adapter.test.ts -t "drops unknown modelOptions keys"

Repository: TanStack/ai

Length of output: 8867


🏁 Script executed:

fd -t f "text.ts" packages/typescript/ai-anthropic

Repository: TanStack/ai

Length of output: 109


🏁 Script executed:

cat -n packages/typescript/ai-anthropic/src/adapters/text.ts | sed -n '300,330p'

Repository: TanStack/ai

Length of output: 1530


🏁 Script executed:

cat -n packages/typescript/ai-anthropic/src/adapters/text.ts | sed -n '1,50p'

Repository: TanStack/ai

Length of output: 1865


🏁 Script executed:

cat -n packages/typescript/ai-anthropic/tests/anthropic-adapter.test.ts | sed -n '144,190p'

Repository: TanStack/ai

Length of output: 1954


🏁 Script executed:

rg -t ts "interface InternalLogger" --no-heading -A 10

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

fd -t f "InternalLogger" --type f

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

rg "InternalLogger" --type ts -l

Repository: TanStack/ai

Length of output: 1307


🏁 Script executed:

cat -n packages/typescript/ai/src/logger/internal-logger.ts

Repository: TanStack/ai

Length of output: 4534


🏁 Script executed:

rg "type Logger" packages/typescript/ai/src/logger/types.ts -A 20

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

cat -n packages/typescript/ai/src/logger/types.ts

Repository: TanStack/ai

Length of output: 4675


Update test logger mock to use errors method instead of error.

The implementation calls options.logger.errors() at line 318 of text.ts, but the test mocks logger.error(). The logger passed to the implementation is of type InternalLogger, which provides an errors() method; however, the raw Logger interface this test provides only has an error() method.

The test will fail when it attempts to call the non-existent errors() method. Fix the mock:

Diff
 const logger = {
   debug: vi.fn(),
   info: vi.fn(),
   warn: vi.fn(),
-  error: vi.fn(),
+  errors: vi.fn(),
 }

 // ...

-expect(logger.error).toHaveBeenCalled()
-const errorCall = logger.error.mock.calls.find((call) =>
+expect(logger.errors).toHaveBeenCalled()
+const errorCall = logger.errors.mock.calls.find((call) =>
📝 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.

Suggested change
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}
const chunks: StreamChunk[] = []
for await (const chunk of chat({
adapter,
messages: [{ role: 'user', content: 'Hi' }],
systemPrompts: ['This should be overridden'],
systemPrompts: ['real system prompt'],
modelOptions: {
system: [
{
type: 'text',
text: 'You are a helpful assistant.',
cache_control: { type: 'ephemeral' },
},
],
},
system: 'ignored escape hatch',
bogus_key: 'also ignored',
} as unknown as AnthropicTextProviderOptions,
debug: { logger, errors: true },
})) {
chunks.push(chunk)
}
const [payload] = mocks.betaMessagesCreate.mock.calls[0]
const [payload] = mocks.betaMessagesCreate.mock.calls[0]!
// modelOptions.system should take precedence over systemPrompts
// systemPrompts wins; modelOptions.system was dropped.
expect(payload.system).toEqual([
{
type: 'text',
text: 'You are a helpful assistant.',
cache_control: { type: 'ephemeral' },
},
{ type: 'text', text: 'real system prompt' },
])
// bogus_key did not leak into the request either.
expect(payload).not.toHaveProperty('bogus_key')
// The drop is loud — error fired with both keys named and a hint for `system`.
expect(logger.error).toHaveBeenCalled()
const errorCall = logger.error.mock.calls.find((call) =>
String(call[0]).includes('dropped unknown modelOptions key'),
)
expect(errorCall).toBeDefined()
const [, meta] = errorCall!
expect((meta as { droppedKeys: Array<string> }).droppedKeys).toEqual(
expect.arrayContaining(['system', 'bogus_key']),
)
expect((meta as { hint?: string }).hint).toMatch(/systemPrompts/)
})
const logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
errors: vi.fn(),
}
const chunks: StreamChunk[] = []
for await (const chunk of chat({
adapter,
messages: [{ role: 'user', content: 'Hi' }],
systemPrompts: ['real system prompt'],
modelOptions: {
system: 'ignored escape hatch',
bogus_key: 'also ignored',
} as unknown as AnthropicTextProviderOptions,
debug: { logger, errors: true },
})) {
chunks.push(chunk)
}
const [payload] = mocks.betaMessagesCreate.mock.calls[0]!
// systemPrompts wins; modelOptions.system was dropped.
expect(payload.system).toEqual([
{ type: 'text', text: 'real system prompt' },
])
// bogus_key did not leak into the request either.
expect(payload).not.toHaveProperty('bogus_key')
// The drop is loud — error fired with both keys named and a hint for `system`.
expect(logger.errors).toHaveBeenCalled()
const errorCall = logger.errors.mock.calls.find((call) =>
String(call[0]).includes('dropped unknown modelOptions key'),
)
expect(errorCall).toBeDefined()
const [, meta] = errorCall!
expect((meta as { droppedKeys: Array<string> }).droppedKeys).toEqual(
expect.arrayContaining(['system', 'bogus_key']),
)
expect((meta as { hint?: string }).hint).toMatch(/systemPrompts/)
🤖 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 `@packages/typescript/ai-anthropic/tests/anthropic-adapter.test.ts` around
lines 144 - 185, The test's logger mock uses logger.error but the implementation
(in text.ts, called via chat) calls options.logger.errors(), so update the mock
used in anthropic-adapter.test.ts to match the InternalLogger shape: replace or
augment the mock logger to include an errors: vi.fn() method (and keep
debug/info/warn as needed), and update any assertions that reference
logger.error to check logger.errors instead; ensure the error call lookup uses
logger.errors.mock.calls and verifies the droppedKeys and hint as before.

Copy link
Copy Markdown
Member

@crutchcorn crutchcorn left a comment

Choose a reason for hiding this comment

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

Only reviewed .changeset/config and scripts/ changes for security needs, nothing else. From that perspective LGTM

Resolves test:types failure introduced by interaction between:

- main commit b05adb1 (#576) added `use-chat-structured-output.test.ts`
  scenario using `let releaseSecond: (() => void) | null = null` with
  a Promise executor closure, called later as `releaseSecond?.()`.
- This branch's tsconfig consolidation (2ab32d9) added `tests/` to the
  package's `tsc` include list.

Combined under strict mode, TS cannot narrow through the executor
closure: outside the Promise constructor `releaseSecond` is still typed
as `null`, so `releaseSecond?.()` collapses to `never` and trips
TS2349 ("Type 'never' has no call signatures").

Switch to a definite-assignment assertion (`let releaseSecond!: () => void`)
so the type stays as `() => void` and the call site no longer needs `?.`.
@AlemTuzlak AlemTuzlak merged commit aa3acf1 into main May 19, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the chore/monorepo-setup-audit branch May 19, 2026 13:06
@github-actions github-actions Bot mentioned this pull request May 19, 2026
AlemTuzlak added a commit that referenced this pull request May 19, 2026
… metadata (#575)

* chore(monorepo): tighten build/CI scope, defensive changeset ignores, bump vite

- Exclude testing/** alongside examples/** in build, build:all, test:ci, test:pr,
  test:lib, test:types, test:eslint, test:build, test:coverage. Internal test
  harnesses (testing/e2e, testing/panel) had build scripts but no exclude,
  so they were getting built on every release pipeline.
- Add an `ignore` list to .changeset/config.json covering every in-workspace
  private package. Defends against accidental publication if `"private": true`
  is ever dropped from a package by mistake.
- Bump vite from ^7.2.7 to ^7.3.3 across every declaring package.json. Resolves
  a transitive version skew (some dev-only plugins pull vite@7.3.x) that was
  breaking typechecking of vite.config.ts files.
- Add `engines` to the root package.json: `node >=24`, `pnpm >=11.0.0`.

* chore(tsconfig): consolidate to root tsconfig.base.json and include tests

- Introduce tsconfig.base.json at the repo root with the shared compilerOptions.
  Root tsconfig.json now extends it for the small set of root-level files
  (scripts, config files) we still typecheck.
- Migrate every package tsconfig to extend tsconfig.base.json with a consistent
  shape: only the package's unique compilerOptions overrides (outDir, rootDir
  where needed, JSX runtime, framework lib) plus a uniform
  `include: ["src", "tests"]` / `exclude: ["node_modules", "dist"]`.
- Drop brittle one-off patterns: per-package duplicated compilerOptions
  (ai-devtools, ai-isolate-cloudflare), explicit single-test-file includes
  (ai-anthropic, ai-gemini), and `**/*.config.ts` excludes that were silently
  hiding test files from `tsc`.
- Config files (vite.config.ts, vitest.config.ts) are no longer in the include
  set. They're typechecked at build time by vite/vitest themselves and the
  cross-tool type matrix makes them brittle to check via `tsc`.

Net effect: `pnpm test:types` now actually typechecks the tests in every
package. Prior to this commit several packages were silently excluding tests
from the typecheck pipeline.

* refactor(ai-gemini): merge GeminiThinking{,Advanced}Options into one interface

The old shape kept thinking config in two separate interfaces — one with
required `includeThoughts` + optional `thinkingBudget`, the other with
`thinkingLevel` — and intersected both into ExternalTextProviderOptions.
The intersection produced a `thinkingConfig` shape where neither
`includeThoughts`+`thinkingBudget` nor `thinkingLevel` could be passed
cleanly, even though the adapter reads both at runtime
(see src/adapters/text.ts mapCommonOptionsToGemini).

Merge into a single GeminiThinkingOptions with all three fields optional.
Drop GeminiThinkingAdvancedOptions entirely. Update model-meta intersections
and the sync-provider-models template that references the type.

GeminiThinkingOptions is the only thinking type exported from
@tanstack/ai-gemini, so no public-API breakage. GeminiThinkingAdvancedOptions
was never exported.

* refactor(ai-anthropic): drop modelOptions.system escape hatch

`system` was sneaking into modelOptions via `validKeys` and getting spread
over the system block constructed from systemPrompts. That was a leaky
abstraction: it bypassed the cross-provider `systemPrompts` API and forced
users to construct Anthropic's TextBlockParam shape manually to attach
features like cache_control.

- Remove `'system'` from validKeys in adapters/text.ts. The adapter no longer
  accepts a system override from modelOptions.
- Keep `system?: string | Array<TextBlockParam>` on InternalTextProviderOptions
  (the adapter still constructs it internally from `options.systemPrompts`).
  Mark it explicitly as internal in the JSDoc.
- Delete the test that exercised the override behaviour and prune the
  `system` field + `& { system: string }` satisfies intersection from the
  one other test that was using modelOptions.system.

Regression to be aware of: there is currently no public way to attach
Anthropic `cache_control` to system prompts. A follow-up will extend
`systemPrompts` to accept `{ content, metadata }` with provider-specific
metadata (cache_control for Anthropic, others later).

* fix(ai): expose Logger via the adapter-internals subpath

`Logger` was declared as an `export interface` in src/logger/types.ts but
wasn't re-exported from src/adapter-internals.ts. Provider adapter packages
consume internals only via the `@tanstack/ai/adapter-internals` subpath, so
without this re-export they couldn't reach the `Logger` type without an
ambient import that would break the encapsulation boundary.

Surfaced by the openai-base tests once `tsc` started checking tests.

* test: fix type rot and unsafe casts surfaced by typecheck standardization

Including tests in `tsc` (see the tsconfig commit) surfaced ~200 pre-existing
type errors across the test suite that had silently rotted as source types
evolved. This commit fixes them and tightens the remaining type assertions.

Patterns fixed:

- Stale model name literals (`claude-3-7-sonnet-20250219` →
  `claude-3-7-sonnet`, `grok-4-0709` → `grok-4`, `chatgpt-4.0` →
  `chatgpt-4o-latest`, etc.).
- EventType string literals replaced with the enum values from
  `@tanstack/ai`/`@ag-ui/core` in groq/grok/openai-base/openai tests.
- Removed/renamed provider option fields (`generationConfig.*` flattened
  onto top-level Gemini options; `system` removed from anthropic
  modelOptions tests).
- Required fields added to mock generation result objects (`id` on
  image/speech/transcription mocks, `id` + `usage` on summarize mocks)
  to satisfy GenerationFetcher result types.
- `MakeInputModalitiesTypes<['text', 'image', ...]>` wrappers and
  provider-typed content parts (`AnthropicTextPart`, `OpenAITextPart`,
  `GeminiImagePart`, etc.) plumbed into the model-meta tests.
- `tools` array entries replaced with typed `AnyClientTool` factories
  for the elevenlabs realtime tests.
- Cloudflare worker tests: 7 `as any` casts collapsed into a single
  `readJson<T>` helper with one trust-boundary cast and `Extract<>` types
  per response status arm.
- ai-react/ai-solid `useChat({ onToolCall })` tests: `onToolCall` is not
  a public UseChatOptions field — it's set internally by ChatClient via
  the `tools` array. Deleted one test of nonexistent behaviour and
  removed dead `onToolCall` options from two `addToolResult` tests.
- ai-react/ai-solid `useGeneration` tests: explicit generic arguments
  instead of `onResult ... as any`; `as const` on inline chunk arrays so
  StreamChunk literals infer with EventType enum values intact.
- ai-svelte `create-generation` tests: EventType enum + required fields
  on AGUIEvent shapes (`threadId` on RUN_STARTED/RUN_FINISHED, `message`
  on RUN_ERROR).
- openai-base tests now build a real `new OpenAI({ apiKey })` and
  monkey-patch a typed `MockChatCompletionCreate` / `MockResponsesCreate`
  signature instead of `as unknown as OpenAI`.

Remaining `as`-style casts in the touched files: 5 total, all at trust
boundaries with justification comments (Response.json() → typed shape;
discriminated-union narrowing for the schemaless useChat branch; AGUI
RUN_ERROR carrying both spec-shape `message` and legacy `error.message`).

`@ts-expect-error` is used in one place (audio-adapter.test.ts) for the
"unsupported model name is rejected" test, replacing an `as never` cast.

No production code changes other than:
- A new export of an existing `Logger` interface (separate commit).
- The anthropic modelOptions.system removal (separate commit).

* docs(contributing): add CONTRIBUTING.md, editorconfig, vscode recommendations

- CONTRIBUTING.md covering prereqs (Node 24, pnpm 11), initial setup,
  repo layout, day-to-day commands, the per-package tsconfig pattern,
  where to add unit tests, the (mandatory) E2E coverage matrix,
  changesets, PR flow, and how to add a new provider adapter. Documents
  the known gaps: .vue/.svelte SFCs are not linted today, and build
  configs are not in the `tsc` pass.
- .editorconfig with the repo's existing conventions (LF, utf-8,
  2-space, final newline).
- .vscode/extensions.json recommending eslint, prettier, vitest
  explorer, nx, svelte, vue, editorconfig.

The PR template already linked to a missing CONTRIBUTING.md; this fixes
that broken link.

* ci: apply automated fixes

* chore(changesets): use ts-* glob instead of listing each example explicitly

* docs: drop incorrect Node.js v24+ requirement from READMEs and CONTRIBUTING.md

* fix: scope Node version constraint to ai-isolate-node (Node >= 22 for isolated-vm)

The previous "Node 24+" claim was overreaching:

- Only `@tanstack/ai-isolate-node` (via `isolated-vm`) has a Node version
  floor. Everything else in the workspace runs fine on older Node.
- `isolated-vm`'s actual `engines.node` is `>=22.0.0` (see
  https://github.com/laverdet/isolated-vm/blob/main/package.json), not 24.

Changes:
- Root package.json: drop the `node: ">=24"` entry from engines (keep the
  pnpm constraint). No global Node floor for development.
- packages/typescript/ai-isolate-node/package.json: bump `engines.node`
  from `>=18` to `>=22` to match upstream.
- packages/typescript/ai-isolate-node/README.md: same — say `>=22`, and
  add the `--no-node-snapshot` flag note for Node 20+ runtime usage.

* chore: address CodeRabbit feedback on PR #572

- package.json: relax engines.pnpm from `>=11.0.0` to `>=10.17.0` to match
  CONTRIBUTING.md ("pnpm 10.17.0 or newer"). packageManager still pins
  pnpm@11.1.1 for corepack users.
- packages/typescript/ai-isolate-node/README.md: rewrite the
  `--no-node-snapshot` bullet so it no longer says "Node.js 20.x and later"
  alongside the "Node.js >= 22" minimum stated two bullets above. The flag
  is required by isolated-vm on every Node version we support.
- packages/typescript/ai-gemini/tests/model-meta.test.ts: the
  "thinking models should allow thinkingConfig" type-test was asserting
  `stopSequences` (a placeholder swapped in during the typecheck rot
  cleanup). Restore the original intent: assert that `thinkingConfig` is
  present on `GeminiChatModelProviderOptionsByName['gemini-2.5-pro']`.
- packages/typescript/ai-openai/tests/openai-adapter.test.ts: the test
  passes `systemPrompts: ['Stay concise']` but never checked that it
  arrived on the outbound payload. Add the missing
  `instructions: 'Stay concise'` assertion (the Responses API field the
  adapter writes `systemPrompts.join('\n')` into).

Skipped:
- packages/typescript/ai-vue-ui/tsconfig.json (and sibling -ui packages):
  CodeRabbit suggested adding `"tests"` to `include`. The *-ui packages
  have no test files on disk and use `composite: true` with `rootDir: src`,
  so widening include would point at a non-existent directory.

* feat(ai): systemPrompts accept { content, metadata } with adapter-inferred metadata typing

Extends `chat({ systemPrompts })` to accept either a plain string (existing
shape — backward compatible) or `{ content, metadata }`. The structured
form's `metadata` type is inferred from the adapter at the chat() call site
via a new `TSystemPromptMetadata` generic on `TextAdapter` — no `satisfies`
needed by callers.

- Anthropic declares `AnthropicSystemPromptMetadata` → users get
  `cache_control` autocomplete + type-checking.
- Adapters with no per-prompt metadata (OpenAI, Gemini, Ollama,
  OpenRouter, openai-base) inherit the default `never`, which makes the
  `metadata` field unusable at the call site. Passing metadata to those
  adapters is a TypeScript error.

Closes the regression introduced by removing the `modelOptions.system`
escape hatch in the audit PR — there is now a public, typed path for
attaching Anthropic `cache_control` to system prompts.

Plumbing

- New `TSystemPromptMetadata = never` generic on `TextAdapter` /
  `BaseTextAdapter`, surfaced via `'~types'['systemPromptMetadata']`.
  `TextActivityOptions.systemPrompts` is now
  `Array<SystemPrompt<TAdapter['~types']['systemPromptMetadata']>>`.
- `AnyTextAdapter` extended to 7 generic slots.
- `TextOptions.systemPrompts` (the wide internal shape adapters receive)
  is `Array<SystemPrompt>`; adapters call `normalizeSystemPrompts<...>()`
  to narrow.
- Chat engine + middleware context/config widened to carry
  `Array<SystemPrompt>` so metadata flows through to the adapter.
- OpenTelemetry middleware extracts `.content` for span events;
  per-prompt metadata is dropped from spans.
- `@tanstack/ai-event-client` mirrors the `SystemPrompt` shape locally
  (avoids a circular import with `@tanstack/ai`) and projects metadata
  away on the devtools wire.

Adapter mappings

- Anthropic reads `metadata.cache_control` and attaches it to the
  matching `TextBlockParam`.
- OpenAI / Gemini / Ollama / OpenRouter / openai-base call
  `normalizeSystemPrompts()` and join `.content` for their respective
  `instructions` / `system` / `systemInstruction` fields. (Their
  metadata type is `never`, so the field can't be set anyway.)

Tests

- ai-anthropic: new test verifies `cache_control` flows from
  `systemPrompts[i].metadata` onto the outbound `TextBlockParam`, and
  plain-string entries still produce metadata-less blocks.
- ai-openai: new test verifies mixed string + object-form input
  (without metadata) produces the expected joined `instructions`.

* ci: apply automated fixes

* address PR #575 review feedback

Maps to tombeckenham's review items C1, I1–I6:

- C1 (runtime validation): `normalizeSystemPrompts` throws TypeError naming
  the offending index when an object-form entry's content isn't a string,
  so stale call sites can't stream a literal "undefined" into the model.
  New test file `packages/typescript/ai/tests/system-prompts.test.ts`
  covers the happy paths and the throw cases.

- I1 (E2E coverage): `testing/e2e/tests/system-prompt-metadata.spec.ts`
  drives the full Anthropic HTTP path with object-form systemPrompts +
  metadata.cache_control. Wire-shape assertion stays in the ai-anthropic
  unit test (aimock's journal normalises Anthropic into an OpenAI-shaped
  request and drops `cache_control`, so a journal-based assertion isn't
  reliable — the spec asserts end-to-end success instead). Required a
  test-only `systemPromptCacheControl` opt-in on `api.chat.ts` to flip
  the system prompt to object form.

- I2 (middleware widened-shape coverage): new
  `middleware.test.ts > should preserve object-form systemPrompts through
  middleware` asserts the middleware sees `Array<SystemPrompt>`, not a
  pre-flattened `Array<string>`, and that mutations preserve metadata
  through to the adapter.

- I3 (per-adapter mapping coverage): direct unit tests for
  Gemini (`systemInstruction` joined; foreign metadata dropped),
  Ollama (`messages.unshift({role:'system'})` joined; foreign metadata
  dropped), and OpenRouter (positional `{role:'system'}` message; foreign
  metadata dropped).

- I4 (OTel metadata observability): when `captureContent: true` and at
  least one entry carries metadata, the iteration span gains a
  `tanstack.ai.system_prompt.metadata` JSON attribute (positional
  per-prompt array, `null` for plain strings / no metadata). Kept off
  span events so the existing one-event-per-message GenAI semconv stays
  intact. New tests in `tests/middlewares/otel.test.ts` cover both the
  set and the absent path.

- I5 (doc/changeset wording): replaced "silently ignore" with the
  accurate "carries no meaningful value … silently dropped, never
  written to the wire" framing in `SystemPrompt`'s JSDoc, the
  `TextOptions.systemPrompts` JSDoc, and the changeset. Removed the
  misleading `satisfies AnthropicSystemPromptMetadata` from the
  `@example` (the adapter narrows the metadata field at the call site,
  so a `satisfies` cast is contradicted by the tests).

- I6 (devtools structural guard):
  `ai-event-client/tests/devtools-middleware-shape.test.ts` re-declares
  the local `DevtoolsSystemPrompt` mirror and uses `expectTypeOf` to
  assert mutual assignability with `@tanstack/ai`'s `SystemPrompt`. If
  the canonical shape gains a third variant the guard breaks at
  type-check time, forcing the maintainer to update the mirror in
  `devtools-middleware.ts`.

Suggestions S1–S5 deferred per the reviewer's "follow-up acceptable" call.

* ci: apply automated fixes

* fix CI: ai-event-client cross-package import + system-prompts lint

Two CI failures on the previous review-feedback commit, both follow-ups to
the same patch:

- `@tanstack/ai-event-client:test:types` failed because the new
  `devtools-middleware-shape.test.ts` imported `SystemPrompt` from
  `@tanstack/ai`, but `@tanstack/ai-event-client` only declares
  `@tanstack/ai` as a peerDependency. Nx's project graph therefore
  doesn't see a build edge and the `^build` predecessor for test:types
  never produces the dist files tsc needs. Adding a `workspace:*` devDep
  would close the loop but reintroduce the circular dev/runtime dep the
  current architecture is built to avoid.

  Move the structural guard test into `@tanstack/ai/tests/` instead.
  `@tanstack/ai` has no edge to ai-event-client's source for this
  assertion (the mirror gets re-declared inline), and the test now lives
  alongside the type it's guarding.

- `@tanstack/ai:test:eslint` failed on
  `@typescript-eslint/no-unnecessary-condition` at the C1 runtime checks
  inside `normalizeSystemPrompts`. TS narrows `p` to the object arm
  after the string branch, so the `p === null || typeof p !== 'object'`
  guard looks redundant to the rule even though it's deliberate
  defence-in-depth at a public API boundary. Cast through `unknown`
  to drop the narrow and re-validate without disabling the rule.

  Same treatment for the `p.content` typeof check — read `content` off
  an `unknown`-typed view of the candidate so the runtime check is
  visible to the linter without an eslint-disable.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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