Anthropic message threads (#11545)

Anthropic can keep a conversation on the server (message threads beta).
A request can then continue from the previous reply and send only the
new messages, instead of the whole history every turn.

pi uses this when a session id is set and caching is on. It still builds
the full request, compares it with the previous one, and only sends the
new part if nothing earlier changed.

- Default: on only for the anthropic provider on api.anthropic.com.
  Gateways that drop unknown fields would lose the conversation.
  Override with compat.supportsMessageThreads.
- If the server rejects a continuation, pi resends the full request
  as a new thread. Other errors turn threads off for the session.
- New option StreamOptions.disableContinuation. The cache warmer sets
  it so warm requests don't replace the session's thread.
- cleanupSessionResources(sessionId) frees a session's thread state.
This commit is contained in:
David Brailovsky 2026-11-03 14:07:57 +01:00 • committed by GitHub
parent 3551ec07eb
commit a758f3ae4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 885 additions and 22 deletions

View file

@ -5,6 +5,7 @@
### Added
- `pi update` now lists each updated package with its npm version or git commit change, for example `npm:foo 1.2.0 -> 1.3.0`, and prints `Packages are already up to date` when nothing changed. When nothing changed but an update printed a warning, for example because npm left the old version installed after resolving stale registry metadata, it prints `No packages were updated, see the warnings above` instead; in offline mode it prints `Skipped package updates (offline mode)`. `PackageManager.update()` reports each changed package as a new `result` progress event whose `message` holds that line ([#11707](https://github.com/earendil-works/pi/pull/11707), [#11711](https://github.com/earendil-works/pi/pull/11711))
- Anthropic sessions now use message threads: each request sends only the new messages and the server continues from the previous reply, falling back to the full request when the conversation changed (for example after compaction or a tool list change). On by default for the `anthropic` provider on `api.anthropic.com`; set `compat.supportsMessageThreads` in `models.json` to enable it for gateways that forward the `thread` field, or to turn it off. Cache warming does not touch the session's thread ([#11545](https://github.com/earendil-works/pi/pull/11545))
## [1.5.0] - 2026-11-02

View file

@ -878,6 +878,8 @@ pi.on("before_provider_request", (event, ctx) => {
This is mainly useful for debugging provider serialization and cache behavior.
The payload is the full request. Providers that continue from a previous response (OpenAI Codex over WebSocket, Anthropic message threads) may send only the part that is new since the previous request.
#### after_provider_response
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.

View file

@ -172,6 +172,7 @@ Also applies to `azure-openai-responses` and `openai-codex-responses`.
| `supportsMidConvoEffort` | boolean | `false` | Whether the exact model transport supports effort-only system messages and thinking binding controls. |
| `supportsMidConvoSystemMessages` | boolean | `false` | Whether the exact model accepts system-role messages inside the conversation. When false, later system messages are folded into the top-level system prompt. |
| `supportsMidConvoToolChanges` | boolean | `false` | Whether the exact model accepts mid-conversation tool_addition and tool_removal blocks. Requires supportsMidConvoSystemMessages. |
| `supportsMessageThreads` | boolean | | Whether the endpoint supports Anthropic message threads, which let a request with a session id send only the new messages and continue from the previous reply. Default: true for provider anthropic with a baseUrl on api.anthropic.com, false otherwise. Set true for gateways that forward the thread request field. |
| `allowedFallbackModels` | `object[]` | | Models Anthropic accepts for server-side refusal fallback, with local pricing metadata for returned fallback responses. When absent or empty, callers must omit fallbacks; Anthropic rejects the field for models with no permitted fallback targets. |
### `bedrock-converse-stream`

View file

@ -1081,6 +1081,10 @@
"description": "Whether the exact model accepts mid-conversation tool_addition and tool_removal blocks. Requires supportsMidConvoSystemMessages.",
"default": false
},
"supportsMessageThreads": {
"type": "boolean",
"description": "Whether the endpoint supports Anthropic message threads, which let a request with a session id send only the new messages and continue from the previous reply. Default: true for provider anthropic with a baseUrl on api.anthropic.com, false otherwise. Set true for gateways that forward the thread request field."
},
"allowedFallbackModels": {
"type": "array",
"items": {

View file

@ -333,6 +333,8 @@ export class CacheWarmer {
.streamSimple(run.model, run.context, {
...run.options,
maxTokens: 1,
// The warm request repeats the last request; it must not replace the conversation's thread state.
disableContinuation: true,
maxRetries: 0,
signal: run.controller.signal,
})

View file

@ -155,7 +155,14 @@ describe("cache warming", () => {
expect(calls[0]).toMatchObject({
model: adaptiveModel,
options: { reasoning: "high", sessionId: "s", transformHeaders, maxTokens: 1, maxRetries: 0 },
options: {
reasoning: "high",
sessionId: "s",
transformHeaders,
maxTokens: 1,
maxRetries: 0,
disableContinuation: true,
},
});
expect(calls[0].options?.signal).not.toBe(signal);
expect(events[0]).toMatchObject({