mirror of
https://github.com/openai/codex.git
synced 2026-04-17 19:24:47 +00:00
Compare commits
9 Commits
dev/ningyi
...
dev/aaronl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0696ac969 | ||
|
|
96558c7e38 | ||
|
|
456cbae4ac | ||
|
|
bb502e9066 | ||
|
|
806e490285 | ||
|
|
95cd861224 | ||
|
|
86dc7d0ccb | ||
|
|
35d08c0fdc | ||
|
|
6412cfd024 |
@@ -480,6 +480,19 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -526,19 +539,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -548,7 +552,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -1,15 +1,110 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"DynamicToolCallOutputContentItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"inputText"
|
||||
],
|
||||
"title": "InputTextDynamicToolCallOutputContentItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text",
|
||||
"type"
|
||||
],
|
||||
"title": "InputTextDynamicToolCallOutputContentItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"imageUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"inputImage"
|
||||
],
|
||||
"title": "InputImageDynamicToolCallOutputContentItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"imageUrl",
|
||||
"type"
|
||||
],
|
||||
"title": "InputImageDynamicToolCallOutputContentItem",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"DynamicToolCallResult": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Preferred structured tool output (for example text + images) that is forwarded directly to the model as content items.",
|
||||
"properties": {
|
||||
"contentItems": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"contentItems"
|
||||
],
|
||||
"title": "ContentItemsDynamicToolCallResultType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"contentItems",
|
||||
"type"
|
||||
],
|
||||
"title": "ContentItemsDynamicToolCallResult",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Plain-text tool output.",
|
||||
"properties": {
|
||||
"output": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"output"
|
||||
],
|
||||
"title": "OutputDynamicToolCallResultType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"output",
|
||||
"type"
|
||||
],
|
||||
"title": "OutputDynamicToolCallResult",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"output": {
|
||||
"type": "string"
|
||||
"result": {
|
||||
"$ref": "#/definitions/DynamicToolCallResult"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"output",
|
||||
"result",
|
||||
"success"
|
||||
],
|
||||
"title": "DynamicToolCallResponse",
|
||||
|
||||
@@ -2811,6 +2811,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -2857,19 +2870,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -2879,7 +2883,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -3431,6 +3431,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -3477,19 +3490,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -3499,7 +3503,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -2240,6 +2240,50 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"DynamicToolCallOutputContentItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"inputText"
|
||||
],
|
||||
"title": "InputTextDynamicToolCallOutputContentItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text",
|
||||
"type"
|
||||
],
|
||||
"title": "InputTextDynamicToolCallOutputContentItem",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"imageUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"inputImage"
|
||||
],
|
||||
"title": "InputImageDynamicToolCallOutputContentItemType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"imageUrl",
|
||||
"type"
|
||||
],
|
||||
"title": "InputImageDynamicToolCallOutputContentItem",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"DynamicToolCallParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -2270,20 +2314,69 @@
|
||||
"DynamicToolCallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"output": {
|
||||
"type": "string"
|
||||
"result": {
|
||||
"$ref": "#/definitions/DynamicToolCallResult"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"output",
|
||||
"result",
|
||||
"success"
|
||||
],
|
||||
"title": "DynamicToolCallResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"DynamicToolCallResult": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Preferred structured tool output (for example text + images) that is forwarded directly to the model as content items.",
|
||||
"properties": {
|
||||
"contentItems": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"contentItems"
|
||||
],
|
||||
"title": "ContentItemsDynamicToolCallResultType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"contentItems",
|
||||
"type"
|
||||
],
|
||||
"title": "ContentItemsDynamicToolCallResult",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Plain-text tool output.",
|
||||
"properties": {
|
||||
"output": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"output"
|
||||
],
|
||||
"title": "OutputDynamicToolCallResultType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"output",
|
||||
"type"
|
||||
],
|
||||
"title": "OutputDynamicToolCallResult",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"EventMsg": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Response event from the agent NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.",
|
||||
@@ -4919,6 +5012,19 @@
|
||||
"title": "ForkConversationResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -4965,19 +5071,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -4987,7 +5084,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -11102,6 +11199,19 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -11148,19 +11258,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/v2/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -11170,7 +11271,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -2811,6 +2811,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -2857,19 +2870,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -2879,7 +2883,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -98,6 +98,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -144,19 +157,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -166,7 +170,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -2811,6 +2811,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -2857,19 +2870,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -2879,7 +2883,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -2811,6 +2811,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -2857,19 +2870,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -2879,7 +2883,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -65,6 +65,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -111,19 +124,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -133,7 +137,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -74,6 +74,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputContentItem": {
|
||||
"description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.",
|
||||
"oneOf": [
|
||||
@@ -120,19 +133,10 @@
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputPayload": {
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.",
|
||||
"description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"content_items": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FunctionCallOutputContentItem"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
"body": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"success": {
|
||||
"type": [
|
||||
@@ -142,7 +146,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
"body"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
|
||||
|
||||
export type FunctionCallOutputBody = string | Array<FunctionCallOutputContentItem>;
|
||||
@@ -1,14 +1,12 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
|
||||
import type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
|
||||
|
||||
/**
|
||||
* The payload we send back to OpenAI when reporting a tool call result.
|
||||
*
|
||||
* `content` preserves the historical plain-string payload so downstream
|
||||
* integrations (tests, logging, etc.) can keep treating tool output as
|
||||
* `String`. When an MCP server returns richer data we additionally populate
|
||||
* `content_items` with the structured form that the Responses API understands.
|
||||
* `body` serializes directly as the wire value for `function_call_output.output`.
|
||||
* `success` remains internal metadata for downstream handling.
|
||||
*/
|
||||
export type FunctionCallOutputPayload = { content: string, content_items: Array<FunctionCallOutputContentItem> | null, success: boolean | null, };
|
||||
export type FunctionCallOutputPayload = { body: FunctionCallOutputBody, success: boolean | null, };
|
||||
|
||||
@@ -69,6 +69,7 @@ export type { FileChange } from "./FileChange";
|
||||
export type { ForcedLoginMethod } from "./ForcedLoginMethod";
|
||||
export type { ForkConversationParams } from "./ForkConversationParams";
|
||||
export type { ForkConversationResponse } from "./ForkConversationResponse";
|
||||
export type { FunctionCallOutputBody } from "./FunctionCallOutputBody";
|
||||
export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
|
||||
export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload";
|
||||
export type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams";
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DynamicToolCallOutputContentItem = { "type": "inputText", text: string, } | { "type": "inputImage", imageUrl: string, };
|
||||
@@ -1,5 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DynamicToolCallResult } from "./DynamicToolCallResult";
|
||||
|
||||
export type DynamicToolCallResponse = { output: string, success: boolean, };
|
||||
export type DynamicToolCallResponse = { result: DynamicToolCallResult, success: boolean, };
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
|
||||
|
||||
export type DynamicToolCallResult = { "type": "contentItems", contentItems: Array<DynamicToolCallOutputContentItem>, } | { "type": "output", output: string, };
|
||||
@@ -46,8 +46,10 @@ export type { ConfigWriteResponse } from "./ConfigWriteResponse";
|
||||
export type { ContextCompactedNotification } from "./ContextCompactedNotification";
|
||||
export type { CreditsSnapshot } from "./CreditsSnapshot";
|
||||
export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification";
|
||||
export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
|
||||
export type { DynamicToolCallParams } from "./DynamicToolCallParams";
|
||||
export type { DynamicToolCallResponse } from "./DynamicToolCallResponse";
|
||||
export type { DynamicToolCallResult } from "./DynamicToolCallResult";
|
||||
export type { DynamicToolSpec } from "./DynamicToolSpec";
|
||||
export type { ErrorNotification } from "./ErrorNotification";
|
||||
export type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
|
||||
|
||||
@@ -2775,10 +2775,37 @@ pub struct DynamicToolCallParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct DynamicToolCallResponse {
|
||||
pub output: String,
|
||||
pub result: DynamicToolCallResult,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum DynamicToolCallResult {
|
||||
/// Preferred structured tool output (for example text + images) that is
|
||||
/// forwarded directly to the model as content items.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContentItems {
|
||||
content_items: Vec<DynamicToolCallOutputContentItem>,
|
||||
},
|
||||
/// Plain-text tool output.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Output { output: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum DynamicToolCallOutputContentItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
InputText { text: String },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
InputImage { image_url: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -3138,4 +3165,55 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_tool_response_serializes_content_items() {
|
||||
let value = serde_json::to_value(DynamicToolCallResponse {
|
||||
result: DynamicToolCallResult::ContentItems {
|
||||
content_items: vec![DynamicToolCallOutputContentItem::InputText {
|
||||
text: "dynamic-ok".to_string(),
|
||||
}],
|
||||
},
|
||||
success: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"result": {
|
||||
"type": "contentItems",
|
||||
"contentItems": [
|
||||
{
|
||||
"type": "inputText",
|
||||
"text": "dynamic-ok"
|
||||
}
|
||||
]
|
||||
},
|
||||
"success": true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_tool_response_serializes_plain_text_output() {
|
||||
let value = serde_json::to_value(DynamicToolCallResponse {
|
||||
result: DynamicToolCallResult::Output {
|
||||
output: "dynamic-ok".to_string(),
|
||||
},
|
||||
success: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"result": {
|
||||
"type": "output",
|
||||
"output": "dynamic-ok"
|
||||
},
|
||||
"success": true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ use codex_core::review_format::format_review_findings_block;
|
||||
use codex_core::review_prompts;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResult as CoreDynamicToolResult;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use codex_protocol::protocol::ReviewOutputEvent;
|
||||
use codex_protocol::request_user_input::RequestUserInputAnswer as CoreRequestUserInputAnswer;
|
||||
@@ -352,7 +353,9 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
id: call_id.clone(),
|
||||
response: CoreDynamicToolResponse {
|
||||
call_id,
|
||||
output: "dynamic tool calls require api v2".to_string(),
|
||||
result: CoreDynamicToolResult::Output {
|
||||
output: "dynamic tool calls require api v2".to_string(),
|
||||
},
|
||||
success: false,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use codex_app_server_protocol::DynamicToolCallResponse;
|
||||
use codex_app_server_protocol::DynamicToolCallResult;
|
||||
use codex_core::CodexThread;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResult as CoreDynamicToolResult;
|
||||
use codex_protocol::protocol::Op;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -18,7 +21,9 @@ pub(crate) async fn on_call_response(
|
||||
error!("request failed: {err:?}");
|
||||
let fallback = CoreDynamicToolResponse {
|
||||
call_id: call_id.clone(),
|
||||
output: "dynamic tool request failed".to_string(),
|
||||
result: CoreDynamicToolResult::Output {
|
||||
output: "dynamic tool request failed".to_string(),
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
if let Err(err) = conversation
|
||||
@@ -37,13 +42,24 @@ pub(crate) async fn on_call_response(
|
||||
let response = serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
|
||||
error!("failed to deserialize DynamicToolCallResponse: {err}");
|
||||
DynamicToolCallResponse {
|
||||
output: "dynamic tool response was invalid".to_string(),
|
||||
result: DynamicToolCallResult::Output {
|
||||
output: "dynamic tool response was invalid".to_string(),
|
||||
},
|
||||
success: false,
|
||||
}
|
||||
});
|
||||
|
||||
let result = match response.result {
|
||||
DynamicToolCallResult::ContentItems { content_items } => {
|
||||
CoreDynamicToolResult::ContentItems {
|
||||
content_items: content_items.into_iter().map(map_content_item).collect(),
|
||||
}
|
||||
}
|
||||
DynamicToolCallResult::Output { output } => CoreDynamicToolResult::Output { output },
|
||||
};
|
||||
let response = CoreDynamicToolResponse {
|
||||
call_id: call_id.clone(),
|
||||
output: response.output,
|
||||
result,
|
||||
success: response.success,
|
||||
};
|
||||
if let Err(err) = conversation
|
||||
@@ -56,3 +72,16 @@ pub(crate) async fn on_call_response(
|
||||
error!("failed to submit DynamicToolResponse: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn map_content_item(
|
||||
item: codex_app_server_protocol::DynamicToolCallOutputContentItem,
|
||||
) -> CoreDynamicToolCallOutputContentItem {
|
||||
match item {
|
||||
codex_app_server_protocol::DynamicToolCallOutputContentItem::InputText { text } => {
|
||||
CoreDynamicToolCallOutputContentItem::InputText { text }
|
||||
}
|
||||
codex_app_server_protocol::DynamicToolCallOutputContentItem::InputImage { image_url } => {
|
||||
CoreDynamicToolCallOutputContentItem::InputImage { image_url }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ use app_test_support::McpProcess;
|
||||
use app_test_support::create_final_assistant_message_sse_response;
|
||||
use app_test_support::create_mock_responses_server_sequence_unchecked;
|
||||
use app_test_support::to_response;
|
||||
use codex_app_server_protocol::DynamicToolCallOutputContentItem;
|
||||
use codex_app_server_protocol::DynamicToolCallParams;
|
||||
use codex_app_server_protocol::DynamicToolCallResponse;
|
||||
use codex_app_server_protocol::DynamicToolCallResult;
|
||||
use codex_app_server_protocol::DynamicToolSpec;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
@@ -15,6 +17,9 @@ use codex_app_server_protocol::ThreadStartResponse;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
@@ -200,7 +205,9 @@ async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> {
|
||||
|
||||
// Respond to the tool call so the model receives a function_call_output.
|
||||
let response = DynamicToolCallResponse {
|
||||
output: "dynamic-ok".to_string(),
|
||||
result: DynamicToolCallResult::Output {
|
||||
output: "dynamic-ok".to_string(),
|
||||
},
|
||||
success: true,
|
||||
};
|
||||
mcp.send_response(request_id, serde_json::to_value(response)?)
|
||||
@@ -213,11 +220,169 @@ async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> {
|
||||
.await??;
|
||||
|
||||
let bodies = responses_bodies(&server).await?;
|
||||
let output = bodies
|
||||
let payload = bodies
|
||||
.iter()
|
||||
.find_map(|body| function_call_output_text(body, call_id))
|
||||
.find_map(|body| function_call_output_payload(body, call_id))
|
||||
.context("expected function_call_output in follow-up request")?;
|
||||
assert_eq!(output, "dynamic-ok");
|
||||
let expected_payload = FunctionCallOutputPayload::from_text("dynamic-ok".to_string());
|
||||
assert_eq!(payload, expected_payload);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures dynamic tool call responses can include structured content items.
|
||||
#[tokio::test]
|
||||
async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<()> {
|
||||
let call_id = "dyn-call-items-1";
|
||||
let tool_name = "demo_tool";
|
||||
let tool_args = json!({ "city": "Paris" });
|
||||
let tool_call_arguments = serde_json::to_string(&tool_args)?;
|
||||
|
||||
let responses = vec![
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_function_call(call_id, tool_name, &tool_call_arguments),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
create_final_assistant_message_sse_response("Done")?,
|
||||
];
|
||||
let server = create_mock_responses_server_sequence_unchecked(responses).await;
|
||||
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let dynamic_tool = DynamicToolSpec {
|
||||
name: tool_name.to_string(),
|
||||
description: "Demo dynamic tool".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["city"],
|
||||
"additionalProperties": false,
|
||||
}),
|
||||
};
|
||||
|
||||
let thread_req = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
dynamic_tools: Some(vec![dynamic_tool]),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let thread_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
|
||||
let turn_req = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "Run the tool".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let turn_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(turn_req)),
|
||||
)
|
||||
.await??;
|
||||
let TurnStartResponse { turn } = to_response::<TurnStartResponse>(turn_resp)?;
|
||||
|
||||
let request = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_request_message(),
|
||||
)
|
||||
.await??;
|
||||
let (request_id, params) = match request {
|
||||
ServerRequest::DynamicToolCall { request_id, params } => (request_id, params),
|
||||
other => panic!("expected DynamicToolCall request, got {other:?}"),
|
||||
};
|
||||
|
||||
let expected = DynamicToolCallParams {
|
||||
thread_id: thread.id,
|
||||
turn_id: turn.id,
|
||||
call_id: call_id.to_string(),
|
||||
tool: tool_name.to_string(),
|
||||
arguments: tool_args,
|
||||
};
|
||||
assert_eq!(params, expected);
|
||||
|
||||
let response_content_items = vec![
|
||||
DynamicToolCallOutputContentItem::InputText {
|
||||
text: "dynamic-ok".to_string(),
|
||||
},
|
||||
DynamicToolCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
},
|
||||
];
|
||||
let content_items = response_content_items
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
DynamicToolCallOutputContentItem::InputText { text } => {
|
||||
FunctionCallOutputContentItem::InputText { text }
|
||||
}
|
||||
DynamicToolCallOutputContentItem::InputImage { image_url } => {
|
||||
FunctionCallOutputContentItem::InputImage { image_url }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<FunctionCallOutputContentItem>>();
|
||||
let response = DynamicToolCallResponse {
|
||||
result: DynamicToolCallResult::ContentItems {
|
||||
content_items: response_content_items,
|
||||
},
|
||||
success: true,
|
||||
};
|
||||
mcp.send_response(request_id, serde_json::to_value(response)?)
|
||||
.await?;
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let bodies = responses_bodies(&server).await?;
|
||||
let output_value = bodies
|
||||
.iter()
|
||||
.find_map(|body| function_call_output_raw_output(body, call_id))
|
||||
.context("expected function_call_output output in follow-up request")?;
|
||||
assert_eq!(
|
||||
output_value,
|
||||
json!([
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "dynamic-ok"
|
||||
},
|
||||
{
|
||||
"type": "input_image",
|
||||
"image_url": "data:image/png;base64,AAA"
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
let payload = bodies
|
||||
.iter()
|
||||
.find_map(|body| function_call_output_payload(body, call_id))
|
||||
.context("expected function_call_output in follow-up request")?;
|
||||
assert_eq!(
|
||||
payload.body,
|
||||
FunctionCallOutputBody::ContentItems(content_items.clone())
|
||||
);
|
||||
assert_eq!(payload.success, None);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&payload)?,
|
||||
serde_json::to_string(&content_items)?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -248,7 +413,12 @@ fn find_tool<'a>(body: &'a Value, name: &str) -> Option<&'a Value> {
|
||||
})
|
||||
}
|
||||
|
||||
fn function_call_output_text(body: &Value, call_id: &str) -> Option<String> {
|
||||
fn function_call_output_payload(body: &Value, call_id: &str) -> Option<FunctionCallOutputPayload> {
|
||||
function_call_output_raw_output(body, call_id)
|
||||
.and_then(|output| serde_json::from_value(output).ok())
|
||||
}
|
||||
|
||||
fn function_call_output_raw_output(body: &Value, call_id: &str) -> Option<Value> {
|
||||
body.get("input")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|items| {
|
||||
@@ -258,8 +428,7 @@ fn function_call_output_text(body: &Value, call_id: &str) -> Option<String> {
|
||||
})
|
||||
})
|
||||
.and_then(|item| item.get("output"))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_string)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::config::types::Personality;
|
||||
use crate::error::Result;
|
||||
pub use codex_api::common::ResponseEvent;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use futures::Stream;
|
||||
use serde::Deserialize;
|
||||
@@ -97,9 +98,11 @@ fn reserialize_shell_outputs(items: &mut [ResponseItem]) {
|
||||
}
|
||||
ResponseItem::FunctionCallOutput { call_id, output } => {
|
||||
if shell_call_ids.remove(call_id)
|
||||
&& let Some(structured) = parse_structured_shell_output(&output.content)
|
||||
&& let Some(structured) = output
|
||||
.text_content()
|
||||
.and_then(parse_structured_shell_output)
|
||||
{
|
||||
output.content = structured
|
||||
output.body = FunctionCallOutputBody::Text(structured);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -5114,13 +5114,14 @@ mod tests {
|
||||
|
||||
let got = FunctionCallOutputPayload::from(&ctr);
|
||||
let expected = FunctionCallOutputPayload {
|
||||
content: serde_json::to_string(&json!({
|
||||
"ok": true,
|
||||
"value": 42
|
||||
}))
|
||||
.unwrap(),
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(
|
||||
serde_json::to_string(&json!({
|
||||
"ok": true,
|
||||
"value": 42
|
||||
}))
|
||||
.unwrap(),
|
||||
),
|
||||
success: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(expected, got);
|
||||
@@ -5157,10 +5158,10 @@ mod tests {
|
||||
|
||||
let got = FunctionCallOutputPayload::from(&ctr);
|
||||
let expected = FunctionCallOutputPayload {
|
||||
content: serde_json::to_string(&vec![text_block("hello"), text_block("world")])
|
||||
.unwrap(),
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(
|
||||
serde_json::to_string(&vec![text_block("hello"), text_block("world")]).unwrap(),
|
||||
),
|
||||
success: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(expected, got);
|
||||
@@ -5177,9 +5178,10 @@ mod tests {
|
||||
|
||||
let got = FunctionCallOutputPayload::from(&ctr);
|
||||
let expected = FunctionCallOutputPayload {
|
||||
content: serde_json::to_string(&json!({ "message": "bad" })).unwrap(),
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(
|
||||
serde_json::to_string(&json!({ "message": "bad" })).unwrap(),
|
||||
),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(expected, got);
|
||||
@@ -5196,9 +5198,10 @@ mod tests {
|
||||
|
||||
let got = FunctionCallOutputPayload::from(&ctr);
|
||||
let expected = FunctionCallOutputPayload {
|
||||
content: serde_json::to_string(&vec![text_block("alpha")]).unwrap(),
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(
|
||||
serde_json::to_string(&vec![text_block("alpha")]).unwrap(),
|
||||
),
|
||||
success: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(expected, got);
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::truncate::truncate_function_output_items_with_policy;
|
||||
use crate::truncate::truncate_text;
|
||||
use crate::user_shell_command::is_user_shell_command_text;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -136,7 +137,7 @@ impl ContextManager {
|
||||
|
||||
match &mut self.items[index] {
|
||||
ResponseItem::FunctionCallOutput { output, .. } => {
|
||||
let Some(content_items) = output.content_items.as_mut() else {
|
||||
let Some(content_items) = output.content_items_mut() else {
|
||||
return false;
|
||||
};
|
||||
let mut replaced = false;
|
||||
@@ -270,19 +271,23 @@ impl ContextManager {
|
||||
let policy_with_serialization_budget = policy * 1.2;
|
||||
match item {
|
||||
ResponseItem::FunctionCallOutput { call_id, output } => {
|
||||
let truncated =
|
||||
truncate_text(output.content.as_str(), policy_with_serialization_budget);
|
||||
let truncated_items = output.content_items.as_ref().map(|items| {
|
||||
truncate_function_output_items_with_policy(
|
||||
items,
|
||||
policy_with_serialization_budget,
|
||||
)
|
||||
});
|
||||
let body = match &output.body {
|
||||
FunctionCallOutputBody::Text(content) => FunctionCallOutputBody::Text(
|
||||
truncate_text(content, policy_with_serialization_budget),
|
||||
),
|
||||
FunctionCallOutputBody::ContentItems(items) => {
|
||||
FunctionCallOutputBody::ContentItems(
|
||||
truncate_function_output_items_with_policy(
|
||||
items,
|
||||
policy_with_serialization_budget,
|
||||
),
|
||||
)
|
||||
}
|
||||
};
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: truncated,
|
||||
content_items: truncated_items,
|
||||
body,
|
||||
success: output.success,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::truncate;
|
||||
use crate::truncate::TruncationPolicy;
|
||||
use codex_git::GhostCommit;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::LocalShellAction;
|
||||
@@ -63,10 +64,7 @@ fn user_input_text_msg(text: &str) -> ResponseItem {
|
||||
fn function_call_output(call_id: &str, content: &str) -> ResponseItem {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: content.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text(content.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,10 +261,7 @@ fn remove_first_item_removes_matching_output_for_function_call() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
];
|
||||
let mut h = create_history_with_items(items);
|
||||
@@ -279,10 +274,7 @@ fn remove_first_item_removes_matching_call_for_output() {
|
||||
let items = vec![
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-2".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
@@ -308,10 +300,7 @@ fn remove_last_item_removes_matching_call_for_output() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-delete-last".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
];
|
||||
let mut h = create_history_with_items(items);
|
||||
@@ -327,10 +316,11 @@ fn replace_last_turn_images_replaces_tool_output_images() {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
content_items: Some(vec![FunctionCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
}]),
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
},
|
||||
]),
|
||||
success: Some(true),
|
||||
},
|
||||
},
|
||||
@@ -346,10 +336,11 @@ fn replace_last_turn_images_replaces_tool_output_images() {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
content_items: Some(vec![FunctionCallOutputContentItem::InputText {
|
||||
text: "Invalid image".to_string(),
|
||||
}]),
|
||||
body: FunctionCallOutputBody::ContentItems(vec![
|
||||
FunctionCallOutputContentItem::InputText {
|
||||
text: "Invalid image".to_string(),
|
||||
},
|
||||
]),
|
||||
success: Some(true),
|
||||
},
|
||||
},
|
||||
@@ -391,10 +382,7 @@ fn remove_first_item_handles_local_shell_pair() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-3".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
];
|
||||
let mut h = create_history_with_items(items);
|
||||
@@ -560,10 +548,7 @@ fn normalization_retains_local_shell_outputs() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "shell-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "Total output lines: 1\n\nok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("Total output lines: 1\n\nok".to_string()),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -583,9 +568,8 @@ fn record_items_truncates_function_call_output_content() {
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-100".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: long_output.clone(),
|
||||
body: FunctionCallOutputBody::Text(long_output.clone()),
|
||||
success: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -594,16 +578,15 @@ fn record_items_truncates_function_call_output_content() {
|
||||
assert_eq!(history.items.len(), 1);
|
||||
match &history.items[0] {
|
||||
ResponseItem::FunctionCallOutput { output, .. } => {
|
||||
assert_ne!(output.content, long_output);
|
||||
let content = output.text_content().unwrap_or_default();
|
||||
assert_ne!(content, long_output);
|
||||
assert!(
|
||||
output.content.contains("tokens truncated"),
|
||||
"expected token-based truncation marker, got {}",
|
||||
output.content
|
||||
content.contains("tokens truncated"),
|
||||
"expected token-based truncation marker, got {content}"
|
||||
);
|
||||
assert!(
|
||||
output.content.contains("tokens truncated"),
|
||||
"expected truncation marker, got {}",
|
||||
output.content
|
||||
content.contains("tokens truncated"),
|
||||
"expected truncation marker, got {content}"
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected history item: {other:?}"),
|
||||
@@ -648,9 +631,8 @@ fn record_items_respects_custom_token_limit() {
|
||||
let item = ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-custom-limit".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: long_output,
|
||||
body: FunctionCallOutputBody::Text(long_output),
|
||||
success: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -660,7 +642,11 @@ fn record_items_respects_custom_token_limit() {
|
||||
ResponseItem::FunctionCallOutput { output, .. } => output,
|
||||
other => panic!("unexpected history item: {other:?}"),
|
||||
};
|
||||
assert!(stored.content.contains("tokens truncated"));
|
||||
assert!(
|
||||
stored
|
||||
.text_content()
|
||||
.is_some_and(|content| content.contains("tokens truncated"))
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_truncated_message_matches(message: &str, line: &str, expected_removed: usize) {
|
||||
@@ -782,10 +768,7 @@ fn normalize_adds_missing_output_for_function_call() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-x".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -859,10 +842,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "shell-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -873,10 +853,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() {
|
||||
fn normalize_removes_orphan_function_call_output() {
|
||||
let items = vec![ResponseItem::FunctionCallOutput {
|
||||
call_id: "orphan-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
}];
|
||||
let mut h = create_history_with_items(items);
|
||||
|
||||
@@ -913,10 +890,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
// Orphan output that should be removed
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "c2".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
// Will get an inserted custom tool output
|
||||
ResponseItem::CustomToolCall {
|
||||
@@ -955,10 +929,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "c1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
},
|
||||
ResponseItem::CustomToolCall {
|
||||
id: None,
|
||||
@@ -985,10 +956,7 @@ fn normalize_mixed_inserts_and_removals() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "s1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -1015,10 +983,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "call-x".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -1065,10 +1030,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug()
|
||||
fn normalize_removes_orphan_function_call_output_panics_in_debug() {
|
||||
let items = vec![ResponseItem::FunctionCallOutput {
|
||||
call_id: "orphan-1".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
}];
|
||||
let mut h = create_history_with_items(items);
|
||||
h.normalize_history();
|
||||
@@ -1099,10 +1061,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
|
||||
},
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: "c2".to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".to_string()),
|
||||
},
|
||||
ResponseItem::CustomToolCall {
|
||||
id: None,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
|
||||
@@ -29,7 +30,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
body: FunctionCallOutputBody::Text("aborted".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
@@ -76,7 +77,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
ResponseItem::FunctionCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "aborted".to_string(),
|
||||
body: FunctionCallOutputBody::Text("aborted".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::protocol::McpInvocation;
|
||||
use crate::protocol::McpToolCallBeginEvent;
|
||||
use crate::protocol::McpToolCallEndEvent;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -44,9 +45,8 @@ pub(crate) async fn handle_mcp_tool_call(
|
||||
return ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: format!("err: {e}"),
|
||||
body: FunctionCallOutputBody::Text(format!("err: {e}")),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::parse_turn_item;
|
||||
use crate::proposed_plan_parser::strip_proposed_plan_blocks;
|
||||
use crate::tools::parallel::ToolCallRuntime;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -108,7 +109,7 @@ pub(crate) async fn handle_output_item_done(
|
||||
let response = ResponseInputItem::FunctionCallOutput {
|
||||
call_id: String::new(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: msg.to_string(),
|
||||
body: FunctionCallOutputBody::Text(msg.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
@@ -131,7 +132,7 @@ pub(crate) async fn handle_output_item_done(
|
||||
let response = ResponseInputItem::FunctionCallOutput {
|
||||
call_id: String::new(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: message,
|
||||
body: FunctionCallOutputBody::Text(message),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
@@ -236,9 +237,8 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti
|
||||
let output = match result {
|
||||
Ok(call_tool_result) => FunctionCallOutputPayload::from(call_tool_result),
|
||||
Err(err) => FunctionCallOutputPayload {
|
||||
content: err.clone(),
|
||||
body: FunctionCallOutputBody::Text(err.clone()),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
Some(ResponseItem::FunctionCallOutput {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::tools::TELEMETRY_PREVIEW_MAX_LINES;
|
||||
use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
@@ -97,13 +98,13 @@ impl ToolOutput {
|
||||
output: content,
|
||||
}
|
||||
} else {
|
||||
let body = match content_items {
|
||||
Some(content_items) => FunctionCallOutputBody::ContentItems(content_items),
|
||||
None => FunctionCallOutputBody::Text(content),
|
||||
};
|
||||
ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call_id.to_string(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content,
|
||||
content_items,
|
||||
success,
|
||||
},
|
||||
output: FunctionCallOutputPayload { body, success },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,8 +197,8 @@ mod tests {
|
||||
match response {
|
||||
ResponseInputItem::FunctionCallOutput { call_id, output } => {
|
||||
assert_eq!(call_id, "fn-1");
|
||||
assert_eq!(output.content, "ok");
|
||||
assert!(output.content_items.is_none());
|
||||
assert_eq!(output.text_content(), Some("ok"));
|
||||
assert!(output.content_items().is_none());
|
||||
assert_eq!(output.success, Some(true));
|
||||
}
|
||||
other => panic!("expected FunctionCallOutput, got {other:?}"),
|
||||
|
||||
@@ -8,8 +8,11 @@ use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
use async_trait::async_trait;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem;
|
||||
use codex_protocol::dynamic_tools::DynamicToolCallRequest;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::dynamic_tools::DynamicToolResult;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::oneshot;
|
||||
@@ -55,10 +58,21 @@ impl ToolHandler for DynamicToolHandler {
|
||||
)
|
||||
})?;
|
||||
|
||||
let DynamicToolResponse {
|
||||
result, success, ..
|
||||
} = response;
|
||||
let (content, content_items) = match result {
|
||||
DynamicToolResult::Output { output } => (output, None),
|
||||
DynamicToolResult::ContentItems { content_items } => (
|
||||
content_items_to_text(Some(&content_items)).unwrap_or_default(),
|
||||
Some(content_items.into_iter().map(map_content_item).collect()),
|
||||
),
|
||||
};
|
||||
|
||||
Ok(ToolOutput::Function {
|
||||
content: response.output,
|
||||
content_items: None,
|
||||
success: Some(response.success),
|
||||
content,
|
||||
content_items,
|
||||
success: Some(success),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -96,3 +110,73 @@ async fn request_dynamic_tool(
|
||||
session.send_event(turn_context, event).await;
|
||||
rx_response.await.ok()
|
||||
}
|
||||
|
||||
fn content_items_to_text(
|
||||
content_items: Option<&[DynamicToolCallOutputContentItem]>,
|
||||
) -> Option<String> {
|
||||
let mut text = Vec::new();
|
||||
|
||||
for item in content_items.unwrap_or_default() {
|
||||
if let DynamicToolCallOutputContentItem::InputText { text: segment } = item
|
||||
&& !segment.trim().is_empty()
|
||||
{
|
||||
text.push(segment.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if text.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(text.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fn map_content_item(item: DynamicToolCallOutputContentItem) -> FunctionCallOutputContentItem {
|
||||
match item {
|
||||
DynamicToolCallOutputContentItem::InputText { text } => {
|
||||
FunctionCallOutputContentItem::InputText { text }
|
||||
}
|
||||
DynamicToolCallOutputContentItem::InputImage { image_url } => {
|
||||
FunctionCallOutputContentItem::InputImage { image_url }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn content_items_to_text_uses_text_content_items() {
|
||||
let content_items = vec![
|
||||
DynamicToolCallOutputContentItem::InputText {
|
||||
text: "line 1".to_string(),
|
||||
},
|
||||
DynamicToolCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
},
|
||||
DynamicToolCallOutputContentItem::InputText {
|
||||
text: "line 2".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let output = content_items_to_text(Some(&content_items)).unwrap_or_default();
|
||||
assert_eq!(output, "line 1\nline 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn content_items_to_text_ignores_empty_and_image_only_content_items() {
|
||||
let content_items = vec![
|
||||
DynamicToolCallOutputContentItem::InputText {
|
||||
text: " ".to_string(),
|
||||
},
|
||||
DynamicToolCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,AAA".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let output = content_items_to_text(Some(&content_items));
|
||||
assert_eq!(output, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +57,18 @@ impl ToolHandler for McpHandler {
|
||||
Ok(ToolOutput::Mcp { result })
|
||||
}
|
||||
codex_protocol::models::ResponseInputItem::FunctionCallOutput { output, .. } => {
|
||||
let codex_protocol::models::FunctionCallOutputPayload {
|
||||
content,
|
||||
content_items,
|
||||
success,
|
||||
} = output;
|
||||
let success = output.success;
|
||||
let (content, content_items) = match output.body {
|
||||
codex_protocol::models::FunctionCallOutputBody::Text(content) => {
|
||||
(content, None)
|
||||
}
|
||||
codex_protocol::models::FunctionCallOutputBody::ContentItems(content_items) => {
|
||||
(
|
||||
serde_json::to_string(&content_items).unwrap_or_default(),
|
||||
Some(content_items),
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(ToolOutput::Function {
|
||||
content,
|
||||
content_items,
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::tools::context::SharedTurnDiffTracker;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::router::ToolCall;
|
||||
use crate::tools::router::ToolRouter;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
|
||||
@@ -119,7 +120,7 @@ impl ToolCallRuntime {
|
||||
_ => ResponseInputItem::FunctionCallOutput {
|
||||
call_id: call.call_id.clone(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: Self::abort_message(call, secs),
|
||||
body: FunctionCallOutputBody::Text(Self::abort_message(call, secs)),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
|
||||
@@ -181,9 +181,8 @@ impl ToolRouter {
|
||||
ResponseInputItem::FunctionCallOutput {
|
||||
call_id,
|
||||
output: codex_protocol::models::FunctionCallOutputPayload {
|
||||
content: message,
|
||||
body: codex_protocol::models::FunctionCallOutputBody::Text(message),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,10 +1251,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
});
|
||||
prompt.input.push(ResponseItem::FunctionCallOutput {
|
||||
call_id: "function-call-id".into(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".into(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".into()),
|
||||
});
|
||||
prompt.input.push(ResponseItem::LocalShellCall {
|
||||
id: Some("local-shell-id".into()),
|
||||
|
||||
@@ -25,6 +25,28 @@ pub struct DynamicToolCallRequest {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DynamicToolResponse {
|
||||
pub call_id: String,
|
||||
pub output: String,
|
||||
pub result: DynamicToolResult,
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
pub enum DynamicToolResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContentItems {
|
||||
content_items: Vec<DynamicToolCallOutputContentItem>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Output { output: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
pub enum DynamicToolCallOutputContentItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
InputText { text: String },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
InputImage { image_url: String },
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ pub enum ResponseItem {
|
||||
arguments: String,
|
||||
call_id: String,
|
||||
},
|
||||
// NOTE: The input schema for `function_call_output` objects that clients send to the
|
||||
// OpenAI /v1/responses endpoint is NOT the same shape as the objects the server returns on the
|
||||
// SSE stream. When *sending* we must wrap the string output inside an object that includes a
|
||||
// required `success` boolean. To ensure we serialize exactly the expected shape we introduce
|
||||
// a dedicated payload struct and flatten it here.
|
||||
// NOTE: The `output` field for `function_call_output` uses a dedicated payload type with
|
||||
// custom serialization. On the wire it is either:
|
||||
// - a plain string (`content`)
|
||||
// - an array of structured content items (`content_items`)
|
||||
// We keep this behavior centralized in `FunctionCallOutputPayload`.
|
||||
FunctionCallOutput {
|
||||
call_id: String,
|
||||
output: FunctionCallOutputPayload,
|
||||
@@ -617,9 +617,8 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
let output = match result {
|
||||
Ok(result) => FunctionCallOutputPayload::from(&result),
|
||||
Err(tool_call_err) => FunctionCallOutputPayload {
|
||||
content: format!("err: {tool_call_err:?}"),
|
||||
body: FunctionCallOutputBody::Text(format!("err: {tool_call_err:?}")),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
Self::FunctionCallOutput { call_id, output }
|
||||
@@ -782,37 +781,82 @@ pub enum FunctionCallOutputContentItem {
|
||||
|
||||
/// The payload we send back to OpenAI when reporting a tool call result.
|
||||
///
|
||||
/// `content` preserves the historical plain-string payload so downstream
|
||||
/// integrations (tests, logging, etc.) can keep treating tool output as
|
||||
/// `String`. When an MCP server returns richer data we additionally populate
|
||||
/// `content_items` with the structured form that the Responses API understands.
|
||||
/// `body` serializes directly as the wire value for `function_call_output.output`.
|
||||
/// `success` remains internal metadata for downstream handling.
|
||||
#[derive(Debug, Default, Clone, PartialEq, JsonSchema, TS)]
|
||||
pub struct FunctionCallOutputPayload {
|
||||
pub content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content_items: Option<Vec<FunctionCallOutputContentItem>>,
|
||||
pub body: FunctionCallOutputBody,
|
||||
pub success: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
enum FunctionCallOutputPayloadSerde {
|
||||
pub enum FunctionCallOutputBody {
|
||||
Text(String),
|
||||
Items(Vec<FunctionCallOutputContentItem>),
|
||||
ContentItems(Vec<FunctionCallOutputContentItem>),
|
||||
}
|
||||
|
||||
// The Responses API expects two *different* shapes depending on success vs failure:
|
||||
// • success → output is a plain string (no nested object)
|
||||
// • failure → output is an object { content, success:false }
|
||||
impl Default for FunctionCallOutputBody {
|
||||
fn default() -> Self {
|
||||
Self::Text(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionCallOutputPayload {
|
||||
pub fn from_text(content: String) -> Self {
|
||||
Self {
|
||||
body: FunctionCallOutputBody::Text(content),
|
||||
success: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_content_items(content_items: Vec<FunctionCallOutputContentItem>) -> Self {
|
||||
Self {
|
||||
body: FunctionCallOutputBody::ContentItems(content_items),
|
||||
success: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_content(&self) -> Option<&str> {
|
||||
match &self.body {
|
||||
FunctionCallOutputBody::Text(content) => Some(content),
|
||||
FunctionCallOutputBody::ContentItems(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_content_mut(&mut self) -> Option<&mut String> {
|
||||
match &mut self.body {
|
||||
FunctionCallOutputBody::Text(content) => Some(content),
|
||||
FunctionCallOutputBody::ContentItems(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_items(&self) -> Option<&[FunctionCallOutputContentItem]> {
|
||||
match &self.body {
|
||||
FunctionCallOutputBody::Text(_) => None,
|
||||
FunctionCallOutputBody::ContentItems(items) => Some(items),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_items_mut(&mut self) -> Option<&mut Vec<FunctionCallOutputContentItem>> {
|
||||
match &mut self.body {
|
||||
FunctionCallOutputBody::Text(_) => None,
|
||||
FunctionCallOutputBody::ContentItems(items) => Some(items),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `function_call_output.output` is encoded as either:
|
||||
// - an array of structured content items
|
||||
// - a plain string
|
||||
impl Serialize for FunctionCallOutputPayload {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(items) = &self.content_items {
|
||||
items.serialize(serializer)
|
||||
} else {
|
||||
serializer.serialize_str(&self.content)
|
||||
match &self.body {
|
||||
FunctionCallOutputBody::Text(content) => serializer.serialize_str(content),
|
||||
FunctionCallOutputBody::ContentItems(items) => items.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -822,20 +866,11 @@ impl<'de> Deserialize<'de> for FunctionCallOutputPayload {
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match FunctionCallOutputPayloadSerde::deserialize(deserializer)? {
|
||||
FunctionCallOutputPayloadSerde::Text(content) => Ok(FunctionCallOutputPayload {
|
||||
content,
|
||||
..Default::default()
|
||||
}),
|
||||
FunctionCallOutputPayloadSerde::Items(items) => {
|
||||
let content = serde_json::to_string(&items).map_err(serde::de::Error::custom)?;
|
||||
Ok(FunctionCallOutputPayload {
|
||||
content,
|
||||
content_items: Some(items),
|
||||
success: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
let body = FunctionCallOutputBody::deserialize(deserializer)?;
|
||||
Ok(FunctionCallOutputPayload {
|
||||
body,
|
||||
success: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,16 +891,14 @@ impl From<&CallToolResult> for FunctionCallOutputPayload {
|
||||
match serde_json::to_string(structured_content) {
|
||||
Ok(serialized_structured_content) => {
|
||||
return FunctionCallOutputPayload {
|
||||
content: serialized_structured_content,
|
||||
body: FunctionCallOutputBody::Text(serialized_structured_content),
|
||||
success: Some(is_success),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
return FunctionCallOutputPayload {
|
||||
content: err.to_string(),
|
||||
body: FunctionCallOutputBody::Text(err.to_string()),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -875,18 +908,21 @@ impl From<&CallToolResult> for FunctionCallOutputPayload {
|
||||
Ok(serialized_content) => serialized_content,
|
||||
Err(err) => {
|
||||
return FunctionCallOutputPayload {
|
||||
content: err.to_string(),
|
||||
body: FunctionCallOutputBody::Text(err.to_string()),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let content_items = convert_mcp_content_to_items(content);
|
||||
|
||||
let body = match content_items {
|
||||
Some(content_items) => FunctionCallOutputBody::ContentItems(content_items),
|
||||
None => FunctionCallOutputBody::Text(serialized_content),
|
||||
};
|
||||
|
||||
FunctionCallOutputPayload {
|
||||
content: serialized_content,
|
||||
content_items,
|
||||
body,
|
||||
success: Some(is_success),
|
||||
}
|
||||
}
|
||||
@@ -937,19 +973,18 @@ fn convert_mcp_content_to_items(
|
||||
}
|
||||
|
||||
// Implement Display so callers can treat the payload like a plain string when logging or doing
|
||||
// trivial substring checks in tests (existing tests call `.contains()` on the output). Display
|
||||
// returns the raw `content` field.
|
||||
// trivial substring checks in tests (existing tests call `.contains()` on the output). For
|
||||
// `ContentItems`, Display emits a JSON representation.
|
||||
|
||||
impl std::fmt::Display for FunctionCallOutputPayload {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.content)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for FunctionCallOutputPayload {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.content
|
||||
match &self.body {
|
||||
FunctionCallOutputBody::Text(content) => f.write_str(content),
|
||||
FunctionCallOutputBody::ContentItems(items) => {
|
||||
let content = serde_json::to_string(items).unwrap_or_default();
|
||||
f.write_str(content.as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,10 +1191,7 @@ mod tests {
|
||||
fn serializes_success_as_plain_string() -> Result<()> {
|
||||
let item = ResponseInputItem::FunctionCallOutput {
|
||||
call_id: "call1".into(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "ok".into(),
|
||||
..Default::default()
|
||||
},
|
||||
output: FunctionCallOutputPayload::from_text("ok".into()),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&item)?;
|
||||
@@ -1175,9 +1207,8 @@ mod tests {
|
||||
let item = ResponseInputItem::FunctionCallOutput {
|
||||
call_id: "call1".into(),
|
||||
output: FunctionCallOutputPayload {
|
||||
content: "bad".into(),
|
||||
body: FunctionCallOutputBody::Text("bad".into()),
|
||||
success: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1202,7 +1233,10 @@ mod tests {
|
||||
|
||||
let payload = FunctionCallOutputPayload::from(&call_tool_result);
|
||||
assert_eq!(payload.success, Some(true));
|
||||
let items = payload.content_items.clone().expect("content items");
|
||||
let Some(items) = payload.content_items() else {
|
||||
panic!("expected content items");
|
||||
};
|
||||
let items = items.to_vec();
|
||||
assert_eq!(
|
||||
items,
|
||||
vec![
|
||||
@@ -1243,9 +1277,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let payload = FunctionCallOutputPayload::from(&call_tool_result);
|
||||
let Some(items) = payload.content_items else {
|
||||
let Some(items) = payload.content_items() else {
|
||||
panic!("expected content items");
|
||||
};
|
||||
let items = items.to_vec();
|
||||
assert_eq!(
|
||||
items,
|
||||
vec![FunctionCallOutputContentItem::InputImage {
|
||||
@@ -1274,10 +1309,14 @@ mod tests {
|
||||
image_url: "data:image/png;base64,XYZ".into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(payload.content_items, Some(expected_items.clone()));
|
||||
|
||||
let expected_content = serde_json::to_string(&expected_items)?;
|
||||
assert_eq!(payload.content, expected_content);
|
||||
assert_eq!(
|
||||
payload.body,
|
||||
FunctionCallOutputBody::ContentItems(expected_items.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&payload)?,
|
||||
serde_json::to_string(&expected_items)?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user