mirror of
https://github.com/openai/codex.git
synced 2026-09-17 12:23:33 +00:00
update
This commit is contained in:
963
codex-rs/core/src/consequential_tool_message_templates.json
Normal file
963
codex-rs/core/src/consequential_tool_message_templates.json
Normal file
@@ -0,0 +1,963 @@
|
||||
{
|
||||
"schema_version": 3,
|
||||
"generated_from_path": "codex-rs/core/src/consequential_tools.json",
|
||||
"templates": [
|
||||
{
|
||||
"source_tool_index": 0,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_comment_to_issue",
|
||||
"action": "add a comment to a pull request",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"label": "Comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 1,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_reaction_to_issue_comment",
|
||||
"action": "add a reaction to an issue comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 2,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_reaction_to_pr",
|
||||
"action": "add a reaction to a pull request",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 3,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_reaction_to_pr_review_comment",
|
||||
"action": "add a reaction to a pull request review comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 4,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_review_to_pr",
|
||||
"action": "submit a pull request review",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "action",
|
||||
"label": "Action"
|
||||
},
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "review",
|
||||
"label": "Review"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 5,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_blob",
|
||||
"action": "create a Git blob",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"label": "Content"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 6,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_branch",
|
||||
"action": "create a branch",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "branch_name",
|
||||
"label": "Branch"
|
||||
},
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 7,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_commit",
|
||||
"action": "create a commit",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "Message"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 8,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_pull_request",
|
||||
"action": "create a pull request",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"name": "head_branch",
|
||||
"label": "Head branch"
|
||||
},
|
||||
{
|
||||
"name": "base_branch",
|
||||
"label": "Base branch"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 9,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_tree",
|
||||
"action": "create a Git tree",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "tree_elements",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 10,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "enable_auto_merge",
|
||||
"action": "enable pull request auto-merge",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 11,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "label_pr",
|
||||
"action": "add a label to a pull request",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "label",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 12,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "remove_reaction_from_issue_comment",
|
||||
"action": "remove a reaction from an issue comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction_id",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 13,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "remove_reaction_from_pr",
|
||||
"action": "remove a reaction from a pull request",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction_id",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 14,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "remove_reaction_from_pr_review_comment",
|
||||
"action": "remove a reaction from a pull request review comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "reaction_id",
|
||||
"label": "Reaction"
|
||||
},
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 15,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "reply_to_review_comment",
|
||||
"action": "reply to a pull request review comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "pr_number",
|
||||
"label": "Pull request"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"label": "Comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 16,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_issue_comment",
|
||||
"action": "update an issue comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"label": "Comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 17,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_ref",
|
||||
"action": "update a branch reference",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "branch_name",
|
||||
"label": "Branch"
|
||||
},
|
||||
{
|
||||
"name": "repository_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "sha",
|
||||
"label": "Commit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 18,
|
||||
"connector_id": "connector_76869538009648d5b282a4bb21c3d157",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_review_comment",
|
||||
"action": "update a pull request review comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"name": "repo_full_name",
|
||||
"label": "Repository"
|
||||
},
|
||||
{
|
||||
"name": "comment",
|
||||
"label": "Comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 19,
|
||||
"connector_id": "connector_947e0d954944416db111db556030eea6",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_event",
|
||||
"action": "create a calendar event",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"name": "start_time",
|
||||
"label": "Start"
|
||||
},
|
||||
{
|
||||
"name": "attendees",
|
||||
"label": "Attendees"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 20,
|
||||
"connector_id": "connector_947e0d954944416db111db556030eea6",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "delete_event",
|
||||
"action": "delete a calendar event",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "event_id",
|
||||
"label": "Event"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 21,
|
||||
"connector_id": "connector_947e0d954944416db111db556030eea6",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "respond_event",
|
||||
"action": "respond to a calendar event",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "response_status",
|
||||
"label": "Response Status"
|
||||
},
|
||||
{
|
||||
"name": "event_id",
|
||||
"label": "Event"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 22,
|
||||
"connector_id": "connector_947e0d954944416db111db556030eea6",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_event",
|
||||
"action": "update a calendar event",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "event_id",
|
||||
"label": "Event"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 23,
|
||||
"connector_id": "connector_9d7cfa34e6654a5f98d3387af34b2e1c",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "batch_update",
|
||||
"action": "apply spreadsheet updates",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "spreadsheet_url",
|
||||
"label": "Spreadsheet"
|
||||
},
|
||||
{
|
||||
"name": "requests",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 24,
|
||||
"connector_id": "connector_9d7cfa34e6654a5f98d3387af34b2e1c",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_spreadsheet",
|
||||
"action": "create a spreadsheet",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 25,
|
||||
"connector_id": "connector_9d7cfa34e6654a5f98d3387af34b2e1c",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "duplicate_sheet_in_new_file",
|
||||
"action": "copy a sheet into a new spreadsheet",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "source_sheet_name",
|
||||
"label": "Source Sheet Name"
|
||||
},
|
||||
{
|
||||
"name": "spreadsheet_url",
|
||||
"label": "Spreadsheet"
|
||||
},
|
||||
{
|
||||
"name": "new_file_name",
|
||||
"label": "New File Name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 26,
|
||||
"connector_id": "connector_6f1ec045b8fa4ced8738e32c7f74514b",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "batch_update",
|
||||
"action": "apply presentation updates",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "presentation_url",
|
||||
"label": "Presentation"
|
||||
},
|
||||
{
|
||||
"name": "requests",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 27,
|
||||
"connector_id": "connector_6f1ec045b8fa4ced8738e32c7f74514b",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_presentation",
|
||||
"action": "create a presentation",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 28,
|
||||
"connector_id": "connector_4964e3b22e3e427e9b4ae1acf2c1fa34",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "batch_update",
|
||||
"action": "apply document updates",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "document_url",
|
||||
"label": "Document"
|
||||
},
|
||||
{
|
||||
"name": "requests",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 29,
|
||||
"connector_id": "connector_4964e3b22e3e427e9b4ae1acf2c1fa34",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_document",
|
||||
"action": "create a document",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 30,
|
||||
"connector_id": "connector_5f3c8c41a1e54ad7a76272c89e2554fa",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "copy_document",
|
||||
"action": "copy a Google Drive file",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "url",
|
||||
"label": "URL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 31,
|
||||
"connector_id": "connector_5f3c8c41a1e54ad7a76272c89e2554fa",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "share_document",
|
||||
"action": "change file sharing",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "url",
|
||||
"label": "URL"
|
||||
},
|
||||
{
|
||||
"name": "permission",
|
||||
"label": "Permission"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 32,
|
||||
"connector_id": "asdk_app_69a1d78e929881919bba0dbda1f6436d",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "slack_send_message",
|
||||
"action": "send a message",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "channel_id",
|
||||
"label": "Conversation"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "Message"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 33,
|
||||
"connector_id": "asdk_app_69a1d78e929881919bba0dbda1f6436d",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "slack_schedule_message",
|
||||
"action": "schedule a message",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "channel_id",
|
||||
"label": "Conversation"
|
||||
},
|
||||
{
|
||||
"name": "post_at",
|
||||
"label": "Send at"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "Message"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 34,
|
||||
"connector_id": "asdk_app_69a1d78e929881919bba0dbda1f6436d",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "slack_create_canvas",
|
||||
"action": "create a canvas",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"label": "Content"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 35,
|
||||
"connector_id": "asdk_app_69a1d78e929881919bba0dbda1f6436d",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "slack_send_message_draft",
|
||||
"action": "create a message draft",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "channel_id",
|
||||
"label": "Conversation"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "Message"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 36,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_comment_to_issue",
|
||||
"action": "add a comment to a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"label": "Body"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 37,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_label_to_issue",
|
||||
"action": "add a label to a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "label_id",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 38,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "add_url_attachment_to_issue",
|
||||
"action": "attach a link to a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "url",
|
||||
"label": "URL"
|
||||
},
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 39,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "assign_issue",
|
||||
"action": "assign a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"label": "User"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 40,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_issue",
|
||||
"action": "create a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "title",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"name": "team_id",
|
||||
"label": "Team"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 41,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_label",
|
||||
"action": "create a Linear label",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "label_name",
|
||||
"label": "Label Name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 42,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_project",
|
||||
"action": "create a Linear project",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"name": "team_id",
|
||||
"label": "Team"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 43,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "remove_label_from_issue",
|
||||
"action": "remove a label from a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "label_id",
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 44,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "resolve_comment",
|
||||
"action": "resolve a Linear comment",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "comment_id",
|
||||
"label": "Comment"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 45,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "set_issue_state",
|
||||
"action": "change a Linear issue state",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
},
|
||||
{
|
||||
"name": "state_id",
|
||||
"label": "State"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 46,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "unassign_issue",
|
||||
"action": "unassign a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 47,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_issue",
|
||||
"action": "update a Linear issue",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "issue_id",
|
||||
"label": "Issue"
|
||||
},
|
||||
{
|
||||
"name": "issue_update",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 48,
|
||||
"connector_id": "connector_686fad9b54914a35b75be6d06a0f6f31",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "update_project",
|
||||
"action": "update a Linear project",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "project_id",
|
||||
"label": "Project"
|
||||
},
|
||||
{
|
||||
"name": "update_fields",
|
||||
"label": "Changes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 49,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "apply_labels_to_emails",
|
||||
"action": "apply label changes to Gmail messages",
|
||||
"template_params": []
|
||||
},
|
||||
{
|
||||
"source_tool_index": 50,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "batch_modify_email",
|
||||
"action": "update Gmail message labels",
|
||||
"template_params": []
|
||||
},
|
||||
{
|
||||
"source_tool_index": 51,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "bulk_label_matching_emails",
|
||||
"action": "label matching Gmail messages",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "label_name",
|
||||
"label": "Label Name"
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"label": "Query"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 52,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_draft",
|
||||
"action": "create an email draft",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "to",
|
||||
"label": "To"
|
||||
},
|
||||
{
|
||||
"name": "subject",
|
||||
"label": "Subject"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"label": "Body"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 53,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "create_label",
|
||||
"action": "create a Gmail label",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"source_tool_index": 54,
|
||||
"connector_id": "connector_2128aebfecb84f64a069897515042a44",
|
||||
"server_name": "codex_apps",
|
||||
"tool_title": "send_email",
|
||||
"action": "send an email",
|
||||
"template_params": [
|
||||
{
|
||||
"name": "to",
|
||||
"label": "To"
|
||||
},
|
||||
{
|
||||
"name": "subject",
|
||||
"label": "Subject"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"label": "Body"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,9 +44,11 @@ use codex_protocol::request_user_input::RequestUserInputResponse;
|
||||
use codex_rmcp_client::ElicitationAction;
|
||||
use codex_rmcp_client::ElicitationResponse;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
use toml_edit::value;
|
||||
|
||||
/// Handles the specified tool call dispatches the appropriate
|
||||
@@ -385,6 +387,40 @@ struct McpToolApprovalPromptOptions {
|
||||
allow_persistent_approval: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ConsequentialToolMessageTemplateCatalog {
|
||||
templates: Vec<ConsequentialToolMessageTemplate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ConsequentialToolMessageTemplate {
|
||||
server_name: String,
|
||||
connector_id: Option<String>,
|
||||
tool_title: String,
|
||||
action: String,
|
||||
template_params: Vec<ConsequentialToolMessageTemplateParam>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct ConsequentialToolMessageTemplateParam {
|
||||
name: String,
|
||||
label: String,
|
||||
}
|
||||
|
||||
static CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES: LazyLock<
|
||||
Option<Vec<ConsequentialToolMessageTemplate>>,
|
||||
> = LazyLock::new(|| {
|
||||
serde_json::from_str::<ConsequentialToolMessageTemplateCatalog>(include_str!(
|
||||
"consequential_tool_message_templates.json",
|
||||
))
|
||||
.map(|catalog| catalog.templates)
|
||||
.map_err(|err| {
|
||||
error!("failed to parse bundled consequential tool message templates: {err}");
|
||||
err
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
const MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX: &str = "mcp_tool_call_approval";
|
||||
const MCP_TOOL_APPROVAL_ACCEPT: &str = "Allow";
|
||||
const MCP_TOOL_APPROVAL_ACCEPT_FOR_SESSION: &str = "Allow for this session";
|
||||
@@ -403,6 +439,11 @@ const MCP_TOOL_APPROVAL_CONNECTOR_DESCRIPTION_KEY: &str = "connector_description
|
||||
const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY: &str = "tool_title";
|
||||
const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY: &str = "tool_description";
|
||||
const MCP_TOOL_APPROVAL_TOOL_PARAMS_KEY: &str = "tool_params";
|
||||
const MCP_TOOL_APPROVAL_ACTOR_MAX_CHARS: usize = 48;
|
||||
const MCP_TOOL_APPROVAL_ACTION_MAX_CHARS: usize = 72;
|
||||
const MCP_TOOL_APPROVAL_DETAIL_VALUE_MAX_CHARS: usize = 80;
|
||||
const MCP_TOOL_APPROVAL_DETAILS_MAX_CHARS: usize = 220;
|
||||
const MCP_TOOL_APPROVAL_TRUNCATION_SUFFIX: &str = "...";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
struct McpToolApprovalKey {
|
||||
@@ -507,9 +548,8 @@ async fn maybe_request_mcp_tool_approval(
|
||||
question_id.clone(),
|
||||
&invocation.server,
|
||||
&invocation.tool,
|
||||
metadata.and_then(|metadata| metadata.tool_title.as_deref()),
|
||||
metadata.and_then(|metadata| metadata.connector_name.as_deref()),
|
||||
annotations,
|
||||
invocation.arguments.as_ref(),
|
||||
metadata,
|
||||
prompt_options,
|
||||
);
|
||||
question.question =
|
||||
@@ -738,34 +778,25 @@ fn build_mcp_tool_approval_question(
|
||||
question_id: String,
|
||||
server: &str,
|
||||
tool_name: &str,
|
||||
tool_title: Option<&str>,
|
||||
connector_name: Option<&str>,
|
||||
annotations: Option<&ToolAnnotations>,
|
||||
tool_params: Option<&serde_json::Value>,
|
||||
metadata: Option<&McpToolApprovalMetadata>,
|
||||
prompt_options: McpToolApprovalPromptOptions,
|
||||
) -> RequestUserInputQuestion {
|
||||
let destructive =
|
||||
annotations.and_then(|annotations| annotations.destructive_hint) == Some(true);
|
||||
let open_world = annotations.and_then(|annotations| annotations.open_world_hint) == Some(true);
|
||||
let reason = match (destructive, open_world) {
|
||||
(true, true) => "may modify data and access external systems",
|
||||
(true, false) => "may modify or delete data",
|
||||
(false, true) => "may access external systems",
|
||||
(false, false) => "may have side effects",
|
||||
};
|
||||
|
||||
let tool_label = tool_title.unwrap_or(tool_name);
|
||||
let app_label = connector_name
|
||||
.map(|name| format!("The {name} app"))
|
||||
.unwrap_or_else(|| {
|
||||
if server == CODEX_APPS_MCP_SERVER_NAME {
|
||||
"This app".to_string()
|
||||
} else {
|
||||
format!("The {server} MCP server")
|
||||
}
|
||||
});
|
||||
let question = format!(
|
||||
"{app_label} wants to run the tool \"{tool_label}\", which {reason}. Allow this action?"
|
||||
);
|
||||
let tool_title = metadata.and_then(|metadata| metadata.tool_title.as_deref());
|
||||
let connector_name = metadata.and_then(|metadata| metadata.connector_name.as_deref());
|
||||
let connector_id = metadata.and_then(|metadata| metadata.connector_id.as_deref());
|
||||
let annotations = metadata.and_then(|metadata| metadata.annotations.as_ref());
|
||||
let app_label = mcp_tool_approval_app_label(server, connector_name);
|
||||
let question = build_templated_mcp_tool_approval_question_text(
|
||||
server,
|
||||
tool_name,
|
||||
connector_id,
|
||||
connector_name,
|
||||
tool_params,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
build_legacy_mcp_tool_approval_question_text(&app_label, tool_name, tool_title, annotations)
|
||||
});
|
||||
|
||||
let mut options = vec![RequestUserInputQuestionOption {
|
||||
label: MCP_TOOL_APPROVAL_ACCEPT.to_string(),
|
||||
@@ -790,7 +821,7 @@ fn build_mcp_tool_approval_question(
|
||||
|
||||
RequestUserInputQuestion {
|
||||
id: question_id,
|
||||
header: "Approve app tool call?".to_string(),
|
||||
header: String::new(),
|
||||
question,
|
||||
is_other: false,
|
||||
is_secret: false,
|
||||
@@ -798,6 +829,187 @@ fn build_mcp_tool_approval_question(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_templated_mcp_tool_approval_question_text(
|
||||
server: &str,
|
||||
tool_name: &str,
|
||||
connector_id: Option<&str>,
|
||||
connector_name: Option<&str>,
|
||||
tool_params: Option<&serde_json::Value>,
|
||||
) -> Option<String> {
|
||||
let actor = truncate_for_mcp_tool_approval_message(
|
||||
&mcp_tool_approval_actor_label(server, connector_name),
|
||||
MCP_TOOL_APPROVAL_ACTOR_MAX_CHARS,
|
||||
);
|
||||
let tool_title = derive_consequential_tool_title(tool_name, connector_name);
|
||||
let template = find_consequential_tool_message_template(server, connector_id, &tool_title)?;
|
||||
let action = truncate_for_mcp_tool_approval_message(
|
||||
&template.action,
|
||||
MCP_TOOL_APPROVAL_ACTION_MAX_CHARS,
|
||||
);
|
||||
let details = render_consequential_tool_details(template, tool_params)?;
|
||||
|
||||
if details.is_empty() {
|
||||
Some(format!("Allow {actor} to {action}?"))
|
||||
} else {
|
||||
Some(format!("Allow {actor} to {action}?\n\n{details}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn find_consequential_tool_message_template(
|
||||
server: &str,
|
||||
connector_id: Option<&str>,
|
||||
tool_title: &str,
|
||||
) -> Option<&'static ConsequentialToolMessageTemplate> {
|
||||
CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES
|
||||
.as_ref()?
|
||||
.iter()
|
||||
.find(|template| {
|
||||
template.server_name == server
|
||||
&& template.connector_id.as_deref() == connector_id
|
||||
&& template.tool_title == tool_title
|
||||
})
|
||||
}
|
||||
|
||||
fn derive_consequential_tool_title(tool_name: &str, connector_name: Option<&str>) -> String {
|
||||
if let Some(connector_name) = connector_name {
|
||||
let connector_name = connector_name.to_lowercase();
|
||||
if let Some(suffix) = tool_name.strip_prefix(&connector_name) {
|
||||
let tool_title = suffix.trim_start_matches('_');
|
||||
if !tool_title.is_empty() {
|
||||
return tool_title.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tool_name.to_string()
|
||||
}
|
||||
|
||||
fn render_consequential_tool_details(
|
||||
template: &ConsequentialToolMessageTemplate,
|
||||
tool_params: Option<&serde_json::Value>,
|
||||
) -> Option<String> {
|
||||
let tool_params = tool_params?.as_object()?;
|
||||
let mut details = String::new();
|
||||
let mut remaining_chars = MCP_TOOL_APPROVAL_DETAILS_MAX_CHARS;
|
||||
for param in &template.template_params {
|
||||
let value = format_consequential_tool_message_value(tool_params.get(¶m.name)?)?;
|
||||
let line = format!("{}: {value}", param.label);
|
||||
let separator_chars = usize::from(!details.is_empty());
|
||||
if remaining_chars <= separator_chars {
|
||||
break;
|
||||
}
|
||||
if !details.is_empty() {
|
||||
details.push('\n');
|
||||
remaining_chars -= 1;
|
||||
}
|
||||
let line_chars = line.chars().count();
|
||||
if line_chars <= remaining_chars {
|
||||
details.push_str(&line);
|
||||
remaining_chars -= line_chars;
|
||||
} else {
|
||||
details.push_str(&truncate_for_mcp_tool_approval_message(
|
||||
&line,
|
||||
remaining_chars,
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Some(details)
|
||||
}
|
||||
|
||||
fn format_consequential_tool_message_value(value: &serde_json::Value) -> Option<String> {
|
||||
let formatted = match value {
|
||||
serde_json::Value::Null => None,
|
||||
serde_json::Value::Bool(value) => Some(value.to_string()),
|
||||
serde_json::Value::Number(value) => Some(value.to_string()),
|
||||
serde_json::Value::String(value) => {
|
||||
let value = value.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if value.is_empty() { None } else { Some(value) }
|
||||
}
|
||||
serde_json::Value::Array(values) => {
|
||||
let values = values
|
||||
.iter()
|
||||
.map(format_consequential_tool_message_value)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
if values.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(values.join(", "))
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(_) => serde_json::to_string(value).ok(),
|
||||
}?;
|
||||
|
||||
Some(truncate_for_mcp_tool_approval_message(
|
||||
&formatted,
|
||||
MCP_TOOL_APPROVAL_DETAIL_VALUE_MAX_CHARS,
|
||||
))
|
||||
}
|
||||
|
||||
fn truncate_for_mcp_tool_approval_message(text: &str, max_chars: usize) -> String {
|
||||
let char_count = text.chars().count();
|
||||
if char_count <= max_chars {
|
||||
return text.to_string();
|
||||
}
|
||||
|
||||
if max_chars == 0 {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let suffix_chars = MCP_TOOL_APPROVAL_TRUNCATION_SUFFIX.chars().count();
|
||||
if max_chars <= suffix_chars {
|
||||
return ".".repeat(max_chars);
|
||||
}
|
||||
|
||||
let prefix: String = text.chars().take(max_chars - suffix_chars).collect();
|
||||
format!("{prefix}{MCP_TOOL_APPROVAL_TRUNCATION_SUFFIX}")
|
||||
}
|
||||
|
||||
fn mcp_tool_approval_app_label(server: &str, connector_name: Option<&str>) -> String {
|
||||
connector_name
|
||||
.map(|name| format!("The {name} app"))
|
||||
.unwrap_or_else(|| {
|
||||
if server == CODEX_APPS_MCP_SERVER_NAME {
|
||||
"This app".to_string()
|
||||
} else {
|
||||
format!("The {server} MCP server")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn mcp_tool_approval_actor_label(server: &str, connector_name: Option<&str>) -> String {
|
||||
connector_name.map(str::to_string).unwrap_or_else(|| {
|
||||
if server == CODEX_APPS_MCP_SERVER_NAME {
|
||||
"this app".to_string()
|
||||
} else {
|
||||
format!("the {server} MCP server")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build_legacy_mcp_tool_approval_question_text(
|
||||
app_label: &str,
|
||||
tool_name: &str,
|
||||
tool_title: Option<&str>,
|
||||
annotations: Option<&ToolAnnotations>,
|
||||
) -> String {
|
||||
let destructive =
|
||||
annotations.and_then(|annotations| annotations.destructive_hint) == Some(true);
|
||||
let open_world = annotations.and_then(|annotations| annotations.open_world_hint) == Some(true);
|
||||
let reason = match (destructive, open_world) {
|
||||
(true, true) => "may modify data and access external systems",
|
||||
(true, false) => "may modify or delete data",
|
||||
(false, true) => "may access external systems",
|
||||
(false, false) => "may have side effects",
|
||||
};
|
||||
|
||||
let tool_label = tool_title.unwrap_or(tool_name);
|
||||
format!(
|
||||
"{app_label} wants to run the tool \"{tool_label}\", which {reason}. Allow this action?"
|
||||
)
|
||||
}
|
||||
|
||||
fn mcp_tool_approval_question_text(question: String, monitor_reason: Option<&str>) -> String {
|
||||
match monitor_reason.map(str::trim) {
|
||||
Some(reason) if !reason.is_empty() => {
|
||||
@@ -1282,17 +1494,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn custom_mcp_tool_question_mentions_server_name() {
|
||||
let mut metadata = approval_metadata(None, None, None, Some("Run Action"), None);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), None));
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
"custom_server",
|
||||
"run_action",
|
||||
Some("Run Action"),
|
||||
None,
|
||||
Some(&annotations(Some(false), Some(true), None)),
|
||||
Some(&metadata),
|
||||
prompt_options(false, false),
|
||||
);
|
||||
|
||||
assert_eq!(question.header, "Approve app tool call?");
|
||||
assert!(question.header.is_empty());
|
||||
assert_eq!(
|
||||
question.question,
|
||||
"The custom_server MCP server wants to run the tool \"Run Action\", which may modify or delete data. Allow this action?"
|
||||
@@ -1309,13 +1522,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn codex_apps_tool_question_keeps_legacy_app_label() {
|
||||
let mut metadata = approval_metadata(None, None, None, Some("Run Action"), None);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), None));
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
Some("Run Action"),
|
||||
None,
|
||||
Some(&annotations(Some(false), Some(true), None)),
|
||||
Some(&metadata),
|
||||
prompt_options(true, true),
|
||||
);
|
||||
|
||||
@@ -1328,13 +1542,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn trusted_codex_apps_tool_question_offers_always_allow() {
|
||||
let mut metadata =
|
||||
approval_metadata(None, Some("Calendar"), None, Some("Run Action"), None);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), None));
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
Some("Run Action"),
|
||||
Some("Calendar"),
|
||||
Some(&annotations(Some(false), Some(true), None)),
|
||||
None,
|
||||
Some(&metadata),
|
||||
prompt_options(true, true),
|
||||
);
|
||||
let options = question.options.expect("options");
|
||||
@@ -1370,13 +1586,15 @@ mod tests {
|
||||
tool_name: "run_action".to_string(),
|
||||
};
|
||||
let persistent_key = session_key.clone();
|
||||
let mut metadata =
|
||||
approval_metadata(None, Some("Calendar"), None, Some("Run Action"), None);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), None));
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"run_action",
|
||||
Some("Run Action"),
|
||||
Some("Calendar"),
|
||||
Some(&annotations(Some(false), Some(true), None)),
|
||||
None,
|
||||
Some(&metadata),
|
||||
mcp_tool_approval_prompt_options(Some(&session_key), Some(&persistent_key), false),
|
||||
);
|
||||
|
||||
@@ -1397,13 +1615,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn custom_mcp_tool_question_offers_session_remember_without_always_allow() {
|
||||
let mut metadata = approval_metadata(None, None, None, Some("Run Action"), None);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), None));
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
"custom_server",
|
||||
"run_action",
|
||||
Some("Run Action"),
|
||||
None,
|
||||
Some(&annotations(Some(false), Some(true), None)),
|
||||
Some(&metadata),
|
||||
prompt_options(true, false),
|
||||
);
|
||||
|
||||
@@ -1422,6 +1641,173 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_consequential_tool_title_from_connector_prefix() {
|
||||
assert_eq!(
|
||||
derive_consequential_tool_title("gmail_send_email", Some("Gmail")),
|
||||
"send_email".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
derive_consequential_tool_title(
|
||||
"slack (oai internal)_slack_send_message",
|
||||
Some("Slack (OAI Internal)")
|
||||
),
|
||||
"slack_send_message".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
derive_consequential_tool_title("custom_tool", None),
|
||||
"custom_tool".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncate_for_mcp_tool_approval_message_preserves_utf8_boundaries() {
|
||||
assert_eq!(
|
||||
truncate_for_mcp_tool_approval_message("aé🙂zbcdef", 8),
|
||||
"aé..."
|
||||
);
|
||||
assert_eq!(truncate_for_mcp_tool_approval_message("aé🙂z", 4), "aé🙂z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn consequential_tool_message_templates_use_at_most_three_params() {
|
||||
let templates = CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES
|
||||
.as_ref()
|
||||
.expect("templates loaded");
|
||||
let oversized = templates
|
||||
.iter()
|
||||
.filter(|template| template.template_params.len() > 3)
|
||||
.map(|template| template.tool_title.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(oversized, Vec::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_tool_question_uses_template_when_params_match() {
|
||||
let template = CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES
|
||||
.as_ref()
|
||||
.expect("templates loaded")
|
||||
.iter()
|
||||
.find(|template| template.tool_title == "send_email")
|
||||
.expect("send_email template");
|
||||
let mut metadata = approval_metadata(
|
||||
template.connector_id.as_deref(),
|
||||
Some("Gmail"),
|
||||
None,
|
||||
Some("Send Email"),
|
||||
None,
|
||||
);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), Some(true)));
|
||||
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"gmail_send_email",
|
||||
Some(&serde_json::json!({
|
||||
"to": "alice@example.com",
|
||||
"subject": "Quarterly update",
|
||||
"body": "Status update body",
|
||||
})),
|
||||
Some(&metadata),
|
||||
prompt_options(false, false),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
question.question,
|
||||
"Allow Gmail to send an email?\n\nTo: alice@example.com\nSubject: Quarterly update\nBody: Status update body"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_tool_question_falls_back_when_template_rendering_fails() {
|
||||
let template = CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES
|
||||
.as_ref()
|
||||
.expect("templates loaded")
|
||||
.iter()
|
||||
.find(|template| template.tool_title == "send_email")
|
||||
.expect("send_email template");
|
||||
let mut metadata = approval_metadata(
|
||||
template.connector_id.as_deref(),
|
||||
Some("Gmail"),
|
||||
None,
|
||||
Some("Send Email"),
|
||||
None,
|
||||
);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), Some(true)));
|
||||
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"gmail_send_email",
|
||||
Some(&serde_json::json!({
|
||||
"subject": "Quarterly update",
|
||||
"body": "Status update body",
|
||||
})),
|
||||
Some(&metadata),
|
||||
prompt_options(false, false),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
question.question,
|
||||
"The Gmail app wants to run the tool \"Send Email\", which may modify data and access external systems. Allow this action?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_tool_question_truncates_long_template_values() {
|
||||
let template = CONSEQUENTIAL_TOOL_MESSAGE_TEMPLATES
|
||||
.as_ref()
|
||||
.expect("templates loaded")
|
||||
.iter()
|
||||
.find(|template| template.tool_title == "create_pull_request")
|
||||
.expect("create_pull_request template");
|
||||
let mut metadata = approval_metadata(
|
||||
template.connector_id.as_deref(),
|
||||
Some("GitHub"),
|
||||
None,
|
||||
Some("Create Pull Request"),
|
||||
None,
|
||||
);
|
||||
metadata.annotations = Some(annotations(Some(false), Some(true), Some(true)));
|
||||
|
||||
let title = "Launch readiness review ".repeat(10);
|
||||
let head_branch = "feature/synchronize-release-notes-and-migration-guides-".repeat(4);
|
||||
let base_branch = "release/2026-03-11-super-long-branch-name".repeat(3);
|
||||
let repository_full_name =
|
||||
"openai/very-long-repository-name-for-approval-prompt-tests".repeat(3);
|
||||
let body =
|
||||
"This pull request includes the release notes, migration guide, and rollout plan. "
|
||||
.repeat(4);
|
||||
|
||||
let question = build_mcp_tool_approval_question(
|
||||
"q".to_string(),
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"github_create_pull_request",
|
||||
Some(&serde_json::json!({
|
||||
"title": title,
|
||||
"head_branch": head_branch,
|
||||
"base_branch": base_branch,
|
||||
"repository_full_name": repository_full_name,
|
||||
"body": body,
|
||||
})),
|
||||
Some(&metadata),
|
||||
prompt_options(false, false),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
question.question,
|
||||
"Allow GitHub to create a pull request?\n\nTitle: Launch readiness review Launch readiness review Launch readiness review La...\nHead branch: feature/synchronize-release-notes-and-migration-guides-feature/synchronize...\nBase branch: release/2026-03-11-..."
|
||||
);
|
||||
assert!(
|
||||
question.question.chars().count()
|
||||
<= format!("Allow GitHub to create a pull request?\n\n")
|
||||
.chars()
|
||||
.count()
|
||||
+ MCP_TOOL_APPROVAL_DETAILS_MAX_CHARS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_servers_keep_session_remember_without_persistent_approval() {
|
||||
let invocation = McpInvocation {
|
||||
|
||||
@@ -144,6 +144,11 @@ impl RequestUserInputOverlay {
|
||||
}
|
||||
|
||||
if !notes_visible {
|
||||
let mut spacer_after_question = 0;
|
||||
if remaining > 0 {
|
||||
spacer_after_question = 1;
|
||||
remaining = remaining.saturating_sub(1);
|
||||
}
|
||||
let mut spacer_after_options = 0;
|
||||
if remaining > footer_pref {
|
||||
spacer_after_options = 1;
|
||||
@@ -151,11 +156,6 @@ impl RequestUserInputOverlay {
|
||||
}
|
||||
let footer_lines = footer_pref.min(remaining);
|
||||
remaining = remaining.saturating_sub(footer_lines);
|
||||
let mut spacer_after_question = 0;
|
||||
if remaining > 0 {
|
||||
spacer_after_question = 1;
|
||||
remaining = remaining.saturating_sub(1);
|
||||
}
|
||||
let grow_by = remaining.min(options.full.saturating_sub(options_height));
|
||||
options_height = options_height.saturating_add(grow_by);
|
||||
return LayoutPlan {
|
||||
|
||||
@@ -1439,6 +1439,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn question_with_long_approval_body(id: &str, header: &str) -> RequestUserInputQuestion {
|
||||
RequestUserInputQuestion {
|
||||
id: id.to_string(),
|
||||
header: header.to_string(),
|
||||
question: "Allow GitHub to create a pull request?\n\nTitle: Launch readiness review for the release branch.\nHead branch: feature/synchronize-release-notes-and-migration-guides\nBase branch: release/2026-03-11".to_string(),
|
||||
is_other: false,
|
||||
is_secret: false,
|
||||
options: Some(vec![
|
||||
RequestUserInputQuestionOption {
|
||||
label: "Allow".to_string(),
|
||||
description: "Run the tool and continue.".to_string(),
|
||||
},
|
||||
RequestUserInputQuestionOption {
|
||||
label: "Cancel".to_string(),
|
||||
description: "Cancel this tool call.".to_string(),
|
||||
},
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
fn question_without_options(id: &str, header: &str) -> RequestUserInputQuestion {
|
||||
RequestUserInputQuestion {
|
||||
id: id.to_string(),
|
||||
@@ -2505,6 +2525,46 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_user_input_long_approval_body_snapshot() {
|
||||
let (tx, _rx) = test_sender();
|
||||
let overlay = RequestUserInputOverlay::new(
|
||||
request_event(
|
||||
"turn-1",
|
||||
vec![question_with_long_approval_body("q1", "Approval")],
|
||||
),
|
||||
tx,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let area = Rect::new(0, 0, 70, 12);
|
||||
insta::assert_snapshot!(
|
||||
"request_user_input_long_approval_body",
|
||||
render_snapshot(&overlay, area)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_approval_body_keeps_spacer_after_question() {
|
||||
let (tx, _rx) = test_sender();
|
||||
let overlay = RequestUserInputOverlay::new(
|
||||
request_event(
|
||||
"turn-1",
|
||||
vec![question_with_long_approval_body("q1", "Approval")],
|
||||
),
|
||||
tx,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let content_area = menu_surface_inset(Rect::new(0, 0, 70, 12));
|
||||
let sections = overlay.layout_sections(content_area);
|
||||
let question_bottom = sections.question_area.y + sections.question_area.height;
|
||||
|
||||
assert_eq!(sections.options_area.y.saturating_sub(question_bottom), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layout_allocates_all_wrapped_options_when_space_allows() {
|
||||
let (tx, _rx) = test_sender();
|
||||
|
||||
Reference in New Issue
Block a user