local M = {} -- Helper function to parse a parameter tag like value -- Returns {name = string, value = string, next_pos = number} or nil if incomplete local function parse_parameter(text, start_pos) local i = start_pos local len = #text -- Skip whitespace while i <= len and string.match(string.sub(text, i, i), "%s") do i = i + 1 end if i > len or string.sub(text, i, i) ~= "<" then return nil end -- Find parameter name local param_name_start = i + 1 local param_name_end = string.find(text, ">", param_name_start) if not param_name_end then return nil -- Incomplete parameter tag end local param_name = string.sub(text, param_name_start, param_name_end - 1) i = param_name_end + 1 -- Find parameter value (everything until closing tag) local param_close_tag = "" local param_value_start = i local param_close_pos = string.find(text, param_close_tag, i, true) if not param_close_pos then -- Incomplete parameter value, return what we have local param_value = string.sub(text, param_value_start) return { name = param_name, value = param_value, next_pos = len + 1, } end local param_value = string.sub(text, param_value_start, param_close_pos - 1) i = param_close_pos + #param_close_tag return { name = param_name, value = param_value, next_pos = i, } end -- Helper function to parse tool use content starting after -- Returns {content = ToolUseContent, next_pos = number} or nil if incomplete local function parse_tool_use(text, start_pos) local i = start_pos local len = #text -- Skip whitespace while i <= len and string.match(string.sub(text, i, i), "%s") do i = i + 1 end if i > len then return nil -- No content after end -- Check if we have opening tag for tool name if string.sub(text, i, i) ~= "<" then return nil -- Invalid format end -- Find tool name local tool_name_start = i + 1 local tool_name_end = string.find(text, ">", tool_name_start) if not tool_name_end then return nil -- Incomplete tool name tag end local tool_name = string.sub(text, tool_name_start, tool_name_end - 1) i = tool_name_end + 1 -- Parse tool parameters local tool_input = {} local partial = false -- Look for tool closing tag or local tool_close_tag = "" local tool_use_close_tag = "" while i <= len do -- Skip whitespace before checking for closing tags while i <= len and string.match(string.sub(text, i, i), "%s") do i = i + 1 end if i > len then partial = true break end -- Check for tool closing tag first local tool_close_pos = string.find(text, tool_close_tag, i, true) local tool_use_close_pos = string.find(text, tool_use_close_tag, i, true) if tool_close_pos and tool_close_pos == i then -- Found tool closing tag i = tool_close_pos + #tool_close_tag -- Skip whitespace while i <= len and string.match(string.sub(text, i, i), "%s") do i = i + 1 end -- Check for if i <= len and string.find(text, tool_use_close_tag, i, true) == i then i = i + #tool_use_close_tag partial = false else partial = true end break elseif tool_use_close_pos and tool_use_close_pos == i then -- Found without tool closing tag (malformed, but handle it) i = tool_use_close_pos + #tool_use_close_tag partial = false break else -- Parse parameter tag local param_result = parse_parameter(text, i) if param_result then tool_input[param_result.name] = param_result.value i = param_result.next_pos else -- Incomplete parameter, mark as partial partial = true break end end end -- If we reached end of text without proper closing, it's partial if i > len then partial = true end return { content = { type = "tool_use", tool_name = tool_name, tool_input = tool_input, partial = partial, }, next_pos = i, } end --- Parse the text into a list of TextContent and ToolUseContent --- The text is a string. --- For example: --- parse("Hello, world!") --- returns --- { --- { --- type = "text", --- text = "Hello, world!", --- partial = false, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txtfoo") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- content = "foo", --- }, --- partial = false, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txtfooI am another tool.path/to/file.txtbarhello") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- content = "foo", --- }, --- partial = false, --- }, --- { --- type = "text", --- text = "I am another tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- content = "bar", --- }, --- partial = false, --- }, --- { --- type = "text", --- text = "hello", --- partial = false, --- }, --- } --- --- parse("Hello, world! I am a tool.") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = {}, --- partial = true, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txt") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- }, --- partial = true, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txtfoo bar") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- content = "foo bar", --- }, --- partial = true, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txtfoo bar") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.path/to/file.txtfoo bar", --- partial = false, --- } --- } --- --- parse("Hello, world! I am a tool.path/to/file.txt") --- returns --- { --- { --- type = "text", --- text = "Hello, world! I am a tool.", --- partial = false, --- }, --- { --- type = "tool_use", --- tool_name = "write", --- tool_input = { --- path = "path/to/file.txt", --- content = "", --- }, --- partial = false, --- }, --- } --- --- parse("Hello, world! I am a tool.path/to/file.txt