Skip to content

Anthropic web_search tool results are not preserved across turns in StreamProcessor #839

Description

@georgesma

TanStack AI version

v0.34.0

Framework/Library version

@tanstack/ai-anthropic: 0.15.7

Describe the bug and the steps to reproduce it

Anthropic web_search results do not seem to be preserved correctly across turns when using @tanstack/ai with @tanstack/ai-anthropic.

I have a minimal repro comparing the same two-turn conversation implemented in two ways:

  1. Directly with the official Anthropic SDK.
  2. With TanStack AI using chat(...) and StreamProcessor.

The first turn asks Claude to use web_search.
The second turn asks Claude to list the exact sources from the previous turn.

The official Anthropic SDK version works as expected: the second turn can still see the previous web_search evidence and returns the source URLs.

The TanStack AI version does not: after processing the first streamed response with StreamProcessor and passing processor.getMessages() into the second turn, Claude says it cannot see the previous web search evidence.

Repro

Anthropic SDK baseline

import "dotenv/config";

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const firstUserMessage =
  "Use the web_search tool now. Find two current web sources about the defense drone market. Answer with only the source names or URLs and one sentence about what each source says.";

const secondUserMessage =
  "Using only the web search evidence from the previous turn, list the exact source names or URLs you relied on. If you cannot see the previous web search evidence, say exactly: I cannot see the previous web search evidence.";

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

async function main() {
  const messages: Anthropic.MessageParam[] = [
    {
      role: "user",
      content: firstUserMessage,
    },
  ];

  const firstResponse = await runTurn(messages);

  messages.push({
    role: "assistant",
    content: firstResponse.content,
  });

  messages.push({
    role: "user",
    content: secondUserMessage,
  });

  const secondResponse = await runTurn(messages);

  messages.push({
    role: "assistant",
    content: secondResponse.content,
  });

  process.stdout.write(`${JSON.stringify(messages, null, 2)}\n`);
}

async function runTurn(messages: Anthropic.MessageParam[]) {
  return client.messages.create({
    model: "claude-opus-4-7",
    max_tokens: 8000,
    output_config: {
      effort: "medium",
    },
    thinking: {
      display: "summarized",
      type: "adaptive",
    },
    messages,
    tools: [
      {
        name: "web_search",
        type: "web_search_20250305",
        allowed_callers: ["direct"],
        max_uses: 2,
      },
    ],
  });
}

Output:

[
  {
    "role": "user",
    "content": "Use the web_search tool now. Find two current web sources about the defense drone market. Answer with only the source names or URLs and one sentence about what each source says."
  },
  // ...
  {
    "role": "user",
    "content": "Using only the web search evidence from the previous turn, list the exact source names or URLs you relied on. If you cannot see the previous web search evidence, say exactly: I cannot see the previous web search evidence."
  },
  {
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "1. https://www.marketsandmarkets.com/Market-Reports/military-drone-market-221577711.html\n2. https://www.globenewswire.com/news-release/2026/03/05/3250316/0/en/Defense-Drone-Market-Accelerates-Toward-40B-Opportunity-as-Autonomous-Warfare-Redefines-Modern-Battlefields.html"
      }
    ]
  }
]

TanStack AI repro

import "dotenv/config";

import { chat, maxIterations, StreamProcessor } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { webSearchTool } from "@tanstack/ai-anthropic/tools";

const firstUserMessage =
  "Use the web_search tool now. Find two current web sources about the defense drone market. Answer with only the source names or URLs and one sentence about what each source says.";

const secondUserMessage =
  "Using only the web search evidence from the previous turn, list the exact source names or URLs you relied on. If you cannot see the previous web search evidence, say exactly: I cannot see the previous web search evidence.";

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

async function main() {
  const firstTurnMessages = await runTurn([
    {
      role: "user",
      content: firstUserMessage,
    },
  ]);

  const secondTurnMessages = await runTurn([
    ...firstTurnMessages,
    {
      role: "user",
      content: secondUserMessage,
    },
  ]);

  process.stdout.write(`${JSON.stringify(secondTurnMessages, null, 2)}\n`);
}

async function runTurn(messages) {
  const stream = chat({
    adapter: anthropicText("claude-opus-4-7"),
    agentLoopStrategy: maxIterations(4),
    messages,
    modelOptions: {
      max_tokens: 8000,
      output_config: {
        effort: "medium",
      },
      thinking: {
        display: "summarized",
        type: "adaptive",
      },
    },
    threadId: "repro-anthropic-web-search-thinking",
    tools: [
      webSearchTool({
        name: "web_search",
        type: "web_search_20250305",
        allowed_callers: ["direct"],
        max_uses: 2,
      }),
    ],
  });

  const processor = new StreamProcessor({
    initialMessages: messages,
  });

  for await (const chunk of stream) {
    processor.processChunk(chunk);
  }

  processor.finalizeStream();

  return processor.getMessages();
}

Output:

[
  {
    "role": "user",
    "content": "Use the web_search tool now. Find two current web sources about the defense drone market. Answer with only the source names or URLs and one sentence about what each source says."
  },
  {
    "id": "anthropic-1782373873430-b5f734ksle6",
    "role": "assistant",
    "parts": [
      {
        "type": "thinking",
        "content": "The user wants me to perform a web search for them.",
        "stepId": "anthropic-1782373874356-g9gt9druvmk",
        "signature": "Eu4BCmMIDxgCKkBUlYA6s9gsfrmpEy9a28XiiB70GEVcmrkFocHZDw049VlPz4RA1bnST1D2u6dinTUyg/S09i0zPyvNbtqrF8F9Mg9jbGF1ZGUtb3B1cy00LTc4AEIIdGhpbmtpbmcSDK/3+XSxaLdc7AIvWhoM2nGQGAen5DDL/8SoIjDrLCzrc4Z6Hhdsj3pgbGsITSCHd+Aw4NIrR54ukP4ISt0F/HyvqnG9SoFl2yZc9zkqOQ14/QXETrqcsv2W/I6lZFeDXzRvGqZubfWOZX1SdaUnrOaFMEvxrMQ1hBjFC83WJAEVZ6ZrbxdRWBgB"
      },
      {
        "type": "text",
        "content": "Here are two current sources on the defense drone market:\n\n1. **MarketsandMarkets** (marketsandmarkets.com) — Valued the global military drones market at USD 34.85 billion in 2026, projecting it to reach USD 109.22 billion by 2031 at a 25.7% CAGR, with procurement volume growing from 16,157 units in 2026 to 26,544 units by 2031.\n\n2. **Global Market Insights** (gminsights.com) — Reports the market growing from USD 20.7 billion in 2026 to USD 39.4 billion in 2031 and USD 66.5 billion in 2035 at a 13.8% CAGR, driven by escalating global defense expenditure, geopolitical tensions, and technological advancements in autonomous systems."
      }
    ],
    "createdAt": "2026-06-25T07:51:15.175Z"
  },
  {
    "role": "user",
    "content": "Using only the web search evidence from the previous turn, list the exact source names or URLs you relied on. If you cannot see the previous web search evidence, say exactly: I cannot see the previous web search evidence."
  },
  {
    "id": "anthropic-1782373886138-j3es62eubsm",
    "role": "assistant",
    "parts": [
      {
        "type": "text",
        "content": "I cannot see the previous web search evidence."
      }
    ],
    "createdAt": "2026-06-25T07:51:26.173Z"
  }
]

Expected behavior

The messages returned by StreamProcessor.getMessages() should preserve Anthropic web_search result blocks in a form that can be reused in a later turn.

The second TanStack AI turn should be able to see the same previous web search evidence that the official Anthropic SDK preserves.

Actual behavior

The second TanStack AI turn cannot see the previous web_search evidence.

The equivalent official Anthropic SDK script preserves the evidence and works correctly.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

See the minimal repro scripts in the issue description below.

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

No, because I do not know how

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions