Skip to content

feat(caching): Anthropic prompt caching with cache-aware cost reporting - #903

Open
addyCooks wants to merge 2 commits into
Nano-Collective:mainfrom
addyCooks:feat/anthropic-prompt-caching
Open

feat(caching): Anthropic prompt caching with cache-aware cost reporting#903
addyCooks wants to merge 2 commits into
Nano-Collective:mainfrom
addyCooks:feat/anthropic-prompt-caching

Conversation

@addyCooks

Copy link
Copy Markdown
Contributor

Description

Marks the stable prompt prefix with Anthropic cache breakpoints so multi-turn sessions
read it back out of cache instead of resending it at full price every turn.

withCacheBreakpoints() folds the system prompt into the message array and places two
breakpoints: the system entry, and the final message of the turn. The system breakpoint
also covers tool schemas, since tools precede system in Anthropic's prefix order. The
system: param is dropped when caching is active a bare string can't carry
providerOptions, and sending both would duplicate the prompt. Below Anthropic's
minimum cacheable length the prompt is still folded in but no breakpoints are emitted.

Anthropic-only: OpenAI and OpenRouter prefix-cache automatically, and local models have
no cache to address. "promptCaching": false on the provider config opts out.

Cost reporting is made cache-aware in the same PR without it, every cache hit would be
billed at the full input rate and overstate spend. cacheReadTokens/cacheWriteTokens
flow from usage.inputTokenDetails through ApiUsageApiCallRecordResponseUsage,
and priceTokens() becomes the single pricing function shared by the per-response
indicator and /usage, billing reads and writes at their own models.dev rates.

Closes #888.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • If this was for an open issue, I was assigned to it
  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging

…orting

Mark the stable prompt prefix with Anthropic cache breakpoints so multi-turn
sessions read it back out of cache instead of resending it at full price
every turn.

The system prompt now travels as a message rather than the `system:` string,
since a bare string cannot carry `providerOptions`. Two breakpoints are
placed: one on the system entry, which also covers the tool schemas because
they precede it in Anthropic's prompt order, and one on the final message so
the next turn's prefix ends there. Prefixes below Anthropic's minimum
cacheable length get no breakpoints, and the whole path is off unless the
provider uses the anthropic SDK, with `promptCaching: false` to opt out.

Cost reporting is made cache-aware in the same change, otherwise every cache
hit would be billed at the full input rate and overstate spend. Cache read
and write counts flow from the SDK through ApiUsage, ApiCallRecord, and
ResponseUsage; `priceTokens` becomes the single pricing function shared by
the per-response indicator and /usage, billing reads and writes at their own
models.dev rates and falling back to the input rate where cache pricing is
unknown.

Closes Nano-Collective#888.
Moving the system prompt into the messages array trips a bare `console.warn`
in the SDK's prompt standardiser on every request. It is not gated by
AI_SDK_LOG_WARNINGS, so it would print to stderr on each turn and corrupt the
Ink frame. Declare `allowSystemInMessages: true` on the caching path, where
the system message is one we placed ourselves, and leave the SDK's warning
in place everywhere else.

Adds handleChat coverage for the cache-marked system message, the absence of
the warning, and the unchanged non-anthropic path.
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.

[Feature] Prompt / Context Caching

1 participant