feat(ai): add lazy tool discovery for chat()#360
Conversation
Tools marked with `lazy: true` are withheld from the LLM. A synthetic __lazy__tool__discovery__ tool lets the LLM discover them by name, receiving descriptions and schemas on demand. Discovered tools are dynamically injected as normal tools in subsequent agent loop iterations. - Add `lazy` flag to Tool, ClientTool, and ToolDefinitionConfig - Add LazyToolManager class with message history scanning for multi-turn - Integrate into TextEngine: dynamic tool refresh, self-correcting errors - 15 unit tests + 4 integration tests
Add three lazy tools (compareGuitars, calculateFinancing, searchGuitars) to the guitar store example for testing lazy tool discovery.
📝 WalkthroughWalkthroughAdds lazy tool discovery: tools annotated Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant ChatActivity as ChatActivity
participant LazyManager as LazyToolManager
participant LLM as LLM
participant DiscoveryTool as __lazy__tool__discovery__
participant Tool as Tool
User->>ChatActivity: send prompt
ChatActivity->>LazyManager: init(tools, messages)
LazyManager-->>ChatActivity: activeTools (eager + discovery tool if needed)
ChatActivity->>LLM: send prompt with activeTools
LLM->>ChatActivity: tool_call(__lazy__tool__discovery__, names)
ChatActivity->>DiscoveryTool: invoke discovery tool
DiscoveryTool->>LazyManager: request metadata for names
LazyManager-->>DiscoveryTool: return descriptions + inputSchemas
DiscoveryTool-->>ChatActivity: return discovery result
ChatActivity-->>LLM: tool_result (metadata)
LLM->>ChatActivity: tool_call(discoveredTool, args)
ChatActivity->>LazyManager: verify discovered(discoveredTool)
ChatActivity->>Tool: execute discoveredTool
Tool-->>ChatActivity: tool result
ChatActivity-->>LLM: tool result
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 1m 8s | View ↗ |
nx run-many --targets=build --exclude=examples/** |
✅ Succeeded | 43s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-03-12 09:12:13 UTC
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-devtools-core
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/typescript/ai/tests/chat.test.ts (1)
1218-1411: Add regressions for discovery payload contents and multi-turn rehydration.These tests script the later adapter iterations independently of what
__lazy__tool__discovery__actually returned, so they still pass if discovery stops surfacing the tool description/schema or if previously discovered tools stop being restored from message history. Please add one case that asserts the discovery tool result contains the requested schema/description, and one that seeds prior discovery messages and verifies the lazy tool is available on the next turn’s first adapter call.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/tests/chat.test.ts` around lines 1218 - 1411, Add two regression test cases: (1) extend the existing "should create discovery tool when lazy tools are provided" or add a new it() that, after the discovery tool run (look for ev.toolStart with '__lazy__tool__discovery__' and ev.toolArgs), parses the discovery tool's toolArgs payload and asserts it contains the expected schema/description fields for 'getWeather' (use createMockAdapter/chat/lazyServerTool and inspect the adapter call's toolArgs emitted by ev.toolArgs); (2) add a multi-turn rehydration test that seeds the adapter/messages with prior discovery events (simulate earlier discovery by including a run that yields ev.toolStart('__lazy__tool__discovery__') and ev.toolArgs containing the 'getWeather' schema in the chat history) and then calls chat again and asserts the first adapter call's tools (calls[0].tools via createMockAdapter) already include 'getWeather' (i.e., lazy tool is restored on the next turn). Ensure tests reference createMockAdapter, chat, lazyServerTool, ev.toolArgs, and inspect calls array to validate payload contents and presence on first turn.packages/typescript/ai/tests/lazy-tool-manager.test.ts (1)
1-3: Fix import order per ESLint rules.Static analysis flagged import ordering issues:
expectshould be sorted alphabetically within the vitest import, and the type import should come after the regular import.♻️ Fix import order
-import { describe, it, expect } from 'vitest' -import type { Tool } from '../src/types' -import { LazyToolManager } from '../src/activities/chat/tools/lazy-tool-manager' +import { describe, expect, it } from 'vitest' +import { LazyToolManager } from '../src/activities/chat/tools/lazy-tool-manager' +import type { Tool } from '../src/types'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/tests/lazy-tool-manager.test.ts` around lines 1 - 3, Reorder the imports to satisfy ESLint: fix the vitest import to alphabetize the named imports as describe, expect, it (change import { describe, it, expect } to import { describe, expect, it }) and move the type-only import for Tool to after the regular LazyToolManager import so the non-type imports come first; adjust the three symbols in this file: the vitest named imports, the LazyToolManager import, and the type import for Tool.packages/typescript/ai/src/activities/chat/index.ts (1)
715-749: Consider extracting duplicate undiscovered-tool filtering logic.This block duplicates the logic from
checkForPendingToolCalls(lines 611-641). While the duplication is acceptable given the different contexts (pending vs. streaming tool calls), consider extracting a helper method if this pattern is needed elsewhere.♻️ Optional: Extract helper method
private filterUndiscoveredLazyTools( toolCalls: Array<ToolCall>, finishEvent: RunFinishedEvent, ): { executable: Array<ToolCall>; errorChunks: Array<StreamChunk> } { const undiscoveredResults: Array<ToolResult> = [] const executable = toolCalls.filter((tc) => { if (this.lazyToolManager.isUndiscoveredLazyTool(tc.function.name)) { undiscoveredResults.push({ toolCallId: tc.id, toolName: tc.function.name, result: { error: this.lazyToolManager.getUndiscoveredToolError(tc.function.name), }, state: 'output-error', }) return false } return true }) const errorChunks = undiscoveredResults.length > 0 ? this.emitToolResults(undiscoveredResults, finishEvent) : [] return { executable, errorChunks } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 715 - 749, The duplicated undiscovered lazy-tool filtering in the streaming branch should be extracted into a helper to avoid repeating logic from checkForPendingToolCalls: create a private method (e.g., filterUndiscoveredLazyTools) that accepts toolCalls and finishEvent and returns { executable: ToolCall[], errorChunks: Iterable<StreamChunk> } by using lazyToolManager.isUndiscoveredLazyTool and lazyToolManager.getUndiscoveredToolError to build ToolResult entries, then call emitToolResults for any errors; replace the inline filter/emit in the streaming code with a call to this helper and keep existing behavior (clearing toolCallManager and calling setToolPhase('continue') when executable is empty).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/lazy-tool-discovery.md`:
- Around line 181-195: The example's input and runtime checks are too weak:
tighten calculateFinancingDef.inputSchema to require months be a positive
integer (e.g., z.number().int().min(1)) and validate productId, then in the
calculateFinancing server handler check that db.products.findUnique(...)
returned a product and that product.price is a finite positive number before
dividing; if not, throw a clear error/return a structured failure so you never
divide by zero or access undefined product fields (refer to
calculateFinancingDef, inputSchema, calculateFinancing, and
db.products.findUnique).
In `@examples/ts-react-chat/src/lib/guitar-tools.ts`:
- Around line 121-144: The compareGuitars server handler currently assumes
selected (from args.guitarIds) is non-empty which makes Math.min/Math.max and
the ! assertions on selected.find unsafe; update compareGuitars to validate that
selected.length >= 2 (or at least >0 per your business rule) after building
selected, and if not, return a clear error/result: either throw a descriptive
error or return a comparison object with an empty comparison array and
null/undefined for cheapest and mostExpensive. Ensure you avoid calling
Math.min/Math.max on an empty prices array and remove the non-null assertions on
selected.find, using guarded values instead.
---
Nitpick comments:
In `@packages/typescript/ai/src/activities/chat/index.ts`:
- Around line 715-749: The duplicated undiscovered lazy-tool filtering in the
streaming branch should be extracted into a helper to avoid repeating logic from
checkForPendingToolCalls: create a private method (e.g.,
filterUndiscoveredLazyTools) that accepts toolCalls and finishEvent and returns
{ executable: ToolCall[], errorChunks: Iterable<StreamChunk> } by using
lazyToolManager.isUndiscoveredLazyTool and
lazyToolManager.getUndiscoveredToolError to build ToolResult entries, then call
emitToolResults for any errors; replace the inline filter/emit in the streaming
code with a call to this helper and keep existing behavior (clearing
toolCallManager and calling setToolPhase('continue') when executable is empty).
In `@packages/typescript/ai/tests/chat.test.ts`:
- Around line 1218-1411: Add two regression test cases: (1) extend the existing
"should create discovery tool when lazy tools are provided" or add a new it()
that, after the discovery tool run (look for ev.toolStart with
'__lazy__tool__discovery__' and ev.toolArgs), parses the discovery tool's
toolArgs payload and asserts it contains the expected schema/description fields
for 'getWeather' (use createMockAdapter/chat/lazyServerTool and inspect the
adapter call's toolArgs emitted by ev.toolArgs); (2) add a multi-turn
rehydration test that seeds the adapter/messages with prior discovery events
(simulate earlier discovery by including a run that yields
ev.toolStart('__lazy__tool__discovery__') and ev.toolArgs containing the
'getWeather' schema in the chat history) and then calls chat again and asserts
the first adapter call's tools (calls[0].tools via createMockAdapter) already
include 'getWeather' (i.e., lazy tool is restored on the next turn). Ensure
tests reference createMockAdapter, chat, lazyServerTool, ev.toolArgs, and
inspect calls array to validate payload contents and presence on first turn.
In `@packages/typescript/ai/tests/lazy-tool-manager.test.ts`:
- Around line 1-3: Reorder the imports to satisfy ESLint: fix the vitest import
to alphabetize the named imports as describe, expect, it (change import {
describe, it, expect } to import { describe, expect, it }) and move the
type-only import for Tool to after the regular LazyToolManager import so the
non-type imports come first; adjust the three symbols in this file: the vitest
named imports, the LazyToolManager import, and the type import for Tool.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0fc45c9b-577e-424f-ac40-d84262b7ecfe
📒 Files selected for processing (13)
.changeset/lazy-tool-discovery.mddocs/config.jsondocs/guides/lazy-tool-discovery.mdexamples/ts-react-chat/README.mdexamples/ts-react-chat/src/lib/guitar-tools.tsexamples/ts-react-chat/src/routes/api.tanchat.tspackages/typescript/ai/src/activities/chat/index.tspackages/typescript/ai/src/activities/chat/tools/lazy-tool-manager.tspackages/typescript/ai/src/activities/chat/tools/tool-definition.tspackages/typescript/ai/src/types.tspackages/typescript/ai/tests/chat.test.tspackages/typescript/ai/tests/lazy-tool-manager.test.tspackages/typescript/ai/tests/tool-definition.test.ts
tools is typed as ReadonlyArray<Tool>, never nullish, so optional chaining is unnecessary and triggers eslint error.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/typescript/ai/src/activities/chat/index.ts (1)
645-696:⚠️ Potential issue | 🟠 MajorRefresh the active tool set after pending discovery calls finish.
This path can execute
__lazy__tool__discovery__, but unlikeprocessToolCalls()it never rehydratesthis.tools/this.toolCallManagerafterward. The next model request still sees the pre-discovery tool list, so a resumed conversation with an outstanding discovery call cannot use the newly discovered tool on the following turn.Proposed fix
const toolResultChunks = this.emitToolResults( executionResult.results, finishEvent, ) for (const chunk of toolResultChunks) { yield chunk } + + if (this.lazyToolManager.hasNewlyDiscoveredTools()) { + this.tools = this.lazyToolManager.getActiveTools() + this.toolCallManager = new ToolCallManager(this.tools) + } return 'continue'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 645 - 696, The executeToolCalls + drainToolCallGenerator path can trigger __lazy__tool__discovery__ but never rehydrates the active tool set, so after getting executionResult you must refresh this.tools and this.toolCallManager the same way processToolCalls does; add a call (or shared helper) to re-fetch/update the active tools/toolCallManager immediately after executionResult is obtained (before returning 'wait' or 'continue') so subsequent turns see newly discovered tools (reference executeToolCalls, drainToolCallGenerator, processToolCalls, this.tools, this.toolCallManager, __lazy__tool__discovery__).
🧹 Nitpick comments (2)
packages/typescript/ai/src/activities/chat/index.ts (2)
611-641: Factor the undiscovered-lazy filtering into one helper.Both branches build the same
output-errorpayload and executable-call split. Pulling that into a shared helper will keep pending-call recovery and same-turn execution from drifting the next time this error contract changes.Also applies to: 715-749
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 611 - 641, The code duplicates logic that filters pendingToolCalls into executablePendingCalls and builds undiscoveredLazyResults with the same 'output-error' payload; refactor by extracting a helper (e.g., buildUndiscoveredLazyFilter) that accepts pendingToolCalls and this.lazyToolManager and returns { executablePendingCalls, undiscoveredLazyResults } using this.lazyToolManager.isUndiscoveredLazyTool and getUndiscoveredToolError to construct the ToolResult entries, then replace the two duplicated blocks (the current block around pendingToolCalls -> executablePendingCalls and the similar block at 715-749) to call the helper and reuse its returned arrays before emitting via this.emitToolResults with finishEvent.
258-264: Keep emittedtoolNamesin sync with the runtime tool surface.
this.toolsis now mutable, but event metadata is still seeded from the originalparams.tools. After a discovery batch, lateraiEventClient.emit(...)calls will keep reporting the stale pre-discovery tool list instead of the actual active tools.Possible follow-up
- const { tools, temperature, topP, maxTokens, metadata } = this.params + const { temperature, topP, maxTokens, metadata } = this.params @@ - this.eventToolNames = tools?.map((t) => t.name) + this.eventToolNames = this.tools.map((t) => t.name)if (this.lazyToolManager.hasNewlyDiscoveredTools()) { this.tools = this.lazyToolManager.getActiveTools() this.toolCallManager = new ToolCallManager(this.tools) + this.eventToolNames = this.tools.map((tool) => tool.name) this.setToolPhase('continue') return }Also applies to: 804-808
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 258 - 264, The emitted event metadata is seeded from the static config.params.tools but runtime tools are mutable (discovery updates), so change event emission to use the active runtime tool list (this.lazyToolManager.getActiveTools() or this.tools after assignment) instead of config.params.tools; update the initialization so this.tools is set from this.lazyToolManager.getActiveTools(), ensure any aiEventClient.emit(...) calls (including where toolNames are assembled) read from this.tools (or call getActiveTools()) at emit time, and after discovery update this.tools = this.lazyToolManager.getActiveTools() so subsequent emits reflect the true active tool surface.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/typescript/ai/src/activities/chat/index.ts`:
- Around line 645-696: The executeToolCalls + drainToolCallGenerator path can
trigger __lazy__tool__discovery__ but never rehydrates the active tool set, so
after getting executionResult you must refresh this.tools and
this.toolCallManager the same way processToolCalls does; add a call (or shared
helper) to re-fetch/update the active tools/toolCallManager immediately after
executionResult is obtained (before returning 'wait' or 'continue') so
subsequent turns see newly discovered tools (reference executeToolCalls,
drainToolCallGenerator, processToolCalls, this.tools, this.toolCallManager,
__lazy__tool__discovery__).
---
Nitpick comments:
In `@packages/typescript/ai/src/activities/chat/index.ts`:
- Around line 611-641: The code duplicates logic that filters pendingToolCalls
into executablePendingCalls and builds undiscoveredLazyResults with the same
'output-error' payload; refactor by extracting a helper (e.g.,
buildUndiscoveredLazyFilter) that accepts pendingToolCalls and
this.lazyToolManager and returns { executablePendingCalls,
undiscoveredLazyResults } using this.lazyToolManager.isUndiscoveredLazyTool and
getUndiscoveredToolError to construct the ToolResult entries, then replace the
two duplicated blocks (the current block around pendingToolCalls ->
executablePendingCalls and the similar block at 715-749) to call the helper and
reuse its returned arrays before emitting via this.emitToolResults with
finishEvent.
- Around line 258-264: The emitted event metadata is seeded from the static
config.params.tools but runtime tools are mutable (discovery updates), so change
event emission to use the active runtime tool list
(this.lazyToolManager.getActiveTools() or this.tools after assignment) instead
of config.params.tools; update the initialization so this.tools is set from
this.lazyToolManager.getActiveTools(), ensure any aiEventClient.emit(...) calls
(including where toolNames are assembled) read from this.tools (or call
getActiveTools()) at emit time, and after discovery update this.tools =
this.lazyToolManager.getActiveTools() so subsequent emits reflect the true
active tool surface.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d1fc5c5f-7d48-41ae-aafe-9d1173a784f1
📒 Files selected for processing (1)
packages/typescript/ai/src/activities/chat/index.ts
🦋 Changeset detectedLatest commit: 788e476 The changes in this PR will be included in the next version bump. This PR includes changesets to release 29 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/typescript/ai/src/activities/chat/index.ts (2)
688-774:⚠️ Potential issue | 🟠 MajorRefresh the active tool set after replaying pending discovery calls.
executeToolCalls()can execute the lazy discovery tool here, but this path never rebuildsthis.tools/ToolCallManagerafterward. If a run resumes with a pending discovery call in history, the next model request is still sent with the pre-discovery tool list, so the newly discovered tool remains unavailable until some later discovery happens.Suggested fix
// Consume the async generator, yielding custom events and collecting the return value const executionResult = yield* this.drainToolCallGenerator(generator) + if (this.lazyToolManager.hasNewlyDiscoveredTools()) { + this.tools = this.lazyToolManager.getActiveTools() + this.toolCallManager.clear() + this.toolCallManager = new ToolCallManager(this.tools) + } + // Check if middleware aborted during pending tool execution if (this.isMiddlewareAborted()) { this.setToolPhase('stop') return 'stop' }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 688 - 774, After draining the generator returned by executeToolCalls (i.e., after const executionResult = yield* this.drainToolCallGenerator(generator)) refresh the active tool set / ToolCallManager so any tools discovered during pending discovery calls become available for the next model request; specifically, reinitialize or rebuild this.tools / the ToolCallManager from the updated discovery state (or call the helper that re-registers tools) before calling middlewareRunner.runOnToolPhaseComplete and before building chunks with buildToolResultChunks/buildApprovalChunks/buildClientToolChunks so the subsequent request uses the updated tool list.
654-732:⚠️ Potential issue | 🟡 MinorInclude undiscovered-tool errors in the tool-phase completion payload.
These branches emit
undiscoveredLazyResultsasTOOL_CALL_ENDchunks, then callrunOnToolPhaseComplete()with onlyexecutionResult.results. Middleware/devtools will see a partial aggregate for this path, because the undiscovered calls have an emitted outcome but never appear in the completion payload.Suggested fix
- await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, { - toolCalls: pendingToolCalls, - results: executionResult.results, + const phaseResults = [...undiscoveredLazyResults, ...executionResult.results] + await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, { + toolCalls: pendingToolCalls, + results: phaseResults, needsApproval: executionResult.needsApproval, needsClientExecution: executionResult.needsClientExecution, })Apply the same merge in
processToolCalls().Also applies to: 793-878
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai/src/activities/chat/index.ts` around lines 654 - 732, The completion payload omits undiscovered lazy-tool errors: merge undiscoveredLazyResults into the final results before calling this.middlewareRunner.runOnToolPhaseComplete so middleware/devtools receive a complete aggregate; specifically, after building undiscoveredLazyResults (from processToolCalls's pendingToolCalls branch using buildToolResultChunks) combine undiscoveredLazyResults with executionResult.results (and update needsApproval/needsClientExecution if applicable) and pass that merged results array to runOnToolPhaseComplete (same fix also apply to the other processToolCalls branch around lines 793-878).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/typescript/ai/src/activities/chat/index.ts`:
- Around line 688-774: After draining the generator returned by executeToolCalls
(i.e., after const executionResult = yield*
this.drainToolCallGenerator(generator)) refresh the active tool set /
ToolCallManager so any tools discovered during pending discovery calls become
available for the next model request; specifically, reinitialize or rebuild
this.tools / the ToolCallManager from the updated discovery state (or call the
helper that re-registers tools) before calling
middlewareRunner.runOnToolPhaseComplete and before building chunks with
buildToolResultChunks/buildApprovalChunks/buildClientToolChunks so the
subsequent request uses the updated tool list.
- Around line 654-732: The completion payload omits undiscovered lazy-tool
errors: merge undiscoveredLazyResults into the final results before calling
this.middlewareRunner.runOnToolPhaseComplete so middleware/devtools receive a
complete aggregate; specifically, after building undiscoveredLazyResults (from
processToolCalls's pendingToolCalls branch using buildToolResultChunks) combine
undiscoveredLazyResults with executionResult.results (and update
needsApproval/needsClientExecution if applicable) and pass that merged results
array to runOnToolPhaseComplete (same fix also apply to the other
processToolCalls branch around lines 793-878).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1f86ef1d-b2ad-44e2-b301-4217c5f6df71
📒 Files selected for processing (4)
docs/config.jsonexamples/ts-react-chat/src/routes/api.tanchat.tspackages/typescript/ai/src/activities/chat/index.tspackages/typescript/ai/tests/chat.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/config.json
- packages/typescript/ai/tests/chat.test.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.changeset/sixty-lions-accept.md (1)
5-5: Make the changeset note describe the shipped behavior.
Fix issue with tool callingis too vague for a user-facing changelog entry, especially for a PR centered on lazy tool discovery. Please name the actual behavior change so consumers can tell what was added or fixed from the release notes.Suggested wording
-Fix issue with tool calling +Add lazy tool discovery support for tool calling and improve undiscovered-tool handling🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.changeset/sixty-lions-accept.md at line 5, Update the changeset note text to a clear, user-facing description of the shipped behavior change: replace the vague phrase "Fix issue with tool calling" with a concise statement that describes the actual change introduced by the PR (e.g., "Fix lazy tool discovery so tools are discovered only when first invoked" or similar), ensuring the changeset file ".changeset/sixty-lions-accept.md" contains that explicit behavior wording so consumers can understand what was added or fixed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.changeset/sixty-lions-accept.md:
- Line 5: Update the changeset note text to a clear, user-facing description of
the shipped behavior change: replace the vague phrase "Fix issue with tool
calling" with a concise statement that describes the actual change introduced by
the PR (e.g., "Fix lazy tool discovery so tools are discovered only when first
invoked" or similar), ensuring the changeset file
".changeset/sixty-lions-accept.md" contains that explicit behavior wording so
consumers can understand what was added or fixed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 43b1c670-3df3-467e-8370-05af2582b761
📒 Files selected for processing (2)
.changeset/sixty-lions-accept.mdpackages/typescript/ai-openrouter/src/adapters/text.ts
|
This is perfect. lazy tool discovery is the future of tools. |

Summary
lazy: trueflag to tool definitions — lazy tools are withheld from the LLM upfront__lazy__tool__discovery__tool lets the LLM discover lazy tools by name, receiving their descriptions and schemas on demandChanges
Core (
@tanstack/ai):lazy?: booleanonTool,ClientTool,ToolDefinitionConfigLazyToolManagerclass (lazy-tool-manager.ts)TextEngineintegration: mutable tools, dynamic refresh after discovery, undiscovered tool error handlingExample (
ts-react-chat):compareGuitars,calculateFinancing,searchGuitarsDocs:
docs/guides/lazy-tool-discovery.mdTest plan
Summary by CodeRabbit
New Features
Documentation
Examples
Types
Tests