Skip to content

feat(ai): systemPrompts accept { content, metadata } for per-provider metadata#575

Open
AlemTuzlak wants to merge 2 commits into
chore/monorepo-setup-auditfrom
feat/system-prompts-metadata
Open

feat(ai): systemPrompts accept { content, metadata } for per-provider metadata#575
AlemTuzlak wants to merge 2 commits into
chore/monorepo-setup-auditfrom
feat/system-prompts-metadata

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

Base: stacked on top of #572. Merge that first.

Summary

Restores the ability to attach Anthropic cache_control to system prompts — the regression introduced by removing the modelOptions.system escape hatch in #572 — via a typed, cross-provider mechanism.

chat({ systemPrompts }) now accepts either a plain string (existing shape — fully backward compatible) or { content, metadata }. The structured form lets providers attach typed metadata per prompt:

import { chat } from '@tanstack/ai'
import { anthropicText, type AnthropicSystemPromptMetadata } from '@tanstack/ai-anthropic'

chat({
  adapter: anthropicText(),
  model: 'claude-sonnet-4-6',
  systemPrompts: [
    {
      content: 'Stable instructions — cache me.',
      metadata: { cache_control: { type: 'ephemeral' } } satisfies AnthropicSystemPromptMetadata,
    },
    'Volatile per-request instruction.',
  ],
})

Design

  • @tanstack/ai exports SystemPrompt<TMetadata = unknown>, NormalizedSystemPrompt, and normalizeSystemPrompts(). The chat layer carries the wide shape through middleware; each adapter calls the helper at the top of its option-mapping pipeline so the rest of its code only sees { content, metadata }.
  • The object form is portable across providers. Adapters that don't recognise a metadata field silently drop it; OpenAI / Gemini / Ollama / OpenRouter / openai-base all just join .content for their respective instructions / system / systemInstruction fields.
  • The Anthropic adapter (the only one that consumes metadata today) reads metadata.cache_control and attaches it to the corresponding TextBlockParam. The new AnthropicSystemPromptMetadata interface is exported from @tanstack/ai-anthropic.

Plumbing

  • TextOptions.systemPrompts widened from Array<string> to Array<SystemPrompt>.
  • ChatMiddlewareContext.systemPrompts / ChatMiddlewareConfig.systemPrompts widened so middleware can read/mutate the wide shape.
  • OpenTelemetry middleware extracts .content for span events; per-prompt metadata is dropped from spans (not useful for observability).
  • @tanstack/ai-event-client mirrors the SystemPrompt shape locally (it intentionally avoids importing @tanstack/ai to prevent a circular dep) and projects metadata away on the devtools wire — devtools UI continues to receive Array<string>.

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 produces the expected joined instructions and that provider-foreign metadata is silently dropped.

Test plan

  • Verified locally: pnpm test:ci is green on this branch (33 projects × 8 targets).
  • CI on this PR matches.
  • Spot-check the API ergonomics: systemPrompts: ['x'] still works for every provider; systemPrompts: [{ content: 'x' }] works for every provider; Anthropic metadata.cache_control reaches the wire payload.

Notes

  • Changeset added (@tanstack/ai + @tanstack/ai-anthropic minor; downstream adapters patch).
  • The full design document with open questions/tradeoffs is in C:\Users\AlemTuzlak\.claude\projects\F--projects-tanstack-ai\design\system-prompts-metadata-2026-05-18.md (outside the repo, not committed).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b182b114-f0e4-41e4-ae0e-b1e92b15889a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/system-prompts-metadata

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

🚀 Changeset Version Preview

8 package(s) bumped directly, 22 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-anthropic 0.8.8 → 1.0.0 Changeset
@tanstack/ai-event-client 0.3.2 → 1.0.0 Changeset
@tanstack/ai-gemini 0.10.5 → 1.0.0 Changeset
@tanstack/ai-ollama 0.6.15 → 1.0.0 Changeset
@tanstack/ai-openai 0.9.1 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.9.1 → 1.0.0 Changeset
@tanstack/openai-base 0.3.1 → 1.0.0 Changeset
@tanstack/ai-code-mode 0.1.12 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.1.12 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.5 → 1.0.0 Dependent
@tanstack/ai-fal 0.7.5 → 1.0.0 Dependent
@tanstack/ai-grok 0.8.1 → 1.0.0 Dependent
@tanstack/ai-groq 0.2.1 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.12 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.12 → 1.0.0 Dependent
@tanstack/ai-preact 0.6.24 → 1.0.0 Dependent
@tanstack/ai-react 0.10.0 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.7.0 → 1.0.0 Dependent
@tanstack/ai-solid 0.9.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.6.5 → 1.0.0 Dependent
@tanstack/ai-svelte 0.9.0 → 1.0.0 Dependent
@tanstack/ai-vue 0.9.0 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.18.0 → 0.19.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-client 0.10.0 → 0.10.1 Dependent
@tanstack/ai-devtools-core 0.3.29 → 0.3.30 Dependent
@tanstack/ai-isolate-cloudflare 0.2.3 → 0.2.4 Dependent
@tanstack/ai-vue-ui 0.1.35 → 0.1.36 Dependent
@tanstack/preact-ai-devtools 0.1.33 → 0.1.34 Dependent
@tanstack/react-ai-devtools 0.2.33 → 0.2.34 Dependent
@tanstack/solid-ai-devtools 0.2.33 → 0.2.34 Dependent

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 18, 2026

View your CI Pipeline Execution ↗ for commit 2cfda79

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

☁️ Nx Cloud last updated this comment at 2026-05-18 13:01:06 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@575

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: df6f675

…erred 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`.
@AlemTuzlak AlemTuzlak force-pushed the feat/system-prompts-metadata branch from 2cfda79 to 8dc3dd7 Compare May 18, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant