import Foundation
enum ToolCallParserError: LocalizedError, Sendable, Equatable {
case multipleCalls
case malformedCall
case contentOutsideCall
case invalidPayload
case unexpectedFields([String])
case missingName
case missingArguments
var errorDescription: String? {
switch self {
case .multipleCalls:
"The model requested more than one tool in a single turn."
case .malformedCall:
"The model returned an incomplete tool-call envelope."
case .contentOutsideCall:
"The model mixed a tool call with other response content."
case .invalidPayload:
"The tool call was not valid JSON."
case .unexpectedFields(let fields):
"The tool call included unexpected fields: \(fields.joined(separator: ", "))."
case .missingName:
"The tool call did not include a valid function name."
case .missingArguments:
"The tool call arguments must be a JSON object."
}
}
}
struct ToolCallParser: Sendable {
private static let openingTag = ""
private static let closingTag = ""
static func parse(_ text: String) throws -> AgentToolCall? {
let openingRanges = text.ranges(of: openingTag)
let closingRanges = text.ranges(of: closingTag)
if openingRanges.isEmpty && closingRanges.isEmpty {
return nil
}
guard openingRanges.count == 1, closingRanges.count == 1 else {
throw ToolCallParserError.multipleCalls
}
guard
let opening = openingRanges.first,
let closing = closingRanges.first,
opening.upperBound <= closing.lowerBound
else {
throw ToolCallParserError.malformedCall
}
let prefix = text[.. [Range] {
var result: [Range] = []
var searchRange = startIndex..