diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs index 84dce39d0d3..f5f987fe41b 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/fake-codex-app-server.rs @@ -84,6 +84,19 @@ fn run() -> Result<(), Box> { }) { return Err("authorized tool was not registered".into()); } + if request + .pointer("/params/permissions") + .is_some_and(Value::is_string) + { + send(json!({ + "id": id, + "error": { + "code": -32600, + "message": "Invalid request: invalid type: string, expected internally tagged enum PermissionProfileSelectionParams" + } + }))?; + continue; + } let config = request.pointer("/params/config").and_then(Value::as_object); planning_thread = request .pointer("/params/permissions/id") diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index 5fc409ece45..7ddf192c73d 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -600,6 +600,7 @@ pub struct CodexProvider { pending_runtime_requests: BTreeMap, collaboration_mode: String, collaboration_mode_payload: Option, + structured_permission_profile: bool, trace: Option, last_trace_frame_id: Option, } @@ -612,6 +613,19 @@ struct PendingCodexRuntimeRequest { question_set: Value, } +fn permission_profile_params(profile_id: &str, structured: bool) -> Value { + if structured { + json!({"type": "profile", "id": profile_id}) + } else { + json!(profile_id) + } +} + +fn requires_structured_permission_profile(error: &LocalRunnerError) -> bool { + let detail = error.to_string(); + detail.contains("invalid type: string") && detail.contains("PermissionProfileSelectionParams") +} + fn latest_active_provider_turn_id(thread_read: &Value) -> Option { thread_read .pointer("/thread/turns") @@ -1180,6 +1194,7 @@ impl CodexProvider { pending_runtime_requests: BTreeMap::new(), collaboration_mode: config.collaboration_mode.clone(), collaboration_mode_payload: None, + structured_permission_profile: false, trace: ProviderTraceSink::from_environment(config.kind), last_trace_frame_id: None, }; @@ -1203,19 +1218,19 @@ impl CodexProvider { )); } let permission_profile = if config.collaboration_mode == "plan" { - json!({"type": "profile", "id": "paperclip-runner-workspace-read-only"}) + "paperclip-runner-workspace-read-only" } else { - json!({"type": "profile", "id": "paperclip-runner-workspace-only"}) + "paperclip-runner-workspace-only" }; - let opened = if let Some(thread_id) = resume_thread_id { - provider.request( + let (open_method, mut open_params) = if let Some(thread_id) = resume_thread_id { + ( "thread/resume", json!({ "threadId": thread_id, "cwd": config.cwd, "model": config.model, "approvalPolicy": config.approval_policy, - "permissions": permission_profile, + "permissions": permission_profile_params(permission_profile, false), "runtimeWorkspaceRoots": [config.cwd], "config": isolated_thread_config( config.include_collaboration_mode_instructions, @@ -1226,15 +1241,15 @@ impl CodexProvider { "experimentalRawEvents": true, "persistExtendedHistory": true, }), - )? + ) } else { - provider.request( + ( "thread/start", json!({ "cwd": config.cwd, "model": config.model, "approvalPolicy": config.approval_policy, - "permissions": permission_profile, + "permissions": permission_profile_params(permission_profile, false), "runtimeWorkspaceRoots": [config.cwd], "config": isolated_thread_config( config.include_collaboration_mode_instructions, @@ -1245,7 +1260,16 @@ impl CodexProvider { "experimentalRawEvents": true, "persistExtendedHistory": true, }), - )? + ) + }; + let opened = match provider.request(open_method, open_params.clone()) { + Ok(opened) => opened, + Err(error) if requires_structured_permission_profile(&error) => { + open_params["permissions"] = permission_profile_params(permission_profile, true); + provider.structured_permission_profile = true; + provider.request(open_method, open_params)? + } + Err(error) => return Err(error), }; if config.collaboration_mode == "plan" { let presets = provider @@ -1318,14 +1342,14 @@ impl CodexProvider { pub fn start_turn(&mut self, message: &str, cwd: &str) -> Result { let thread_id = self.thread_id.clone(); let permission_profile = if self.collaboration_mode == "plan" { - json!({"type": "profile", "id": "paperclip-runner-workspace-read-only"}) + "paperclip-runner-workspace-read-only" } else { - json!({"type": "profile", "id": "paperclip-runner-workspace-only"}) + "paperclip-runner-workspace-only" }; let mut params = json!({ "threadId": thread_id, "cwd": cwd, - "permissions": permission_profile, + "permissions": permission_profile_params(permission_profile, self.structured_permission_profile), "runtimeWorkspaceRoots": [cwd], "input": [{"type": "text", "text": message, "text_elements": []}], });