Skip to content

Conversation

@hannesrudolph
Copy link
Collaborator

Removes all legacy XML-based tool calling support with zero fallback.

  • Deletes XML protocol/types/parsers/formatters and XML-only tests/fixtures
  • Removes toolProtocol config plumbing and UI settings
  • Makes tool invocation native-only; fails fast if XML tool markup is encountered
  • Updates assistant message rendering to reject XML tool tags and invalid legacy tool blocks

Validation:

  • turbo lint
  • turbo check-types
  • turbo test

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 20, 2026
@roomote
Copy link
Contributor

roomote bot commented Jan 20, 2026

Oroocle Clock   See task on Roo Cloud

1 follow-up remaining.

  • Bedrock: native-only message conversion can emit toolUse/toolResult blocks even when no toolConfig is being sent (may error)
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

@ellipsis-dev
Copy link
Contributor

ellipsis-dev bot commented Jan 20, 2026

⚠️ This PR is too big for Ellipsis, but support for larger PRs is coming soon. If you want us to prioritize this feature, let us know at help@ellipsis.dev


Generated with ❤️ by ellipsis.dev

}
// Tool calling is native-only. If the model emits XML-style tool tags in a text block,
// fail fast with a clear error.
if (containsXmlToolMarkup(content)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

containsXmlToolMarkup() lowercases the full text and then checks for <${toolName} / </${toolName} substrings. That will also match normal examples like \<read_file>`` inside code fences or documentation snippets, so a user pasting docs can hard-fail the turn even though it is not attempting tool execution. Consider scoping the check (e.g., ignore content inside markdown code fences, or only fail when the tag appears outside backticks) so ordinary docs/examples do not immediately break the conversation.

Fix it with Roo Code or mention @roomote and request a fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed — fixed by scoping XML-tag detection to ignore markdown code fences / inline code so docs/examples like <read_file> don't hard-fail the turn.

// If we couldn't construct it, the model produced an invalid tool call payload.
if (!nativeArgs && !customToolRegistry.has(resolvedName)) {
throw new Error(
`[NativeToolCallParser] Invalid arguments for tool '${resolvedName}'. ` +
Copy link
Contributor

Choose a reason for hiding this comment

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

This new fail-fast is a good tightening for native-only tool execution, but there is still a fallback path in that can surface a without when returns null. In that case we end up reaching the tool layer with missing and producing tool_result payloads via error paths rather than a single consistent structured tool_result. Consider aligning the streaming fallback to emit a single for malformed args instead of attempting to execute.

Fix it with Roo Code or mention @roomote and request a fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Aligned now: malformed/incomplete tool args no longer reach the tool layer. If a non-partial tool_use is missing nativeArgs, we emit exactly one structured tool_result error instead of attempting execution.

…allParser

- Add list_files case to parseToolCall() switch with path/recursive args
- Add new_task case to parseToolCall() switch with mode/message/todos args
- Add list_files to NativeToolArgs type in tools.ts
- Both tools were in toolNames but lacked case handlers, causing the
  fail-fast check on line 852 to throw 'Invalid arguments for tool'
nativeArgs = {
path: partialArgs.path,
recursive: partialArgs.recursive,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In NativeToolCallParser.createPartialToolUse(), list_files treats recursive as-is. Since the schema is boolean, partial-json may yield a string (e.g. "true") during streaming, which can later trip the nativeArgs validation in parseToolCall() and surface as an invalid-args error for an otherwise valid call. Coercing recursive to boolean in nativeArgs (similar to ListFilesTool.handlePartial()) would make streaming progress and finalization consistent.

Fix it with Roo Code or mention @roomote and request a fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: list_files.recursive is now coerced to boolean during streaming (partial nativeArgs) and on final parse, so validation won't reject otherwise-valid calls.

- Remove redundant pushToolResult after handleError in BaseTool.ts
  (handleError already emits tool_result via formatResponse.toolError)
- Fix containsXmlToolMarkup to exclude content inside markdown code
  fences, preventing false positives when users paste docs/examples
- Make 'wait for confirmation' guideline conditional on
  !isMultipleNativeToolCallsEnabled to avoid contradicting
  multiple-tools-per-message guidance
- Update tests to reflect intentional behavior changes
// For native protocol, the footer is less relevant since multiple tools can execute in one message
const footer = isNativeProtocol(protocol)
const footer = isMultipleNativeToolCallsEnabled
? `\n\nBy carefully considering the user's response after tool executions, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.`
Copy link
Contributor

Choose a reason for hiding this comment

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

When MULTIPLE_NATIVE_TOOL_CALLS is enabled, the footer is updated but the numbered guideline still says "After each tool use, the user will respond...", which reads inconsistent with batching multiple tool calls per message. Consider making that guideline conditional too (e.g. "After tool executions..." in the multi-call branch) so the section doesn't contradict itself.

Fix it with Roo Code or mention @roomote and request a fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated: when MULTIPLE_NATIVE_TOOL_CALLS is enabled, the guidelines no longer say “After each tool use…” — that wording is now conditional to avoid contradicting batching.

* Router providers (Requesty, Unbound, LiteLLM) assume all models support native tools.
*/
export const NATIVE_TOOL_DEFAULTS = {
supportsNativeTools: true,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like we can get rid of this too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed in this PR — TOOL_PROTOCOL/ToolProtocol are now gone from packages/types/src/tool.ts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Clarifying this thread: this inline comment was attached to the removed TOOL_PROTOCOL / ToolProtocol block above (GitHub is now showing the nearby NATIVE_TOOL_DEFAULTS lines as context).

TOOL_PROTOCOL / ToolProtocol are removed.

🟦 NATIVE_TOOL_DEFAULTS is still intentionally kept because it’s referenced in multiple places to merge defaults into cached/custom model info (e.g. router providers) so supportsNativeTools doesn’t become accidentally undefined.

...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }),
...(metadata?.toolProtocol === "native" && {
parallel_tool_calls: metadata.parallelToolCalls ?? false,
...(metadata?.parallelToolCalls !== undefined && {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we explicitly setting parallelToolCalls to false everywhere? I'm a little nervous about changing this unintentionally.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. Updated so we only send parallel_tool_calls when metadata.parallelToolCalls is explicitly provided (no implicit false). See src/api/providers/deepseek.ts.

Comment on lines 189 to 191
// Check if model supports native tools and tools are provided with native protocol
const supportsNativeTools = info.supportsNativeTools ?? false
const useNativeTools = supportsNativeTools && metadata?.tools && metadata.tools.length > 0 && isNativeProtocol
const useNativeTools = supportsNativeTools && metadata?.tools && metadata.tools.length > 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this still a case we need to support?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No — with XML tool calling removed, we don't need to gate native tools on toolProtocol here anymore. useNativeTools is now purely capability + tools-present in src/api/providers/lite-llm.ts.

Comment on lines 50 to +51
// LM Studio always supports native tools (https://lmstudio.ai/docs/developer/core/tools)
const useNativeTools = metadata?.tools && metadata.tools.length > 0 && metadata?.toolProtocol !== "xml"
const useNativeTools = !!(metadata?.tools && metadata.tools.length > 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably remove?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep — removed. LM Studio now enables native tools whenever tools are present (no toolProtocol check) in src/api/providers/lm-studio.ts.

const convertedMessages = sharedConverter(anthropicMessages as Anthropic.Messages.MessageParam[], {
useNativeTools,
})
const convertedMessages = sharedConverter(anthropicMessages as Anthropic.Messages.MessageParam[])
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that Bedrock message conversion is native-only, it will emit toolUse/toolResult blocks for any historical tool_use/tool_result content, even when this request is not sending toolConfig (because useNativeTools is false when tools are omitted or tool_choice is none). Bedrock errors if toolUse/toolResult appear without toolConfig, so it may be worth either (a) failing fast here when convertedMessages contain tool blocks but toolConfig is missing, or (b) deriving the useNativeTools decision from the presence of tool blocks in the messages, not just metadata.tools.

Fix it with Roo Code or mention @roomote and request a fix.

metadata?.toolProtocol !== "xml" &&
metadata?.tool_choice !== "none"
// NOTE: tool inclusion is controlled by caller metadata, not per-model flags.
const useNativeTools = Boolean(metadata?.tools?.length) && metadata?.tool_choice !== "none"
Copy link
Contributor

Choose a reason for hiding this comment

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

In AwsBedrockHandler.createMessage(), useNativeTools is derived only from metadata.tools/tool_choice, but convertToBedrockConverseMessages() is now native-only and can still produce toolUse/toolResult blocks from historical tool_use/tool_result message content. Bedrock will reject requests that contain tool blocks when toolConfig is omitted, so this can 400 on resumed conversations that have old tool history but no tools are being sent in the current request.

Fix it with Roo Code or mention @roomote and request a fix.

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

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

3 participants