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 = "" .. param_name .. ">"
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 = "" .. tool_name .. ">"
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