Skip to content

feat(integrations): support extended thinking on Claude and skip thought and empty parts - #1160

Open
ScottMansfield wants to merge 1 commit into
foundry/3aac7bc5-6036-49bb-81a0-519c88e137aefrom
foundry/4d0ea73c-114a-4942-8df3-1f045a4f6a06
Open

ScottMansfield wants to merge 1 commit into
foundry/3aac7bc5-6036-49bb-81a0-519c88e137aefrom
foundry/4d0ea73c-114a-4942-8df3-1f045a4f6a06

Conversation

@ScottMansfield

@ScottMansfield ScottMansfield commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Adds extended thinking support to AnthropicLlm and Claude in @google/adk-integrations. It also skips request parts that the Anthropic Messages API rejects. Before this change, a session that ran a Gemini turn and then switched to Claude failed on every later turn, because partToMessageBlock threw on thought parts.

Files changed

5 files changed, 838 insertions(+), 21 deletions(-):

  • integrations/src/models/anthropic_utils.ts (+158 / -9)
  • integrations/src/models/anthropic_llm.ts (+6 / -3)
  • integrations/test/models/anthropic_utils_test.ts (+433 / -7)
  • integrations/test/models/anthropic_llm_test.ts (+185 / -0)
  • docs/guides/integrations/models/anthropic_llm/index.md (+56 / -2)

The deletions are the replaced throw on thought parts, the rewritten test that expected that throw, the unsupported-block test fixture (it used thinking, which is now supported), the request assembly lines in AnthropicLlm, and the outdated Limitations bullets in the guide.

Ported adk-python behavior

  • thinkingConfig.thinkingBudget maps to the Anthropic thinking parameter: 0 gives disabled, a negative value gives {type: 'adaptive', display: 'summarized'}, a positive value gives {type: 'enabled', budget_tokens}, and an unset budget throws.
  • thinking and redacted_thinking response blocks become thought: true parts, and these parts convert back to the same blocks on later turns.
  • A part that carries only a thoughtSignature from another model is dropped with a warning. A turn left with no blocks is not sent.
  • Empty text parts are skipped. A user turn left empty only by skipped empty text sends one '.' text block.
  • Empty and whitespace-only response text blocks are skipped.
  • usage.output_tokens_details.thinking_tokens is reported as thoughtsTokenCount and subtracted from candidatesTokenCount. totalTokenCount stays input plus output.

Interface differences

Member adk-python adk-js Why
thought_signature / thoughtSignature bytes (UTF-8 encoded) string @google/genai types Part.thoughtSignature as string, so the code stores Anthropic signature and data as-is.
_is_empty_text_part / isEmptyTextPart Checks model_fields_set Checks text === '', no signature, and no content payload field Part is a plain interface with no record of which fields are set.
_build_anthropic_thinking_param / buildAnthropicThinkingParam Returns a dict or None Returns an @anthropic-ai/sdk ThinkingConfig* union or undefined Native SDK types.

Verification

  • npx vitest run --project unit:integrations: 3 files and 172 tests passed (104 in anthropic_utils_test.ts, 66 in anthropic_llm_test.ts, 2 in version_test.ts).
  • npm run build, ts:check, ts:check:samples, lint, format:check, docs:check, and npx secretlint "**/*": all exited 0.
  • npm run test:coverage on Linux: 6558 tests passed. Three files failed only because of the local machine: the Playwright browser is not installed, and the local npm wrapper writes notices to stderr.
  • Mutation check: changing display: 'summarized' to 'omitted' made the 2 adaptive thinking tests fail.

What the reviewer must decide

  • Is a single '.' text block the right way to keep a user turn that held only empty text parts? Without it, the conversation ends on an assistant turn, which Anthropic treats as prefill.
  • Is it right to store Anthropic thinking.signature and redacted_thinking.data verbatim in Part.thoughtSignature?
  • A thinking block with empty text but a signature comes back as redacted_thinking on the next turn. adk-python has the same gap. Should a follow-up change fix it?

Produced by Foundry (task 4d0ea73c-114a-4942-8df3-1f045a4f6a06). Reviewed by an automated
reviewer before upload; still needs human review.

@ScottMansfield ScottMansfield added foundry parity Requested by the Foundry task that opened this pull request integrations Requested by the Foundry task that opened this pull request labels Oct 27, 2026
@ScottMansfield ScottMansfield added the claude Requested by the Foundry task that opened this pull request label Oct 27, 2026
@ScottMansfield
ScottMansfield force-pushed the foundry/4d0ea73c-114a-4942-8df3-1f045a4f6a06 branch from 0988b16 to 7031104 Compare October 27, 2026 20:30
@ScottMansfield
ScottMansfield force-pushed the foundry/4d0ea73c-114a-4942-8df3-1f045a4f6a06 branch from 7031104 to 4e64228 Compare October 27, 2026 22:49
@ScottMansfield ScottMansfield changed the title feat(integrations): support extended thinking on Claude and skip thought and empty parts it canno... feat(integrations): support extended thinking on Claude and skip thought and empty parts Oct 27, 2026
@ScottMansfield

ScottMansfield commented Oct 27, 2026 •

Copy link
Copy Markdown
Member Author

Scored 2026-10-28 16:56 UTC at 8d65696, round 1. Earlier runs are in this comment's edit history.

Foundry merge critic: APPROVE

Add extended thinking support to AnthropicLlm and Claude and skip empty text parts and foreign thought signatures that the Anthropic API rejects.

Rubric — the default is approve; each rule is a reason a human should read this change first.

[no ] public_api — leaves package exports in integrations/src/index.ts and AnthropicLlm and Claude signatures unchanged; new helpers and union members in anthropic_utils.ts are additive
[no ] weakened_guarantee — adds thinkingBudget validation and converts thinking parts while preserving unsupported-part throws on bare thought and unknown parts
[no ] sensitive_surface — touches only message, thinking config, and token usage conversion in integrations/src/models/ and docs
[no ] release_surface — does not modify package.json, lockfiles, build scripts, or CI workflows
[no ] test_removed — adds 21 unit tests and updates two unsupported-kind test fixtures in anthropic_utils_test.ts so they still assert the unsupported error branches
[no ] inexplicable — all five modified files directly implement, test, and document extended thinking and part filtering for Claude

Posted by Foundry's merge critic as evidence rather than a request, so nothing here is waiting on you. Advisory only: Foundry has given no approval.

…ght and empty parts

Adds extended thinking support to `AnthropicLlm` and `Claude` in `@google/adk-integrations`. It also skips request parts that the Anthropic Messages API rejects. Before this change, a session that ran a Gemini turn and then switched to Claude failed on every later turn, because `partToMessageBlock` threw on thought parts.

### Files changed

5 files changed, 838 insertions(+), 21 deletions(-):
- `integrations/src/models/anthropic_utils.ts` (+158 / -9)
- `integrations/src/models/anthropic_llm.ts` (+6 / -3)
- `integrations/test/models/anthropic_utils_test.ts` (+433 / -7)
- `integrations/test/models/anthropic_llm_test.ts` (+185 / -0)
- `docs/guides/integrations/models/anthropic_llm/index.md` (+56 / -2)

The deletions are the replaced throw on thought parts, the rewritten test that expected that throw, the unsupported-block test fixture (it used `thinking`, which is now supported), the request assembly lines in `AnthropicLlm`, and the outdated Limitations bullets in the guide.

### Ported adk-python behavior

- `thinkingConfig.thinkingBudget` maps to the Anthropic `thinking` parameter: `0` gives `disabled`, a negative value gives `{type: 'adaptive', display: 'summarized'}`, a positive value gives `{type: 'enabled', budget_tokens}`, and an unset budget throws.
- `thinking` and `redacted_thinking` response blocks become `thought: true` parts, and these parts convert back to the same blocks on later turns.
- A part that carries only a `thoughtSignature` from another model is dropped with a warning. A turn left with no blocks is not sent.
- Empty text parts are skipped. A user turn left empty only by skipped empty text sends one `'.'` text block.
- Empty and whitespace-only response text blocks are skipped.
- `usage.output_tokens_details.thinking_tokens` is reported as `thoughtsTokenCount` and subtracted from `candidatesTokenCount`. `totalTokenCount` stays input plus output.

### Interface differences

| Member | adk-python | adk-js | Why |
| :--- | :--- | :--- | :--- |
| `thought_signature` / `thoughtSignature` | `bytes` (UTF-8 encoded) | `string` | `@google/genai` types `Part.thoughtSignature` as `string`, so the code stores Anthropic `signature` and `data` as-is. |
| `_is_empty_text_part` / `isEmptyTextPart` | Checks `model_fields_set` | Checks `text === ''`, no signature, and no content payload field | `Part` is a plain interface with no record of which fields are set. |
| `_build_anthropic_thinking_param` / `buildAnthropicThinkingParam` | Returns a dict or `None` | Returns an `@anthropic-ai/sdk` `ThinkingConfig*` union or `undefined` | Native SDK types. |

### Verification

- `npx vitest run --project unit:integrations`: 3 files and 172 tests passed (104 in `anthropic_utils_test.ts`, 66 in `anthropic_llm_test.ts`, 2 in `version_test.ts`).
- `npm run build`, `ts:check`, `ts:check:samples`, `lint`, `format:check`, `docs:check`, and `npx secretlint "**/*"`: all exited 0.
- `npm run test:coverage` on Linux: 6558 tests passed. Three files failed only because of the local machine: the Playwright browser is not installed, and the local npm wrapper writes notices to stderr.
- Mutation check: changing `display: 'summarized'` to `'omitted'` made the 2 adaptive thinking tests fail.

### What the reviewer must decide

- Is a single `'.'` text block the right way to keep a user turn that held only empty text parts? Without it, the conversation ends on an assistant turn, which Anthropic treats as prefill.
- Is it right to store Anthropic `thinking.signature` and `redacted_thinking.data` verbatim in `Part.thoughtSignature`?
- A `thinking` block with empty text but a signature comes back as `redacted_thinking` on the next turn. adk-python has the same gap. Should a follow-up change fix it?

Produced by Foundry (task 4d0ea73c-114a-4942-8df3-1f045a4f6a06). Reviewed by an automated
reviewer before upload; still needs human review.
@ScottMansfield
ScottMansfield force-pushed the foundry/4d0ea73c-114a-4942-8df3-1f045a4f6a06 branch from 4e64228 to 8d65696 Compare October 28, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Requested by the Foundry task that opened this pull request foundry integrations Requested by the Foundry task that opened this pull request parity Requested by the Foundry task that opened this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants