Skip to content

Commit 75c714c

Browse files
committed
Enhance Responses API with new tool call types and JSON formats
- Added support for new tool call types: ImageGenerationToolCall, CodeInterpreterToolCall, LocalShellToolCall, CustomToolCall, and their respective outputs. - Introduced MCP-related classes: MCPApprovalRequest, MCPApprovalResponse, MCPListTools, MCPTool, and MCPToolCall. - Updated Input and JsonFormats to accommodate new tool calls and their serialization. - Enhanced InputMessageContent to include detail options for images. - Improved overall structure and organization of tool call handling.
1 parent f9e490c commit 75c714c

16 files changed

+534
-24
lines changed

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/Input.scala

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.cequence.openaiscala.domain.responsesapi
22

33
import io.cequence.openaiscala.domain.ChatRole
44
import io.cequence.openaiscala.domain.responsesapi.tools._
5+
import io.cequence.openaiscala.domain.responsesapi.tools.mcp._
56

67
/**
78
* Input types hierarchy:
@@ -20,10 +21,21 @@ import io.cequence.openaiscala.domain.responsesapi.tools._
2021
* - Computer tool call (object)
2122
* - Computer tool call output (object)
2223
* - Web search tool call (object)
23-
* - Function tool call (object)
24-
* - Function tool call output (object)
24+
* - Function tool call (object) - Deprecated?
25+
* - Function tool call output (object) - Deprecated?
2526
* - Reasoning (object)
27+
* - Reasoning output (object)
2628
* - Item reference (object)
29+
* - Image generation call (object)
30+
* - Code interpreter tool call (object)
31+
* - Local shell call (object)
32+
* - Local shell call output (object)
33+
* - MCP list tools (object)
34+
* - MCP approval request (object)
35+
* - MCP approval response (object)
36+
* - MCP tool call (object)
37+
* - Custom tool call output (object)
38+
* - Custom tool call (object)
2739
*/
2840
trait Input {
2941
val `type`: String
@@ -137,6 +149,102 @@ object Input {
137149
status: Option[ModelStatus] = None
138150
) = Reasoning(id, summary, status)
139151

152+
def ofImageGenerationToolCall(
153+
id: String,
154+
result: Option[String],
155+
status: String
156+
) = ImageGenerationToolCall(
157+
id,
158+
result,
159+
status
160+
)
161+
162+
def ofCodeInterpreterToolCall(
163+
id: String,
164+
code: Option[String],
165+
containerId: String,
166+
outputs: Seq[CodeInterpreterOutput],
167+
status: String
168+
) = CodeInterpreterToolCall(
169+
id,
170+
code,
171+
containerId,
172+
outputs,
173+
status
174+
)
175+
176+
def ofCodeInterpreterOutputLogs(
177+
logs: String
178+
) = CodeInterpreterOutputLogs(logs)
179+
180+
def ofCodeInterpreterOutputImage(
181+
url: String
182+
) = CodeInterpreterOutputImage(url)
183+
184+
def ofLocalShellToolCall(
185+
action: LocalShellAction,
186+
callId: String,
187+
id: String,
188+
status: String
189+
) = LocalShellToolCall(
190+
action,
191+
callId,
192+
id,
193+
status
194+
)
195+
196+
def ofLocalShellAction(
197+
command: Seq[String],
198+
env: Map[String, String],
199+
timeoutMs: Option[Int] = None,
200+
user: Option[String] = None,
201+
workingDirectory: Option[String] = None
202+
) = LocalShellAction(
203+
command,
204+
env,
205+
timeoutMs,
206+
user,
207+
workingDirectory
208+
)
209+
210+
def ofLocalShellCallOutput(
211+
id: String,
212+
output: String,
213+
status: Option[String] = None
214+
) = LocalShellCallOutput(
215+
id,
216+
output,
217+
status
218+
)
219+
220+
def ofMCPListTools(
221+
id: String,
222+
serverLabel: String,
223+
tools: Seq[MCPTool],
224+
error: Option[String] = None
225+
) = MCPListTools(
226+
id,
227+
serverLabel,
228+
tools,
229+
error
230+
)
231+
232+
def ofMCPToolCall(
233+
arguments: String,
234+
id: String,
235+
name: String,
236+
serverLabel: String,
237+
error: Option[String] = None,
238+
output: Option[String] = None
239+
) = MCPToolCall(
240+
arguments,
241+
id,
242+
name,
243+
serverLabel,
244+
error,
245+
output
246+
)
247+
140248
def ofItemReference(
141249
id: String
142250
) = ItemReference(id)

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/InputMessageContent.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ object InputMessageContent {
3232
final case class Image(
3333
fileId: Option[String] = None,
3434
imageUrl: Option[String] = None,
35-
detail: Option[String] = None
35+
detail: Option[String] = None // low, high, auto
3636
) extends InputMessageContent {
3737
val `type`: String = "input_image"
3838
}

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/JsonFormats.scala

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ import io.cequence.openaiscala.domain.responsesapi.tools.JsonFormats.{
2020
import io.cequence.openaiscala.domain.responsesapi.tools.JsonFormats.{
2121
fileSearchToolCallFormat,
2222
webSearchToolCallFormat,
23-
computerToolCallFormat
23+
computerToolCallFormat,
24+
imageGenerationToolCallFormat,
25+
codeInterpreterToolCallFormat,
26+
localShellToolCallFormat,
27+
localShellCallOutputFormat,
28+
mcpListToolsFormat,
29+
mcpToolCallFormat,
30+
customToolCallOutputFormat,
31+
customToolCallFormat
2432
}
2533
import play.api.libs.json._
2634
import play.api.libs.functional.syntax._
2735
import io.cequence.openaiscala.JsonFormats.chatRoleFormat
2836
import io.cequence.openaiscala.domain.responsesapi.tools.JsonFormats.functionToolCallFormat
37+
import io.cequence.openaiscala.domain.responsesapi.tools.mcp._
2938

3039
object JsonFormats {
3140

@@ -226,17 +235,25 @@ object JsonFormats {
226235

227236
implicit lazy val inputWrites: Writes[Input] = Writes[Input] { (input: Input) =>
228237
val jsObject = input match {
229-
case input: Message.InputText => inputTextMessageFormat.writes(input)
230-
case input: Message.InputContent => inputContentMessageFormat.writes(input)
231-
case input: Message.OutputContent => outputContentMessageFormat.writes(input)
232-
case input: FileSearchToolCall => fileSearchToolCallFormat.writes(input)
233-
case input: ComputerToolCall => computerToolCallFormat.writes(input)
234-
case input: ComputerToolCallOutput => computerToolCallOutputFormat.writes(input)
235-
case input: WebSearchToolCall => webSearchToolCallFormat.writes(input)
236-
case input: FunctionToolCall => functionToolCallFormat.writes(input)
237-
case input: FunctionToolCallOutput => functionToolCallOutputFormat.writes(input)
238-
case input: Reasoning => reasoningFormat.writes(input)
239-
case input: ItemReference => itemReferenceFormat.writes(input)
238+
case input: Message.InputText => inputTextMessageFormat.writes(input)
239+
case input: Message.InputContent => inputContentMessageFormat.writes(input)
240+
case input: Message.OutputContent => outputContentMessageFormat.writes(input)
241+
case input: FileSearchToolCall => fileSearchToolCallFormat.writes(input)
242+
case input: ComputerToolCall => computerToolCallFormat.writes(input)
243+
case input: ComputerToolCallOutput => computerToolCallOutputFormat.writes(input)
244+
case input: WebSearchToolCall => webSearchToolCallFormat.writes(input)
245+
case input: FunctionToolCall => functionToolCallFormat.writes(input)
246+
case input: FunctionToolCallOutput => functionToolCallOutputFormat.writes(input)
247+
case input: Reasoning => reasoningFormat.writes(input)
248+
case input: ItemReference => itemReferenceFormat.writes(input)
249+
case input: ImageGenerationToolCall => imageGenerationToolCallFormat.writes(input)
250+
case input: CodeInterpreterToolCall => codeInterpreterToolCallFormat.writes(input)
251+
case input: LocalShellToolCall => localShellToolCallFormat.writes(input)
252+
case input: LocalShellCallOutput => localShellCallOutputFormat.writes(input)
253+
case input: MCPListTools => mcpListToolsFormat.writes(input)
254+
case input: MCPToolCall => mcpToolCallFormat.writes(input)
255+
case input: CustomToolCallOutput => customToolCallOutputFormat.writes(input)
256+
case input: CustomToolCall => customToolCallFormat.writes(input)
240257
}
241258
jsObject + ("type" -> JsString(input.`type`))
242259
}
@@ -263,15 +280,23 @@ object JsonFormats {
263280
case JsError(_) => JsError("Missing 'content' field for Input")
264281
}
265282

266-
case "file_search_call" => fileSearchToolCallFormat.reads(json)
267-
case "computer_call" => computerToolCallFormat.reads(json)
268-
case "computer_call_output" => computerToolCallOutputFormat.reads(json)
269-
case "web_search_call" => webSearchToolCallFormat.reads(json)
270-
case "function_call" => functionToolCallFormat.reads(json)
271-
case "function_call_output" => functionToolCallOutputFormat.reads(json)
272-
case "reasoning" => reasoningFormat.reads(json)
273-
case "item_reference" => itemReferenceFormat.reads(json)
274-
case _ => JsError("Missing type field for Input")
283+
case "file_search_call" => fileSearchToolCallFormat.reads(json)
284+
case "computer_call" => computerToolCallFormat.reads(json)
285+
case "computer_call_output" => computerToolCallOutputFormat.reads(json)
286+
case "web_search_call" => webSearchToolCallFormat.reads(json)
287+
case "function_call" => functionToolCallFormat.reads(json)
288+
case "function_call_output" => functionToolCallOutputFormat.reads(json)
289+
case "reasoning" => reasoningFormat.reads(json)
290+
case "item_reference" => itemReferenceFormat.reads(json)
291+
case "image_generation_call" => imageGenerationToolCallFormat.reads(json)
292+
case "code_interpreter_call" => codeInterpreterToolCallFormat.reads(json)
293+
case "local_shell_call" => localShellToolCallFormat.reads(json)
294+
case "local_shell_call_output" => localShellCallOutputFormat.reads(json)
295+
case "mcp_list_tools" => mcpListToolsFormat.reads(json)
296+
case "mcp_tool_call" => mcpToolCallFormat.reads(json)
297+
case "custom_tool_call_output" => customToolCallOutputFormat.reads(json)
298+
case "custom_tool_call" => customToolCallFormat.reads(json)
299+
case _ => JsError("Missing type field for Input")
275300
}
276301
}
277302

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.cequence.openaiscala.domain.responsesapi.tools
2+
3+
import io.cequence.openaiscala.domain.responsesapi.Input
4+
5+
/**
6+
* Represents a code interpreter tool call made by the model.
7+
*
8+
* @param code
9+
* The code to run, or null if not available.
10+
* @param containerId
11+
* The ID of the container used to run the code.
12+
* @param id
13+
* The unique ID of the code interpreter tool call.
14+
* @param outputs
15+
* The outputs generated by the code interpreter, such as logs or images. Can be null if no
16+
* outputs are available.
17+
* @param status
18+
* The status of the code interpreter tool call. Valid values are: in_progress, completed,
19+
* incomplete, interpreting, and failed. TODO: introduce enum
20+
*/
21+
final case class CodeInterpreterToolCall(
22+
id: String,
23+
code: Option[String],
24+
containerId: String,
25+
outputs: Seq[CodeInterpreterOutput],
26+
status: String
27+
) extends Input {
28+
val `type`: String = "code_interpreter_call"
29+
}
30+
31+
trait CodeInterpreterOutput {
32+
val `type`: String
33+
}
34+
35+
/**
36+
* The logs output from the code interpreter.
37+
*
38+
* @param logs
39+
* The logs output from the code interpreter.
40+
*/
41+
final case class CodeInterpreterOutputLogs(
42+
logs: String
43+
) extends CodeInterpreterOutput {
44+
val `type`: String = "logs"
45+
}
46+
47+
/**
48+
* The image output from the code interpreter.
49+
*
50+
* @param url
51+
* The URL of the image output from the code interpreter.
52+
*/
53+
final case class CodeInterpreterOutputImage(
54+
url: String
55+
) extends CodeInterpreterOutput {
56+
val `type`: String = "image"
57+
}

openai-core/src/main/scala/io/cequence/openaiscala/domain/responsesapi/tools/ComputerToolCall.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,4 @@ final case class PendingSafetyCheck(
205205
id: String,
206206
message: String
207207
)
208+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.cequence.openaiscala.domain.responsesapi.tools
2+
3+
import io.cequence.openaiscala.domain.responsesapi.Input
4+
5+
/**
6+
* A call to a custom tool created by the model.
7+
*
8+
* @param callId
9+
* An identifier used to map this custom tool call to a tool call output.
10+
* @param input
11+
* The input for the custom tool call generated by the model.
12+
* @param name
13+
* The name of the custom tool being called.
14+
* @param id
15+
* The unique ID of the custom tool call in the OpenAI platform (optional).
16+
*/
17+
final case class CustomToolCall(
18+
callId: String,
19+
input: String,
20+
name: String,
21+
id: Option[String] = None
22+
) extends Input {
23+
val `type`: String = "custom_tool_call"
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.cequence.openaiscala.domain.responsesapi.tools
2+
3+
import io.cequence.openaiscala.domain.responsesapi.Input
4+
5+
/**
6+
* The output of a custom tool call from your code, being sent back to the model.
7+
*
8+
* @param callId
9+
* The call ID, used to map this custom tool call output to a custom tool call.
10+
* @param output
11+
* The output from the custom tool call generated by your code.
12+
* @param id
13+
* The unique ID of the custom tool call output in the OpenAI platform (optional).
14+
*/
15+
final case class CustomToolCallOutput(
16+
callId: String,
17+
output: String,
18+
id: Option[String] = None
19+
) extends Input {
20+
val `type`: String = "custom_tool_call_output"
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.cequence.openaiscala.domain.responsesapi.tools
2+
3+
import io.cequence.openaiscala.domain.responsesapi.Input
4+
5+
/**
6+
* Represents an image generation call made by the model.
7+
*
8+
* @param id
9+
* The unique ID of the image generation call.
10+
* @param result
11+
* The generated image encoded in base64, or null if not available.
12+
* @param status
13+
* The status of the image generation call.
14+
*/
15+
final case class ImageGenerationToolCall(
16+
id: String,
17+
result: Option[String],
18+
status: String
19+
) extends ToolCall
20+
with Input {
21+
val `type`: String = "image_generation_call"
22+
}

0 commit comments

Comments
 (0)