Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
79f2b941 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`.