mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Detect connectors used in external agent sessions (#36336)
## What changed - Add session connector detection to `ExternalAgentConfigService` for Claude and Cursor migrations. - Resolve Claude connector attributions through session manifests and map Cursor `CallMcpTool` server IDs through cached plugin metadata. - Return deduplicated connector candidates with their session counts and detection sources, and expose the new result types from the migration crate. ## Testing - Cover Claude connector resolution by server name and UUID. - Cover Cursor connector detection and per-session counting from MCP tool calls. GitOrigin-RevId: 772bfe949bdc734b385a6e06f6a4b1d35b4cf210
This commit is contained in:
committed by
copyberry
parent
ef293f7ac9
commit
448118f544
@@ -1,10 +1,16 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::io::BufRead;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::model::DetectedConnectorCandidate;
|
||||
use crate::model::DetectedConnectorSource;
|
||||
use crate::sessions::ExternalAgentSessionMigration;
|
||||
|
||||
const SESSION_MANIFESTS_DIR: &str = "claude-code-sessions";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -27,6 +33,32 @@ struct RemoteMcpServerConfig {
|
||||
uuid: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn detect_cla_session_connectors(
|
||||
sessions: &[ExternalAgentSessionMigration],
|
||||
connector_metadata_roots: &[PathBuf],
|
||||
) -> Vec<DetectedConnectorCandidate> {
|
||||
let session_attributions = sessions
|
||||
.iter()
|
||||
.filter_map(session_connector_attribution)
|
||||
.collect::<Vec<_>>();
|
||||
let connector_names_by_session =
|
||||
detect_imported_cla_session_connectors(&session_attributions, connector_metadata_roots);
|
||||
|
||||
let mut candidates = BTreeMap::<String, DetectedConnectorCandidate>::new();
|
||||
for names in connector_names_by_session.into_values() {
|
||||
for name in names {
|
||||
let key = name.to_lowercase();
|
||||
let candidate = candidates.entry(key).or_insert(DetectedConnectorCandidate {
|
||||
name,
|
||||
session_count: 0,
|
||||
source: DetectedConnectorSource::RemoteMcpServersConfig,
|
||||
});
|
||||
candidate.session_count = candidate.session_count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
candidates.into_values().collect()
|
||||
}
|
||||
|
||||
pub fn detect_imported_cla_session_connectors(
|
||||
session_attributions: &[ImportedSessionConnectorAttribution],
|
||||
connector_metadata_roots: &[PathBuf],
|
||||
@@ -93,6 +125,36 @@ pub fn detect_imported_cla_session_connectors(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn session_connector_attribution(
|
||||
session: &ExternalAgentSessionMigration,
|
||||
) -> Option<ImportedSessionConnectorAttribution> {
|
||||
let session_id = session
|
||||
.path
|
||||
.file_stem()
|
||||
.and_then(|stem| stem.to_str())
|
||||
.map(str::trim)
|
||||
.filter(|session_id| !session_id.is_empty())?
|
||||
.to_string();
|
||||
let file = fs::File::open(&session.path).ok()?;
|
||||
let server_ids = std::io::BufReader::new(file)
|
||||
.lines()
|
||||
.map_while(Result::ok)
|
||||
.filter_map(|line| serde_json::from_str::<JsonValue>(line.trim()).ok())
|
||||
.filter_map(|record| {
|
||||
record
|
||||
.get("attributionMcpServer")
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|server_id| !server_id.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
(!server_ids.is_empty()).then_some(ImportedSessionConnectorAttribution {
|
||||
session_id,
|
||||
server_ids,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_session_manifest(path: &Path) -> Option<SessionManifest> {
|
||||
let contents = fs::read_to_string(path).ok()?;
|
||||
serde_json::from_str(&contents).ok()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeSet;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
@@ -22,19 +21,28 @@ fn resolves_attributed_server_name_from_session_manifest() {
|
||||
.to_string(),
|
||||
)
|
||||
.expect("manifest");
|
||||
let attributions = vec![ImportedSessionConnectorAttribution {
|
||||
session_id: "session-1".to_string(),
|
||||
server_ids: BTreeSet::from(["figma".to_string()]),
|
||||
let session_path = metadata_root.path().join("session-1.jsonl");
|
||||
std::fs::write(
|
||||
&session_path,
|
||||
serde_json::json!({"attributionMcpServer": "figma"}).to_string(),
|
||||
)
|
||||
.expect("session");
|
||||
let sessions = vec![ExternalAgentSessionMigration {
|
||||
path: session_path,
|
||||
cwd: metadata_root.path().to_path_buf(),
|
||||
title: None,
|
||||
}];
|
||||
|
||||
let connector_names = detect_imported_cla_session_connectors(
|
||||
&attributions,
|
||||
&[metadata_root.path().to_path_buf()],
|
||||
);
|
||||
let connectors =
|
||||
detect_cla_session_connectors(&sessions, &[metadata_root.path().to_path_buf()]);
|
||||
|
||||
assert_eq!(
|
||||
connector_names,
|
||||
BTreeMap::from([("session-1".to_string(), vec!["Figma".to_string()])])
|
||||
connectors,
|
||||
vec![DetectedConnectorCandidate {
|
||||
name: "Figma".to_string(),
|
||||
session_count: 1,
|
||||
source: DetectedConnectorSource::RemoteMcpServersConfig,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,18 +65,30 @@ fn still_resolves_attributed_server_uuid_from_session_manifest() {
|
||||
.to_string(),
|
||||
)
|
||||
.expect("manifest");
|
||||
let attributions = vec![ImportedSessionConnectorAttribution {
|
||||
session_id: "session-1".to_string(),
|
||||
server_ids: BTreeSet::from(["c58ac595-58b5-48f8-ac77-d8d01523dede".to_string()]),
|
||||
let session_path = metadata_root.path().join("session-1.jsonl");
|
||||
std::fs::write(
|
||||
&session_path,
|
||||
serde_json::json!({
|
||||
"attributionMcpServer": "c58ac595-58b5-48f8-ac77-d8d01523dede"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("session");
|
||||
let sessions = vec![ExternalAgentSessionMigration {
|
||||
path: session_path,
|
||||
cwd: metadata_root.path().to_path_buf(),
|
||||
title: None,
|
||||
}];
|
||||
|
||||
let connector_names = detect_imported_cla_session_connectors(
|
||||
&attributions,
|
||||
&[metadata_root.path().to_path_buf()],
|
||||
);
|
||||
let connectors =
|
||||
detect_cla_session_connectors(&sessions, &[metadata_root.path().to_path_buf()]);
|
||||
|
||||
assert_eq!(
|
||||
connector_names,
|
||||
BTreeMap::from([("session-1".to_string(), vec!["Figma".to_string()])])
|
||||
connectors,
|
||||
vec![DetectedConnectorCandidate {
|
||||
name: "Figma".to_string(),
|
||||
session_count: 1,
|
||||
source: DetectedConnectorSource::RemoteMcpServersConfig,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::io::BufRead;
|
||||
use std::path::Component;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::model::DetectedConnectorCandidate;
|
||||
use crate::model::DetectedConnectorSource;
|
||||
use crate::sessions::ExternalAgentSessionMigration;
|
||||
|
||||
const PLUGIN_CACHE_DIR: &str = "plugins/cache";
|
||||
const PLUGIN_MANIFEST_PATH: &str = ".cursor-plugin/plugin.json";
|
||||
const MCP_CONFIG_PATH: &str = ".mcp.json";
|
||||
const MCP_TOOL_CALL: &str = "CallMcpTool";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PluginManifest {
|
||||
name: String,
|
||||
display_name: Option<String>,
|
||||
mcp_servers: Option<JsonValue>,
|
||||
}
|
||||
|
||||
pub(crate) fn detect_cur_session_connectors(
|
||||
sessions: &[ExternalAgentSessionMigration],
|
||||
external_agent_home: &Path,
|
||||
) -> Vec<DetectedConnectorCandidate> {
|
||||
let connector_names_by_server_id = cached_connector_names_by_server_id(external_agent_home);
|
||||
if connector_names_by_server_id.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut candidates = BTreeMap::<String, DetectedConnectorCandidate>::new();
|
||||
for session in sessions {
|
||||
for (key, name) in session_connector_names(session, &connector_names_by_server_id) {
|
||||
let candidate = candidates.entry(key).or_insert(DetectedConnectorCandidate {
|
||||
name,
|
||||
session_count: 0,
|
||||
source: DetectedConnectorSource::SessionToolUse,
|
||||
});
|
||||
candidate.session_count = candidate.session_count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
candidates.into_values().collect()
|
||||
}
|
||||
|
||||
fn session_connector_names(
|
||||
session: &ExternalAgentSessionMigration,
|
||||
connector_names_by_server_id: &BTreeMap<String, String>,
|
||||
) -> BTreeMap<String, String> {
|
||||
let Ok(file) = fs::File::open(&session.path) else {
|
||||
return BTreeMap::new();
|
||||
};
|
||||
std::io::BufReader::new(file)
|
||||
.lines()
|
||||
.map_while(Result::ok)
|
||||
.filter_map(|line| serde_json::from_str::<JsonValue>(line.trim()).ok())
|
||||
.flat_map(|record| session_mcp_server_ids(&record).into_iter())
|
||||
.filter_map(|server_id| connector_names_by_server_id.get(&server_id).cloned())
|
||||
.map(|name| (name.to_lowercase(), name))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn session_mcp_server_ids(record: &JsonValue) -> BTreeSet<String> {
|
||||
record
|
||||
.get("message")
|
||||
.and_then(|message| message.get("content"))
|
||||
.and_then(JsonValue::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|content| {
|
||||
content.get("type").and_then(JsonValue::as_str) == Some("tool_use")
|
||||
&& content.get("name").and_then(JsonValue::as_str) == Some(MCP_TOOL_CALL)
|
||||
})
|
||||
.filter_map(|content| {
|
||||
content
|
||||
.get("input")
|
||||
.and_then(|input| input.get("server"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|server_id| !server_id.is_empty())
|
||||
.map(str::to_lowercase)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn cached_connector_names_by_server_id(external_agent_home: &Path) -> BTreeMap<String, String> {
|
||||
let cache_root = external_agent_home.join(PLUGIN_CACHE_DIR);
|
||||
let mut connector_names = BTreeMap::new();
|
||||
for marketplace_root in child_directories(&cache_root) {
|
||||
for plugin_root in child_directories(&marketplace_root) {
|
||||
for version_root in child_directories(&plugin_root) {
|
||||
let Some(manifest) = read_plugin_manifest(&version_root) else {
|
||||
continue;
|
||||
};
|
||||
let Some(display_name) = crate::sessions::normalized_connector_display_name(
|
||||
manifest.display_name.as_deref().or(Some(&manifest.name)),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
for server_name in cached_mcp_server_names(&version_root, &manifest) {
|
||||
connector_names.insert(
|
||||
format!("plugin-{}-{server_name}", manifest.name).to_lowercase(),
|
||||
display_name.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
connector_names
|
||||
}
|
||||
|
||||
fn child_directories(root: &Path) -> Vec<std::path::PathBuf> {
|
||||
let Ok(entries) = fs::read_dir(root) else {
|
||||
return Vec::new();
|
||||
};
|
||||
entries
|
||||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
entry
|
||||
.file_type()
|
||||
.ok()
|
||||
.filter(std::fs::FileType::is_dir)
|
||||
.map(|_| entry.path())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_plugin_manifest(version_root: &Path) -> Option<PluginManifest> {
|
||||
let contents = fs::read_to_string(version_root.join(PLUGIN_MANIFEST_PATH)).ok()?;
|
||||
serde_json::from_str(&contents).ok()
|
||||
}
|
||||
|
||||
fn cached_mcp_server_names(version_root: &Path, manifest: &PluginManifest) -> Vec<String> {
|
||||
manifest
|
||||
.mcp_servers
|
||||
.as_ref()
|
||||
.map_or_else(
|
||||
|| mcp_server_names_from_file(version_root, MCP_CONFIG_PATH),
|
||||
|declaration| manifest_mcp_server_names(version_root, declaration),
|
||||
)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn manifest_mcp_server_names(version_root: &Path, declaration: &JsonValue) -> BTreeSet<String> {
|
||||
match declaration {
|
||||
JsonValue::String(path) => mcp_server_names_from_file(version_root, path),
|
||||
JsonValue::Object(_) => mcp_server_names_from_config(declaration),
|
||||
JsonValue::Array(declarations) => declarations
|
||||
.iter()
|
||||
.flat_map(|declaration| manifest_mcp_server_names(version_root, declaration))
|
||||
.collect(),
|
||||
_ => BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mcp_server_names_from_file(version_root: &Path, relative_path: &str) -> BTreeSet<String> {
|
||||
let relative_path = Path::new(relative_path);
|
||||
if relative_path.as_os_str().is_empty()
|
||||
|| relative_path
|
||||
.components()
|
||||
.any(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
|
||||
{
|
||||
return BTreeSet::new();
|
||||
}
|
||||
let Ok(contents) = fs::read_to_string(version_root.join(relative_path)) else {
|
||||
return BTreeSet::new();
|
||||
};
|
||||
let Ok(config) = serde_json::from_str::<JsonValue>(&contents) else {
|
||||
return BTreeSet::new();
|
||||
};
|
||||
mcp_server_names_from_config(&config)
|
||||
}
|
||||
|
||||
fn mcp_server_names_from_config(config: &JsonValue) -> BTreeSet<String> {
|
||||
config
|
||||
.get("mcpServers")
|
||||
.and_then(JsonValue::as_object)
|
||||
.or_else(|| config.as_object())
|
||||
.into_iter()
|
||||
.flat_map(|servers| servers.keys())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "connectors_cur_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,131 @@
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn detects_connector_used_by_session_mcp_tool_call() {
|
||||
let source_home = TempDir::new().expect("source home");
|
||||
write_cached_plugin(source_home.path(), "figma", "Figma", "figma");
|
||||
let first_session = write_session(
|
||||
source_home.path(),
|
||||
"session-1",
|
||||
&[
|
||||
serde_json::json!({
|
||||
"role": "assistant",
|
||||
"message": {
|
||||
"content": [{
|
||||
"type": "tool_use",
|
||||
"name": "GetMcpTools",
|
||||
"input": {"server": "plugin-figma-figma"}
|
||||
}]
|
||||
}
|
||||
}),
|
||||
serde_json::json!({
|
||||
"role": "assistant",
|
||||
"message": {
|
||||
"content": [{
|
||||
"type": "tool_use",
|
||||
"name": "CallMcpTool",
|
||||
"input": {"server": "plugin-figma-figma", "toolName": "whoami"}
|
||||
}]
|
||||
}
|
||||
}),
|
||||
serde_json::json!({
|
||||
"role": "assistant",
|
||||
"message": {
|
||||
"content": [{
|
||||
"type": "tool_use",
|
||||
"name": "CallMcpTool",
|
||||
"input": {"server": "plugin-figma-figma", "toolName": "get_metadata"}
|
||||
}]
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
let second_session = write_session(
|
||||
source_home.path(),
|
||||
"session-2",
|
||||
&[serde_json::json!({
|
||||
"role": "assistant",
|
||||
"message": {
|
||||
"content": [{
|
||||
"type": "tool_use",
|
||||
"name": "CallMcpTool",
|
||||
"input": {"server": "plugin-figma-figma", "toolName": "get_screenshot"}
|
||||
}]
|
||||
}
|
||||
})],
|
||||
);
|
||||
|
||||
let connectors = detect_cur_session_connectors(
|
||||
&[
|
||||
ExternalAgentSessionMigration {
|
||||
path: first_session,
|
||||
cwd: source_home.path().to_path_buf(),
|
||||
title: None,
|
||||
},
|
||||
ExternalAgentSessionMigration {
|
||||
path: second_session,
|
||||
cwd: source_home.path().to_path_buf(),
|
||||
title: None,
|
||||
},
|
||||
],
|
||||
source_home.path(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
connectors,
|
||||
vec![DetectedConnectorCandidate {
|
||||
name: "Figma".to_string(),
|
||||
session_count: 2,
|
||||
source: DetectedConnectorSource::SessionToolUse,
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
fn write_cached_plugin(
|
||||
source_home: &Path,
|
||||
plugin_name: &str,
|
||||
display_name: &str,
|
||||
server_name: &str,
|
||||
) {
|
||||
let version_root = source_home
|
||||
.join(PLUGIN_CACHE_DIR)
|
||||
.join("marketplace")
|
||||
.join(plugin_name)
|
||||
.join("version");
|
||||
fs::create_dir_all(version_root.join(".cursor-plugin")).expect("plugin directory");
|
||||
fs::write(
|
||||
version_root.join(PLUGIN_MANIFEST_PATH),
|
||||
serde_json::json!({
|
||||
"name": plugin_name,
|
||||
"displayName": display_name,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("plugin manifest");
|
||||
fs::write(
|
||||
version_root.join(MCP_CONFIG_PATH),
|
||||
serde_json::json!({
|
||||
"mcpServers": {
|
||||
(server_name): {"type": "http", "url": "https://example.invalid/mcp"}
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("mcp config");
|
||||
}
|
||||
|
||||
fn write_session(source_home: &Path, name: &str, records: &[JsonValue]) -> std::path::PathBuf {
|
||||
let path = source_home.join(format!("{name}.jsonl"));
|
||||
fs::write(
|
||||
&path,
|
||||
records
|
||||
.iter()
|
||||
.map(JsonValue::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
.expect("session");
|
||||
path
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
mod cla;
|
||||
mod common;
|
||||
mod connectors_cla;
|
||||
mod connectors_cur;
|
||||
mod cur;
|
||||
|
||||
pub use cla::detect_recent_cla_sessions;
|
||||
pub(crate) use cla::detect_recent_cla_sessions_with_limits;
|
||||
pub use connectors_cla::ImportedSessionConnectorAttribution;
|
||||
pub(crate) use connectors_cla::detect_cla_session_connectors;
|
||||
pub use connectors_cla::detect_imported_cla_session_connectors;
|
||||
pub(crate) use connectors_cur::detect_cur_session_connectors;
|
||||
pub use cur::detect_recent_cur_sessions;
|
||||
pub(crate) use cur::detect_recent_cur_sessions_with_limits;
|
||||
|
||||
@@ -56,7 +56,10 @@ use mcp::parse_env_placeholder;
|
||||
pub use memory::ExternalMemoryFile;
|
||||
pub use memory::discover_external_memory_files;
|
||||
pub use rewrite::RewriteProfile;
|
||||
pub use service::DetectedConnectorCandidate;
|
||||
pub use service::DetectedConnectorSource;
|
||||
pub use service::ExternalAgentConfigDetectOptions;
|
||||
pub use service::ExternalAgentConfigDetection;
|
||||
pub use service::ExternalAgentConfigImportItemResult;
|
||||
pub use service::ExternalAgentConfigImportOutcome;
|
||||
pub use service::ExternalAgentConfigImportRawError;
|
||||
|
||||
@@ -2,8 +2,11 @@ use crate::ClaSource;
|
||||
use crate::CurSource;
|
||||
use crate::RewriteProfile;
|
||||
use crate::detect::plugins;
|
||||
use crate::detect::sessions::detect_cla_session_connectors;
|
||||
use crate::detect::sessions::detect_cur_session_connectors;
|
||||
use crate::detect::sessions::detect_recent_cla_sessions_with_limits;
|
||||
use crate::detect::sessions::detect_recent_cur_sessions_with_limits;
|
||||
use crate::model::DetectedConnectorCandidate;
|
||||
use crate::model::ExternalAgentSessionImportLimits;
|
||||
use crate::sessions::ExternalAgentSessionMigration;
|
||||
use crate::sessions::SessionMetadataMode;
|
||||
@@ -149,6 +152,18 @@ impl ExternalAgentSource {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn detect_session_connectors(
|
||||
self,
|
||||
sessions: &[ExternalAgentSessionMigration],
|
||||
connector_metadata_roots: &[PathBuf],
|
||||
external_agent_home: &Path,
|
||||
) -> Vec<DetectedConnectorCandidate> {
|
||||
match self {
|
||||
Self::Cla => detect_cla_session_connectors(sessions, connector_metadata_roots),
|
||||
Self::Cur => detect_cur_session_connectors(sessions, external_agent_home),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn marketplace_import_sources(
|
||||
self,
|
||||
external_agent_home: &Path,
|
||||
|
||||
@@ -30,6 +30,25 @@ pub struct ExternalAgentConfigDetectOptions {
|
||||
pub cwds: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ExternalAgentConfigDetection {
|
||||
pub items: Vec<ExternalAgentConfigMigrationItem>,
|
||||
pub connectors: Vec<DetectedConnectorCandidate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DetectedConnectorCandidate {
|
||||
pub name: String,
|
||||
pub session_count: u32,
|
||||
pub source: DetectedConnectorSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DetectedConnectorSource {
|
||||
RemoteMcpServersConfig,
|
||||
SessionToolUse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExternalAgentConfigMigrationItemType {
|
||||
Config,
|
||||
|
||||
@@ -6,7 +6,10 @@ use crate::config_values::write_toml_file;
|
||||
use crate::memory_import;
|
||||
use crate::migration_source::ExternalAgentSource;
|
||||
use crate::migration_source::InstructionSourceGroup;
|
||||
pub use crate::model::DetectedConnectorCandidate;
|
||||
pub use crate::model::DetectedConnectorSource;
|
||||
pub use crate::model::ExternalAgentConfigDetectOptions;
|
||||
pub use crate::model::ExternalAgentConfigDetection;
|
||||
pub use crate::model::ExternalAgentConfigImportItemResult;
|
||||
pub use crate::model::ExternalAgentConfigImportOutcome;
|
||||
pub use crate::model::ExternalAgentConfigImportRawError;
|
||||
@@ -24,6 +27,7 @@ use crate::reporting::emit_migration_metric;
|
||||
use crate::reporting::migration_metric_tags;
|
||||
pub use crate::reporting::record_import_error;
|
||||
use crate::scope::MigrationScope;
|
||||
use crate::sessions::ExternalAgentSessionMigration;
|
||||
use crate::sessions::SessionMetadataMode;
|
||||
#[cfg(test)]
|
||||
use crate::source_cla::KNOWN_MARKETPLACES_PATH as EXTERNAL_AGENT_KNOWN_MARKETPLACES_PATH;
|
||||
@@ -118,6 +122,17 @@ impl ExternalAgentConfigService {
|
||||
&self.connector_metadata_roots
|
||||
}
|
||||
|
||||
pub fn detect_session_connectors(
|
||||
&self,
|
||||
sessions: &[ExternalAgentSessionMigration],
|
||||
) -> Vec<DetectedConnectorCandidate> {
|
||||
self.source.detect_session_connectors(
|
||||
sessions,
|
||||
&self.connector_metadata_roots,
|
||||
&self.external_agent_home,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn codex_home(&self) -> &Path {
|
||||
&self.codex_home
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user