Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 41 additions & 41 deletions Sources/AnyLanguageModel/Models/OpenAILanguageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1050,59 +1050,59 @@ private enum Responses {
outputs.append(object)

case .tool(let id):
let toolMessage = msg
// Wrap user content into a single top-level message as required by Responses API
var contentBlocks: [JSONValue]
switch toolMessage.content {
let outputValue: JSONValue
switch msg.content {
case .text(let text):
contentBlocks = [
.object(["type": .string("input_text"), "text": .string(text)])
]
outputValue = .string(text)
case .blocks(let blocks):
contentBlocks = blocks.map { block in
switch block {
case .text(let text):
return .object(["type": .string("input_text"), "text": .string(text)])
case .imageURL(let url):
return .object([
"type": .string("input_image"),
"image_url": .object(["url": .string(url)]),
])
outputValue = .array(
blocks.map { block in
switch block {
case .text(let text):
return .object(["type": .string("input_text"), "text": .string(text)])
case .imageURL(let url):
return .object([
"type": .string("input_image"),
"image_url": .string(url),
])
}
}
}
}
let outputString: String
if contentBlocks.count > 1 {
let encoder = JSONEncoder()
if let data = try? encoder.encode(JSONValue.array(contentBlocks)),
let str = String(data: data, encoding: .utf8)
{
outputString = str
} else {
outputString = "[]"
}
} else if let block = contentBlocks.first {
let encoder = JSONEncoder()
if let data = try? encoder.encode(block),
let str = String(data: data, encoding: .utf8)
{
outputString = str
} else {
outputString = "{}"
}
} else {
outputString = "{}"
)
}
outputs.append(
.object([
"type": .string("function_call_output"),
"call_id": .string(id),
"output": .string(outputString),
"output": outputValue,
])
)

case .raw(rawContent: let rawContent):
outputs.append(rawContent)
// Convert Chat Completions assistant+tool_calls to Responses API function_call items
if case .object(let dict) = rawContent,
case .string(let role) = dict["role"],
role == "assistant",
case .array(let toolCallsArr) = dict["tool_calls"]
{
for tc in toolCallsArr {
guard case .object(let tcDict) = tc,
case .string(let tcId) = tcDict["id"],
case .object(let fnDict) = tcDict["function"],
case .string(let fnName) = fnDict["name"],
case .string(let fnArgs) = fnDict["arguments"]
Comment on lines +1083 to +1092
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The pattern matches against dict["role"] / dict["tool_calls"] are missing optional unwrapping (?). Since dict[...] is optional (as used elsewhere in this file, e.g. case let .string(type)? = obj["type"]), this won’t compile. Update these to case .string(let role)? = ... / case .array(let toolCallsArr)? = ... (or unwrap with guard let).

Suggested change
case .string(let role) = dict["role"],
role == "assistant",
case .array(let toolCallsArr) = dict["tool_calls"]
{
for tc in toolCallsArr {
guard case .object(let tcDict) = tc,
case .string(let tcId) = tcDict["id"],
case .object(let fnDict) = tcDict["function"],
case .string(let fnName) = fnDict["name"],
case .string(let fnArgs) = fnDict["arguments"]
case .string(let role)? = dict["role"],
role == "assistant",
case .array(let toolCallsArr)? = dict["tool_calls"]
{
for tc in toolCallsArr {
guard case .object(let tcDict) = tc,
case .string(let tcId)? = tcDict["id"],
case .object(let fnDict)? = tcDict["function"],
case .string(let fnName)? = fnDict["name"],
case .string(let fnArgs)? = fnDict["arguments"]

Copilot uses AI. Check for mistakes.
Comment on lines +1089 to +1092
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Same optional-pattern issue inside the tool call loop: tcDict["id"], tcDict["function"], fnDict["name"], and fnDict["arguments"] are dictionary subscripts and need ? in the pattern match (or explicit unwrapping). As written, this block won’t compile.

Suggested change
case .string(let tcId) = tcDict["id"],
case .object(let fnDict) = tcDict["function"],
case .string(let fnName) = fnDict["name"],
case .string(let fnArgs) = fnDict["arguments"]
case .string(let tcId)? = tcDict["id"],
case .object(let fnDict)? = tcDict["function"],
case .string(let fnName)? = fnDict["name"],
case .string(let fnArgs)? = fnDict["arguments"]

Copilot uses AI. Check for mistakes.
else { continue }
outputs.append(
.object([
"type": .string("function_call"),
"call_id": .string(tcId),
"name": .string(fnName),
"arguments": .string(fnArgs),
])
)
}
} else {
outputs.append(rawContent)
}
Comment on lines 1080 to +1105
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This PR changes request-body formatting for Responses tool-call round trips, but there’s no deterministic unit test asserting the generated input items (e.g., .raw assistant+tool_callsfunction_call items, and .toolfunction_call_output with a plain string output). Adding a unit test around Responses.createRequestBody would prevent regressions without requiring a live API key.

Copilot uses AI. Check for mistakes.

case .system:
let systemMessage = msg
Expand Down