diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 3c6303b14f..7a554c2bcf 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1843,7 +1843,6 @@ dependencies = [ "codex-app-server-protocol", "codex-apply-patch", "codex-arg0", - "codex-artifacts", "codex-async-utils", "codex-code-mode", "codex-config", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index dd7e708c82..556976fc1a 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -396,6 +396,7 @@ unwrap_used = "deny" ignored = [ "icu_provider", "openssl-sys", + "codex-artifacts", "codex-utils-readiness", "codex-utils-template", "codex-v8-poc", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 09a16db5c1..9497da9ebd 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -45,7 +45,6 @@ codex-hooks = { workspace = true } codex-instructions = { workspace = true } codex-network-proxy = { workspace = true } codex-otel = { workspace = true } -codex-artifacts = { workspace = true } codex-plugin = { workspace = true } codex-protocol = { workspace = true } codex-rollout = { workspace = true } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 1d1d6dd9a4..300716dc1f 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -338,9 +338,6 @@ "apps": { "type": "boolean" }, - "artifact": { - "type": "boolean" - }, "child_agents_md": { "type": "boolean" }, @@ -1980,9 +1977,6 @@ "apps": { "type": "boolean" }, - "artifact": { - "type": "boolean" - }, "child_agents_md": { "type": "boolean" }, diff --git a/codex-rs/core/src/config/schema.rs b/codex-rs/core/src/config/schema.rs index 102b7da514..53e0f7f028 100644 --- a/codex-rs/core/src/config/schema.rs +++ b/codex-rs/core/src/config/schema.rs @@ -22,6 +22,9 @@ pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema { let mut validation = ObjectValidation::default(); for feature in FEATURES { + if feature.id == codex_features::Feature::Artifact { + continue; + } validation .properties .insert(feature.key.to_string(), schema_gen.subschema_for::()); diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 446a1a27e5..1ed3250f3d 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -53,7 +53,6 @@ pub mod models_manager; mod network_policy_decision; pub mod network_proxy_loader; mod original_image_detail; -mod packages; pub use mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY; pub use mcp_connection_manager::MCP_SANDBOX_STATE_METHOD; pub use mcp_connection_manager::SandboxState; diff --git a/codex-rs/core/src/packages/mod.rs b/codex-rs/core/src/packages/mod.rs deleted file mode 100644 index f324a25efa..0000000000 --- a/codex-rs/core/src/packages/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod versions; diff --git a/codex-rs/core/src/packages/versions.rs b/codex-rs/core/src/packages/versions.rs deleted file mode 100644 index 5dfa8e8d15..0000000000 --- a/codex-rs/core/src/packages/versions.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// Pinned versions for package-manager-backed installs. -pub(crate) const ARTIFACT_RUNTIME: &str = "2.5.6"; diff --git a/codex-rs/core/src/tools/handlers/artifacts.rs b/codex-rs/core/src/tools/handlers/artifacts.rs deleted file mode 100644 index 910dcbb470..0000000000 --- a/codex-rs/core/src/tools/handlers/artifacts.rs +++ /dev/null @@ -1,295 +0,0 @@ -use async_trait::async_trait; -use codex_artifacts::ArtifactBuildRequest; -use codex_artifacts::ArtifactCommandOutput; -use codex_artifacts::ArtifactRuntimeManager; -use codex_artifacts::ArtifactRuntimeManagerConfig; -use codex_artifacts::ArtifactsClient; -use codex_artifacts::ArtifactsError; -use serde_json::Value as JsonValue; -use std::time::Duration; -use std::time::Instant; - -use crate::codex::Session; -use crate::codex::TurnContext; -use crate::exec::ExecToolCallOutput; -use crate::exec::StreamOutput; -use crate::function_tool::FunctionCallError; -use crate::packages::versions; -use crate::protocol::ExecCommandSource; -use crate::tools::context::FunctionToolOutput; -use crate::tools::context::ToolInvocation; -use crate::tools::context::ToolPayload; -use crate::tools::events::ToolEmitter; -use crate::tools::events::ToolEventCtx; -use crate::tools::events::ToolEventFailure; -use crate::tools::events::ToolEventStage; -use crate::tools::registry::ToolHandler; -use crate::tools::registry::ToolKind; -use codex_features::Feature; - -const ARTIFACTS_TOOL_NAME: &str = "artifacts"; -const ARTIFACT_TOOL_PRAGMA_PREFIX: &str = "// codex-artifact-tool:"; -const DEFAULT_EXECUTION_TIMEOUT: Duration = Duration::from_secs(30); - -pub struct ArtifactsHandler; - -#[derive(Debug, Clone, PartialEq, Eq)] -struct ArtifactsToolArgs { - source: String, - timeout_ms: Option, -} - -#[async_trait] -impl ToolHandler for ArtifactsHandler { - type Output = FunctionToolOutput; - - fn kind(&self) -> ToolKind { - ToolKind::Function - } - - fn matches_kind(&self, payload: &ToolPayload) -> bool { - matches!(payload, ToolPayload::Custom { .. }) - } - - async fn is_mutating(&self, _invocation: &ToolInvocation) -> bool { - true - } - - async fn handle(&self, invocation: ToolInvocation) -> Result { - let ToolInvocation { - session, - turn, - payload, - call_id, - .. - } = invocation; - - if !session.enabled(Feature::Artifact) { - return Err(FunctionCallError::RespondToModel( - "artifacts is disabled by feature flag".to_string(), - )); - } - - let args = match payload { - ToolPayload::Custom { input } => parse_freeform_args(&input)?, - _ => { - return Err(FunctionCallError::RespondToModel( - "artifacts expects freeform JavaScript input authored against the preloaded @oai/artifact-tool exports".to_string(), - )); - } - }; - - let client = ArtifactsClient::from_runtime_manager(default_runtime_manager( - turn.config.codex_home.clone(), - )); - - let started_at = Instant::now(); - emit_exec_begin(session.as_ref(), turn.as_ref(), &call_id).await; - - let result = client - .execute_build(ArtifactBuildRequest { - source: args.source, - cwd: turn.cwd.to_path_buf(), - timeout: Some(Duration::from_millis( - args.timeout_ms - .unwrap_or(DEFAULT_EXECUTION_TIMEOUT.as_millis() as u64), - )), - env: Default::default(), - }) - .await; - - let (success, output) = match result { - Ok(output) => (output.success(), output), - Err(error) => (false, error_output(&error)), - }; - - emit_exec_end( - session.as_ref(), - turn.as_ref(), - &call_id, - &output, - started_at.elapsed(), - success, - ) - .await; - - Ok(FunctionToolOutput::from_text( - format_artifact_output(&output), - Some(success), - )) - } -} - -fn parse_freeform_args(input: &str) -> Result { - if input.trim().is_empty() { - return Err(FunctionCallError::RespondToModel( - "artifacts expects raw JavaScript source text (non-empty) authored against the preloaded @oai/artifact-tool exports. Provide JS only, optionally with first-line `// codex-artifact-tool: timeout_ms=15000`." - .to_string(), - )); - } - - let mut args = ArtifactsToolArgs { - source: input.to_string(), - timeout_ms: None, - }; - - let mut lines = input.splitn(2, '\n'); - let first_line = lines.next().unwrap_or_default(); - let rest = lines.next().unwrap_or_default(); - let trimmed = first_line.trim_start(); - let Some(pragma) = parse_pragma_prefix(trimmed) else { - reject_json_or_quoted_source(&args.source)?; - return Ok(args); - }; - - let mut timeout_ms = None; - let directive = pragma.trim(); - if !directive.is_empty() { - for token in directive.split_whitespace() { - let (key, value) = token.split_once('=').ok_or_else(|| { - FunctionCallError::RespondToModel(format!( - "artifacts pragma expects space-separated key=value pairs (supported keys: timeout_ms); got `{token}`" - )) - })?; - match key { - "timeout_ms" => { - if timeout_ms.is_some() { - return Err(FunctionCallError::RespondToModel( - "artifacts pragma specifies timeout_ms more than once".to_string(), - )); - } - let parsed = value.parse::().map_err(|_| { - FunctionCallError::RespondToModel(format!( - "artifacts pragma timeout_ms must be an integer; got `{value}`" - )) - })?; - timeout_ms = Some(parsed); - } - _ => { - return Err(FunctionCallError::RespondToModel(format!( - "artifacts pragma only supports timeout_ms; got `{key}`" - ))); - } - } - } - } - - if rest.trim().is_empty() { - return Err(FunctionCallError::RespondToModel( - "artifacts pragma must be followed by JavaScript source on subsequent lines" - .to_string(), - )); - } - - reject_json_or_quoted_source(rest)?; - args.source = rest.to_string(); - args.timeout_ms = timeout_ms; - Ok(args) -} - -fn reject_json_or_quoted_source(code: &str) -> Result<(), FunctionCallError> { - let trimmed = code.trim(); - if trimmed.starts_with("```") { - return Err(FunctionCallError::RespondToModel( - "artifacts expects raw JavaScript source, not markdown code fences. Resend plain JS only (optional first line `// codex-artifact-tool: ...`)." - .to_string(), - )); - } - let Ok(value) = serde_json::from_str::(trimmed) else { - return Ok(()); - }; - match value { - JsonValue::Object(_) | JsonValue::String(_) => Err(FunctionCallError::RespondToModel( - "artifacts is a freeform tool and expects raw JavaScript source authored against the preloaded @oai/artifact-tool exports. Resend plain JS only (optional first line `// codex-artifact-tool: ...`); do not send JSON (`{\"code\":...}`), quoted code, or markdown fences." - .to_string(), - )), - _ => Ok(()), - } -} - -fn parse_pragma_prefix(line: &str) -> Option<&str> { - line.strip_prefix(ARTIFACT_TOOL_PRAGMA_PREFIX) -} - -fn default_runtime_manager(codex_home: std::path::PathBuf) -> ArtifactRuntimeManager { - ArtifactRuntimeManager::new(ArtifactRuntimeManagerConfig::with_default_release( - codex_home, - versions::ARTIFACT_RUNTIME, - )) -} - -async fn emit_exec_begin(session: &Session, turn: &TurnContext, call_id: &str) { - let emitter = ToolEmitter::shell( - vec![ARTIFACTS_TOOL_NAME.to_string()], - turn.cwd.to_path_buf(), - ExecCommandSource::Agent, - /*freeform*/ true, - ); - let ctx = ToolEventCtx::new(session, turn, call_id, /*turn_diff_tracker*/ None); - emitter.emit(ctx, ToolEventStage::Begin).await; -} - -async fn emit_exec_end( - session: &Session, - turn: &TurnContext, - call_id: &str, - output: &ArtifactCommandOutput, - duration: Duration, - success: bool, -) { - let exec_output = ExecToolCallOutput { - exit_code: output.exit_code.unwrap_or(1), - stdout: StreamOutput::new(output.stdout.clone()), - stderr: StreamOutput::new(output.stderr.clone()), - aggregated_output: StreamOutput::new(format_artifact_output(output)), - duration, - timed_out: false, - }; - let emitter = ToolEmitter::shell( - vec![ARTIFACTS_TOOL_NAME.to_string()], - turn.cwd.to_path_buf(), - ExecCommandSource::Agent, - /*freeform*/ true, - ); - let ctx = ToolEventCtx::new(session, turn, call_id, /*turn_diff_tracker*/ None); - let stage = if success { - ToolEventStage::Success(exec_output) - } else { - ToolEventStage::Failure(ToolEventFailure::Output(exec_output)) - }; - emitter.emit(ctx, stage).await; -} - -fn format_artifact_output(output: &ArtifactCommandOutput) -> String { - let stdout = output.stdout.trim(); - let stderr = output.stderr.trim(); - let mut sections = vec![format!( - "exit_code: {}", - output - .exit_code - .map(|code| code.to_string()) - .unwrap_or_else(|| "null".to_string()) - )]; - if !stdout.is_empty() { - sections.push(format!("stdout:\n{stdout}")); - } - if !stderr.is_empty() { - sections.push(format!("stderr:\n{stderr}")); - } - if stdout.is_empty() && stderr.is_empty() && output.success() { - sections.push("artifact JS completed successfully.".to_string()); - } - sections.join("\n\n") -} - -fn error_output(error: &ArtifactsError) -> ArtifactCommandOutput { - ArtifactCommandOutput { - exit_code: Some(1), - stdout: String::new(), - stderr: error.to_string(), - } -} - -#[cfg(test)] -#[path = "artifacts_tests.rs"] -mod tests; diff --git a/codex-rs/core/src/tools/handlers/artifacts_tests.rs b/codex-rs/core/src/tools/handlers/artifacts_tests.rs deleted file mode 100644 index a55f12676a..0000000000 --- a/codex-rs/core/src/tools/handlers/artifacts_tests.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::*; -use crate::packages::versions; -use tempfile::TempDir; - -#[test] -fn parse_freeform_args_without_pragma() { - let args = parse_freeform_args("console.log('ok');").expect("parse args"); - assert_eq!(args.source, "console.log('ok');"); - assert_eq!(args.timeout_ms, None); -} - -#[test] -fn parse_freeform_args_with_artifact_tool_pragma() { - let args = parse_freeform_args("// codex-artifact-tool: timeout_ms=45000\nconsole.log('ok');") - .expect("parse args"); - assert_eq!(args.source, "console.log('ok');"); - assert_eq!(args.timeout_ms, Some(45_000)); -} - -#[test] -fn parse_freeform_args_rejects_json_wrapped_code() { - let err = parse_freeform_args("{\"code\":\"console.log('ok')\"}").expect_err("expected error"); - assert!( - err.to_string() - .contains("artifacts is a freeform tool and expects raw JavaScript source") - ); -} - -#[test] -fn default_runtime_manager_uses_openai_codex_release_base() { - let codex_home = TempDir::new().expect("create temp codex home"); - let manager = default_runtime_manager(codex_home.path().to_path_buf()); - - assert_eq!( - manager.config().release().base_url().as_str(), - "https://github.com/openai/codex/releases/download/" - ); - assert_eq!( - manager.config().release().runtime_version(), - versions::ARTIFACT_RUNTIME - ); -} - -#[test] -fn load_cached_runtime_reads_pinned_cache_path() { - let codex_home = TempDir::new().expect("create temp codex home"); - let platform = - codex_artifacts::ArtifactRuntimePlatform::detect_current().expect("detect platform"); - let install_dir = codex_home - .path() - .join("packages") - .join("artifacts") - .join(versions::ARTIFACT_RUNTIME) - .join(platform.as_str()); - std::fs::create_dir_all(&install_dir).expect("create install dir"); - std::fs::create_dir_all(install_dir.join("dist")).expect("create build entrypoint dir"); - std::fs::write( - install_dir.join("package.json"), - serde_json::json!({ - "name": "@oai/artifact-tool", - "version": versions::ARTIFACT_RUNTIME, - "type": "module", - "exports": { - ".": "./dist/artifact_tool.mjs" - } - }) - .to_string(), - ) - .expect("write package json"); - std::fs::write( - install_dir.join("dist/artifact_tool.mjs"), - "export const ok = true;\n", - ) - .expect("write build entrypoint"); - - let runtime = codex_artifacts::load_cached_runtime( - &codex_home - .path() - .join(codex_artifacts::DEFAULT_CACHE_ROOT_RELATIVE), - versions::ARTIFACT_RUNTIME, - ) - .expect("resolve runtime"); - assert_eq!(runtime.runtime_version(), versions::ARTIFACT_RUNTIME); - assert_eq!( - runtime.build_js_path(), - install_dir.join("dist/artifact_tool.mjs") - ); -} - -#[test] -fn format_artifact_output_includes_success_message_when_silent() { - let formatted = format_artifact_output(&ArtifactCommandOutput { - exit_code: Some(0), - stdout: String::new(), - stderr: String::new(), - }); - assert!(formatted.contains("artifact JS completed successfully.")); -} diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index ba0a0eb50d..3241b323b8 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -1,6 +1,5 @@ pub(crate) mod agent_jobs; pub mod apply_patch; -mod artifacts; mod dynamic; mod js_repl; mod list_dir; @@ -35,7 +34,6 @@ use crate::sandboxing::SandboxPermissions; pub(crate) use crate::tools::code_mode::CodeModeExecuteHandler; pub(crate) use crate::tools::code_mode::CodeModeWaitHandler; pub use apply_patch::ApplyPatchHandler; -pub use artifacts::ArtifactsHandler; use codex_protocol::models::PermissionProfile; use codex_protocol::protocol::AskForApproval; pub use dynamic::DynamicToolHandler; diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 3c10c3b844..7c8fb0d9b3 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -334,7 +334,6 @@ pub(crate) struct ToolsConfig { pub can_request_original_image_detail: bool, pub collab_tools: bool, pub multi_agent_v2: bool, - pub artifact_tools: bool, pub request_user_input: bool, pub default_mode_request_user_input: bool, pub experimental_supported_tools: Vec, @@ -394,8 +393,6 @@ impl ToolsConfig { && features.enabled(Feature::Apps) && features.enabled(Feature::Plugins); let include_original_image_detail = can_request_original_image_detail(features, model_info); - let include_artifact_tools = - features.enabled(Feature::Artifact) && codex_artifacts::can_manage_artifact_runtime(); let include_image_gen_tool = features.enabled(Feature::ImageGeneration) && supports_image_generation(model_info); let exec_permission_approvals_enabled = features.enabled(Feature::ExecPermissionApprovals); @@ -471,7 +468,6 @@ impl ToolsConfig { can_request_original_image_detail: include_original_image_detail, collab_tools: include_collab_tools, multi_agent_v2: include_multi_agent_v2, - artifact_tools: include_artifact_tools, request_user_input: include_request_user_input, default_mode_request_user_input: include_default_mode_request_user_input, experimental_supported_tools: model_info.experimental_supported_tools.clone(), @@ -2125,33 +2121,6 @@ JS_SOURCE: /(?:\s*)(?:[^\s{\"`]|`[^`]|``[^`])[\s\S]*/ }) } -fn create_artifacts_tool() -> ToolSpec { - const ARTIFACTS_FREEFORM_GRAMMAR: &str = r#" -start: pragma_source | plain_source - -pragma_source: PRAGMA_LINE NEWLINE js_source -plain_source: PLAIN_JS_SOURCE - -js_source: JS_SOURCE - -PRAGMA_LINE: /[ \t]*\/\/ codex-artifact-tool:[^\r\n]*/ -NEWLINE: /\r?\n/ -PLAIN_JS_SOURCE: /(?:\s*)(?:[^\s{\"`]|`[^`]|``[^`])[\s\S]*/ -JS_SOURCE: /(?:\s*)(?:[^\s{\"`]|`[^`]|``[^`])[\s\S]*/ -"#; - - ToolSpec::Freeform(FreeformTool { - name: "artifacts".to_string(), - description: "Runs raw JavaScript against the installed `@oai/artifact-tool` package for creating presentations or spreadsheets. This is plain JavaScript executed by a local Node-compatible runtime with top-level await, not TypeScript: do not use type annotations, `interface`, `type`, or `import type`. Author code the same way you would for `import { Presentation, Workbook, PresentationFile, SpreadsheetFile, FileBlob, ... } from \"@oai/artifact-tool\"`, but omit that import line because the package is preloaded before your code runs. Named exports are copied onto `globalThis`, and the full module namespace is available as `globalThis.artifactTool`. This matches the upstream library-first API: create with `Presentation.create()` / `Workbook.create()`, preview with `presentation.export(...)` or `slide.export(...)`, and save files with `PresentationFile.exportPptx(...)` or `SpreadsheetFile.exportXlsx(...)`. Node built-ins such as `node:fs/promises` may still be imported when needed for saving preview bytes. This is a freeform tool: send raw JavaScript source text, optionally with a first-line pragma like `// codex-artifact-tool: timeout_ms=15000`; do not send JSON/quotes/markdown fences." - .to_string(), - format: FreeformToolFormat { - r#type: "grammar".to_string(), - syntax: "lark".to_string(), - definition: ARTIFACTS_FREEFORM_GRAMMAR.to_string(), - }, - }) -} - fn create_js_repl_reset_tool() -> ToolSpec { ToolSpec::Function(ResponsesApiTool { name: "js_repl_reset".to_string(), @@ -2588,7 +2557,6 @@ pub(crate) fn build_specs_with_discoverable_tools( dynamic_tools: &[DynamicToolSpec], ) -> ToolRegistryBuilder { use crate::tools::handlers::ApplyPatchHandler; - use crate::tools::handlers::ArtifactsHandler; use crate::tools::handlers::CodeModeExecuteHandler; use crate::tools::handlers::CodeModeWaitHandler; use crate::tools::handlers::DynamicToolHandler; @@ -2639,7 +2607,6 @@ pub(crate) fn build_specs_with_discoverable_tools( let code_mode_wait_handler = Arc::new(CodeModeWaitHandler); let js_repl_handler = Arc::new(JsReplHandler); let js_repl_reset_handler = Arc::new(JsReplResetHandler); - let artifacts_handler = Arc::new(ArtifactsHandler); let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled; if config.code_mode_enabled { @@ -2956,16 +2923,6 @@ pub(crate) fn build_specs_with_discoverable_tools( ); builder.register_handler("view_image", view_image_handler); - if config.artifact_tools { - push_tool_spec( - &mut builder, - create_artifacts_tool(), - /*supports_parallel_tool_calls*/ false, - config.code_mode_enabled, - ); - builder.register_handler("artifacts", artifacts_handler); - } - if config.collab_tools { push_tool_spec( &mut builder, diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index 6089e8bf72..0cd8c2e1f8 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -775,28 +775,6 @@ fn view_image_tool_includes_detail_with_original_detail_feature() { assert!(description.contains("omit this field for default resized behavior")); } -#[test] -fn test_build_specs_artifact_tool_enabled() { - let mut config = test_config(); - let runtime_root = tempfile::TempDir::new().expect("create temp codex home"); - config.codex_home = runtime_root.path().to_path_buf(); - let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config); - let mut features = Features::with_defaults(); - features.enable(Feature::Artifact); - let available_models = Vec::new(); - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - available_models: &available_models, - features: &features, - web_search_mode: Some(WebSearchMode::Cached), - session_source: SessionSource::Cli, - sandbox_policy: &SandboxPolicy::DangerFullAccess, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - }); - let (tools, _) = build_specs(&tools_config, None, None, &[]).build(); - assert_contains_tool_names(&tools, &["artifacts"]); -} - #[test] fn test_build_specs_agent_job_worker_tools_enabled() { let config = test_config(); diff --git a/codex-rs/core/templates/tools/presentation_artifact.md b/codex-rs/core/templates/tools/presentation_artifact.md deleted file mode 100644 index 45a91bc43b..0000000000 --- a/codex-rs/core/templates/tools/presentation_artifact.md +++ /dev/null @@ -1,200 +0,0 @@ -Create and edit PowerPoint presentation artifacts inside the current thread. - -- This is a stateful built-in tool. `artifact_id` values are returned by earlier calls and persist only for 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` -- `import_pptx` -- `export_pptx` -- `export_preview` -- `get_summary` -- `list_slides` -- `list_layouts` -- `list_layout_placeholders` -- `list_slide_placeholders` -- `inspect` -- `resolve` -- `to_proto` -- `record_patch` -- `apply_patch` -- `undo` -- `redo` -- `create_layout` -- `add_layout_placeholder` -- `set_slide_layout` -- `update_placeholder_text` -- `set_theme` -- `add_style` -- `get_style` -- `describe_styles` -- `set_notes` -- `set_notes_rich_text` -- `append_notes` -- `clear_notes` -- `set_notes_visibility` -- `set_active_slide` -- `add_slide` -- `insert_slide` -- `duplicate_slide` -- `move_slide` -- `delete_slide` -- `set_slide_background` -- `add_text_shape` -- `add_shape` -- `add_connector` -- `add_image` -- `replace_image` -- `add_table` -- `update_table_style` -- `style_table_block` -- `update_table_cell` -- `merge_table_cells` -- `add_chart` -- `update_chart` -- `add_chart_series` -- `update_text` -- `set_rich_text` -- `format_text_range` -- `replace_text` -- `insert_text_after` -- `set_hyperlink` -- `set_comment_author` -- `add_comment_thread` -- `add_comment_reply` -- `toggle_comment_reaction` -- `resolve_comment_thread` -- `reopen_comment_thread` -- `update_shape_style` -- `bring_to_front` -- `send_to_back` -- `delete_element` -- `delete_artifact` - -Example create: -`{"actions":[{"action":"create","args":{"name":"Quarterly Update"}}]}` - -Example create with custom slide size: -`{"actions":[{"action":"create","args":{"name":"Quarterly Update","slide_size":{"width":960,"height":540}}}]}` - -Example edit: -`{"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. Tables also support `style_options`, `borders`, and `right_to_left`, with `update_table_style` and `style_table_block` available for incremental styling after creation. - -Example export: -`{"artifact_id":"presentation_x","actions":[{"action":"export_pptx","args":{"path":"artifacts/q2-update.pptx"}}]}` - -Example layout flow: -`{"artifact_id":"presentation_x","actions":[{"action":"create_layout","args":{"name":"Title Slide"}}]}` - -`{"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","actions":[{"action":"set_slide_layout","args":{"slide_index":0,"layout_id":"layout_1"}}]}` - -`{"artifact_id":"presentation_x","actions":[{"action":"list_layout_placeholders","args":{"layout_id":"layout_1"}}]}` - -`{"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","actions":[{"action":"inspect","args":{"include":"deck,slide,textbox,shape,table,chart,image,notes,layoutList,textRange,comment","exclude":"notes","search":"roadmap","max_chars":12000}}]}` - -Example inspect target window: -`{"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","actions":[{"action":"resolve","args":{"id":"sh/element_3"}}]}` - -Example proto export: -`{"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. - -Rich text is supported on notes, text boxes, shapes with text, and table cells. Use `set_rich_text` to replace a full rich-text payload, `set_notes_rich_text` for speaker notes, and `format_text_range` to annotate a substring by `query` or explicit codepoint range. `inspect`, `resolve`, and `to_proto` surface text-range anchors as `tr/`. - -Comment threads are supported through `set_comment_author`, `add_comment_thread`, `add_comment_reply`, `toggle_comment_reaction`, `resolve_comment_thread`, and `reopen_comment_thread`. Thread anchors resolve as `th/`, and comment records appear in both `inspect` and `to_proto`. - -Charts support richer series metadata plus `update_chart` and `add_chart_series`, including legend, axis, data-label, marker, fill, and per-point override state. - -Exported PPTX files embed Codex metadata so rich text, comment threads, and advanced table/chart state round-trip through `export_pptx` and `import_pptx` even when the base OOXML representation is lossy. - -Example patch recording: -`{"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","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","actions":[{"action":"undo","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. - -Theme snapshots and `to_proto` both expose the deck theme hex color map via `hex_color_map` / `hexColorMap`. - -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","actions":[{"action":"add_style","args":{"name":"callout","font_size":18,"color":"#336699","italic":true,"underline":true}}]}` - -Example style lookup: -`{"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. - -Text-bearing elements also support literal `replace_text` and `insert_text_after` helpers for in-place edits without resending the full string. - -Example rich text update: -`{"artifact_id":"presentation_x","actions":[{"action":"set_rich_text","args":{"element_id":"element_3","text":[[{"run":"Quarterly ","text_style":{"bold":true}},"update pipeline"]],"text_layout":{"wrap":"square","auto_fit":"shrinkText","vertical_alignment":"middle","insets":{"left":6,"right":6,"top":4,"bottom":4}}}}]}` - -Example substring formatting: -`{"artifact_id":"presentation_x","actions":[{"action":"format_text_range","args":{"element_id":"element_3","query":"update","styling":{"italic":true},"link":{"uri":"https://example.com/update","is_external":true}}}]}` - -Text boxes and shapes support whole-element hyperlinks via `set_hyperlink`. Supported `link_type` values are `url`, `slide`, `first_slide`, `last_slide`, `next_slide`, `previous_slide`, `end_show`, `email`, and `file`. Use `clear: true` to remove an existing hyperlink. - -Notes visibility is honored on export: `set_notes_visibility` controls whether speaker notes are emitted into exported PPTX output. - -Example comment thread: -`{"artifact_id":"presentation_x","actions":[{"action":"set_comment_author","args":{"display_name":"Jamie Fox","initials":"JF"}},{"action":"add_comment_thread","args":{"slide_index":0,"element_id":"element_3","query":"Quarterly","text":"Tighten this headline"}},{"action":"add_comment_reply","args":{"thread_id":"thread_1","text":"Applied to the draft."}}]}` - -Image placeholders can be prompt-only. `add_image` accepts `prompt` without `path`/`data_url`, and unresolved placeholders export as a visible placeholder box instead of failing. - -Layout placeholders with `placeholder_type: "picture"` or `"image"` materialize as placeholder image elements on slides, so they appear in `list_slide_placeholders`, `inspect`, and `resolve` as `image` records rather than generic shapes. - -Remote images are supported. `add_image` and `replace_image` accept `uri` in addition to local `path`, raw base64 `blob`, and `data_url`. - -Image edits can target inspect/resolve anchors like `im/element_3`, and `update_shape_style` now accepts image `fit`, `crop`, `rotation`, `flip_horizontal`, `flip_vertical`, and `lock_aspect_ratio` updates. - -`add_image` and `replace_image` also accept optional `rotation`, `flip_horizontal`, and `flip_vertical` fields for image transforms. - -`update_shape_style.position` accepts partial updates, so you can move or resize an element without resending the full rect. - -Shape strokes accept an optional `style` field such as `solid`, `dashed`, `dotted`, `dash-dot`, `dash-dot-dot`, `long-dash`, or `long-dash-dot`. This applies to ordinary shapes via `add_shape.stroke` and `update_shape_style.stroke`. - -`add_shape` also accepts optional `rotation`, `flip_horizontal`, and `flip_vertical` fields. Those same transform fields can also be provided inside the `position` object for `add_shape`, `add_image`, and `update_shape_style`. - -Connectors are supported via `add_connector`, with straight/elbow/curved types plus dash styles and arrow heads. - -Example chart update: -`{"artifact_id":"presentation_x","actions":[{"action":"update_chart","args":{"element_id":"element_7","style_index":12,"legend_position":"bottom","y_axis_title":"USD (millions)"}},{"action":"add_chart_series","args":{"element_id":"element_7","name":"Target","values":[11,13],"fill":"#70AD47","marker":{"symbol":"diamond","size":6}}}]}` - -Example preview: -`{"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","actions":[{"action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.jpg","format":"jpeg","scale":0.75,"quality":85}}]}`