How NativeToolCallParser Converts API Tool Calls to Typed Arguments in Roo Code

The NativeToolCallParser class transforms raw OpenAI-style function call payloads into strongly-typed ToolUse objects by parsing JSON arguments, coercing values to specific TypeScript types, and validating them against the NativeToolArgs definitions.

Roo Code, developed by RooCodeInc, processes LLM-generated tool calls through a strict parsing pipeline that ensures type safety across its entire tool ecosystem. The NativeToolCallParser located in src/core/assistant-message/NativeToolCallParser.ts serves as the bridge between unstructured API responses and the typed execution layer. This conversion guarantees that built-in tools, MCP integrations, and custom user tools receive nativeArgs that exactly match their TypeScript interfaces, preventing runtime errors while maintaining backward compatibility with legacy parameter formats.

Entry Point: The parseToolCall Method

The conversion process begins with the static parseToolCall method, which acts as the primary entry point for all incoming tool calls.

public static parseToolCall<TName extends ToolName>(toolCall: {
  id: string
  name: TName
  arguments: string
}): ToolUse<TName> | McpToolUse | null

This method receives the raw payload from the LLM, which includes a tool name and a JSON string of arguments. First, it normalizes dynamic MCP tool names using resolveToolAlias to handle the mcp--server--tool naming convention, falling back to standard tool names if the alias resolution does not match an MCP pattern. Invalid tool names are rejected early with a logged warning and a null return value, preventing downstream execution of undefined tools.

Parsing and Type Coercion Pipeline

Once the tool name is resolved, the parser executes a multi-stage pipeline to convert the raw JSON string into typed arguments.

JSON Extraction and Error Handling

The parser attempts to parse the arguments string into a JavaScript object, treating empty strings as empty objects to handle edge cases in API responses.

const args = toolCall.arguments === "" ? {} : JSON.parse(toolCall.arguments);

If JSON.parse throws due to malformed JSON, the catch block logs the specific error and returns null, ensuring that corrupt payloads never reach the tool executors.

Legacy Parameter Construction

For backward compatibility with the UI layer, the parser constructs a params object that stringifies all values. This legacy format maps each argument key to its string representation, validating keys against the known toolParamNames registry and warning about unknown parameters unless the tool is a custom user-registered tool.

const params: Partial<Record<ToolParamName, string>> = {};
for (const [key, value] of Object.entries(args)) {
  if (!toolParamNames.includes(key as ToolParamName) && !customToolRegistry.has(resolvedName)) {
    console.warn(`Unknown parameter '${key}' for tool '${resolvedName}'`);
    continue;
  }
  params[key as ToolParamName] = typeof value === "string" ? value : JSON.stringify(value);
}

Typed nativeArgs Assembly

The core type conversion happens in a type-guarded switch statement that matches the resolved tool name against entries in NativeToolArgs. For each tool, the parser extracts specific fields from args and applies coercion helpers to ensure type safety.

For example, the read_file tool handles both legacy array formats and modern single-file arguments:

case "read_file":
  // Legacy format { files: [...] } → convert to typed FileEntry[]
  if (args.files !== undefined) {
    nativeArgs = {
      files: this.convertFileEntries(filesArray),
      _legacyFormat: true as const,
    } as NativeArgsFor<TName>;
  }
  // New format { path, mode, offset, … }
  if (!nativeArgs && args.path !== undefined) {
    nativeArgs = {
      path: args.path,
      mode: args.mode,
      offset: this.coerceOptionalNumber(args.offset),
      limit: this.coerceOptionalNumber(args.limit),
    } as NativeArgsFor<TName>;
  }
  break;

The parser uses helper methods coerceOptionalNumber and coerceOptionalBoolean to safely convert strings or numbers into the expected types defined in src/shared/tools.ts. Custom tools skip this switch entirely and pass the raw args object directly as nativeArgs, allowing dynamic schemas without strict type checking.

Validation and Final Object Construction

After constructing nativeArgs, the parser validates that the object exists for all non-custom tools. If validation fails, it throws a descriptive error identifying the problematic tool and the invalid arguments.

The final ToolUse object combines both the legacy params for UI rendering and the strongly-typed nativeArgs for execution:

const result: ToolUse<TName> = {
  type: "tool_use",
  name: resolvedName,
  params,
  partial: false,
  nativeArgs,
};
if (toolCall.name !== resolvedName) {
  result.originalName = toolCall.name;   // preserve alias for API history
}
return result;

Streaming Tool Call Support

For streaming LLM responses, processStreamingChunk accumulates partial JSON and uses the partial-json library to parse incomplete argument strings. The parser then calls createPartialToolUse, which mirrors the main conversion logic but sets partial: true and only builds arguments from the data received so far. This allows the UI to display live tool call formation while the final execution waits for finalizeStreamingToolCall to complete the typed conversion.

Practical Implementation Example

When integrating the parser into custom handlers or tests, you can invoke the conversion directly:

import { NativeToolCallParser } from "@roo-code/core/assistant-message/NativeToolCallParser";

const rawCall = {
  id: "call_1",
  name: "read_file",
  arguments: JSON.stringify({
    path: "src/index.ts",
    mode: "full",
    offset: "10",  // String input from API
    limit: 20,
  }),
};

const toolUse = NativeToolCallParser.parseToolCall(rawCall);

if (toolUse) {
  // toolUse.nativeArgs is typed as NativeToolArgs["read_file"]
  // offset is now number | undefined, not string
  console.log(toolUse.nativeArgs.path);  // string
  console.log(toolUse.nativeArgs.offset); // number (10)
}

Summary

  • Entry Point: The parseToolCall static method in src/core/assistant-message/NativeToolCallParser.ts receives raw tool calls and resolves aliases via resolveToolAlias.
  • Type Safety: The parser coerces JSON values using coerceOptionalNumber and coerceOptionalBoolean to match the NativeToolArgs TypeScript definitions from src/shared/tools.ts.
  • Dual Format: Each ToolUse object contains both legacy params (stringified for UI) and strongly-typed nativeArgs (for execution).
  • Validation: Unknown parameters trigger warnings for built-in tools, while custom tools pass through unvalidated to support dynamic schemas.
  • Streaming: processStreamingChunk handles partial JSON using the partial-json library, enabling real-time tool call display.

Frequently Asked Questions

What is the difference between params and nativeArgs in Roo Code's tool system?

The params field contains stringified key-value pairs intended for display in the UI layer, maintaining backward compatibility with legacy string-based formats. The nativeArgs field contains strongly-typed objects that match the TypeScript definitions in src/shared/tools.ts, providing type-safe arguments for the actual tool execution logic in src/core/tools/BaseTool.ts.

How does NativeToolCallParser handle invalid or malformed tool arguments?

If JSON.parse fails on the arguments string, the parser logs the error and returns null. After parsing, if the tool is not a custom registered tool and nativeArgs remains undefined after the type-specific switch block, the parser throws an explicit error stating that the arguments are invalid for the specific tool name. This prevents malformed payloads from reaching downstream executors.

Can NativeToolCallParser process streaming tool calls from the LLM?

Yes, the parser supports streaming through processStreamingChunk, which accumulates partial JSON strings and uses the partial-json library to parse incomplete data. It creates partial ToolUse objects with partial: true that update the UI in real-time, then finalizes the conversion with finalizeStreamingToolCall once the complete arguments are received.

Where are the TypeScript type definitions for tool arguments maintained?

The source of truth for typed tool arguments is src/shared/tools.ts, which exports the NativeToolArgs interface. This interface maps each tool name to its specific argument structure, and NativeToolCallParser uses these definitions through the NativeArgsFor<TName> generic to ensure that the nativeArgs field matches the expected TypeScript shape for every built-in tool.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →