Compare commits

...

1 Commits

Author SHA1 Message Date
pab-oai
a75168d31c Expose used plugin IDs in turn metadata 2026-04-28 13:59:18 -07:00
3 changed files with 104 additions and 17 deletions

View File

@@ -274,6 +274,11 @@ pub(crate) async fn run_turn(
.iter()
.filter_map(crate::plugins::PluginCapabilitySummary::telemetry_metadata)
.collect::<Vec<_>>();
turn_context.turn_metadata_state.set_plugin_ids_used(
mentioned_plugin_metadata
.iter()
.map(|plugin| plugin.plugin_id.as_key()),
);
let mut explicitly_enabled_connectors = collect_explicit_app_ids(&input);
explicitly_enabled_connectors.extend(collect_explicit_app_ids_from_skill_items(

View File

@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
@@ -19,6 +20,9 @@ use codex_protocol::protocol::SessionSource;
use codex_utils_absolute_path::AbsolutePathBuf;
const TURN_STARTED_AT_UNIX_MS_KEY: &str = "turn_started_at_unix_ms";
const PLUGIN_IDS_USED_KEY: &str = "plugin_ids_used";
const RESERVED_DYNAMIC_METADATA_KEYS: [&str; 2] =
[TURN_STARTED_AT_UNIX_MS_KEY, PLUGIN_IDS_USED_KEY];
#[derive(Clone, Debug, Default)]
struct WorkspaceGitMetadata {
@@ -77,23 +81,20 @@ impl TurnMetadataBag {
fn merge_turn_metadata(
header: &str,
turn_started_at_unix_ms: Option<i64>,
additional_metadata: &BTreeMap<String, Value>,
responsesapi_client_metadata: Option<&HashMap<String, String>>,
) -> Option<String> {
if turn_started_at_unix_ms.is_none() && responsesapi_client_metadata.is_none() {
if additional_metadata.is_empty() && responsesapi_client_metadata.is_none() {
return None;
}
let mut metadata = serde_json::from_str::<serde_json::Map<String, Value>>(header).ok()?;
if let Some(turn_started_at_unix_ms) = turn_started_at_unix_ms {
metadata.insert(
TURN_STARTED_AT_UNIX_MS_KEY.to_string(),
Value::Number(turn_started_at_unix_ms.into()),
);
for (key, value) in additional_metadata {
metadata.insert(key.clone(), value.clone());
}
if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {
for (key, value) in responsesapi_client_metadata {
if key == TURN_STARTED_AT_UNIX_MS_KEY {
if RESERVED_DYNAMIC_METADATA_KEYS.contains(&key.as_str()) {
continue;
}
metadata
@@ -170,7 +171,7 @@ pub(crate) struct TurnMetadataState {
base_metadata: TurnMetadataBag,
base_header: String,
enriched_header: Arc<RwLock<Option<String>>>,
turn_started_at_unix_ms: Arc<RwLock<Option<i64>>>,
additional_metadata: Arc<RwLock<BTreeMap<String, Value>>>,
responsesapi_client_metadata: Arc<RwLock<Option<HashMap<String, String>>>>,
enrichment_task: Arc<Mutex<Option<JoinHandle<()>>>>,
}
@@ -212,7 +213,7 @@ impl TurnMetadataState {
base_metadata,
base_header,
enriched_header: Arc::new(RwLock::new(None)),
turn_started_at_unix_ms: Arc::new(RwLock::new(None)),
additional_metadata: Arc::new(RwLock::new(BTreeMap::new())),
responsesapi_client_metadata: Arc::new(RwLock::new(None)),
enrichment_task: Arc::new(Mutex::new(None)),
}
@@ -230,10 +231,11 @@ impl TurnMetadataState {
} else {
self.base_header.clone()
};
let turn_started_at_unix_ms = *self
.turn_started_at_unix_ms
let additional_metadata = self
.additional_metadata
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
let responsesapi_client_metadata = self
.responsesapi_client_metadata
.read()
@@ -241,7 +243,7 @@ impl TurnMetadataState {
.clone();
merge_turn_metadata(
&header,
turn_started_at_unix_ms,
&additional_metadata,
responsesapi_client_metadata.as_ref(),
)
.or(Some(header))
@@ -264,10 +266,33 @@ impl TurnMetadataState {
}
pub(crate) fn set_turn_started_at_unix_ms(&self, turn_started_at_unix_ms: i64) {
*self
.turn_started_at_unix_ms
self.additional_metadata
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(turn_started_at_unix_ms);
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(
TURN_STARTED_AT_UNIX_MS_KEY.to_string(),
Value::Number(turn_started_at_unix_ms.into()),
);
}
pub(crate) fn set_plugin_ids_used<I>(&self, plugin_ids: I)
where
I: IntoIterator<Item = String>,
{
let plugin_ids = plugin_ids.into_iter().collect::<BTreeSet<_>>();
let mut additional_metadata = self
.additional_metadata
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if plugin_ids.is_empty() {
additional_metadata.remove(PLUGIN_IDS_USED_KEY);
return;
}
additional_metadata.insert(
PLUGIN_IDS_USED_KEY.to_string(),
Value::Array(plugin_ids.into_iter().map(Value::String).collect()),
);
}
pub(crate) fn spawn_git_enrichment_task(&self) {

View File

@@ -148,6 +148,36 @@ fn turn_metadata_state_includes_turn_started_at_unix_ms_after_start() {
);
}
#[test]
fn turn_metadata_state_includes_plugin_ids_used() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().abs();
let permission_profile = PermissionProfile::read_only();
let state = TurnMetadataState::new(
"session-a".to_string(),
&SessionSource::Exec,
"turn-a".to_string(),
cwd,
&permission_profile,
WindowsSandboxLevel::Disabled,
/*enforce_managed_network*/ false,
);
state.set_plugin_ids_used([
"slack@openai-curated".to_string(),
"github@openai-curated".to_string(),
"github@openai-curated".to_string(),
]);
let header = state.current_header_value().expect("header");
let json: Value = serde_json::from_str(&header).expect("json");
assert_eq!(
json["plugin_ids_used"],
serde_json::json!(["github@openai-curated", "slack@openai-curated"])
);
}
#[test]
fn turn_metadata_state_ignores_client_turn_started_at_unix_ms_before_start() {
let temp_dir = TempDir::new().expect("temp dir");
@@ -174,6 +204,32 @@ fn turn_metadata_state_ignores_client_turn_started_at_unix_ms_before_start() {
assert!(json.get("turn_started_at_unix_ms").is_none());
}
#[test]
fn turn_metadata_state_ignores_client_plugin_ids_used_when_unused() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = temp_dir.path().abs();
let permission_profile = PermissionProfile::read_only();
let state = TurnMetadataState::new(
"session-a".to_string(),
&SessionSource::Exec,
"turn-a".to_string(),
cwd,
&permission_profile,
WindowsSandboxLevel::Disabled,
/*enforce_managed_network*/ false,
);
state.set_responsesapi_client_metadata(HashMap::from([(
"plugin_ids_used".to_string(),
"client-supplied".to_string(),
)]));
let header = state.current_header_value().expect("header");
let json: Value = serde_json::from_str(&header).expect("json");
assert!(json.get("plugin_ids_used").is_none());
}
#[test]
fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields() {
let temp_dir = TempDir::new().expect("temp dir");
@@ -193,6 +249,7 @@ fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields(
("fiber_run_id".to_string(), "fiber-123".to_string()),
("session_id".to_string(), "client-supplied".to_string()),
("thread_source".to_string(), "client-supplied".to_string()),
("turn_id".to_string(), "client-supplied".to_string()),
(
"turn_started_at_unix_ms".to_string(),
"client-supplied".to_string(),