Skip to content

feat(ai): rework tool-call budgets as middleware hooks - #972

Merged
tombeckenham merged 3 commits into
mainfrom
feat/max-tool-calls-middleware
Jul 28, 2026
Merged

feat(ai): rework tool-call budgets as middleware hooks#972
tombeckenham merged 3 commits into
mainfrom
feat/max-tool-calls-middleware

Conversation

@tombeckenham

@tombeckenham tombeckenham commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Removes unreleased fix(ai): bound agent tool-call fan-out (#964) #965 APIs: maxToolCalls() strategy and chat({ maxToolCallsPerTurn })
  • Adds onShouldContinue middleware hook so policies can stop further agent-loop turns without hard-aborting
  • Keeps AgentLoopState.toolCallCount / lastTurnToolCallCount for custom strategies and middleware
  • Documents an app-owned tool-budget recipe (onBeforeToolCall skip + onShouldContinue) in agentic-cycle docs — not a built-in library export

This addresses maintainer feedback that tool-call budgeting belongs in middleware extensibility rather than first-class loop strategy + chat options (unreleased, so safe to rework).

Test plan

  • Unit: agent-loop strategies (no maxToolCalls)
  • Unit: chat tests for onBeforeToolCall per-turn cap, onShouldContinue cumulative stop, pending/resume
  • Types / rebuild clean
  • E2E: testing/e2e/tests/max-tool-calls.spec.ts (updated to inline budget middleware)
  • Docs recipe readable at docs/chat/agentic-cycle.md § Tool-call budgets

Summary by CodeRabbit

  • New Features
    • Added a onShouldContinue chat middleware hook to control whether the agent starts another model turn (supports async; middleware uses AND-style stopping on explicit false).
    • Tool-call budgets are now handled via app-owned middleware with per-turn and overall limits.
  • Breaking Changes
    • Removed the unreleased maxToolCalls agent-loop strategy.
    • Removed the maxToolCallsPerTurn chat option.
  • Documentation
    • Updated loop-control and budgeting guidance across middleware, agentic-cycle, and API/reference pages.
  • Tests
    • Updated unit and end-to-end tests to use the middleware-based tool-call budget behavior.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

16 package(s) bumped directly, 34 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-angular 0.3.1 → 1.0.0 Changeset
@tanstack/ai-durable-stream 0.0.0 → 1.0.0 Changeset
@tanstack/ai-memory 0.0.0 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.10 → 1.0.0 Changeset
@tanstack/ai-persistence 0.0.0 → 1.0.0 Changeset
@tanstack/ai-preact 0.11.1 → 1.0.0 Changeset
@tanstack/ai-react 0.18.1 → 1.0.0 Changeset
@tanstack/ai-sandbox 0.2.4 → 1.0.0 Changeset
@tanstack/ai-solid 0.15.1 → 1.0.0 Changeset
@tanstack/ai-svelte 0.15.1 → 1.0.0 Changeset
@tanstack/ai-vue 0.15.1 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.3 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.16.3 → 1.0.0 Dependent
@tanstack/ai-bedrock 0.1.4 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.3 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.8 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.11 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.3 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.34 → 1.0.0 Dependent
@tanstack/ai-fal 0.9.12 → 1.0.0 Dependent
@tanstack/ai-gemini 0.20.1 → 1.0.0 Dependent
@tanstack/ai-grok 0.14.9 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.3 → 1.0.0 Dependent
@tanstack/ai-groq 0.5.3 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.47 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.47 → 1.0.0 Dependent
@tanstack/ai-mistral 0.2.3 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.16 → 1.0.0 Dependent
@tanstack/ai-openai 0.17.1 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.3 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.15 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.4 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.14 → 1.0.0 Dependent
@tanstack/openai-base 0.9.9 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.42.0 → 0.43.0 Changeset
@tanstack/ai-client 0.22.1 → 0.23.0 Changeset
@tanstack/ai-devtools-core 0.4.24 → 0.5.0 Changeset
@tanstack/ai-event-client 0.6.8 → 0.7.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-mcp 0.2.5 → 0.2.6 Changeset
@tanstack/ai-isolate-cloudflare 0.2.38 → 0.2.39 Dependent
@tanstack/ai-vue-ui 0.2.34 → 0.2.35 Dependent
@tanstack/preact-ai-devtools 0.1.67 → 0.1.68 Dependent
@tanstack/react-ai-devtools 0.2.67 → 0.2.68 Dependent
@tanstack/solid-ai-devtools 0.2.67 → 0.2.68 Dependent
ag-ui 0.0.2 → 0.0.3 Dependent

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e35f1cd1-289b-4278-8da1-c8e054f44290

📥 Commits

Reviewing files that changed from the base of the PR and between abf5532 and 0c52b42.

📒 Files selected for processing (12)
  • .changeset/max-tool-calls-middleware.md
  • docs/advanced/built-in-middleware.md
  • docs/advanced/middleware.md
  • docs/api/ai.md
  • docs/chat/agentic-cycle.md
  • docs/config.json
  • docs/reference/functions/combineStrategies.md
  • docs/reference/functions/maxIterations.md
  • docs/reference/functions/maxToolCalls.md
  • docs/reference/index.md
  • docs/reference/interfaces/AgentLoopState.md
  • docs/reference/interfaces/TextOptions.md
💤 Files with no reviewable changes (4)
  • docs/reference/functions/maxToolCalls.md
  • docs/reference/functions/combineStrategies.md
  • docs/reference/index.md
  • docs/reference/interfaces/TextOptions.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • docs/reference/interfaces/AgentLoopState.md
  • docs/config.json
  • docs/advanced/built-in-middleware.md
  • .changeset/max-tool-calls-middleware.md
  • docs/api/ai.md
  • docs/advanced/middleware.md
  • docs/chat/agentic-cycle.md
  • docs/reference/functions/maxIterations.md

📝 Walkthrough

Walkthrough

The chat engine removes built-in tool-call budgets and adds middleware-driven continuation control. Tool-call accounting remains available through AgentLoopState, while tests and documentation adopt app-owned budgeting middleware.

Changes

Tool-call budgeting middleware

Layer / File(s) Summary
Middleware and strategy contracts
packages/ai/src/activities/chat/middleware/*, packages/ai/src/activities/chat/agent-loop-strategies.ts, packages/ai/src/types.ts, packages/ai/src/index.ts
Adds onShouldContinue, removes maxToolCalls, and removes maxToolCallsPerTurn from public options.
Engine continuation and tool accounting
packages/ai/src/activities/chat/index.ts
Removes engine-side per-turn caps, records deduplicated tool calls, and combines strategy and middleware continuation decisions asynchronously.
Budget middleware tests and integration
packages/ai/tests/*, testing/e2e/src/routes/*, testing/e2e/tests/*
Updates unit and end-to-end coverage for middleware-enforced per-turn and cumulative budgets, including skipped and resumed tool calls.
API and budgeting documentation
.changeset/*, docs/*, packages/ai/skills/*
Documents the new hook and app-owned budgeting recipe while removing references to the previous built-in limits.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TextEngine
  participant AgentLoopStrategy
  participant MiddlewareRunner
  participant ToolExecution
  TextEngine->>ToolExecution: record deduplicated tool calls
  TextEngine->>AgentLoopStrategy: evaluate loop state
  TextEngine->>MiddlewareRunner: runOnShouldContinue(ctx, state)
  MiddlewareRunner-->>TextEngine: continue or stop
  TextEngine->>ToolExecution: execute executable tool calls
Loading

Possibly related PRs

Suggested reviewers: alemtuzlak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is relevant, but it does not follow the required template and omits the Changes, Checklist, and Release Impact sections. Rewrite the PR description using the repository template and include the three required sections with checklist items and release-impact selection.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: reworking tool-call budgets into middleware hooks.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/max-tool-calls-middleware

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jul 21, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 0c52b42

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 50s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-28 02:44:49 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@972

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@972

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@972

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@972

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@972

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@972

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@972

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@972

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@972

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@972

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@972

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@972

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@972

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@972

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@972

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@972

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@972

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@972

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@972

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@972

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@972

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@972

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@972

@tanstack/ai-memory

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-memory@972

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@972

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@972

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@972

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@972

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@972

@tanstack/ai-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence@972

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@972

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@972

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@972

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@972

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@972

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@972

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@972

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@972

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@972

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@972

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@972

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@972

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@972

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@972

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@972

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@972

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@972

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@972

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@972

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@972

commit: 0c52b42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
testing/e2e/src/routes/api.max-tool-calls-wire.ts (1)

8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move type-only import to the existing import type block.

To resolve the static analysis linting warning and maintain a consistent top-level type import style, consider moving ChatMiddleware to the existing type-only import statement.

♻️ Proposed refactor
-  type ChatMiddleware,
 } from '`@tanstack/ai`'
 import { z } from 'zod'
-import type { AnyTextAdapter, StreamChunk } from '`@tanstack/ai`'
+import type { AnyTextAdapter, ChatMiddleware, StreamChunk } from '`@tanstack/ai`'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/api.max-tool-calls-wire.ts` around lines 8 - 11, Move
ChatMiddleware from the regular `@tanstack/ai` import into the existing import
type declaration alongside AnyTextAdapter and StreamChunk, leaving the runtime
zod import unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai/src/activities/chat/index.ts`:
- Around line 1964-1994: Update shouldContinue so loopStrategy(state) and
middlewareRunner.runOnShouldContinue(this.middlewareCtx, state) are both
evaluated on every non-executeToolCalls check, including when the strategy
returns false. Combine their boolean results afterward with the existing
toolPhase === 'continue' condition, preserving the executeToolCalls early
return.

---

Nitpick comments:
In `@testing/e2e/src/routes/api.max-tool-calls-wire.ts`:
- Around line 8-11: Move ChatMiddleware from the regular `@tanstack/ai` import
into the existing import type declaration alongside AnyTextAdapter and
StreamChunk, leaving the runtime zod import unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4df1b917-28b5-4bea-9899-f4105b50345f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7aa09 and e237fb4.

📒 Files selected for processing (24)
  • .changeset/max-tool-calls-middleware.md
  • docs/advanced/built-in-middleware.md
  • docs/advanced/middleware.md
  • docs/api/ai.md
  • docs/chat/agentic-cycle.md
  • docs/config.json
  • docs/reference/functions/combineStrategies.md
  • docs/reference/functions/maxIterations.md
  • docs/reference/functions/maxToolCalls.md
  • docs/reference/index.md
  • docs/reference/interfaces/AgentLoopState.md
  • docs/reference/interfaces/TextOptions.md
  • packages/ai/skills/ai-core/middleware/SKILL.md
  • packages/ai/skills/ai-core/tool-calling/SKILL.md
  • packages/ai/src/activities/chat/agent-loop-strategies.ts
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/activities/chat/middleware/compose.ts
  • packages/ai/src/activities/chat/middleware/types.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/tests/agent-loop-strategies.test.ts
  • packages/ai/tests/chat.test.ts
  • testing/e2e/src/routes/api.max-tool-calls-wire.ts
  • testing/e2e/tests/max-tool-calls.spec.ts
💤 Files with no reviewable changes (6)
  • docs/reference/functions/maxToolCalls.md
  • docs/reference/interfaces/TextOptions.md
  • docs/reference/index.md
  • docs/reference/functions/combineStrategies.md
  • packages/ai/tests/agent-loop-strategies.test.ts
  • packages/ai/src/index.ts

Comment thread packages/ai/src/activities/chat/index.ts
@tombeckenham
tombeckenham requested review from a team and AlemTuzlak July 21, 2026 07:44
@tombeckenham
tombeckenham force-pushed the feat/max-tool-calls-middleware branch from abf5532 to 18f47a4 Compare July 21, 2026 10:15
@tombeckenham tombeckenham added the waiting-on: author Waiting for the author to respond or update label Jul 23, 2026
tombeckenham and others added 3 commits July 28, 2026 12:39
Replace the unreleased maxToolCalls strategy and chat({ maxToolCallsPerTurn })
with an onShouldContinue middleware hook so apps can stop further agent turns
without aborting. Keep toolCallCount / lastTurnToolCallCount on AgentLoopState
for strategies and middleware. Document an app-owned tool-budget recipe
instead of shipping a built-in maxToolCallsMiddleware.
Evaluate agentLoopStrategy and middleware onShouldContinue together so
middleware still observes the final state when the strategy is what stops
the loop. Also use a type-only import for ChatMiddleware in the E2E wire route.
@tombeckenham
tombeckenham force-pushed the feat/max-tool-calls-middleware branch from 18f47a4 to 0c52b42 Compare July 28, 2026 02:41
@tombeckenham
tombeckenham merged commit 478a4da into main Jul 28, 2026
10 checks passed
@tombeckenham
tombeckenham deleted the feat/max-tool-calls-middleware branch July 28, 2026 03:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: author Waiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants