fix(coding-agent): preserved tool-search history on disk
This commit is contained in:
@@ -152,7 +152,7 @@ The Anthropic provider (`packages/ai/src/providers/anthropic.ts`) implements the
|
||||
- **Claude Code Fingerprint Headers & Betas**: Default headers include `anthropic-version: 2023-06-01`, `anthropic-dangerous-direct-browser-access: true`, `x-app: cli`, and `User-Agent: claude-cli/2.1.220 (external, claude-desktop)` (`coworkUserAgent`). Active beta flags (`buildCoworkBetas`) include `claude-code-20250219`, `interleaved-thinking-2025-05-14`, `thinking-token-count-2026-05-13`, `context-management-2025-06-27`, `prompt-caching-scope-2026-01-05`, `mid-conversation-system-2026-04-07`, `advanced-tool-use-2025-11-20`, `effort-2025-11-24`, and `fallback-credit-2026-06-01` (`context-1m-2025-08-07` is omitted to avoid 429 credit errors on subscription tokens, #7238). Fingerprint metadata (`generateClaudeCloakingUserId`, `deriveClaudeDeviceId`, `generateClaudeJsonUserId`) generates device/session IDs. Billing attestation headers (`createClaudeBillingHeader`, `wrapFetchForCch`, `patchCch`) embed `cch=00000` XXHash64 hashes into `system[0]`.
|
||||
- **System-Prompt Injection**: `buildAnthropicSystemBlocks` (`packages/ai/src/providers/anthropic.ts`) automatically prepends `claudeCodeSystemInstruction` ("You are a Claude agent, built on Anthropic's Claude Agent SDK.") as `system[0]` for OAuth credentials. Mid-conversation system messages in turn history are enabled for Opus 4.8+ / Sonnet 5+ via `mid-conversation-system-2026-04-07`.
|
||||
- **Thinking Signatures & Redacted Thinking**: Replaying modified or unsigned thinking blocks causes Anthropic API errors (`invalid signature in thinking block`). `convertAnthropicMessages` converts `ThinkingContent` and `RedactedThinkingContent` (`type: "redacted_thinking"`, `data`). `maybeAddReplayUnsignedThinkingHint` attaches recovery hints on signature errors, while `unwrapAnthropicThinkingEnvelope` strips legacy `<thinking>` XML wrappers.
|
||||
- **Tool Use Replay & Prefixes**: `encodeAnthropicToolName` / `decodeAnthropicToolName` (`packages/ai/src/providers/anthropic.ts`) prefixes custom tool names with `_` (`claudeToolPrefix`) when using OAuth to prevent collisions with built-in tools (`web_search`, `code_execution`, `text_editor`, `computer`). Server-executed web searches (`ServerToolUseBlockParam`, `WebSearchToolResultBlockParam` in `anthropic-wire.ts`) are detected via `isAnthropicWebSearchHistoryBlock` for turn replay. Empty tool errors are filled by `ensureErrorToolResultWireContent`.
|
||||
- **Tool Use Replay & Prefixes**: `encodeAnthropicToolName` / `decodeAnthropicToolName` (`packages/ai/src/providers/anthropic.ts`) prefixes custom tool names with `_` (`claudeToolPrefix`) when using OAuth to prevent collisions with built-in tools (`web_search`, `code_execution`, `text_editor`, `computer`). Server-executed web searches and tool searches (`AnthropicServerToolHistoryBlockParam` in `anthropic-wire.ts`) are detected via `isAnthropicServerToolHistoryBlock` for turn replay. Empty tool errors are filled by `ensureErrorToolResultWireContent`.
|
||||
- **Strict-Tool Schema Normalization & Fallback**: `normalizeAnthropicToolSchema` and `normalizeAnthropicStrictSchema` strip unsupported JSON schema keywords (e.g. `minItems`/`maxItems` on objects) for the `structured-outputs-2025-12-15` beta. If a strict tool schema causes HTTP 400, `streamAnthropicOnce` calls `dropAnthropicStrictTools` and automatically retries without strict mode.
|
||||
- **Adaptive vs Budget Thinking**: `ThinkingConfigParam` (`anthropic-wire.ts`) supports budget thinking (`{ type: "enabled", budget_tokens: N }` enforced by `ensureMaxTokensForThinking`) and adaptive thinking (`{ type: "adaptive" }` paired with `output_config: { effort: level }` via `effort-2025-11-24` beta). Forced tool choices (`disableThinkingIfToolChoiceForced`) automatically disable thinking.
|
||||
- **Prompt Cache Breakpoints**: `applyPromptCaching` (`packages/ai/src/providers/anthropic.ts`) attaches `{ type: "ephemeral", scope: "global" }` breakpoints to system prompts (`cacheSystemPrefixBreakpoints`), tool definitions, and historical user turns. `enforceCacheControlLimit` caps total breakpoints to 4 per request.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isAnthropicWebSearchHistoryBlock } from "@oh-my-pi/pi-ai/providers/anthropic-wire";
|
||||
import { isAnthropicServerToolHistoryBlock } from "@oh-my-pi/pi-ai/providers/anthropic-wire";
|
||||
import {
|
||||
type BlobStore,
|
||||
externalizeImageDataSync,
|
||||
@@ -100,8 +100,8 @@ function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string
|
||||
// Persist signed blocks verbatim — never truncate, externalize, or descend.
|
||||
// Unsigned blocks (e.g. an interrupted stream) have no such binding and stay
|
||||
// truncatable for size control.
|
||||
// Anthropic validates native web-search history byte-for-byte on replay.
|
||||
// Keep the complete typed block atomic, including nested encrypted_content.
|
||||
// Anthropic validates native web-search and tool-search history byte-for-byte
|
||||
// on replay. Keep the complete typed block atomic, including opaque content.
|
||||
if (typeof obj === "object" && "type" in obj && obj.type === "anthropicServerTool" && "block" in obj) {
|
||||
const block = obj.block;
|
||||
if (typeof block === "object" && block !== null && "type" in block && typeof block.type === "string") {
|
||||
@@ -112,7 +112,7 @@ function truncateForPersistence(obj: unknown, blobStore: BlobStore, key?: string
|
||||
...("tool_use_id" in block ? { tool_use_id: block.tool_use_id } : {}),
|
||||
...("content" in block ? { content: block.content } : {}),
|
||||
};
|
||||
if (isAnthropicWebSearchHistoryBlock(validationView)) return obj;
|
||||
if (isAnthropicServerToolHistoryBlock(validationView)) return obj;
|
||||
}
|
||||
}
|
||||
if (typeof obj === "object" && "type" in obj) {
|
||||
|
||||
@@ -278,16 +278,16 @@ describe("SessionManager signature persistence", () => {
|
||||
await reloaded.close();
|
||||
}, 15_000);
|
||||
|
||||
it("preserves oversized Anthropic web-search results byte-for-byte across reload", async () => {
|
||||
it("preserves oversized Anthropic server-tool results byte-for-byte across reload", async () => {
|
||||
using tempDir = TempDir.createSync("@pi-session-anthropic-server-tool-persistence-");
|
||||
const session = SessionManager.create(tempDir.path(), tempDir.path());
|
||||
const encryptedContent = `ENCRYPTED_WEB_SEARCH_RESULT_${"W".repeat(600_000)}`;
|
||||
const oversizedPayload = "W".repeat(600_000);
|
||||
const serverToolContent: AssistantMessage["content"] = [
|
||||
{
|
||||
type: "anthropicServerTool",
|
||||
block: {
|
||||
type: "server_tool_use",
|
||||
id: "srvtoolu_search",
|
||||
id: "srvtoolu_web_search",
|
||||
name: "web_search",
|
||||
input: { query: "current UTC date" },
|
||||
},
|
||||
@@ -296,8 +296,28 @@ describe("SessionManager signature persistence", () => {
|
||||
type: "anthropicServerTool",
|
||||
block: {
|
||||
type: "web_search_tool_result",
|
||||
tool_use_id: "srvtoolu_search",
|
||||
content: [{ type: "web_search_result", encrypted_content: encryptedContent }],
|
||||
tool_use_id: "srvtoolu_web_search",
|
||||
content: [{ type: "web_search_result", encrypted_content: `ENCRYPTED_WEB_SEARCH_${oversizedPayload}` }],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "anthropicServerTool",
|
||||
block: {
|
||||
type: "server_tool_use",
|
||||
id: "srvtoolu_tool_search",
|
||||
name: "tool_search_tool_bm25",
|
||||
input: { query: "read" },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "anthropicServerTool",
|
||||
block: {
|
||||
type: "tool_search_tool_result",
|
||||
tool_use_id: "srvtoolu_tool_search",
|
||||
content: {
|
||||
type: "tool_search_tool_search_result",
|
||||
tool_references: [{ type: "tool_reference", tool_name: `READ_TOOL_${oversizedPayload}` }],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user