diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 6a47ec78cae..cce8be90180 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -299,6 +299,7 @@ fn sample_turn_start_response(turn_id: &str) -> ClientResponsePayload { started_at: None, completed_at: None, duration_ms: None, + attribution: None, }, }) } @@ -315,6 +316,7 @@ fn sample_turn_started_notification(thread_id: &str, turn_id: &str) -> ServerNot started_at: Some(455), completed_at: None, duration_ms: None, + attribution: None, }, }) } @@ -354,6 +356,7 @@ fn sample_turn_completed_notification( started_at: None, completed_at: Some(456), duration_ms: Some(1234), + attribution: None, }, }) } diff --git a/codex-rs/analytics/src/client.rs b/codex-rs/analytics/src/client.rs index fbcfa32dc5e..5f90ca362fc 100644 --- a/codex-rs/analytics/src/client.rs +++ b/codex-rs/analytics/src/client.rs @@ -29,11 +29,18 @@ use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::ServerResponse; +use codex_app_server_protocol::ThreadItem; +use codex_app_server_protocol::TurnAppAttribution; +use codex_app_server_protocol::TurnAttribution; +use codex_app_server_protocol::TurnPluginAttribution; +use codex_app_server_protocol::TurnSkillAttribution; +use codex_app_server_protocol::TurnToolAttribution; use codex_login::AuthManager; use codex_login::CodexAuth; use codex_login::default_client::create_client; use codex_plugin::PluginTelemetryMetadata; use codex_protocol::request_permissions::RequestPermissionsResponse; +use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use std::sync::Mutex; @@ -54,6 +61,7 @@ pub(crate) struct AnalyticsEventsQueue { #[derive(Clone)] pub struct AnalyticsEventsClient { queue: Option, + turn_attributions: Arc>>, } impl AnalyticsEventsQueue { @@ -124,11 +132,15 @@ impl AnalyticsEventsClient { Self { queue: (analytics_enabled != Some(false)) .then(|| AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url)), + turn_attributions: Arc::new(Mutex::new(HashMap::new())), } } pub fn disabled() -> Self { - Self { queue: None } + Self { + queue: None, + turn_attributions: Arc::new(Mutex::new(HashMap::new())), + } } pub fn track_skill_invocations( @@ -139,6 +151,7 @@ impl AnalyticsEventsClient { if invocations.is_empty() { return; } + self.record_skill_invocations(&tracking, &invocations); self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::SkillInvoked( SkillInvokedInput { tracking, @@ -210,11 +223,13 @@ impl AnalyticsEventsClient { pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) { let Some(queue) = self.queue.as_ref() else { + self.record_app_used(&tracking, &app); return; }; if !queue.should_enqueue_app_used(&tracking, &app) { return; } + self.record_app_used(&tracking, &app); self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed( AppUsedInput { tracking, app }, ))); @@ -228,16 +243,27 @@ impl AnalyticsEventsClient { pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) { let Some(queue) = self.queue.as_ref() else { + self.record_plugin_used(&tracking, &plugin); return; }; if !queue.should_enqueue_plugin_used(&tracking, &plugin) { return; } + self.record_plugin_used(&tracking, &plugin); self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::PluginUsed( crate::facts::PluginUsedInput { tracking, plugin }, ))); } + pub fn take_turn_attribution(&self, turn_id: &str) -> Option { + let attribution = self + .turn_attributions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .remove(turn_id)?; + (!attribution.is_empty()).then_some(attribution) + } + pub fn track_compaction(&self, event: crate::facts::CodexCompactionEvent) { self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::Compaction( Box::new(event), @@ -371,6 +397,7 @@ impl AnalyticsEventsClient { } pub fn track_notification(&self, notification: ServerNotification) { + self.record_tool_from_notification(¬ification); if !matches!( notification, ServerNotification::TurnStarted(_) @@ -385,6 +412,166 @@ impl AnalyticsEventsClient { } self.record_fact(AnalyticsFact::Notification(Box::new(notification))); } + + fn update_turn_attribution(&self, turn_id: &str, update: impl FnOnce(&mut TurnAttribution)) { + let mut attributions = self + .turn_attributions + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + update(attributions.entry(turn_id.to_string()).or_default()); + } + + fn record_skill_invocations( + &self, + tracking: &TrackEventsContext, + invocations: &[SkillInvocation], + ) { + self.update_turn_attribution(&tracking.turn_id, |attribution| { + for invocation in invocations { + let skill = TurnSkillAttribution { + skill_id: crate::reducer::skill_id_for_local_skill( + None, + None, + invocation.skill_path.as_path(), + invocation.skill_name.as_str(), + ), + skill_name: invocation.skill_name.clone(), + skill_scope: Some(skill_scope_name(invocation.skill_scope).to_string()), + plugin_id: invocation.plugin_id.clone(), + invoke_type: Some(invocation_type_name(invocation.invocation_type).to_string()), + }; + push_unique(&mut attribution.skills, skill); + } + }); + } + + fn record_app_used(&self, tracking: &TrackEventsContext, app: &AppInvocation) { + self.update_turn_attribution(&tracking.turn_id, |attribution| { + let app = TurnAppAttribution { + connector_id: app.connector_id.clone(), + app_name: app.app_name.clone(), + invoke_type: app + .invocation_type + .map(invocation_type_name) + .map(str::to_string), + }; + push_unique(&mut attribution.apps, app); + }); + } + + fn record_plugin_used(&self, tracking: &TrackEventsContext, plugin: &PluginTelemetryMetadata) { + self.update_turn_attribution(&tracking.turn_id, |attribution| { + let plugin = TurnPluginAttribution { + plugin_id: plugin + .remote_plugin_id + .clone() + .unwrap_or_else(|| plugin.plugin_id.as_key()), + plugin_name: plugin.plugin_id.plugin_name.clone(), + marketplace_name: plugin.plugin_id.marketplace_name.clone(), + display_name: plugin + .capability_summary + .as_ref() + .map(|summary| summary.display_name.clone()), + }; + push_unique(&mut attribution.plugins, plugin); + }); + } + + fn record_tool_from_notification(&self, notification: &ServerNotification) { + let (turn_id, item) = match notification { + ServerNotification::ItemStarted(notification) => { + (¬ification.turn_id, ¬ification.item) + } + _ => return, + }; + let Some(tool) = tool_attribution_from_item(item) else { + return; + }; + self.update_turn_attribution(turn_id, |attribution| { + push_unique(&mut attribution.tools, tool); + }); + } +} + +fn push_unique(items: &mut Vec, item: T) { + if !items.contains(&item) { + items.push(item); + } +} + +fn invocation_type_name(invocation_type: crate::facts::InvocationType) -> &'static str { + match invocation_type { + crate::facts::InvocationType::Explicit => "explicit", + crate::facts::InvocationType::Implicit => "implicit", + } +} + +fn skill_scope_name(skill_scope: codex_protocol::protocol::SkillScope) -> &'static str { + match skill_scope { + codex_protocol::protocol::SkillScope::User => "user", + codex_protocol::protocol::SkillScope::Repo => "repo", + codex_protocol::protocol::SkillScope::System => "system", + codex_protocol::protocol::SkillScope::Admin => "admin", + } +} + +fn tool_attribution_from_item(item: &ThreadItem) -> Option { + match item { + ThreadItem::CommandExecution { id, .. } => Some(TurnToolAttribution { + id: id.clone(), + kind: "command_execution".to_string(), + name: Some("shell".to_string()), + server: None, + plugin_id: None, + }), + ThreadItem::McpToolCall { + id, + server, + tool, + plugin_id, + .. + } => Some(TurnToolAttribution { + id: id.clone(), + kind: "mcp".to_string(), + name: Some(tool.clone()), + server: Some(server.clone()), + plugin_id: plugin_id.clone(), + }), + ThreadItem::DynamicToolCall { + id, + namespace, + tool, + .. + } => Some(TurnToolAttribution { + id: id.clone(), + kind: "dynamic".to_string(), + name: Some(tool.clone()), + server: namespace.clone(), + plugin_id: None, + }), + ThreadItem::CollabAgentToolCall { id, tool, .. } => Some(TurnToolAttribution { + id: id.clone(), + kind: "collab_agent".to_string(), + name: Some(format!("{tool:?}")), + server: None, + plugin_id: None, + }), + ThreadItem::WebSearch { id, .. } => Some(TurnToolAttribution { + id: id.clone(), + kind: "web_search".to_string(), + name: Some("web_search".to_string()), + server: None, + plugin_id: None, + }), + ThreadItem::ImageGeneration { id, .. } => Some(TurnToolAttribution { + id: id.clone(), + kind: "image_generation".to_string(), + name: Some("image_generation".to_string()), + server: None, + plugin_id: None, + }), + _ => None, + } } async fn send_track_events( diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index 4a375b06395..977a23783c3 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -7,18 +7,24 @@ use crate::events::SkillInvocationEventParams; use crate::events::SkillInvocationEventRequest; use crate::events::TrackEventRequest; use crate::facts::AnalyticsFact; +use crate::facts::AppInvocation; use crate::facts::InvocationType; +use crate::facts::SkillInvocation; +use crate::facts::TrackEventsContext; use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer; use codex_app_server_protocol::AskForApproval as AppServerAskForApproval; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::ClientResponsePayload; +use codex_app_server_protocol::ItemStartedNotification; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy; +use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::SessionSource as AppServerSessionSource; use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; use codex_app_server_protocol::ThreadForkResponse; +use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadResumeResponse; use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::ThreadStatus as AppServerThreadStatus; @@ -28,8 +34,12 @@ use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStatus as AppServerTurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; +use codex_plugin::PluginId; +use codex_plugin::PluginTelemetryMetadata; +use codex_protocol::protocol::SkillScope; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_absolute_path::test_support::test_path_buf; +use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use std::sync::Mutex; @@ -81,7 +91,13 @@ fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver ClientRequest { @@ -206,6 +222,7 @@ fn sample_turn_start_response() -> ClientResponsePayload { started_at: None, completed_at: None, duration_ms: None, + attribution: None, }, }) } @@ -266,6 +283,67 @@ fn track_response_only_enqueues_analytics_relevant_responses() { assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty))); } +#[test] +fn records_turn_attribution_for_skill_plugin_app_and_tool() { + let client = AnalyticsEventsClient::disabled(); + let tracking = TrackEventsContext { + model_slug: "gpt-5".to_string(), + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + }; + + client.track_skill_invocations( + tracking.clone(), + vec![SkillInvocation { + skill_name: "build-helper".to_string(), + skill_scope: SkillScope::User, + skill_path: "/repo/.codex/skills/build-helper/SKILL.md".into(), + plugin_id: Some("plugin@example".to_string()), + invocation_type: InvocationType::Explicit, + }], + ); + client.track_plugin_used( + tracking.clone(), + PluginTelemetryMetadata { + plugin_id: PluginId::new("plugin".to_string(), "example".to_string()).unwrap(), + remote_plugin_id: None, + capability_summary: None, + }, + ); + client.track_app_used( + tracking.clone(), + AppInvocation { + connector_id: Some("calendar".to_string()), + app_name: Some("Calendar".to_string()), + invocation_type: Some(InvocationType::Implicit), + }, + ); + client.track_notification(ServerNotification::ItemStarted(ItemStartedNotification { + item: ThreadItem::WebSearch { + id: "tool-1".to_string(), + query: "docs".to_string(), + action: None, + }, + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + started_at_ms: 123, + })); + + let attribution = client.take_turn_attribution("turn-1").unwrap(); + assert_eq!(attribution.skills.len(), 1); + assert_eq!(attribution.skills[0].skill_name, "build-helper"); + assert_eq!(attribution.plugins.len(), 1); + assert_eq!(attribution.plugins[0].plugin_id, "plugin@example"); + assert_eq!(attribution.apps.len(), 1); + assert_eq!( + attribution.apps[0].connector_id.as_deref(), + Some("calendar") + ); + assert_eq!(attribution.tools.len(), 1); + assert_eq!(attribution.tools[0].kind, "web_search"); + assert!(client.take_turn_attribution("turn-1").is_none()); +} + #[test] fn track_event_request_batches_only_isolates_accepted_line_fingerprint_events() { let batches = track_event_request_batches(vec![ diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index b0501b0e9ef..d248b84ac2b 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -4719,6 +4719,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -4784,6 +4795,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnCompletedNotification": { "properties": { "threadId": { @@ -4923,6 +4992,64 @@ ], "type": "object" }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStartedNotification": { "properties": { "threadId": { @@ -4947,6 +5074,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 92549d74c8e..12ab12f8bc4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -18192,6 +18192,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -18257,6 +18268,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/v2/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/v2/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/v2/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/v2/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -18439,6 +18508,64 @@ "title": "TurnPlanUpdatedNotification", "type": "object" }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStartParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -18638,6 +18765,39 @@ "title": "TurnSteerResponse", "type": "object" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "TurnsPage": { "properties": { "backwardsCursor": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d266a3e9773..bd1019cd131 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -16016,6 +16016,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -16081,6 +16092,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -16263,6 +16332,64 @@ "title": "TurnPlanUpdatedNotification", "type": "object" }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStartParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -16462,6 +16589,39 @@ "title": "TurnSteerResponse", "type": "object" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "TurnsPage": { "properties": { "backwardsCursor": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index a644ce8c4e6..115806af065 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -1314,6 +1314,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1379,6 +1390,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1432,6 +1501,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1441,6 +1568,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index ec46130357c..f05f5e5a992 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1874,6 +1874,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1939,6 +1950,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1992,6 +2061,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -2001,6 +2128,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 5ace3f7af76..781483a20db 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index a4cc7d91ff7..5df3afb1cbd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index 234e0867f88..4e59ebf852d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 59ed236c480..97b8345eccc 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1874,6 +1874,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1939,6 +1950,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1992,6 +2061,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -2001,6 +2128,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "TurnsPage": { "properties": { "backwardsCursor": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 7815f261c0a..e191d2f97e4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index ca52f10ca53..13447825bdb 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1874,6 +1874,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1939,6 +1950,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1992,6 +2061,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -2001,6 +2128,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index f6cb72b6b20..4f00aba7b02 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index e4e317e608b..b6757d33eda 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -1689,6 +1689,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1754,6 +1765,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1807,6 +1876,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1816,6 +1943,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 6c64eb32753..1b4ccc31ad9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -1314,6 +1314,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1379,6 +1390,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1432,6 +1501,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1441,6 +1568,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index 5fe97545762..2af86c61f4c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -1314,6 +1314,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1379,6 +1390,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1432,6 +1501,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1441,6 +1568,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 1db14972182..cda34d4747f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -1314,6 +1314,17 @@ }, "Turn": { "properties": { + "attribution": { + "anyOf": [ + { + "$ref": "#/definitions/TurnAttribution" + }, + { + "type": "null" + } + ], + "description": "Skills, plugins, apps, and tools observed during this turn." + }, "completedAt": { "description": "Unix timestamp (in seconds) when the turn completed.", "format": "int64", @@ -1379,6 +1390,64 @@ ], "type": "object" }, + "TurnAppAttribution": { + "properties": { + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "invokeType": { + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "TurnAttribution": { + "properties": { + "apps": { + "items": { + "$ref": "#/definitions/TurnAppAttribution" + }, + "type": "array" + }, + "plugins": { + "items": { + "$ref": "#/definitions/TurnPluginAttribution" + }, + "type": "array" + }, + "skills": { + "items": { + "$ref": "#/definitions/TurnSkillAttribution" + }, + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/definitions/TurnToolAttribution" + }, + "type": "array" + } + }, + "required": [ + "apps", + "plugins", + "skills", + "tools" + ], + "type": "object" + }, "TurnError": { "properties": { "additionalDetails": { @@ -1432,6 +1501,64 @@ } ] }, + "TurnPluginAttribution": { + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "marketplaceName": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + } + }, + "required": [ + "marketplaceName", + "pluginId", + "pluginName" + ], + "type": "object" + }, + "TurnSkillAttribution": { + "properties": { + "invokeType": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "skillId": { + "type": "string" + }, + "skillName": { + "type": "string" + }, + "skillScope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "skillId", + "skillName" + ], + "type": "object" + }, "TurnStatus": { "enum": [ "completed", @@ -1441,6 +1568,39 @@ ], "type": "string" }, + "TurnToolAttribution": { + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "kind" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts index 6505ec345f9..c047cf2dae8 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Turn.ts @@ -2,6 +2,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ThreadItem } from "./ThreadItem"; +import type { TurnAttribution } from "./TurnAttribution"; import type { TurnError } from "./TurnError"; import type { TurnItemsView } from "./TurnItemsView"; import type { TurnStatus } from "./TurnStatus"; @@ -30,4 +31,8 @@ completedAt: number | null, /** * Duration between turn start and completion in milliseconds, if known. */ -durationMs: number | null, }; +durationMs: number | null, +/** + * Skills, plugins, apps, and tools observed during this turn. + */ +attribution?: TurnAttribution, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnAppAttribution.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnAppAttribution.ts new file mode 100644 index 00000000000..3bb5d57a350 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnAppAttribution.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnAppAttribution = { connectorId: string | null, appName: string | null, invokeType: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnAttribution.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnAttribution.ts new file mode 100644 index 00000000000..9dc10b4542b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnAttribution.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TurnAppAttribution } from "./TurnAppAttribution"; +import type { TurnPluginAttribution } from "./TurnPluginAttribution"; +import type { TurnSkillAttribution } from "./TurnSkillAttribution"; +import type { TurnToolAttribution } from "./TurnToolAttribution"; + +export type TurnAttribution = { skills: Array, plugins: Array, apps: Array, tools: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnPluginAttribution.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPluginAttribution.ts new file mode 100644 index 00000000000..94b54944f96 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnPluginAttribution.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnPluginAttribution = { pluginId: string, pluginName: string, marketplaceName: string, displayName: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnSkillAttribution.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSkillAttribution.ts new file mode 100644 index 00000000000..51c716cc3b6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSkillAttribution.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnSkillAttribution = { skillId: string, skillName: string, skillScope: string | null, pluginId: string | null, invokeType: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnToolAttribution.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnToolAttribution.ts new file mode 100644 index 00000000000..966f8938239 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnToolAttribution.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type TurnToolAttribution = { id: string, kind: string, name: string | null, server: string | null, pluginId: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 66b67fb1b4a..e3e2666ed70 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -437,6 +437,8 @@ export type { ToolRequestUserInputQuestion } from "./ToolRequestUserInputQuestio export type { ToolRequestUserInputResponse } from "./ToolRequestUserInputResponse"; export type { ToolsV2 } from "./ToolsV2"; export type { Turn } from "./Turn"; +export type { TurnAppAttribution } from "./TurnAppAttribution"; +export type { TurnAttribution } from "./TurnAttribution"; export type { TurnCompletedNotification } from "./TurnCompletedNotification"; export type { TurnDiffUpdatedNotification } from "./TurnDiffUpdatedNotification"; export type { TurnEnvironmentParams } from "./TurnEnvironmentParams"; @@ -447,12 +449,15 @@ export type { TurnItemsView } from "./TurnItemsView"; export type { TurnPlanStep } from "./TurnPlanStep"; export type { TurnPlanStepStatus } from "./TurnPlanStepStatus"; export type { TurnPlanUpdatedNotification } from "./TurnPlanUpdatedNotification"; +export type { TurnPluginAttribution } from "./TurnPluginAttribution"; +export type { TurnSkillAttribution } from "./TurnSkillAttribution"; export type { TurnStartParams } from "./TurnStartParams"; export type { TurnStartResponse } from "./TurnStartResponse"; export type { TurnStartedNotification } from "./TurnStartedNotification"; export type { TurnStatus } from "./TurnStatus"; export type { TurnSteerParams } from "./TurnSteerParams"; export type { TurnSteerResponse } from "./TurnSteerResponse"; +export type { TurnToolAttribution } from "./TurnToolAttribution"; export type { TurnsPage } from "./TurnsPage"; export type { UserInput } from "./UserInput"; export type { WarningNotification } from "./WarningNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index aa65f9ab9d8..b3fd606e082 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -1185,6 +1185,7 @@ impl From for Turn { started_at: value.started_at, completed_at: value.completed_at, duration_ms: value.duration_ms, + attribution: None, } } } @@ -1200,6 +1201,7 @@ impl From<&PendingTurn> for Turn { started_at: value.started_at, completed_at: value.completed_at, duration_ms: value.duration_ms, + attribution: None, } } } @@ -1587,6 +1589,7 @@ mod tests { completed_at: None, duration_ms: None, items_view: TurnItemsView::Full, + attribution: None, items: vec![ ThreadItem::UserMessage { id: "item-1".into(), @@ -2947,6 +2950,7 @@ mod tests { completed_at: None, duration_ms: None, items_view: TurnItemsView::Full, + attribution: None, items: Vec::new(), }] ); @@ -3218,6 +3222,7 @@ mod tests { completed_at: None, duration_ms: None, items_view: TurnItemsView::Full, + attribution: None, items: vec![ThreadItem::UserMessage { id: "item-1".into(), client_id: None, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs index f0c518adf8d..b77f4763681 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs @@ -169,6 +169,10 @@ pub struct Turn { /// Duration between turn start and completion in milliseconds, if known. #[ts(type = "number | null")] pub duration_ms: Option, + /// Skills, plugins, apps, and tools observed during this turn. + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub attribution: Option, } #[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] @@ -184,6 +188,66 @@ pub enum TurnItemsView { Full, } +#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnAttribution { + pub skills: Vec, + pub plugins: Vec, + pub apps: Vec, + pub tools: Vec, +} + +impl TurnAttribution { + pub fn is_empty(&self) -> bool { + self.skills.is_empty() + && self.plugins.is_empty() + && self.apps.is_empty() + && self.tools.is_empty() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnSkillAttribution { + pub skill_id: String, + pub skill_name: String, + pub skill_scope: Option, + pub plugin_id: Option, + pub invoke_type: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnPluginAttribution { + pub plugin_id: String, + pub plugin_name: String, + pub marketplace_name: String, + pub display_name: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnAppAttribution { + pub connector_id: Option, + pub app_name: Option, + pub invoke_type: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnToolAttribution { + pub id: String, + pub kind: String, + pub name: Option, + pub server: Option, + pub plugin_id: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index b870b5b17c7..8ea352f1a35 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -70,6 +70,7 @@ use codex_app_server_protocol::ToolRequestUserInputParams; use codex_app_server_protocol::ToolRequestUserInputQuestion; use codex_app_server_protocol::ToolRequestUserInputResponse; use codex_app_server_protocol::Turn; +use codex_app_server_protocol::TurnAttribution; use codex_app_server_protocol::TurnCompletedNotification; use codex_app_server_protocol::TurnDiffUpdatedNotification; use codex_app_server_protocol::TurnError; @@ -165,6 +166,7 @@ pub(crate) async fn apply_bespoke_event_handling( started_at: payload.started_at, completed_at: None, duration_ms: None, + attribution: None, }); turn.items.clear(); turn.items_view = TurnItemsView::NotLoaded; @@ -188,6 +190,7 @@ pub(crate) async fn apply_bespoke_event_handling( .await; handle_turn_complete( conversation_id, + &conversation, event_turn_id, turn_complete_event, &outgoing, @@ -1120,6 +1123,7 @@ pub(crate) async fn apply_bespoke_event_handling( .await; handle_turn_interrupted( conversation_id, + &conversation, event_turn_id, turn_aborted_event, &outgoing, @@ -1292,6 +1296,7 @@ async fn emit_turn_completed_with_status( conversation_id: ThreadId, event_turn_id: String, turn_completion_metadata: TurnCompletionMetadata, + attribution: Option, outgoing: &ThreadScopedOutgoingMessageSender, ) { let notification = TurnCompletedNotification { @@ -1305,6 +1310,7 @@ async fn emit_turn_completed_with_status( started_at: turn_completion_metadata.started_at, completed_at: turn_completion_metadata.completed_at, duration_ms: turn_completion_metadata.duration_ms, + attribution, }, }; outgoing @@ -1468,12 +1474,14 @@ async fn find_and_remove_turn_summary( async fn handle_turn_complete( conversation_id: ThreadId, + conversation: &Arc, event_turn_id: String, turn_complete_event: TurnCompleteEvent, outgoing: &ThreadScopedOutgoingMessageSender, thread_state: &Arc>, ) { let turn_summary = find_and_remove_turn_summary(conversation_id, thread_state).await; + let attribution = conversation.take_turn_attribution(&event_turn_id); let (status, error) = match turn_summary.last_error { Some(error) => (TurnStatus::Failed, Some(error)), @@ -1490,6 +1498,7 @@ async fn handle_turn_complete( completed_at: turn_complete_event.completed_at, duration_ms: turn_complete_event.duration_ms, }, + attribution, outgoing, ) .await; @@ -1497,12 +1506,14 @@ async fn handle_turn_complete( async fn handle_turn_interrupted( conversation_id: ThreadId, + conversation: &Arc, event_turn_id: String, turn_aborted_event: TurnAbortedEvent, outgoing: &ThreadScopedOutgoingMessageSender, thread_state: &Arc>, ) { let turn_summary = find_and_remove_turn_summary(conversation_id, thread_state).await; + let attribution = conversation.take_turn_attribution(&event_turn_id); emit_turn_completed_with_status( conversation_id, @@ -1514,6 +1525,7 @@ async fn handle_turn_interrupted( completed_at: turn_aborted_event.completed_at, duration_ms: turn_aborted_event.duration_ms, }, + attribution, outgoing, ) .await; diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index f7072d0fa17..de24fac02ca 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -890,6 +890,7 @@ mod tests { started_at: None, completed_at: Some(0), duration_ms: None, + attribution: None, }, }) )); diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 2a4b2722ecb..e7e64a567c3 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -237,6 +237,7 @@ mod thread_processor_behavior_tests { started_at: None, completed_at: None, duration_ms: None, + attribution: None, }; let turns = reconstruct_thread_turns_for_turns_list( diff --git a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs index b265e3a632f..824f3378e3f 100644 --- a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs +++ b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs @@ -196,6 +196,7 @@ mod tests { started_at: None, completed_at: None, duration_ms: None, + attribution: None, }], } } diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 2fbc64c3987..130e23653d9 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -486,6 +486,7 @@ impl TurnRequestProcessor { started_at: None, completed_at: None, duration_ms: None, + attribution: None, }; Ok(TurnStartResponse { turn }) @@ -1013,6 +1014,7 @@ impl TurnRequestProcessor { started_at: None, completed_at: None, duration_ms: None, + attribution: None, } } diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index dec1ce68952..40112aec806 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -138,6 +138,17 @@ impl CodexThread { self.codex.session.services.session_telemetry.clone() } + pub fn take_turn_attribution( + &self, + turn_id: &str, + ) -> Option { + self.codex + .session + .services + .analytics_events_client + .take_turn_attribution(turn_id) + } + pub async fn shutdown_and_wait(&self) -> CodexResult<()> { self.codex.shutdown_and_wait().await }