Skip to content

Conversation

@pokey
Copy link
Contributor

@pokey pokey commented Oct 20, 2025

Relax tool node validation to allow middleware to intercept and handle unregistered tools.

This is the JS equivalent of Python PR langchain-ai/langchain#33512

Background

Some tools, like Anthropic's text editor and memory tools, use "schema-less" tool definitions that are added to model requests but not registered as actual tools with ToolNode. These tools exist only as type identifiers (e.g., {"type": "text_editor_20250728"}) that the Anthropic API understands natively.

Previously, ToolNode would throw an error immediately if a tool wasn't registered, before middleware could intercept the call. This prevented middleware from handling these unregistered tools.

Changes

Type Updates

  • Updated ToolCallRequest.tool type from ClientTool | ServerTool to ClientTool | ServerTool | undefined
  • Added documentation explaining that undefined indicates an unregistered tool

ToolNode Changes

  • In runTool(): Removed immediate error throw after tool lookup - undefined tools can now proceed to middleware
  • In baseHandler: Uses tool directly from request instead of redundant lookup. Added validation that returns error ToolMessage (instead of throwing) if tool is undefined

This allows wrapToolCall middleware to intercept unregistered tools by checking request.tool === undefined and short-circuiting without calling the handler

Related

This is a prerequisite for implementing Anthropic text editor and memory middleware (Python PR langchain-ai/langchain#33384).

This change allows middleware to intercept and handle tool calls for tools
that aren't registered with ToolNode, enabling support for schema-less tools
like Anthropic's text editor and memory tools.

Key changes:
- Updated ToolCallRequest.tool type to allow undefined
- Deferred tool validation to allow wrapToolCall middleware to intercept
- Added comprehensive tests for unregistered tool handling

This is the JS equivalent of Python PR langchain-ai/langchain#33512

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@changeset-bot
Copy link

changeset-bot bot commented Oct 20, 2025

⚠️ No Changeset found

Latest commit: 643ef31

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Comment on lines +25 to +31
/**
* skip as test requires primitives from `@langchain/core` that aren't released yet
* and fails in dependency range tests, remove after next release
*/
if (process.env.LC_DEPENDENCY_RANGE_TESTS) {
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

is this necessary here? claude code thought it was but I don't have enough context to have an opinion

Copy link
Member

Choose a reason for hiding this comment

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

It shouldn't as we have published @langchain/core by now. We can remove all these occurrences. Long term we will revisit these environment tests and disable them if a PR contains both, changes to langchain and @langchain/core.

pokey and others added 2 commits October 20, 2025 14:53
Use the simpler array format [AIMessage(...)] for ToolNode.invoke() calls,
matching the Python test structure, instead of unnecessarily wrapping in
{ messages: [...] }.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add test for dict format { messages: [...] } to verify both input formats
work with unregistered tool handling, matching Python's
test_interceptor_with_dict_input_format.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@pokey pokey marked this pull request as ready for review October 20, 2025 13:55
Copy link
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

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

Let's remove the if statements in these tests.

Comment on lines +25 to +31
/**
* skip as test requires primitives from `@langchain/core` that aren't released yet
* and fails in dependency range tests, remove after next release
*/
if (process.env.LC_DEPENDENCY_RANGE_TESTS) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

It shouldn't as we have published @langchain/core by now. We can remove all these occurrences. Long term we will revisit these environment tests and disable them if a PR contains both, changes to langchain and @langchain/core.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants