feat: add multi-actions to presentation tool (#13357)

This commit is contained in:
jif-oai
2026-03-03 14:37:26 +00:00
committed by GitHub
parent ad393fa753
commit 1df040e62b
8 changed files with 262 additions and 55 deletions
+3 -3
View File
@@ -56,7 +56,7 @@ use async_channel::Sender;
use chrono::Local;
use chrono::Utc;
use codex_artifact_presentation::PresentationArtifactError;
use codex_artifact_presentation::PresentationArtifactRequest;
use codex_artifact_presentation::PresentationArtifactExecutionRequest;
use codex_artifact_presentation::PresentationArtifactResponse;
use codex_hooks::HookEvent;
use codex_hooks::HookEventAfterAgent;
@@ -1782,11 +1782,11 @@ impl Session {
pub(crate) async fn execute_presentation_artifact(
&self,
request: PresentationArtifactRequest,
request: PresentationArtifactExecutionRequest,
cwd: &Path,
) -> Result<PresentationArtifactResponse, PresentationArtifactError> {
let mut state = self.state.lock().await;
state.presentation_artifacts.execute(request, cwd)
state.presentation_artifacts.execute_requests(request, cwd)
}
async fn record_initial_history(&self, conversation_history: InitialHistory) {
@@ -2,7 +2,7 @@ use async_trait::async_trait;
use codex_artifact_presentation::PathAccessKind;
use codex_artifact_presentation::PathAccessRequirement;
use codex_artifact_presentation::PresentationArtifactError;
use codex_artifact_presentation::PresentationArtifactRequest;
use codex_artifact_presentation::PresentationArtifactToolRequest;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
use serde_json::to_string;
@@ -37,23 +37,10 @@ impl ToolHandler for PresentationArtifactHandler {
let ToolPayload::Function { arguments } = &invocation.payload else {
return true;
};
let Ok(request) = parse_arguments::<PresentationArtifactRequest>(arguments) else {
let Ok(request) = parse_arguments::<PresentationArtifactToolRequest>(arguments) else {
return true;
};
!matches!(
request.action.as_str(),
"get_summary"
| "list_slides"
| "list_layouts"
| "list_layout_placeholders"
| "list_slide_placeholders"
| "inspect"
| "resolve"
| "to_proto"
| "get_style"
| "describe_styles"
| "record_patch"
)
request.is_mutating().unwrap_or(true)
}
async fn handle(&self, invocation: ToolInvocation) -> Result<ToolOutput, FunctionCallError> {
@@ -80,7 +67,7 @@ impl ToolHandler for PresentationArtifactHandler {
}
};
let request: PresentationArtifactRequest = parse_arguments(&arguments)?;
let request: PresentationArtifactToolRequest = parse_arguments(&arguments)?;
for access in request
.required_path_accesses(&turn.cwd)
.map_err(presentation_error)?
@@ -89,7 +76,12 @@ impl ToolHandler for PresentationArtifactHandler {
}
let response = session
.execute_presentation_artifact(request, &turn.cwd)
.execute_presentation_artifact(
request
.into_execution_request()
.map_err(presentation_error)?,
&turn.cwd,
)
.await
.map_err(presentation_error)?;
+27 -12
View File
@@ -567,6 +567,26 @@ fn create_view_image_tool() -> ToolSpec {
}
fn create_presentation_artifact_tool() -> ToolSpec {
let action_step_schema = JsonSchema::Object {
properties: BTreeMap::from([
(
"action".to_string(),
JsonSchema::String {
description: Some("Action name to run for this step.".to_string()),
},
),
(
"args".to_string(),
JsonSchema::Object {
properties: BTreeMap::new(),
required: None,
additional_properties: Some(true.into()),
},
),
]),
required: Some(vec!["action".to_string(), "args".to_string()]),
additional_properties: Some(false.into()),
};
let properties = BTreeMap::from([
(
"artifact_id".to_string(),
@@ -577,17 +597,12 @@ fn create_presentation_artifact_tool() -> ToolSpec {
},
),
(
"action".to_string(),
JsonSchema::String {
description: Some("Action name to run against the artifact.".to_string()),
},
),
(
"args".to_string(),
JsonSchema::Object {
properties: BTreeMap::new(),
required: None,
additional_properties: Some(true.into()),
"actions".to_string(),
JsonSchema::Array {
items: Box::new(action_step_schema),
description: Some(
"Array of `(action, args)` steps to execute sequentially.".to_string(),
),
},
),
]);
@@ -598,7 +613,7 @@ fn create_presentation_artifact_tool() -> ToolSpec {
strict: false,
parameters: JsonSchema::Object {
properties,
required: Some(vec!["action".to_string(), "args".to_string()]),
required: Some(vec!["actions".to_string()]),
additional_properties: Some(false.into()),
},
})
@@ -4,6 +4,7 @@ Create and edit PowerPoint presentation artifacts inside the current thread.
- Resume and fork do not restore live artifact state. Export files if you need a durable handoff.
- Relative paths resolve from the current working directory.
- Position and size values are in slide points.
- Every tool call uses a top-level `actions` array of sequential steps. Each call operates on a single top-level `artifact_id` when one is needed. If a call starts with `create` or `import_pptx`, later steps in the same call automatically reuse the returned artifact id.
Supported actions:
- `create`
@@ -61,60 +62,63 @@ Supported actions:
- `delete_artifact`
Example create:
`{"action":"create","args":{"name":"Quarterly Update"}}`
`{"actions":[{"action":"create","args":{"name":"Quarterly Update"}}]}`
Example create with custom slide size:
`{"action":"create","args":{"name":"Quarterly Update","slide_size":{"width":960,"height":540}}}`
`{"actions":[{"action":"create","args":{"name":"Quarterly Update","slide_size":{"width":960,"height":540}}}]}`
Example edit:
`{"artifact_id":"presentation_x","action":"add_text_shape","args":{"slide_index":0,"text":"Revenue up 24%","position":{"left":48,"top":72,"width":260,"height":80}}}`
`{"artifact_id":"presentation_x","actions":[{"action":"add_text_shape","args":{"slide_index":0,"text":"Revenue up 24%","position":{"left":48,"top":72,"width":260,"height":80}}}]}`
Example sequential batch:
`{"actions":[{"action":"create","args":{"name":"Quarterly Update"}},{"action":"add_slide","args":{}},{"action":"add_text_shape","args":{"slide_index":0,"text":"Revenue up 24%","position":{"left":48,"top":72,"width":260,"height":80}}}]}`
Table creation also accepts optional `column_widths` and `row_heights` arrays in points when you need explicit table sizing instead of even splits.
Example export:
`{"artifact_id":"presentation_x","action":"export_pptx","args":{"path":"artifacts/q2-update.pptx"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"export_pptx","args":{"path":"artifacts/q2-update.pptx"}}]}`
Example layout flow:
`{"artifact_id":"presentation_x","action":"create_layout","args":{"name":"Title Slide"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"create_layout","args":{"name":"Title Slide"}}]}`
`{"artifact_id":"presentation_x","action":"add_layout_placeholder","args":{"layout_id":"layout_1","name":"title","placeholder_type":"title","text":"Click to add title","position":{"left":48,"top":48,"width":624,"height":72}}}`
`{"artifact_id":"presentation_x","actions":[{"action":"add_layout_placeholder","args":{"layout_id":"layout_1","name":"title","placeholder_type":"title","text":"Click to add title","position":{"left":48,"top":48,"width":624,"height":72}}}]}`
`{"artifact_id":"presentation_x","action":"set_slide_layout","args":{"slide_index":0,"layout_id":"layout_1"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"set_slide_layout","args":{"slide_index":0,"layout_id":"layout_1"}}]}`
`{"artifact_id":"presentation_x","action":"list_layout_placeholders","args":{"layout_id":"layout_1"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"list_layout_placeholders","args":{"layout_id":"layout_1"}}]}`
`{"artifact_id":"presentation_x","action":"list_slide_placeholders","args":{"slide_index":0}}`
`{"artifact_id":"presentation_x","actions":[{"action":"list_slide_placeholders","args":{"slide_index":0}}]}`
Layout references in `create_layout.parent_layout_id`, `add_layout_placeholder.layout_id`, `add_slide`, `insert_slide`, `set_slide_layout`, and `list_layout_placeholders` accept either a layout id or a layout name. Name matching prefers exact id, then exact name, then case-insensitive name.
`insert_slide` accepts `index` or `after_slide_index`. If neither is provided, the new slide is inserted immediately after the active slide, or appended if no active slide is set yet.
Example inspect:
`{"artifact_id":"presentation_x","action":"inspect","args":{"include":"deck,slide,textbox,shape,table,chart,image,notes,layoutList","exclude":"notes","search":"roadmap","max_chars":12000}}`
`{"artifact_id":"presentation_x","actions":[{"action":"inspect","args":{"include":"deck,slide,textbox,shape,table,chart,image,notes,layoutList","exclude":"notes","search":"roadmap","max_chars":12000}}]}`
Example inspect target window:
`{"artifact_id":"presentation_x","action":"inspect","args":{"include":"textbox","target":{"id":"sh/element_3","before_lines":1,"after_lines":1}}}`
`{"artifact_id":"presentation_x","actions":[{"action":"inspect","args":{"include":"textbox","target":{"id":"sh/element_3","before_lines":1,"after_lines":1}}}]}`
Example resolve:
`{"artifact_id":"presentation_x","action":"resolve","args":{"id":"sh/element_3"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"resolve","args":{"id":"sh/element_3"}}]}`
Example proto export:
`{"artifact_id":"presentation_x","action":"to_proto","args":{}}`
`{"artifact_id":"presentation_x","actions":[{"action":"to_proto","args":{}}]}`
`to_proto` returns a full JSON snapshot of the current in-memory presentation document, including slide/layout records, anchors, notes, theme state, and typed element payloads.
Example patch recording:
`{"artifact_id":"presentation_x","action":"record_patch","args":{"operations":[{"action":"add_text_shape","args":{"slide_index":0,"text":"Headline","position":{"left":48,"top":48,"width":320,"height":72}}},{"action":"set_slide_background","args":{"slide_index":0,"fill":"#F7F1E8"}}]}}`
`{"artifact_id":"presentation_x","actions":[{"action":"record_patch","args":{"operations":[{"action":"add_text_shape","args":{"slide_index":0,"text":"Headline","position":{"left":48,"top":48,"width":320,"height":72}}},{"action":"set_slide_background","args":{"slide_index":0,"fill":"#F7F1E8"}}]}}]}`
Example patch application:
`{"artifact_id":"presentation_x","action":"apply_patch","args":{"patch":{"version":1,"artifactId":"presentation_x","operations":[{"action":"add_text_shape","args":{"slide_index":0,"text":"Headline","position":{"left":48,"top":48,"width":320,"height":72}}},{"action":"set_slide_background","args":{"slide_index":0,"fill":"#F7F1E8"}}]}}}`
`{"artifact_id":"presentation_x","actions":[{"action":"apply_patch","args":{"patch":{"version":1,"artifactId":"presentation_x","operations":[{"action":"add_text_shape","args":{"slide_index":0,"text":"Headline","position":{"left":48,"top":48,"width":320,"height":72}}},{"action":"set_slide_background","args":{"slide_index":0,"fill":"#F7F1E8"}}]}}}]}`
Patch payloads are single-artifact and currently support existing in-memory editing actions like slide/element/layout/theme/text updates. Lifecycle, import/export, and nested history actions are intentionally excluded.
Example undo/redo:
`{"artifact_id":"presentation_x","action":"undo","args":{}}`
`{"artifact_id":"presentation_x","actions":[{"action":"undo","args":{}}]}`
`{"artifact_id":"presentation_x","action":"redo","args":{}}`
`{"artifact_id":"presentation_x","actions":[{"action":"redo","args":{}}]}`
Deck summaries, slide listings, `inspect`, and `resolve` now include active-slide metadata. Use `set_active_slide` to change it explicitly.
@@ -123,10 +127,10 @@ Theme snapshots and `to_proto` both expose the deck theme hex color map via `hex
Named text styles are supported through `add_style`, `get_style`, and `describe_styles`. Built-in styles include `title`, `heading1`, `body`, `list`, and `numberedList`.
Example style creation:
`{"artifact_id":"presentation_x","action":"add_style","args":{"name":"callout","font_size":18,"color":"#336699","italic":true,"underline":true}}`
`{"artifact_id":"presentation_x","actions":[{"action":"add_style","args":{"name":"callout","font_size":18,"color":"#336699","italic":true,"underline":true}}]}`
Example style lookup:
`{"artifact_id":"presentation_x","action":"get_style","args":{"name":"title"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"get_style","args":{"name":"title"}}]}`
Text styling payloads on `add_text_shape`, `add_shape.text_style`, `update_text.styling`, and `update_table_cell.styling` accept `style` and `underline` in addition to the existing whole-element fields.
@@ -155,9 +159,9 @@ Shape strokes accept an optional `style` field such as `solid`, `dashed`, `dotte
Connectors are supported via `add_connector`, with straight/elbow/curved types plus dash styles and arrow heads.
Example preview:
`{"artifact_id":"presentation_x","action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.png"}}`
`{"artifact_id":"presentation_x","actions":[{"action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.png"}}]}`
`export_preview` also accepts `format`, `scale`, and `quality` for rendered previews. `format` currently supports `png`, `jpeg`, and `svg`.
Example JPEG preview:
`{"artifact_id":"presentation_x","action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.jpg","format":"jpeg","scale":0.75,"quality":85}}`
`{"artifact_id":"presentation_x","actions":[{"action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.jpg","format":"jpeg","scale":0.75,"quality":85}}]}`