Merge 7d9c3547fe into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-02-25 12:30:11 -08:00
committed by GitHub
62 changed files with 3129 additions and 252 deletions
@@ -59,7 +59,7 @@
"type": "object"
},
{
"description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.",
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
"enum": [
"approved_for_session"
],
@@ -202,6 +202,85 @@
}
]
},
"CommandExecutionApprovalDecision": {
"oneOf": [
{
"description": "User approved the command.",
"enum": [
"accept"
],
"type": "string"
},
{
"description": "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.",
"enum": [
"acceptForSession"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.",
"properties": {
"acceptWithExecpolicyAmendment": {
"properties": {
"execpolicy_amendment": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"execpolicy_amendment"
],
"type": "object"
}
},
"required": [
"acceptWithExecpolicyAmendment"
],
"title": "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision",
"type": "object"
},
{
"additionalProperties": false,
"description": "User chose a persistent network policy rule (allow/deny) for this host.",
"properties": {
"applyNetworkPolicyAmendment": {
"properties": {
"network_policy_amendment": {
"$ref": "#/definitions/NetworkPolicyAmendment"
}
},
"required": [
"network_policy_amendment"
],
"type": "object"
}
},
"required": [
"applyNetworkPolicyAmendment"
],
"title": "ApplyNetworkPolicyAmendmentCommandExecutionApprovalDecision",
"type": "object"
},
{
"description": "User denied the command. The agent will continue the turn.",
"enum": [
"decline"
],
"type": "string"
},
{
"description": "User denied the command. The turn will also be immediately interrupted.",
"enum": [
"cancel"
],
"type": "string"
}
]
},
"MacOsAutomationValue": {
"anyOf": [
{
@@ -11,7 +11,7 @@
"type": "string"
},
{
"description": "User approved the command and future identical commands should run without prompting.",
"description": "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.",
"enum": [
"acceptForSession"
],
@@ -500,6 +500,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"
}
]
},
"EventMsg": {
"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.",
"oneOf": [
@@ -1631,6 +1675,16 @@
"null"
]
},
"available_decisions": {
"description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.",
"items": {
"$ref": "#/definitions/ReviewDecision"
},
"type": [
"array",
"null"
]
},
"call_id": {
"description": "Identifier for the associated command execution item.",
"type": "string"
@@ -1776,6 +1830,70 @@
"title": "DynamicToolCallRequestEventMsg",
"type": "object"
},
{
"properties": {
"arguments": {
"description": "Dynamic tool call arguments."
},
"call_id": {
"description": "Identifier for the corresponding DynamicToolCallRequest.",
"type": "string"
},
"content_items": {
"description": "Dynamic tool response content items.",
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": "array"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the dynamic tool call."
},
"error": {
"description": "Optional error text when the tool call failed before producing a response.",
"type": [
"string",
"null"
]
},
"success": {
"description": "Whether the tool call succeeded.",
"type": "boolean"
},
"tool": {
"description": "Dynamic tool name.",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this dynamic tool call belongs to.",
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_response"
],
"title": "DynamicToolCallResponseEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"content_items",
"duration",
"success",
"tool",
"turn_id",
"type"
],
"title": "DynamicToolCallResponseEventMsg",
"type": "object"
},
{
"properties": {
"item_id": {
@@ -4887,6 +5005,86 @@
],
"type": "object"
},
"ReviewDecision": {
"description": "User's decision in response to an ExecApprovalRequest.",
"oneOf": [
{
"description": "User has approved this command and the agent should execute it.",
"enum": [
"approved"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.",
"properties": {
"approved_execpolicy_amendment": {
"properties": {
"proposed_execpolicy_amendment": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"proposed_execpolicy_amendment"
],
"type": "object"
}
},
"required": [
"approved_execpolicy_amendment"
],
"title": "ApprovedExecpolicyAmendmentReviewDecision",
"type": "object"
},
{
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
"enum": [
"approved_for_session"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User chose to persist a network policy rule (allow/deny) for future requests to the same host.",
"properties": {
"network_policy_amendment": {
"properties": {
"network_policy_amendment": {
"$ref": "#/definitions/NetworkPolicyAmendment"
}
},
"required": [
"network_policy_amendment"
],
"type": "object"
}
},
"required": [
"network_policy_amendment"
],
"title": "NetworkPolicyAmendmentReviewDecision",
"type": "object"
},
{
"description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.",
"enum": [
"denied"
],
"type": "string"
},
{
"description": "User has denied this command and the agent should not do anything until the user's next command.",
"enum": [
"abort"
],
"type": "string"
}
]
},
"ReviewFinding": {
"description": "A single review finding describing an observed issue or recommendation.",
"properties": {
@@ -7057,6 +7255,16 @@
"null"
]
},
"available_decisions": {
"description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.",
"items": {
"$ref": "#/definitions/ReviewDecision"
},
"type": [
"array",
"null"
]
},
"call_id": {
"description": "Identifier for the associated command execution item.",
"type": "string"
@@ -7202,6 +7410,70 @@
"title": "DynamicToolCallRequestEventMsg",
"type": "object"
},
{
"properties": {
"arguments": {
"description": "Dynamic tool call arguments."
},
"call_id": {
"description": "Identifier for the corresponding DynamicToolCallRequest.",
"type": "string"
},
"content_items": {
"description": "Dynamic tool response content items.",
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": "array"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the dynamic tool call."
},
"error": {
"description": "Optional error text when the tool call failed before producing a response.",
"type": [
"string",
"null"
]
},
"success": {
"description": "Whether the tool call succeeded.",
"type": "boolean"
},
"tool": {
"description": "Dynamic tool name.",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this dynamic tool call belongs to.",
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_response"
],
"title": "DynamicToolCallResponseEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"content_items",
"duration",
"success",
"tool",
"turn_id",
"type"
],
"title": "DynamicToolCallResponseEventMsg",
"type": "object"
},
{
"properties": {
"item_id": {
@@ -59,7 +59,7 @@
"type": "object"
},
{
"description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.",
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
"enum": [
"approved_for_session"
],
@@ -778,6 +778,58 @@
],
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"ErrorNotification": {
"properties": {
"error": {
@@ -1965,6 +2017,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -268,6 +268,85 @@
}
]
},
"CommandExecutionApprovalDecision": {
"oneOf": [
{
"description": "User approved the command.",
"enum": [
"accept"
],
"type": "string"
},
{
"description": "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.",
"enum": [
"acceptForSession"
],
"type": "string"
},
{
"additionalProperties": false,
"description": "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.",
"properties": {
"acceptWithExecpolicyAmendment": {
"properties": {
"execpolicy_amendment": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"execpolicy_amendment"
],
"type": "object"
}
},
"required": [
"acceptWithExecpolicyAmendment"
],
"title": "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision",
"type": "object"
},
{
"additionalProperties": false,
"description": "User chose a persistent network policy rule (allow/deny) for this host.",
"properties": {
"applyNetworkPolicyAmendment": {
"properties": {
"network_policy_amendment": {
"$ref": "#/definitions/NetworkPolicyAmendment"
}
},
"required": [
"network_policy_amendment"
],
"type": "object"
}
},
"required": [
"applyNetworkPolicyAmendment"
],
"title": "ApplyNetworkPolicyAmendmentCommandExecutionApprovalDecision",
"type": "object"
},
{
"description": "User denied the command. The agent will continue the turn.",
"enum": [
"decline"
],
"type": "string"
},
{
"description": "User denied the command. The turn will also be immediately interrupted.",
"enum": [
"cancel"
],
"type": "string"
}
]
},
"CommandExecutionRequestApprovalParams": {
"properties": {
"approvalId": {
@@ -1418,7 +1418,7 @@
"type": "string"
},
{
"description": "User approved the command and future identical commands should run without prompting.",
"description": "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.",
"enum": [
"acceptForSession"
],
@@ -1640,50 +1640,6 @@
],
"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": {
@@ -1716,7 +1672,7 @@
"properties": {
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
"$ref": "#/definitions/v2/DynamicToolCallOutputContentItem"
},
"type": "array"
},
@@ -2863,6 +2819,16 @@
"null"
]
},
"available_decisions": {
"description": "Ordered list of decisions the client may present for this prompt.\n\nWhen absent, clients should derive the legacy default set from the other fields on this request.",
"items": {
"$ref": "#/definitions/ReviewDecision"
},
"type": [
"array",
"null"
]
},
"call_id": {
"description": "Identifier for the associated command execution item.",
"type": "string"
@@ -3008,6 +2974,70 @@
"title": "DynamicToolCallRequestEventMsg",
"type": "object"
},
{
"properties": {
"arguments": {
"description": "Dynamic tool call arguments."
},
"call_id": {
"description": "Identifier for the corresponding DynamicToolCallRequest.",
"type": "string"
},
"content_items": {
"description": "Dynamic tool response content items.",
"items": {
"$ref": "#/definitions/v2/DynamicToolCallOutputContentItem"
},
"type": "array"
},
"duration": {
"allOf": [
{
"$ref": "#/definitions/Duration"
}
],
"description": "The duration of the dynamic tool call."
},
"error": {
"description": "Optional error text when the tool call failed before producing a response.",
"type": [
"string",
"null"
]
},
"success": {
"description": "Whether the tool call succeeded.",
"type": "boolean"
},
"tool": {
"description": "Dynamic tool name.",
"type": "string"
},
"turn_id": {
"description": "Turn ID that this dynamic tool call belongs to.",
"type": "string"
},
"type": {
"enum": [
"dynamic_tool_call_response"
],
"title": "DynamicToolCallResponseEventMsgType",
"type": "string"
}
},
"required": [
"arguments",
"call_id",
"content_items",
"duration",
"success",
"tool",
"turn_id",
"type"
],
"title": "DynamicToolCallResponseEventMsg",
"type": "object"
},
{
"properties": {
"item_id": {
@@ -5656,7 +5686,7 @@
"type": "object"
},
{
"description": "User has approved this command and wants to automatically approve any future identical instances (`command` and `cwd` match exactly) for the remainder of the session.",
"description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.",
"enum": [
"approved_for_session"
],
@@ -9094,6 +9124,58 @@
"title": "DeprecationNoticeNotification",
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"DynamicToolSpec": {
"properties": {
"description": {
@@ -12895,6 +12977,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/v2/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/v2/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -185,6 +185,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -633,6 +685,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -185,6 +185,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -633,6 +685,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -747,6 +799,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -345,6 +345,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -1207,6 +1259,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -980,6 +1032,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -980,6 +1032,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -345,6 +345,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -1207,6 +1259,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -980,6 +1032,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -345,6 +345,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -1207,6 +1259,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -980,6 +1032,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -980,6 +1032,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -747,6 +799,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -747,6 +799,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -299,6 +299,58 @@
],
"type": "string"
},
"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"
}
]
},
"DynamicToolCallStatus": {
"enum": [
"inProgress",
"completed",
"failed"
],
"type": "string"
},
"FileUpdateChange": {
"properties": {
"diff": {
@@ -747,6 +799,59 @@
"title": "McpToolCallThreadItem",
"type": "object"
},
{
"properties": {
"arguments": true,
"contentItems": {
"items": {
"$ref": "#/definitions/DynamicToolCallOutputContentItem"
},
"type": [
"array",
"null"
]
},
"durationMs": {
"description": "The duration of the dynamic tool call in milliseconds.",
"format": "int64",
"type": [
"integer",
"null"
]
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/definitions/DynamicToolCallStatus"
},
"success": {
"type": [
"boolean",
"null"
]
},
"tool": {
"type": "string"
},
"type": {
"enum": [
"dynamicToolCall"
],
"title": "DynamicToolCallThreadItemType",
"type": "string"
}
},
"required": [
"arguments",
"id",
"status",
"tool",
"type"
],
"title": "DynamicToolCallThreadItem",
"type": "object"
},
{
"properties": {
"agentsStates": {
@@ -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, };
@@ -0,0 +1,39 @@
// 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";
import type { JsonValue } from "./serde_json/JsonValue";
export type DynamicToolCallResponseEvent = {
/**
* Identifier for the corresponding DynamicToolCallRequest.
*/
call_id: string,
/**
* Turn ID that this dynamic tool call belongs to.
*/
turn_id: string,
/**
* Dynamic tool name.
*/
tool: string,
/**
* Dynamic tool call arguments.
*/
arguments: JsonValue,
/**
* Dynamic tool response content items.
*/
content_items: Array<DynamicToolCallOutputContentItem>,
/**
* Whether the tool call succeeded.
*/
success: boolean,
/**
* Optional error text when the tool call failed before producing a response.
*/
error: string | null,
/**
* The duration of the dynamic tool call.
*/
duration: string, };
@@ -24,6 +24,7 @@ import type { CollabWaitingEndEvent } from "./CollabWaitingEndEvent";
import type { ContextCompactedEvent } from "./ContextCompactedEvent";
import type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent";
import type { DynamicToolCallRequest } from "./DynamicToolCallRequest";
import type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent";
import type { ElicitationRequestEvent } from "./ElicitationRequestEvent";
import type { ErrorEvent } from "./ErrorEvent";
import type { ExecApprovalRequestEvent } from "./ExecApprovalRequestEvent";
@@ -79,4 +80,4 @@ import type { WebSearchEndEvent } from "./WebSearchEndEvent";
* Response event from the agent
* NOTE: Make sure none of these values have optional types, as it will mess up the extension code-gen.
*/
export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "skill_request_approval" } & SkillRequestApprovalEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent;
export type EventMsg = { "type": "error" } & ErrorEvent | { "type": "warning" } & WarningEvent | { "type": "realtime_conversation_started" } & RealtimeConversationStartedEvent | { "type": "realtime_conversation_realtime" } & RealtimeConversationRealtimeEvent | { "type": "realtime_conversation_closed" } & RealtimeConversationClosedEvent | { "type": "model_reroute" } & ModelRerouteEvent | { "type": "context_compacted" } & ContextCompactedEvent | { "type": "thread_rolled_back" } & ThreadRolledBackEvent | { "type": "task_started" } & TurnStartedEvent | { "type": "task_complete" } & TurnCompleteEvent | { "type": "token_count" } & TokenCountEvent | { "type": "agent_message" } & AgentMessageEvent | { "type": "user_message" } & UserMessageEvent | { "type": "agent_message_delta" } & AgentMessageDeltaEvent | { "type": "agent_reasoning" } & AgentReasoningEvent | { "type": "agent_reasoning_delta" } & AgentReasoningDeltaEvent | { "type": "agent_reasoning_raw_content" } & AgentReasoningRawContentEvent | { "type": "agent_reasoning_raw_content_delta" } & AgentReasoningRawContentDeltaEvent | { "type": "agent_reasoning_section_break" } & AgentReasoningSectionBreakEvent | { "type": "session_configured" } & SessionConfiguredEvent | { "type": "thread_name_updated" } & ThreadNameUpdatedEvent | { "type": "mcp_startup_update" } & McpStartupUpdateEvent | { "type": "mcp_startup_complete" } & McpStartupCompleteEvent | { "type": "mcp_tool_call_begin" } & McpToolCallBeginEvent | { "type": "mcp_tool_call_end" } & McpToolCallEndEvent | { "type": "web_search_begin" } & WebSearchBeginEvent | { "type": "web_search_end" } & WebSearchEndEvent | { "type": "exec_command_begin" } & ExecCommandBeginEvent | { "type": "exec_command_output_delta" } & ExecCommandOutputDeltaEvent | { "type": "terminal_interaction" } & TerminalInteractionEvent | { "type": "exec_command_end" } & ExecCommandEndEvent | { "type": "view_image_tool_call" } & ViewImageToolCallEvent | { "type": "exec_approval_request" } & ExecApprovalRequestEvent | { "type": "request_user_input" } & RequestUserInputEvent | { "type": "dynamic_tool_call_request" } & DynamicToolCallRequest | { "type": "dynamic_tool_call_response" } & DynamicToolCallResponseEvent | { "type": "skill_request_approval" } & SkillRequestApprovalEvent | { "type": "elicitation_request" } & ElicitationRequestEvent | { "type": "apply_patch_approval_request" } & ApplyPatchApprovalRequestEvent | { "type": "deprecation_notice" } & DeprecationNoticeEvent | { "type": "background_event" } & BackgroundEventEvent | { "type": "undo_started" } & UndoStartedEvent | { "type": "undo_completed" } & UndoCompletedEvent | { "type": "stream_error" } & StreamErrorEvent | { "type": "patch_apply_begin" } & PatchApplyBeginEvent | { "type": "patch_apply_end" } & PatchApplyEndEvent | { "type": "turn_diff" } & TurnDiffEvent | { "type": "get_history_entry_response" } & GetHistoryEntryResponseEvent | { "type": "mcp_list_tools_response" } & McpListToolsResponseEvent | { "type": "list_custom_prompts_response" } & ListCustomPromptsResponseEvent | { "type": "list_skills_response" } & ListSkillsResponseEvent | { "type": "list_remote_skills_response" } & ListRemoteSkillsResponseEvent | { "type": "remote_skill_downloaded" } & RemoteSkillDownloadedEvent | { "type": "skills_update_available" } | { "type": "plan_update" } & UpdatePlanArgs | { "type": "turn_aborted" } & TurnAbortedEvent | { "type": "shutdown_complete" } | { "type": "entered_review_mode" } & ReviewRequest | { "type": "exited_review_mode" } & ExitedReviewModeEvent | { "type": "raw_response_item" } & RawResponseItemEvent | { "type": "item_started" } & ItemStartedEvent | { "type": "item_completed" } & ItemCompletedEvent | { "type": "agent_message_content_delta" } & AgentMessageContentDeltaEvent | { "type": "plan_delta" } & PlanDeltaEvent | { "type": "reasoning_content_delta" } & ReasoningContentDeltaEvent | { "type": "reasoning_raw_content_delta" } & ReasoningRawContentDeltaEvent | { "type": "collab_agent_spawn_begin" } & CollabAgentSpawnBeginEvent | { "type": "collab_agent_spawn_end" } & CollabAgentSpawnEndEvent | { "type": "collab_agent_interaction_begin" } & CollabAgentInteractionBeginEvent | { "type": "collab_agent_interaction_end" } & CollabAgentInteractionEndEvent | { "type": "collab_waiting_begin" } & CollabWaitingBeginEvent | { "type": "collab_waiting_end" } & CollabWaitingEndEvent | { "type": "collab_close_begin" } & CollabCloseBeginEvent | { "type": "collab_close_end" } & CollabCloseEndEvent | { "type": "collab_resume_begin" } & CollabResumeBeginEvent | { "type": "collab_resume_end" } & CollabResumeEndEvent;
@@ -6,6 +6,7 @@ import type { NetworkApprovalContext } from "./NetworkApprovalContext";
import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
import type { ParsedCommand } from "./ParsedCommand";
import type { PermissionProfile } from "./PermissionProfile";
import type { ReviewDecision } from "./ReviewDecision";
export type ExecApprovalRequestEvent = {
/**
@@ -51,4 +52,11 @@ proposed_network_policy_amendments?: Array<NetworkPolicyAmendment>,
/**
* Optional additional filesystem permissions requested for this command.
*/
additional_permissions?: PermissionProfile, parsed_cmd: Array<ParsedCommand>, };
additional_permissions?: PermissionProfile,
/**
* Ordered list of decisions the client may present for this prompt.
*
* When absent, clients should derive the legacy default set from the
* other fields on this request.
*/
available_decisions?: Array<ReviewDecision>, parsed_cmd: Array<ParsedCommand>, };
@@ -53,7 +53,9 @@ export type { ConversationSummary } from "./ConversationSummary";
export type { CreditsSnapshot } from "./CreditsSnapshot";
export type { CustomPrompt } from "./CustomPrompt";
export type { DeprecationNoticeEvent } from "./DeprecationNoticeEvent";
export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
export type { DynamicToolCallRequest } from "./DynamicToolCallRequest";
export type { DynamicToolCallResponseEvent } from "./DynamicToolCallResponseEvent";
export type { ElicitationRequestEvent } from "./ElicitationRequestEvent";
export type { ErrorEvent } from "./ErrorEvent";
export type { EventMsg } from "./EventMsg";
@@ -3,6 +3,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile";
import type { CommandAction } from "./CommandAction";
import type { CommandExecutionApprovalDecision } from "./CommandExecutionApprovalDecision";
import type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
import type { NetworkApprovalContext } from "./NetworkApprovalContext";
import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
@@ -49,4 +50,8 @@ proposedExecpolicyAmendment?: ExecPolicyAmendment | null,
/**
* Optional proposed network policy amendments (allow/deny host) for future requests.
*/
proposedNetworkPolicyAmendments?: Array<NetworkPolicyAmendment> | null, };
proposedNetworkPolicyAmendments?: Array<NetworkPolicyAmendment> | null,
/**
* Ordered list of decisions the client may present for this prompt.
*/
availableDecisions?: Array<CommandExecutionApprovalDecision> | null, };
@@ -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 DynamicToolCallStatus = "inProgress" | "completed" | "failed";
@@ -8,6 +8,8 @@ import type { CollabAgentTool } from "./CollabAgentTool";
import type { CollabAgentToolCallStatus } from "./CollabAgentToolCallStatus";
import type { CommandAction } from "./CommandAction";
import type { CommandExecutionStatus } from "./CommandExecutionStatus";
import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
import type { DynamicToolCallStatus } from "./DynamicToolCallStatus";
import type { FileUpdateChange } from "./FileUpdateChange";
import type { McpToolCallError } from "./McpToolCallError";
import type { McpToolCallResult } from "./McpToolCallResult";
@@ -50,6 +52,10 @@ durationMs: number | null, } | { "type": "fileChange", id: string, changes: Arra
/**
* The duration of the MCP tool call in milliseconds.
*/
durationMs: number | null, } | { "type": "dynamicToolCall", id: string, tool: string, arguments: JsonValue, status: DynamicToolCallStatus, contentItems: Array<DynamicToolCallOutputContentItem> | null, success: boolean | null,
/**
* The duration of the dynamic tool call in milliseconds.
*/
durationMs: number | null, } | { "type": "collabAgentToolCall",
/**
* Unique identifier for this collab tool call.
@@ -61,6 +61,7 @@ export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotificat
export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem";
export type { DynamicToolCallParams } from "./DynamicToolCallParams";
export type { DynamicToolCallResponse } from "./DynamicToolCallResponse";
export type { DynamicToolCallStatus } from "./DynamicToolCallStatus";
export type { DynamicToolSpec } from "./DynamicToolSpec";
export type { ErrorNotification } from "./ErrorNotification";
export type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
@@ -1540,6 +1540,7 @@ mod tests {
}),
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
};
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&params);
assert_eq!(
@@ -3,6 +3,8 @@ use crate::protocol::v2::CollabAgentTool;
use crate::protocol::v2::CollabAgentToolCallStatus;
use crate::protocol::v2::CommandAction;
use crate::protocol::v2::CommandExecutionStatus;
use crate::protocol::v2::DynamicToolCallOutputContentItem;
use crate::protocol::v2::DynamicToolCallStatus;
use crate::protocol::v2::FileUpdateChange;
use crate::protocol::v2::McpToolCallError;
use crate::protocol::v2::McpToolCallResult;
@@ -22,6 +24,7 @@ use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::ContextCompactedEvent;
use codex_protocol::protocol::DynamicToolCallResponseEvent;
use codex_protocol::protocol::ErrorEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandBeginEvent;
@@ -125,6 +128,12 @@ impl ThreadHistoryBuilder {
EventMsg::ExecCommandEnd(payload) => self.handle_exec_command_end(payload),
EventMsg::PatchApplyBegin(payload) => self.handle_patch_apply_begin(payload),
EventMsg::PatchApplyEnd(payload) => self.handle_patch_apply_end(payload),
EventMsg::DynamicToolCallRequest(payload) => {
self.handle_dynamic_tool_call_request(payload)
}
EventMsg::DynamicToolCallResponse(payload) => {
self.handle_dynamic_tool_call_response(payload)
}
EventMsg::McpToolCallBegin(payload) => self.handle_mcp_tool_call_begin(payload),
EventMsg::McpToolCallEnd(payload) => self.handle_mcp_tool_call_end(payload),
EventMsg::ViewImageToolCall(payload) => self.handle_view_image_tool_call(payload),
@@ -382,6 +391,49 @@ impl ThreadHistoryBuilder {
}
}
fn handle_dynamic_tool_call_request(
&mut self,
payload: &codex_protocol::dynamic_tools::DynamicToolCallRequest,
) {
let item = ThreadItem::DynamicToolCall {
id: payload.call_id.clone(),
tool: payload.tool.clone(),
arguments: payload.arguments.clone(),
status: DynamicToolCallStatus::InProgress,
content_items: None,
success: None,
duration_ms: None,
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
self.upsert_item_in_turn_id(&payload.turn_id, item);
}
}
fn handle_dynamic_tool_call_response(&mut self, payload: &DynamicToolCallResponseEvent) {
let status = if payload.success {
DynamicToolCallStatus::Completed
} else {
DynamicToolCallStatus::Failed
};
let duration_ms = i64::try_from(payload.duration.as_millis()).ok();
let item = ThreadItem::DynamicToolCall {
id: payload.call_id.clone(),
tool: payload.tool.clone(),
arguments: payload.arguments.clone(),
status,
content_items: Some(convert_dynamic_tool_content_items(&payload.content_items)),
success: Some(payload.success),
duration_ms,
};
if payload.turn_id.is_empty() {
self.upsert_item_in_current_turn(item);
} else {
self.upsert_item_in_turn_id(&payload.turn_id, item);
}
}
fn handle_mcp_tool_call_begin(&mut self, payload: &McpToolCallBeginEvent) {
let item = ThreadItem::McpToolCall {
id: payload.call_id.clone(),
@@ -913,6 +965,23 @@ pub fn convert_patch_changes(
converted
}
fn convert_dynamic_tool_content_items(
items: &[codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem],
) -> Vec<DynamicToolCallOutputContentItem> {
items
.iter()
.cloned()
.map(|item| match item {
codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem::InputText { text } => {
DynamicToolCallOutputContentItem::InputText { text }
}
codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem::InputImage {
image_url,
} => DynamicToolCallOutputContentItem::InputImage { image_url },
})
.collect()
}
fn map_patch_change_kind(change: &codex_protocol::protocol::FileChange) -> PatchChangeKind {
match change {
codex_protocol::protocol::FileChange::Add { .. } => PatchChangeKind::Add,
@@ -1002,6 +1071,7 @@ impl From<&PendingTurn> for Turn {
mod tests {
use super::*;
use codex_protocol::ThreadId;
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
use codex_protocol::items::TurnItem as CoreTurnItem;
use codex_protocol::items::UserMessageItem as CoreUserMessageItem;
use codex_protocol::models::MessagePhase as CoreMessagePhase;
@@ -1012,6 +1082,7 @@ mod tests {
use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::CodexErrorInfo;
use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::DynamicToolCallResponseEvent;
use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ItemStartedEvent;
@@ -1606,6 +1677,65 @@ mod tests {
);
}
#[test]
fn reconstructs_dynamic_tool_items_from_request_and_response_events() {
let events = vec![
EventMsg::TurnStarted(TurnStartedEvent {
turn_id: "turn-1".into(),
model_context_window: None,
collaboration_mode_kind: Default::default(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "run dynamic tool".into(),
images: None,
text_elements: Vec::new(),
local_images: Vec::new(),
}),
EventMsg::DynamicToolCallRequest(
codex_protocol::dynamic_tools::DynamicToolCallRequest {
call_id: "dyn-1".into(),
turn_id: "turn-1".into(),
tool: "lookup_ticket".into(),
arguments: serde_json::json!({"id":"ABC-123"}),
},
),
EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
call_id: "dyn-1".into(),
turn_id: "turn-1".into(),
tool: "lookup_ticket".into(),
arguments: serde_json::json!({"id":"ABC-123"}),
content_items: vec![CoreDynamicToolCallOutputContentItem::InputText {
text: "Ticket is open".into(),
}],
success: true,
error: None,
duration: Duration::from_millis(42),
}),
];
let items = events
.into_iter()
.map(RolloutItem::EventMsg)
.collect::<Vec<_>>();
let turns = build_turns_from_rollout_items(&items);
assert_eq!(turns.len(), 1);
assert_eq!(turns[0].items.len(), 2);
assert_eq!(
turns[0].items[1],
ThreadItem::DynamicToolCall {
id: "dyn-1".into(),
tool: "lookup_ticket".into(),
arguments: serde_json::json!({"id":"ABC-123"}),
status: DynamicToolCallStatus::Completed,
content_items: Some(vec![DynamicToolCallOutputContentItem::InputText {
text: "Ticket is open".into(),
}]),
success: Some(true),
duration_ms: Some(42),
}
);
}
#[test]
fn reconstructs_declined_exec_and_patch_items() {
let events = vec![
@@ -48,6 +48,7 @@ use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
use codex_protocol::protocol::RejectConfig as CoreRejectConfig;
use codex_protocol::protocol::ReviewDecision as CoreReviewDecision;
use codex_protocol::protocol::SessionSource as CoreSessionSource;
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo;
@@ -740,7 +741,8 @@ pub struct ConfigEdit {
pub enum CommandExecutionApprovalDecision {
/// User approved the command.
Accept,
/// User approved the command and future identical commands should run without prompting.
/// User approved the command and future prompts in the same session-scoped
/// approval cache should run without prompting.
AcceptForSession,
/// User approved the command, and wants to apply the proposed execpolicy amendment so future
/// matching commands can run without prompting.
@@ -757,6 +759,27 @@ pub enum CommandExecutionApprovalDecision {
Cancel,
}
impl From<CoreReviewDecision> for CommandExecutionApprovalDecision {
fn from(value: CoreReviewDecision) -> Self {
match value {
CoreReviewDecision::Approved => Self::Accept,
CoreReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment,
} => Self::AcceptWithExecpolicyAmendment {
execpolicy_amendment: proposed_execpolicy_amendment.into(),
},
CoreReviewDecision::ApprovedForSession => Self::AcceptForSession,
CoreReviewDecision::NetworkPolicyAmendment {
network_policy_amendment,
} => Self::ApplyNetworkPolicyAmendment {
network_policy_amendment: network_policy_amendment.into(),
},
CoreReviewDecision::Denied => Self::Decline,
CoreReviewDecision::Abort => Self::Cancel,
}
}
}
v2_enum_from_core! {
pub enum NetworkApprovalProtocol from CoreNetworkApprovalProtocol {
Http,
@@ -3057,6 +3080,19 @@ pub enum ThreadItem {
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
DynamicToolCall {
id: String,
tool: String,
arguments: JsonValue,
status: DynamicToolCallStatus,
content_items: Option<Vec<DynamicToolCallOutputContentItem>>,
success: Option<bool>,
/// The duration of the dynamic tool call in milliseconds.
#[ts(type = "number | null")]
duration_ms: Option<i64>,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
CollabAgentToolCall {
/// Unique identifier for this collab tool call.
id: String,
@@ -3105,6 +3141,7 @@ impl ThreadItem {
| ThreadItem::CommandExecution { id, .. }
| ThreadItem::FileChange { id, .. }
| ThreadItem::McpToolCall { id, .. }
| ThreadItem::DynamicToolCall { id, .. }
| ThreadItem::CollabAgentToolCall { id, .. }
| ThreadItem::WebSearch { id, .. }
| ThreadItem::ImageView { id, .. }
@@ -3285,6 +3322,15 @@ pub enum McpToolCallStatus {
Failed,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum DynamicToolCallStatus {
InProgress,
Completed,
Failed,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3724,6 +3770,11 @@ pub struct CommandExecutionRequestApprovalParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub proposed_network_policy_amendments: Option<Vec<NetworkPolicyAmendment>>,
/// Ordered list of decisions the client may present for this prompt.
#[experimental("item/commandExecution/requestApproval.availableDecisions")]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub available_decisions: Option<Vec<CommandExecutionApprovalDecision>>,
}
impl CommandExecutionRequestApprovalParams {
@@ -1540,6 +1540,7 @@ impl CodexClient {
additional_permissions,
proposed_execpolicy_amendment,
proposed_network_policy_amendments,
available_decisions,
} = params;
println!(
@@ -1554,6 +1555,9 @@ impl CodexClient {
if let Some(network_approval_context) = network_approval_context.as_ref() {
println!("< network approval context: {network_approval_context:?}");
}
if let Some(available_decisions) = available_decisions.as_ref() {
println!("< available decisions: {available_decisions:?}");
}
if let Some(command) = command.as_deref() {
println!("< command: {command}");
}
+8 -1
View File
@@ -689,7 +689,7 @@ Certain actions (shell commands or modifying files) may require explicit user ap
Order of messages:
1. `item/started` — shows the pending `commandExecution` item with `command`, `cwd`, and other fields so you can render the proposed action.
2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `approvalId` (for subcommand callbacks), and `reason`. For normal command approvals, it also includes `command`, `cwd`, and `commandActions` for friendly display. When `initialize.params.capabilities.experimentalApi = true`, it may also include experimental `additionalPermissions` describing requested per-command sandbox access. For network-only approvals, those command fields may be omitted and `networkApprovalContext` is provided instead. Optional persistence hints may also be included via `proposedExecpolicyAmendment` and `proposedNetworkPolicyAmendments`.
2. `item/commandExecution/requestApproval` (request) — carries the same `itemId`, `threadId`, `turnId`, optionally `approvalId` (for subcommand callbacks), and `reason`. For normal command approvals, it also includes `command`, `cwd`, and `commandActions` for friendly display. When `initialize.params.capabilities.experimentalApi = true`, it may also include experimental `additionalPermissions` describing requested per-command sandbox access. For network-only approvals, those command fields may be omitted and `networkApprovalContext` is provided instead. Optional persistence hints may also be included via `proposedExecpolicyAmendment` and `proposedNetworkPolicyAmendments`. Clients can prefer `availableDecisions` when present to render the exact set of choices the server wants to expose, while still falling back to the older heuristics if it is omitted.
3. Client response — for example `{ "decision": "accept" }`, `{ "decision": "acceptForSession" }`, `{ "decision": { "acceptWithExecpolicyAmendment": { "execpolicy_amendment": [...] } } }`, `{ "decision": { "applyNetworkPolicyAmendment": { "network_policy_amendment": { "host": "example.com", "action": "allow" } } } }`, `{ "decision": "decline" }`, or `{ "decision": "cancel" }`.
4. `item/completed` — final `commandExecution` item with `status: "completed" | "failed" | "declined"` and execution output. Render this as the authoritative result.
@@ -724,6 +724,13 @@ When a dynamic tool is invoked during a turn, the server sends an `item/tool/cal
}
```
The server also emits item lifecycle notifications around the request:
1. `item/started` with `item.type = "dynamicToolCall"`, `status = "inProgress"`, plus `tool` and `arguments`.
2. `item/tool/call` request.
3. Client response.
4. `item/completed` with `item.type = "dynamicToolCall"`, final `status`, and the returned `contentItems`/`success`.
The client must respond with content items. Use `inputText` for text and `inputImage` for image URLs/data URLs:
```json
@@ -27,7 +27,9 @@ use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;
use codex_app_server_protocol::CommandExecutionStatus;
use codex_app_server_protocol::ContextCompactedNotification;
use codex_app_server_protocol::DeprecationNoticeNotification;
use codex_app_server_protocol::DynamicToolCallOutputContentItem;
use codex_app_server_protocol::DynamicToolCallParams;
use codex_app_server_protocol::DynamicToolCallStatus;
use codex_app_server_protocol::ErrorNotification;
use codex_app_server_protocol::ExecCommandApprovalParams;
use codex_app_server_protocol::ExecCommandApprovalResponse;
@@ -334,6 +336,11 @@ pub(crate) async fn apply_bespoke_event_handling(
.note_permission_requested(&conversation_id.to_string())
.await;
let approval_id_for_op = ev.effective_approval_id();
let available_decisions = ev
.effective_available_decisions()
.into_iter()
.map(CommandExecutionApprovalDecision::from)
.collect::<Vec<_>>();
let ExecApprovalRequestEvent {
call_id,
approval_id,
@@ -430,6 +437,7 @@ pub(crate) async fn apply_bespoke_event_handling(
additional_permissions,
proposed_execpolicy_amendment: proposed_execpolicy_amendment_v2,
proposed_network_policy_amendments: proposed_network_policy_amendments_v2,
available_decisions: Some(available_decisions),
};
let rx = outgoing
.send_request(ServerRequestPayload::CommandExecutionRequestApproval(
@@ -550,12 +558,32 @@ pub(crate) async fn apply_bespoke_event_handling(
EventMsg::DynamicToolCallRequest(request) => {
if matches!(api_version, ApiVersion::V2) {
let call_id = request.call_id;
let turn_id = request.turn_id;
let tool = request.tool;
let arguments = request.arguments;
let item = ThreadItem::DynamicToolCall {
id: call_id.clone(),
tool: tool.clone(),
arguments: arguments.clone(),
status: DynamicToolCallStatus::InProgress,
content_items: None,
success: None,
duration_ms: None,
};
let notification = ItemStartedNotification {
thread_id: conversation_id.to_string(),
turn_id: turn_id.clone(),
item,
};
outgoing
.send_server_notification(ServerNotification::ItemStarted(notification))
.await;
let params = DynamicToolCallParams {
thread_id: conversation_id.to_string(),
turn_id: request.turn_id,
turn_id: turn_id.clone(),
call_id: call_id.clone(),
tool: request.tool,
arguments: request.arguments,
tool: tool.clone(),
arguments: arguments.clone(),
};
let rx = outgoing
.send_request(ServerRequestPayload::DynamicToolCall(params))
@@ -582,6 +610,46 @@ pub(crate) async fn apply_bespoke_event_handling(
.await;
}
}
EventMsg::DynamicToolCallResponse(response) => {
if matches!(api_version, ApiVersion::V2) {
let status = if response.success {
DynamicToolCallStatus::Completed
} else {
DynamicToolCallStatus::Failed
};
let duration_ms = i64::try_from(response.duration.as_millis()).ok();
let item = ThreadItem::DynamicToolCall {
id: response.call_id,
tool: response.tool,
arguments: response.arguments,
status,
content_items: Some(
response
.content_items
.into_iter()
.map(|item| match item {
CoreDynamicToolCallOutputContentItem::InputText { text } => {
DynamicToolCallOutputContentItem::InputText { text }
}
CoreDynamicToolCallOutputContentItem::InputImage { image_url } => {
DynamicToolCallOutputContentItem::InputImage { image_url }
}
})
.collect(),
),
success: Some(response.success),
duration_ms,
};
let notification = ItemCompletedNotification {
thread_id: conversation_id.to_string(),
turn_id: response.turn_id,
item,
};
outgoing
.send_server_notification(ServerNotification::ItemCompleted(notification))
.await;
}
}
// TODO(celia): properly construct McpToolCall TurnItem in core.
EventMsg::McpToolCallBegin(begin_event) => {
let notification = construct_mcp_tool_call_notification(
+31 -50
View File
@@ -1,3 +1,4 @@
use codex_app_server_protocol::DynamicToolCallOutputContentItem;
use codex_app_server_protocol::DynamicToolCallResponse;
use codex_core::CodexThread;
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
@@ -15,65 +16,23 @@ pub(crate) async fn on_call_response(
conversation: Arc<CodexThread>,
) {
let response = receiver.await;
let value = match response {
Ok(Ok(value)) => value,
let (response, _error) = match response {
Ok(Ok(value)) => decode_response(value),
Ok(Err(err)) => {
error!("request failed with client error: {err:?}");
let fallback = CoreDynamicToolResponse {
content_items: vec![CoreDynamicToolCallOutputContentItem::InputText {
text: "dynamic tool request failed".to_string(),
}],
success: false,
};
if let Err(err) = conversation
.submit(Op::DynamicToolResponse {
id: call_id.clone(),
response: fallback,
})
.await
{
error!("failed to submit DynamicToolResponse: {err}");
}
return;
fallback_response("dynamic tool request failed")
}
Err(err) => {
error!("request failed: {err:?}");
let fallback = CoreDynamicToolResponse {
content_items: vec![CoreDynamicToolCallOutputContentItem::InputText {
text: "dynamic tool request failed".to_string(),
}],
success: false,
};
if let Err(err) = conversation
.submit(Op::DynamicToolResponse {
id: call_id.clone(),
response: fallback,
})
.await
{
error!("failed to submit DynamicToolResponse: {err}");
}
return;
fallback_response("dynamic tool request failed")
}
};
let response = serde_json::from_value::<DynamicToolCallResponse>(value).unwrap_or_else(|err| {
error!("failed to deserialize DynamicToolCallResponse: {err}");
DynamicToolCallResponse {
content_items: vec![
codex_app_server_protocol::DynamicToolCallOutputContentItem::InputText {
text: "dynamic tool response was invalid".to_string(),
},
],
success: false,
}
});
let DynamicToolCallResponse {
content_items,
success,
} = response;
let response = CoreDynamicToolResponse {
} = response.clone();
let core_response = CoreDynamicToolResponse {
content_items: content_items
.into_iter()
.map(CoreDynamicToolCallOutputContentItem::from)
@@ -82,11 +41,33 @@ pub(crate) async fn on_call_response(
};
if let Err(err) = conversation
.submit(Op::DynamicToolResponse {
id: call_id,
response,
id: call_id.clone(),
response: core_response,
})
.await
{
error!("failed to submit DynamicToolResponse: {err}");
}
}
fn decode_response(value: serde_json::Value) -> (DynamicToolCallResponse, Option<String>) {
match serde_json::from_value::<DynamicToolCallResponse>(value) {
Ok(response) => (response, None),
Err(err) => {
error!("failed to deserialize DynamicToolCallResponse: {err}");
fallback_response("dynamic tool response was invalid")
}
}
}
fn fallback_response(message: &str) -> (DynamicToolCallResponse, Option<String>) {
(
DynamicToolCallResponse {
content_items: vec![DynamicToolCallOutputContentItem::InputText {
text: message.to_string(),
}],
success: false,
},
Some(message.to_string()),
)
}
+2
View File
@@ -986,6 +986,7 @@ mod tests {
),
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
},
}),
},
@@ -1047,6 +1048,7 @@ mod tests {
),
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
available_decisions: None,
},
}),
},
@@ -7,10 +7,15 @@ 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::DynamicToolCallStatus;
use codex_app_server_protocol::DynamicToolSpec;
use codex_app_server_protocol::ItemCompletedNotification;
use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ServerRequest;
use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::TurnStartParams;
@@ -163,11 +168,12 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
let thread_id = thread.id.clone();
// Start a turn so the tool call is emitted.
let turn_req = mcp
.send_turn_start_request(TurnStartParams {
thread_id: thread.id.clone(),
thread_id: thread_id.clone(),
input: vec![V2UserInput::Text {
text: "Run the tool".to_string(),
text_elements: Vec::new(),
@@ -181,6 +187,30 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res
)
.await??;
let TurnStartResponse { turn } = to_response::<TurnStartResponse>(turn_resp)?;
let turn_id = turn.id.clone();
let started = wait_for_dynamic_tool_started(&mut mcp, call_id).await?;
assert_eq!(started.thread_id, thread_id);
assert_eq!(started.turn_id, turn_id.clone());
let ThreadItem::DynamicToolCall {
id,
tool,
arguments,
status,
content_items,
success,
duration_ms,
} = started.item
else {
panic!("expected dynamic tool call item");
};
assert_eq!(id, call_id);
assert_eq!(tool, tool_name);
assert_eq!(arguments, tool_args);
assert_eq!(status, DynamicToolCallStatus::InProgress);
assert_eq!(content_items, None);
assert_eq!(success, None);
assert_eq!(duration_ms, None);
// Read the tool call request from the app server.
let request = timeout(
@@ -194,8 +224,8 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res
};
let expected = DynamicToolCallParams {
thread_id: thread.id,
turn_id: turn.id,
thread_id: thread_id.clone(),
turn_id: turn_id.clone(),
call_id: call_id.to_string(),
tool: tool_name.to_string(),
arguments: tool_args.clone(),
@@ -212,6 +242,34 @@ async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Res
mcp.send_response(request_id, serde_json::to_value(response)?)
.await?;
let completed = wait_for_dynamic_tool_completed(&mut mcp, call_id).await?;
assert_eq!(completed.thread_id, thread_id);
assert_eq!(completed.turn_id, turn_id);
let ThreadItem::DynamicToolCall {
id,
tool,
arguments,
status,
content_items,
success,
duration_ms,
} = completed.item
else {
panic!("expected dynamic tool call item");
};
assert_eq!(id, call_id);
assert_eq!(tool, tool_name);
assert_eq!(arguments, tool_args);
assert_eq!(status, DynamicToolCallStatus::Completed);
assert_eq!(
content_items,
Some(vec![DynamicToolCallOutputContentItem::InputText {
text: "dynamic-ok".to_string(),
}])
);
assert_eq!(success, Some(true));
assert!(duration_ms.is_some());
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("turn/completed"),
@@ -282,10 +340,11 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<(
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
let thread_id = thread.id.clone();
let turn_req = mcp
.send_turn_start_request(TurnStartParams {
thread_id: thread.id.clone(),
thread_id: thread_id.clone(),
input: vec![V2UserInput::Text {
text: "Run the tool".to_string(),
text_elements: Vec::new(),
@@ -299,6 +358,11 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<(
)
.await??;
let TurnStartResponse { turn } = to_response::<TurnStartResponse>(turn_resp)?;
let turn_id = turn.id.clone();
let started = wait_for_dynamic_tool_started(&mut mcp, call_id).await?;
assert_eq!(started.thread_id, thread_id.clone());
assert_eq!(started.turn_id, turn_id.clone());
let request = timeout(
DEFAULT_READ_TIMEOUT,
@@ -311,8 +375,8 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<(
};
let expected = DynamicToolCallParams {
thread_id: thread.id,
turn_id: turn.id,
thread_id,
turn_id: turn_id.clone(),
call_id: call_id.to_string(),
tool: tool_name.to_string(),
arguments: tool_args,
@@ -346,6 +410,32 @@ async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<(
mcp.send_response(request_id, serde_json::to_value(response)?)
.await?;
let completed = wait_for_dynamic_tool_completed(&mut mcp, call_id).await?;
assert_eq!(completed.thread_id, expected.thread_id.clone());
assert_eq!(completed.turn_id, turn_id);
let ThreadItem::DynamicToolCall {
status,
content_items: completed_content_items,
success,
..
} = completed.item
else {
panic!("expected dynamic tool call item");
};
assert_eq!(status, DynamicToolCallStatus::Completed);
assert_eq!(
completed_content_items,
Some(vec![
DynamicToolCallOutputContentItem::InputText {
text: "dynamic-ok".to_string(),
},
DynamicToolCallOutputContentItem::InputImage {
image_url: "data:image/png;base64,AAA".to_string(),
},
])
);
assert_eq!(success, Some(true));
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("turn/completed"),
@@ -432,6 +522,46 @@ fn function_call_output_raw_output(body: &Value, call_id: &str) -> Option<Value>
.cloned()
}
async fn wait_for_dynamic_tool_started(
mcp: &mut McpProcess,
call_id: &str,
) -> Result<ItemStartedNotification> {
loop {
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("item/started"),
)
.await??;
let Some(params) = notification.params else {
continue;
};
let started: ItemStartedNotification = serde_json::from_value(params)?;
if matches!(&started.item, ThreadItem::DynamicToolCall { id, .. } if id == call_id) {
return Ok(started);
}
}
}
async fn wait_for_dynamic_tool_completed(
mcp: &mut McpProcess,
call_id: &str,
) -> Result<ItemCompletedNotification> {
loop {
let notification: JSONRPCNotification = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("item/completed"),
)
.await??;
let Some(params) = notification.params else {
continue;
};
let completed: ItemCompletedNotification = serde_json::from_value(params)?;
if matches!(&completed.item, ThreadItem::DynamicToolCall { id, .. } if id == call_id) {
return Ok(completed);
}
}
}
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
let config_toml = codex_home.join("config.toml");
std::fs::write(
+4 -1
View File
@@ -315,7 +315,10 @@ impl ModelClient {
instructions: &instructions,
};
let extra_headers = self.build_subagent_headers();
let mut extra_headers = self.build_subagent_headers();
extra_headers.extend(build_conversation_headers(Some(
self.state.conversation_id.to_string(),
)));
client
.compact_input(&payload, extra_headers)
.await
+41 -3
View File
@@ -1379,7 +1379,7 @@ impl Session {
otel_manager,
models_manager: Arc::clone(&models_manager),
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashSet::new()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
file_watcher,
agent_control,
@@ -2604,6 +2604,35 @@ impl Session {
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
additional_permissions: Option<PermissionProfile>,
) -> ReviewDecision {
self.request_command_approval_with_options(
turn_context,
call_id,
approval_id,
command,
cwd,
reason,
network_approval_context,
proposed_execpolicy_amendment,
additional_permissions,
None,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn request_command_approval_with_options(
&self,
turn_context: &TurnContext,
call_id: String,
approval_id: Option<String>,
command: Vec<String>,
cwd: PathBuf,
reason: Option<String>,
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
additional_permissions: Option<PermissionProfile>,
available_decisions: Option<Vec<ReviewDecision>>,
) -> ReviewDecision {
// command-level approvals use `call_id`.
// `approval_id` is only present for subcommand callbacks (execve intercept)
@@ -2637,6 +2666,14 @@ impl Session {
},
]
});
let available_decisions = available_decisions.unwrap_or_else(|| {
ExecApprovalRequestEvent::default_available_decisions(
network_approval_context.as_ref(),
proposed_execpolicy_amendment.as_ref(),
proposed_network_policy_amendments.as_deref(),
additional_permissions.as_ref(),
)
});
let event = EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
call_id,
approval_id,
@@ -2648,6 +2685,7 @@ impl Session {
proposed_execpolicy_amendment,
proposed_network_policy_amendments,
additional_permissions,
available_decisions: Some(available_decisions),
parsed_cmd,
});
self.send_event(turn_context, event).await;
@@ -8412,7 +8450,7 @@ mod tests {
otel_manager: otel_manager.clone(),
models_manager: Arc::clone(&models_manager),
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashSet::new()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
file_watcher,
agent_control,
@@ -8570,7 +8608,7 @@ mod tests {
otel_manager: otel_manager.clone(),
models_manager: Arc::clone(&models_manager),
tool_approvals: Mutex::new(ApprovalStore::default()),
execve_session_approvals: RwLock::new(HashSet::new()),
execve_session_approvals: RwLock::new(HashMap::new()),
skills_manager,
file_watcher,
agent_control,
+3 -1
View File
@@ -322,10 +322,11 @@ async fn handle_exec_approval(
network_approval_context,
proposed_execpolicy_amendment,
additional_permissions,
available_decisions,
..
} = event;
// Race approval with cancellation and timeout to avoid hangs.
let approval_fut = parent_session.request_command_approval(
let approval_fut = parent_session.request_command_approval_with_options(
parent_ctx,
call_id,
approval_id,
@@ -335,6 +336,7 @@ async fn handle_exec_approval(
network_approval_context,
proposed_execpolicy_amendment,
additional_permissions,
available_decisions,
);
let decision = await_approval_with_cancel(
approval_fut,
@@ -29,6 +29,7 @@ use serde_json::Value;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tracing::debug;
use tracing::error;
use tracing::warn;
@@ -217,6 +218,7 @@ pub(crate) async fn handle_start(
_ => None,
};
if let Some(text) = maybe_routed_text {
debug!(text = %text, "[realtime-text] realtime conversation text output");
let sess_for_routed_text = Arc::clone(&sess_clone);
sess_for_routed_text.route_realtime_text_input(text).await;
}
@@ -279,6 +281,8 @@ pub(crate) async fn handle_text(
sub_id: String,
params: ConversationTextParams,
) {
debug!(text = %params.text, "[realtime-text] appending realtime conversation text input");
if let Err(err) = sess.conversation.text_in(params.text).await {
send_conversation_error(sess, sub_id, err.to_string(), CodexErrorInfo::BadRequest).await;
}
+3 -2
View File
@@ -120,7 +120,9 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option<EventPersistenceMode> {
| EventMsg::CollabAgentInteractionEnd(_)
| EventMsg::CollabWaitingEnd(_)
| EventMsg::CollabCloseEnd(_)
| EventMsg::CollabResumeEnd(_) => Some(EventPersistenceMode::Extended),
| EventMsg::CollabResumeEnd(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_) => Some(EventPersistenceMode::Extended),
EventMsg::Warning(_)
| EventMsg::RealtimeConversationStarted(_)
| EventMsg::RealtimeConversationRealtime(_)
@@ -141,7 +143,6 @@ fn event_msg_persistence_mode(ev: &EventMsg) -> Option<EventPersistenceMode> {
| EventMsg::ExecApprovalRequest(_)
| EventMsg::SkillRequestApproval(_)
| EventMsg::RequestUserInput(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::ElicitationRequest(_)
| EventMsg::ApplyPatchApprovalRequest(_)
| EventMsg::BackgroundEvent(_)
+3 -2
View File
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::collections::HashMap;
use std::sync::Arc;
use crate::AuthManager;
@@ -14,6 +14,7 @@ use crate::models_manager::manager::ModelsManager;
use crate::skills::SkillsManager;
use crate::state_db::StateDbHandle;
use crate::tools::network_approval::NetworkApprovalService;
use crate::tools::runtimes::ExecveSessionApproval;
use crate::tools::sandboxing::ApprovalStore;
use crate::unified_exec::UnifiedExecProcessManager;
use codex_hooks::Hooks;
@@ -45,7 +46,7 @@ pub(crate) struct SessionServices {
pub(crate) otel_manager: OtelManager,
pub(crate) tool_approvals: Mutex<ApprovalStore>,
#[cfg_attr(not(unix), allow(dead_code))]
pub(crate) execve_session_approvals: RwLock<HashSet<AbsolutePathBuf>>,
pub(crate) execve_session_approvals: RwLock<HashMap<AbsolutePathBuf, ExecveSessionApproval>>,
pub(crate) skills_manager: Arc<SkillsManager>,
pub(crate) file_watcher: Arc<FileWatcher>,
pub(crate) agent_control: AgentControl,
+35 -6
View File
@@ -12,8 +12,10 @@ use codex_protocol::dynamic_tools::DynamicToolCallRequest;
use codex_protocol::dynamic_tools::DynamicToolResponse;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_protocol::protocol::DynamicToolCallResponseEvent;
use codex_protocol::protocol::EventMsg;
use serde_json::Value;
use std::time::Instant;
use tokio::sync::oneshot;
use tracing::warn;
@@ -81,7 +83,7 @@ async fn request_dynamic_tool(
tool: String,
arguments: Value,
) -> Option<DynamicToolResponse> {
let _sub_id = turn_context.sub_id.clone();
let turn_id = turn_context.sub_id.clone();
let (tx_response, rx_response) = oneshot::channel();
let event_id = call_id.clone();
let prev_entry = {
@@ -98,12 +100,39 @@ async fn request_dynamic_tool(
warn!("Overwriting existing pending dynamic tool call for call_id: {event_id}");
}
let started_at = Instant::now();
let event = EventMsg::DynamicToolCallRequest(DynamicToolCallRequest {
call_id,
turn_id: turn_context.sub_id.clone(),
tool,
arguments,
call_id: call_id.clone(),
turn_id: turn_id.clone(),
tool: tool.clone(),
arguments: arguments.clone(),
});
session.send_event(turn_context, event).await;
rx_response.await.ok()
let response = rx_response.await.ok();
let response_event = match &response {
Some(response) => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
call_id,
turn_id,
tool,
arguments,
content_items: response.content_items.clone(),
success: response.success,
error: None,
duration: started_at.elapsed(),
}),
None => EventMsg::DynamicToolCallResponse(DynamicToolCallResponseEvent {
call_id,
turn_id,
tool,
arguments,
content_items: Vec::new(),
success: false,
error: Some("dynamic tool call was cancelled before receiving a response".to_string()),
duration: started_at.elapsed(),
}),
};
session.send_event(turn_context, response_event).await;
response
}
+8
View File
@@ -9,6 +9,7 @@ use crate::path_utils;
use crate::sandboxing::CommandSpec;
use crate::sandboxing::SandboxPermissions;
use crate::shell::Shell;
use crate::skills::SkillMetadata;
use crate::tools::sandboxing::ToolError;
use codex_protocol::models::PermissionProfile;
use std::collections::HashMap;
@@ -18,6 +19,13 @@ pub mod apply_patch;
pub mod shell;
pub mod unified_exec;
#[derive(Debug, Clone)]
pub(crate) struct ExecveSessionApproval {
/// If this exeve session approval is associated with a skill script, this
/// field contains metadata about the skill.
pub skill: Option<SkillMetadata>,
}
/// Shared helper to construct a CommandSpec from a tokenized command line.
/// Validates that at least a program is present.
pub(crate) fn build_command_spec(
@@ -10,6 +10,7 @@ use crate::sandboxing::SandboxPermissions;
use crate::shell::ShellType;
use crate::skills::SkillMetadata;
use crate::tools::runtimes::build_command_spec;
use crate::tools::runtimes::ExecveSessionApproval;
use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::ToolCtx;
use crate::tools::sandboxing::ToolError;
@@ -164,7 +165,9 @@ struct CoreShellActionProvider {
}
enum DecisionSource {
SkillScript,
SkillScript {
skill: SkillMetadata,
},
PrefixRule,
/// Often, this is `is_safe_command()`.
UnmatchedCommandFallback,
@@ -263,13 +266,7 @@ impl CoreShellActionProvider {
}
} else {
match self
.prompt(
program,
argv,
workdir,
&self.stopwatch,
additional_permissions,
)
.prompt(program, argv, workdir, &self.stopwatch, additional_permissions)
.await?
{
ReviewDecision::Approved
@@ -284,13 +281,21 @@ impl CoreShellActionProvider {
// Currently, we only add session approvals for
// skill scripts because we are storing only the
// `program` whereas prefix rules may be restricted by a longer prefix.
if matches!(decision_source, DecisionSource::SkillScript) {
if let DecisionSource::SkillScript { skill } = decision_source {
tracing::debug!(
"Adding session approval for {program:?} due to user approval of skill script {skill:?}"
);
self.session
.services
.execve_session_approvals
.write()
.await
.insert(program.clone());
.insert(
program.clone(),
ExecveSessionApproval {
skill: Some(skill.clone()),
},
);
}
if needs_escalation {
@@ -351,37 +356,61 @@ impl EscalationPolicy for CoreShellActionProvider {
// In the usual case, the execve wrapper reports the command being
// executed in `program`, so a direct skill lookup is sufficient.
let approval = {
self.session
.services
.execve_session_approvals
.read()
.await
.get(program)
.cloned()
};
if let Some(approval) = approval {
tracing::debug!(
"Found session approval for {program:?}, allowing execution without further checks"
);
let needs_escalation = true;
let (permissions, decision_source) = if let Some(skill) = approval.skill.as_ref() {
(
skill.permission_profile.clone(),
DecisionSource::SkillScript {
skill: skill.clone(),
},
)
} else {
(None, DecisionSource::UnmatchedCommandFallback)
};
return self
.process_decision(
Decision::Allow,
needs_escalation,
program,
argv,
workdir,
permissions,
decision_source,
)
.await;
}
if let Some(skill) = self.find_skill(program).await {
// For now, we always prompt for scripts that look like they belong
// to skills, which means we ignore exec policy rules for those
// scripts.
tracing::debug!("Matched {program:?} to skill {skill:?}, prompting for approval");
// TODO(mbolin): We should read the permissions associated with the
// skill and use those specific permissions in the
// EscalateAction::Run case, rather than always escalating when a
// skill matches.
let needs_escalation = true;
let is_approved_for_session = self
.session
.services
.execve_session_approvals
.read()
.await
.contains(program);
let decision = if is_approved_for_session {
Decision::Allow
} else {
Decision::Prompt
let decision_source = DecisionSource::SkillScript {
skill: skill.clone(),
};
return self
.process_decision(
decision,
Decision::Prompt,
needs_escalation,
program,
argv,
workdir,
skill.permission_profile.clone(),
DecisionSource::SkillScript,
decision_source,
)
.await;
}
@@ -77,6 +77,7 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
)
.await?;
let codex = harness.test().codex.clone();
let session_id = harness.test().session_configured.session_id.to_string();
let responses_mock = responses::mount_sse_sequence(
harness.server(),
@@ -137,6 +138,10 @@ async fn remote_compact_replaces_history_for_followups() -> Result<()> {
compact_request.header("authorization").as_deref(),
Some("Bearer Access Token")
);
assert_eq!(
compact_request.header("session_id").as_deref(),
Some(session_id.as_str())
);
let compact_body = compact_request.body_json();
assert_eq!(
compact_body.get("model").and_then(|v| v.as_str()),
@@ -195,6 +200,7 @@ async fn remote_compact_runs_automatically() -> Result<()> {
)
.await?;
let codex = harness.test().codex.clone();
let session_id = harness.test().session_configured.session_id.to_string();
mount_sse_once(
harness.server(),
@@ -237,6 +243,13 @@ async fn remote_compact_runs_automatically() -> Result<()> {
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
assert!(message);
assert_eq!(compact_mock.requests().len(), 1);
assert_eq!(
compact_mock
.single_request()
.header("session_id")
.as_deref(),
Some(session_id.as_str())
);
let follow_up_request = responses_mock.single_request();
let follow_up_body = follow_up_request.body_json().to_string();
assert!(follow_up_body.contains("REMOTE_COMPACTED_SUMMARY"));
@@ -854,6 +854,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
| EventMsg::RealtimeConversationRealtime(_)
| EventMsg::RealtimeConversationClosed(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_)
| EventMsg::SkillRequestApproval(_) => {}
}
CodexStatus::Running
@@ -925,6 +926,7 @@ impl EventProcessorWithHumanOutput {
| EventMsg::ThreadRolledBack(_)
| EventMsg::RequestUserInput(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_)
)
}
+1
View File
@@ -696,6 +696,7 @@ fn should_suppress_agent_job_event(msg: &EventMsg) -> bool {
| EventMsg::ApplyPatchApprovalRequest(_)
| EventMsg::RequestUserInput(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_)
| EventMsg::ElicitationRequest(_)
| EventMsg::Error(_)
| EventMsg::Warning(_)
@@ -227,6 +227,7 @@ async fn run_codex_tool_session_inner(
parsed_cmd,
network_approval_context: _,
additional_permissions: _,
available_decisions: _,
} = ev;
handle_exec_approval_request(
command,
@@ -363,6 +364,7 @@ async fn run_codex_tool_session_inner(
| EventMsg::ExitedReviewMode(_)
| EventMsg::RequestUserInput(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_)
| EventMsg::SkillRequestApproval(_)
| EventMsg::ContextCompacted(_)
| EventMsg::ModelReroute(_)
+54
View File
@@ -5,6 +5,7 @@ use crate::mcp::RequestId;
use crate::models::PermissionProfile;
use crate::parse_command::ParsedCommand;
use crate::protocol::FileChange;
use crate::protocol::ReviewDecision;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
@@ -107,6 +108,13 @@ pub struct ExecApprovalRequestEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
/// Ordered list of decisions the client may present for this prompt.
///
/// When absent, clients should derive the legacy default set from the
/// other fields on this request.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub available_decisions: Option<Vec<ReviewDecision>>,
pub parsed_cmd: Vec<ParsedCommand>,
}
@@ -116,6 +124,52 @@ impl ExecApprovalRequestEvent {
.clone()
.unwrap_or_else(|| self.call_id.clone())
}
pub fn default_available_decisions(
network_approval_context: Option<&NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<&ExecPolicyAmendment>,
proposed_network_policy_amendments: Option<&[NetworkPolicyAmendment]>,
additional_permissions: Option<&PermissionProfile>,
) -> Vec<ReviewDecision> {
if network_approval_context.is_some() {
let mut decisions = vec![ReviewDecision::Approved, ReviewDecision::ApprovedForSession];
if let Some(amendment) = proposed_network_policy_amendments.and_then(|amendments| {
amendments
.iter()
.find(|amendment| amendment.action == NetworkPolicyRuleAction::Allow)
}) {
decisions.push(ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: amendment.clone(),
});
}
decisions.push(ReviewDecision::Abort);
return decisions;
}
if additional_permissions.is_some() {
return vec![ReviewDecision::Approved, ReviewDecision::Abort];
}
let mut decisions = vec![ReviewDecision::Approved];
if let Some(prefix) = proposed_execpolicy_amendment {
decisions.push(ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment: prefix.clone(),
});
}
decisions.push(ReviewDecision::Abort);
decisions
}
pub fn effective_available_decisions(&self) -> Vec<ReviewDecision> {
self.available_decisions.clone().unwrap_or_else(|| {
Self::default_available_decisions(
self.network_approval_context.as_ref(),
self.proposed_execpolicy_amendment.as_ref(),
self.proposed_network_policy_amendments.as_deref(),
self.additional_permissions.as_ref(),
)
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
+26 -2
View File
@@ -20,6 +20,7 @@ use crate::config_types::Personality;
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
use crate::config_types::WindowsSandboxLevel;
use crate::custom_prompts::CustomPrompt;
use crate::dynamic_tools::DynamicToolCallOutputContentItem;
use crate::dynamic_tools::DynamicToolCallRequest;
use crate::dynamic_tools::DynamicToolResponse;
use crate::dynamic_tools::DynamicToolSpec;
@@ -1053,6 +1054,8 @@ pub enum EventMsg {
DynamicToolCallRequest(DynamicToolCallRequest),
DynamicToolCallResponse(DynamicToolCallResponseEvent),
SkillRequestApproval(SkillRequestApprovalEvent),
ElicitationRequest(ElicitationRequestEvent),
@@ -1781,6 +1784,27 @@ pub struct McpToolCallEndEvent {
pub result: Result<CallToolResult, String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
pub struct DynamicToolCallResponseEvent {
/// Identifier for the corresponding DynamicToolCallRequest.
pub call_id: String,
/// Turn ID that this dynamic tool call belongs to.
pub turn_id: String,
/// Dynamic tool name.
pub tool: String,
/// Dynamic tool call arguments.
pub arguments: serde_json::Value,
/// Dynamic tool response content items.
pub content_items: Vec<DynamicToolCallOutputContentItem>,
/// Whether the tool call succeeded.
pub success: bool,
/// Optional error text when the tool call failed before producing a response.
pub error: Option<String>,
/// The duration of the dynamic tool call.
#[ts(type = "string")]
pub duration: Duration,
}
impl McpToolCallEndEvent {
pub fn is_success(&self) -> bool {
match &self.result {
@@ -2765,8 +2789,8 @@ pub enum ReviewDecision {
proposed_execpolicy_amendment: ExecPolicyAmendment,
},
/// User has approved this command and wants to automatically approve any
/// future identical instances (`command` and `cwd` match exactly) for the
/// User has approved this request and wants future prompts in the same
/// session-scoped approval cache to be automatically approved for the
/// remainder of the session.
ApprovedForSession,
+1
View File
@@ -3563,6 +3563,7 @@ mod tests {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: Vec::new(),
},
),
@@ -402,6 +402,7 @@ mod tests {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: Vec::new(),
},
),
@@ -544,6 +545,7 @@ mod tests {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: Vec::new(),
},
),
@@ -622,6 +624,7 @@ mod tests {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: Vec::new(),
},
),
+154 -93
View File
@@ -46,6 +46,7 @@ pub(crate) enum ApprovalRequest {
id: String,
command: Vec<String>,
reason: Option<String>,
available_decisions: Vec<ReviewDecision>,
network_approval_context: Option<NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
proposed_network_policy_amendments: Option<Vec<NetworkPolicyAmendment>>,
@@ -115,15 +116,13 @@ impl ApprovalOverlay {
) -> (Vec<ApprovalOption>, SelectionViewParams) {
let (options, title) = match &variant {
ApprovalVariant::Exec {
available_decisions,
network_approval_context,
proposed_execpolicy_amendment,
proposed_network_policy_amendments,
additional_permissions,
..
} => (
exec_options(
proposed_execpolicy_amendment.clone(),
proposed_network_policy_amendments.clone(),
available_decisions,
network_approval_context.as_ref(),
additional_permissions.as_ref(),
),
@@ -365,6 +364,7 @@ impl From<ApprovalRequest> for ApprovalRequestState {
id,
command,
reason,
available_decisions,
network_approval_context,
proposed_execpolicy_amendment,
proposed_network_policy_amendments,
@@ -397,6 +397,7 @@ impl From<ApprovalRequest> for ApprovalRequestState {
variant: ApprovalVariant::Exec {
id,
command,
available_decisions,
network_approval_context,
proposed_execpolicy_amendment,
proposed_network_policy_amendments,
@@ -451,11 +452,16 @@ impl From<ApprovalRequest> for ApprovalRequestState {
}
#[derive(Clone)]
#[allow(clippy::large_enum_variant)]
#[allow(dead_code)]
enum ApprovalVariant {
Exec {
id: String,
command: Vec<String>,
available_decisions: Vec<ReviewDecision>,
#[allow(dead_code)]
network_approval_context: Option<NetworkApprovalContext>,
#[allow(dead_code)]
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
proposed_network_policy_amendments: Option<Vec<NetworkPolicyAmendment>>,
additional_permissions: Option<PermissionProfile>,
@@ -492,100 +498,93 @@ impl ApprovalOption {
}
fn exec_options(
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
proposed_network_policy_amendments: Option<Vec<NetworkPolicyAmendment>>,
available_decisions: &[ReviewDecision],
network_approval_context: Option<&NetworkApprovalContext>,
additional_permissions: Option<&PermissionProfile>,
) -> Vec<ApprovalOption> {
if network_approval_context.is_some() {
let mut options = vec![
ApprovalOption {
label: "Yes, just this once".to_string(),
available_decisions
.iter()
.filter_map(|decision| match decision {
ReviewDecision::Approved => Some(ApprovalOption {
label: if network_approval_context.is_some() {
"Yes, just this once".to_string()
} else {
"Yes, proceed".to_string()
},
decision: ApprovalDecision::Review(ReviewDecision::Approved),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('y'))],
},
ApprovalOption {
label: "Yes, and allow this host for this conversation".to_string(),
}),
ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment,
} => {
let rendered_prefix =
strip_bash_lc_and_escape(proposed_execpolicy_amendment.command());
if rendered_prefix.contains('\n') || rendered_prefix.contains('\r') {
return None;
}
Some(ApprovalOption {
label: format!(
"Yes, and don't ask again for commands that start with `{rendered_prefix}`"
),
decision: ApprovalDecision::Review(
ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment: proposed_execpolicy_amendment.clone(),
},
),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('p'))],
})
}
ReviewDecision::ApprovedForSession => Some(ApprovalOption {
label: if network_approval_context.is_some() {
"Yes, and allow this host for this conversation".to_string()
} else if additional_permissions.is_some() {
"Yes, and allow these permissions for this session".to_string()
} else {
"Yes, and don't ask again for this command in this session".to_string()
},
decision: ApprovalDecision::Review(ReviewDecision::ApprovedForSession),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('a'))],
},
];
for amendment in proposed_network_policy_amendments.unwrap_or_default() {
let (label, shortcut) = match amendment.action {
NetworkPolicyRuleAction::Allow => (
"Yes, and allow this host in the future".to_string(),
KeyCode::Char('p'),
),
NetworkPolicyRuleAction::Deny => continue,
};
options.push(ApprovalOption {
label,
decision: ApprovalDecision::Review(ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: amendment,
}),
}),
ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment,
} => {
let (label, shortcut) = match network_policy_amendment.action {
NetworkPolicyRuleAction::Allow => (
"Yes, and allow this host in the future".to_string(),
KeyCode::Char('p'),
),
NetworkPolicyRuleAction::Deny => (
"No, and block this host in the future".to_string(),
KeyCode::Char('d'),
),
};
Some(ApprovalOption {
label,
decision: ApprovalDecision::Review(ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: network_policy_amendment.clone(),
}),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(shortcut)],
})
}
ReviewDecision::Denied => Some(ApprovalOption {
label: "No, continue without running it".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Denied),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(shortcut)],
});
}
options.push(ApprovalOption {
label: "No, and tell Codex what to do differently".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Abort),
display_shortcut: Some(key_hint::plain(KeyCode::Esc)),
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('n'))],
});
return options;
}
if additional_permissions.is_some() {
return vec![
ApprovalOption {
label: "Yes, proceed".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Approved),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('y'))],
},
ApprovalOption {
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('d'))],
}),
ReviewDecision::Abort => Some(ApprovalOption {
label: "No, and tell Codex what to do differently".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Abort),
display_shortcut: Some(key_hint::plain(KeyCode::Esc)),
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('n'))],
},
];
}
vec![ApprovalOption {
label: "Yes, proceed".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Approved),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('y'))],
}]
.into_iter()
.chain(proposed_execpolicy_amendment.and_then(|prefix| {
let rendered_prefix = strip_bash_lc_and_escape(prefix.command());
if rendered_prefix.contains('\n') || rendered_prefix.contains('\r') {
return None;
}
Some(ApprovalOption {
label: format!(
"Yes, and don't ask again for commands that start with `{rendered_prefix}`"
),
decision: ApprovalDecision::Review(ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment: prefix,
}),
display_shortcut: None,
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('p'))],
})
}))
.chain([ApprovalOption {
label: "No, and tell Codex what to do differently".to_string(),
decision: ApprovalDecision::Review(ReviewDecision::Abort),
display_shortcut: Some(key_hint::plain(KeyCode::Esc)),
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('n'))],
}])
.collect()
.collect()
}
fn format_additional_permissions_rule(
@@ -695,6 +694,7 @@ mod tests {
id: "test".to_string(),
command: vec!["echo".to_string(), "hi".to_string()],
reason: Some("reason".to_string()),
available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort],
network_approval_context: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
@@ -740,6 +740,15 @@ mod tests {
id: "test".to_string(),
command: vec!["echo".to_string()],
reason: None,
available_decisions: vec![
ReviewDecision::Approved,
ReviewDecision::ApprovedExecpolicyAmendment {
proposed_execpolicy_amendment: ExecPolicyAmendment::new(vec![
"echo".to_string(),
]),
},
ReviewDecision::Abort,
],
network_approval_context: None,
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
"echo".to_string(),
@@ -781,6 +790,17 @@ mod tests {
id: "test".to_string(),
command: vec!["curl".to_string(), "https://example.com".to_string()],
reason: None,
available_decisions: vec![
ReviewDecision::Approved,
ReviewDecision::ApprovedForSession,
ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: NetworkPolicyAmendment {
host: "example.com".to_string(),
action: NetworkPolicyRuleAction::Allow,
},
},
ReviewDecision::Abort,
],
network_approval_context: Some(NetworkApprovalContext {
host: "example.com".to_string(),
protocol: NetworkApprovalProtocol::Https,
@@ -818,6 +838,7 @@ mod tests {
id: "test".into(),
command,
reason: None,
available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort],
network_approval_context: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
@@ -850,17 +871,17 @@ mod tests {
protocol: NetworkApprovalProtocol::Https,
};
let options = exec_options(
Some(ExecPolicyAmendment::new(vec!["curl".to_string()])),
Some(vec![
NetworkPolicyAmendment {
host: "example.com".to_string(),
action: NetworkPolicyRuleAction::Allow,
&[
ReviewDecision::Approved,
ReviewDecision::ApprovedForSession,
ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: NetworkPolicyAmendment {
host: "example.com".to_string(),
action: NetworkPolicyRuleAction::Allow,
},
},
NetworkPolicyAmendment {
host: "example.com".to_string(),
action: NetworkPolicyRuleAction::Deny,
},
]),
ReviewDecision::Abort,
],
Some(&network_context),
None,
);
@@ -877,6 +898,29 @@ mod tests {
);
}
#[test]
fn generic_exec_options_can_offer_allow_for_session() {
let options = exec_options(
&[
ReviewDecision::Approved,
ReviewDecision::ApprovedForSession,
ReviewDecision::Abort,
],
None,
None,
);
let labels: Vec<String> = options.into_iter().map(|option| option.label).collect();
assert_eq!(
labels,
vec![
"Yes, proceed".to_string(),
"Yes, and don't ask again for this command in this session".to_string(),
"No, and tell Codex what to do differently".to_string(),
]
);
}
#[test]
fn additional_permissions_exec_options_hide_execpolicy_amendment() {
let additional_permissions = PermissionProfile {
@@ -886,7 +930,11 @@ mod tests {
}),
..Default::default()
};
let options = exec_options(None, None, None, Some(&additional_permissions));
let options = exec_options(
&[ReviewDecision::Approved, ReviewDecision::Abort],
None,
Some(&additional_permissions),
);
let labels: Vec<String> = options.into_iter().map(|option| option.label).collect();
assert_eq!(
@@ -906,6 +954,7 @@ mod tests {
id: "test".into(),
command: vec!["cat".into(), "/tmp/readme.txt".into()],
reason: None,
available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort],
network_approval_context: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
@@ -946,6 +995,7 @@ mod tests {
id: "test".into(),
command: vec!["cat".into(), "/tmp/readme.txt".into()],
reason: Some("need filesystem access".into()),
available_decisions: vec![ReviewDecision::Approved, ReviewDecision::Abort],
network_approval_context: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
@@ -973,6 +1023,17 @@ mod tests {
id: "test".into(),
command: vec!["curl".into(), "https://example.com".into()],
reason: Some("network request blocked".into()),
available_decisions: vec![
ReviewDecision::Approved,
ReviewDecision::ApprovedForSession,
ReviewDecision::NetworkPolicyAmendment {
network_policy_amendment: NetworkPolicyAmendment {
host: "example.com".to_string(),
action: NetworkPolicyRuleAction::Allow,
},
},
ReviewDecision::Abort,
],
network_approval_context: Some(NetworkApprovalContext {
host: "example.com".to_string(),
protocol: NetworkApprovalProtocol::Https,
+4
View File
@@ -1117,6 +1117,10 @@ mod tests {
id: "1".to_string(),
command: vec!["echo".into(), "ok".into()],
reason: None,
available_decisions: vec![
codex_protocol::protocol::ReviewDecision::Approved,
codex_protocol::protocol::ReviewDecision::Abort,
],
network_approval_context: None,
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
+3
View File
@@ -2568,10 +2568,12 @@ impl ChatWidget {
.unwrap_or_else(|_| ev.command.join(" "));
self.notify(Notification::ExecApprovalRequested { command });
let available_decisions = ev.effective_available_decisions();
let request = ApprovalRequest::Exec {
id: ev.effective_approval_id(),
command: ev.command,
reason: ev.reason,
available_decisions,
network_approval_context: ev.network_approval_context,
proposed_execpolicy_amendment: ev.proposed_execpolicy_amendment,
proposed_network_policy_amendments: ev.proposed_network_policy_amendments,
@@ -4464,6 +4466,7 @@ impl ChatWidget {
| EventMsg::ReasoningContentDelta(_)
| EventMsg::ReasoningRawContentDelta(_)
| EventMsg::DynamicToolCallRequest(_)
| EventMsg::DynamicToolCallResponse(_)
| EventMsg::SkillRequestApproval(_) => {}
EventMsg::RealtimeConversationStarted(ev) => {
if !from_replay {
+8
View File
@@ -2784,6 +2784,7 @@ async fn exec_approval_emits_proposed_command_and_decision_history() {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -2833,6 +2834,7 @@ async fn exec_approval_uses_approval_id_when_present() {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
}),
});
@@ -2869,6 +2871,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -2923,6 +2926,7 @@ async fn exec_approval_decision_truncates_multiline_and_long_commands() {
proposed_execpolicy_amendment: None,
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -6742,6 +6746,7 @@ async fn approval_modal_exec_snapshot() -> anyhow::Result<()> {
])),
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -6802,6 +6807,7 @@ async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()> {
])),
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -6849,6 +6855,7 @@ async fn approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot()
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(command)),
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -7215,6 +7222,7 @@ async fn status_widget_and_approval_modal_snapshot() {
])),
proposed_network_policy_amendments: None,
additional_permissions: None,
available_decisions: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
+1 -1
View File
@@ -447,7 +447,7 @@ impl AuthModeWidget {
" Decide how much autonomy you want to grant Codex".into(),
Line::from(vec![
" For more details see the ".into(),
"\u{1b}]8;;https://github.com/openai/codex\u{7}Codex docs\u{1b}]8;;\u{7}".underlined(),
"\u{1b}]8;;https://developers.openai.com/codex/security\u{7}Codex docs\u{1b}]8;;\u{7}".underlined(),
])
.dim(),
"".into(),