Move external agent migration into its crate (#33456)

## What changed

- Move migration detection, import orchestration, models, and reporting from
  `codex-app-server` into `codex-external-agent-migration`.
- Keep app-server protocol conversion in a dedicated boundary module.
- Organize source-specific configuration, sessions, hooks, plugins, commands,
  subagents, and memory handling behind shared migration adapters.
- Relocate and split the migration tests alongside the extracted service.

GitOrigin-RevId: 3378ec2064806a28136d6ac98664ca1d75414a4e
This commit is contained in:
charlesgong-openai authored and copyberry committed 2026-07-16 00:34:36 +00:00
1 parent 2edad72de3
commit e7efc5b04b
57 files changed
+7713 -7471

No files matched your search

@@ -9,31 +9,29 @@ use crate::request_processors::ConfigRequestProcessor;
use codex_analytics::AnalyticsEventsClient;
use codex_analytics::ExternalAgentConfigImportCompletedInput;
use codex_analytics::ExternalAgentConfigImportFailureInput;
use codex_app_server_protocol::CommandMigration;
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
use codex_app_server_protocol::ExternalAgentConfigDetectResponse;
use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification;
use codex_app_server_protocol::ExternalAgentConfigImportHistoriesReadResponse;
use codex_app_server_protocol::ExternalAgentConfigImportHistory;
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeFailure as ProtocolImportFailure;
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeSuccess as ProtocolImportSuccess;
use codex_app_server_protocol::ExternalAgentConfigImportParams;
use codex_app_server_protocol::ExternalAgentConfigImportProgressNotification;
use codex_app_server_protocol::ExternalAgentConfigImportResponse;
use codex_app_server_protocol::ExternalAgentConfigImportTypeResult as ProtocolImportTypeResult;
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
use codex_app_server_protocol::ExternalAgentImportedConnectorCandidate;
use codex_app_server_protocol::ExternalAgentImportedConnectorSource;
use codex_app_server_protocol::HookMigration;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::McpServerMigration;
use codex_app_server_protocol::MigrationDetails;
use codex_app_server_protocol::PluginsMigration;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SkillMigration;
use codex_arg0::Arg0DispatchPaths;
use codex_core::ThreadManager;
use codex_external_agent_migration::ExternalAgentConfigDetectOptions;
use codex_external_agent_migration::ExternalAgentConfigImportItemResult as CoreImportItemResult;
use codex_external_agent_migration::ExternalAgentConfigImportOutcome as CoreImportOutcome;
use codex_external_agent_migration::ExternalAgentConfigMigrationItemType as CoreMigrationItemType;
use codex_external_agent_migration::ExternalAgentConfigService;
use codex_external_agent_migration::PluginImportOutcome;
use codex_external_agent_migration::record_import_error;
use codex_external_agent_migration::sessions::ExternalAgentSessionMigration as CoreSessionMigration;
use codex_external_agent_migration::sessions::read_imported_connector_candidates;
use codex_features::Feature;
@@ -44,16 +42,11 @@ use codex_thread_store::ThreadStore;
use std::collections::HashSet;
use std::path::PathBuf;
use super::service::ExternalAgentConfigDetectOptions;
use super::service::ExternalAgentConfigImportItemResult as CoreImportItemResult;
use super::service::ExternalAgentConfigImportOutcome as CoreImportOutcome;
use super::service::ExternalAgentConfigImportRawError as CoreImportRawError;
use super::service::ExternalAgentConfigMigrationItem as CoreMigrationItem;
use super::service::ExternalAgentConfigMigrationItemType as CoreMigrationItemType;
use super::service::ExternalAgentConfigService;
use super::service::NamedMigration as CoreNamedMigration;
use super::service::PluginImportOutcome;
use super::service::record_import_error;
use super::protocol::completed_notification;
use super::protocol::core_migration_items;
use super::protocol::detect_response;
use super::protocol::protocol_import_history;
use super::protocol::protocol_import_type_result;
use super::session_importer::ExternalAgentSessionImporter;
use uuid::Uuid;
@@ -101,7 +94,7 @@ impl ExternalAgentConfigRequestProcessor {
);
let session_importer = ExternalAgentSessionImporter::new(
codex_home,
migration_service.connector_metadata_roots.clone(),
migration_service.connector_metadata_roots().to_vec(),
Arc::clone(&thread_manager),
thread_store,
config_manager.clone(),
@@ -136,94 +129,7 @@ impl ExternalAgentConfigRequestProcessor {
.await
.map_err(|err| internal_error(err.to_string()))?;
Ok(ExternalAgentConfigDetectResponse {
items: items
.into_iter()
.map(|migration_item| ExternalAgentConfigMigrationItem {
item_type: match migration_item.item_type {
CoreMigrationItemType::Config => {
ExternalAgentConfigMigrationItemType::Config
}
CoreMigrationItemType::Skills => {
ExternalAgentConfigMigrationItemType::Skills
}
CoreMigrationItemType::AgentsMd => {
ExternalAgentConfigMigrationItemType::AgentsMd
}
CoreMigrationItemType::Plugins => {
ExternalAgentConfigMigrationItemType::Plugins
}
CoreMigrationItemType::McpServerConfig => {
ExternalAgentConfigMigrationItemType::McpServerConfig
}
CoreMigrationItemType::Subagents => {
ExternalAgentConfigMigrationItemType::Subagents
}
CoreMigrationItemType::Hooks => ExternalAgentConfigMigrationItemType::Hooks,
CoreMigrationItemType::Commands => {
ExternalAgentConfigMigrationItemType::Commands
}
CoreMigrationItemType::Memory => {
ExternalAgentConfigMigrationItemType::Memory
}
CoreMigrationItemType::Sessions => {
ExternalAgentConfigMigrationItemType::Sessions
}
},
description: migration_item.description,
cwd: migration_item.cwd,
details: migration_item.details.map(|details| MigrationDetails {
plugins: details
.plugins
.into_iter()
.map(|plugin| PluginsMigration {
marketplace_name: plugin.marketplace_name,
plugin_names: plugin.plugin_names,
})
.collect(),
skills: details
.skills
.into_iter()
.map(|skill| SkillMigration { name: skill.name })
.collect(),
sessions: details
.sessions
.into_iter()
.map(|session| codex_app_server_protocol::SessionMigration {
path: session.path,
cwd: session.cwd,
title: session.title,
})
.collect(),
mcp_servers: details
.mcp_servers
.into_iter()
.map(|mcp_server| McpServerMigration {
name: mcp_server.name,
})
.collect(),
hooks: details
.hooks
.into_iter()
.map(|hook| HookMigration { name: hook.name })
.collect(),
subagents: details
.subagents
.into_iter()
.map(|subagent| codex_app_server_protocol::SubagentMigration {
name: subagent.name,
})
.collect(),
commands: details
.commands
.into_iter()
.map(|command| CommandMigration { name: command.name })
.collect(),
memory: details.memory,
}),
})
.collect(),
})
Ok(detect_response(items))
}
pub(crate) async fn import(
@@ -437,7 +343,7 @@ impl ExternalAgentConfigRequestProcessor {
.into_iter()
.map(protocol_import_history)
.collect::<Result<Vec<_>, _>>()?;
let connectors = read_imported_connector_candidates(&self.migration_service.codex_home)
let connectors = read_imported_connector_candidates(self.migration_service.codex_home())
.map_err(|err| {
internal_error(format!(
"failed to read imported connector candidates: {err}"
@@ -525,105 +431,13 @@ impl ExternalAgentConfigRequestProcessor {
migration_service: &ExternalAgentConfigService,
) -> CoreImportOutcome {
migration_service
.import(
.import(core_migration_items(
params
.migration_items
.into_iter()
.filter(|migration_item| {
!matches!(
migration_item.item_type,
ExternalAgentConfigMigrationItemType::Sessions
)
})
.map(|migration_item| CoreMigrationItem {
item_type: match migration_item.item_type {
ExternalAgentConfigMigrationItemType::Config => {
CoreMigrationItemType::Config
}
ExternalAgentConfigMigrationItemType::Skills => {
CoreMigrationItemType::Skills
}
ExternalAgentConfigMigrationItemType::AgentsMd => {
CoreMigrationItemType::AgentsMd
}
ExternalAgentConfigMigrationItemType::Plugins => {
CoreMigrationItemType::Plugins
}
ExternalAgentConfigMigrationItemType::McpServerConfig => {
CoreMigrationItemType::McpServerConfig
}
ExternalAgentConfigMigrationItemType::Subagents => {
CoreMigrationItemType::Subagents
}
ExternalAgentConfigMigrationItemType::Hooks => {
CoreMigrationItemType::Hooks
}
ExternalAgentConfigMigrationItemType::Commands => {
CoreMigrationItemType::Commands
}
ExternalAgentConfigMigrationItemType::Memory => {
CoreMigrationItemType::Memory
}
ExternalAgentConfigMigrationItemType::Sessions => {
CoreMigrationItemType::Sessions
}
},
description: migration_item.description,
cwd: migration_item.cwd,
details: migration_item.details.map(|details| {
super::service::MigrationDetails {
plugins: details
.plugins
.into_iter()
.map(|plugin| super::service::PluginsMigration {
marketplace_name: plugin.marketplace_name,
plugin_names: plugin.plugin_names,
})
.collect(),
skills: details
.skills
.into_iter()
.map(|skill| CoreNamedMigration { name: skill.name })
.collect(),
sessions: details
.sessions
.into_iter()
.map(|session| CoreSessionMigration {
path: session.path,
cwd: session.cwd,
title: session.title,
})
.collect(),
mcp_servers: details
.mcp_servers
.into_iter()
.map(|mcp_server| CoreNamedMigration {
name: mcp_server.name,
})
.collect(),
hooks: details
.hooks
.into_iter()
.map(|hook| CoreNamedMigration { name: hook.name })
.collect(),
subagents: details
.subagents
.into_iter()
.map(|subagent| CoreNamedMigration {
name: subagent.name,
})
.collect(),
commands: details
.commands
.into_iter()
.map(|command| CoreNamedMigration { name: command.name })
.collect(),
memory: details.memory,
}
}),
})
.filter(|item| item.item_type != ExternalAgentConfigMigrationItemType::Sessions)
.collect(),
)
))
.await
}
}
@@ -783,171 +597,6 @@ async fn record_completed_import_notification(
.await
}
fn protocol_import_history(
record: codex_state::ExternalAgentConfigImportHistoryRecord,
) -> Result<ExternalAgentConfigImportHistory, JSONRPCErrorError> {
let successes = record
.successes
.into_iter()
.map(protocol_import_success_record)
.collect::<Result<Vec<_>, _>>()?;
let failures = record
.failures
.into_iter()
.map(protocol_import_failure_record)
.collect::<Result<Vec<_>, _>>()?;
Ok(ExternalAgentConfigImportHistory {
import_id: record.import_id,
completed_at_ms: record.completed_at_ms,
successes,
failures,
})
}
fn protocol_import_success_record(
record: ExternalAgentConfigImportSuccessRecord,
) -> Result<ProtocolImportSuccess, JSONRPCErrorError> {
Ok(ProtocolImportSuccess {
item_type: protocol_import_record_item_type(record.item_type)?,
cwd: record.cwd,
source: record.source,
target: record.target,
})
}
fn protocol_import_failure_record(
record: ExternalAgentConfigImportFailureRecord,
) -> Result<ProtocolImportFailure, JSONRPCErrorError> {
Ok(ProtocolImportFailure {
item_type: protocol_import_record_item_type(record.item_type)?,
error_type: record.error_type,
sub_error_type: record.sub_error_type,
failure_stage: record.failure_stage,
message: record.message,
cwd: record.cwd,
source: record.source,
})
}
fn protocol_import_record_item_type(
item_type: String,
) -> Result<ExternalAgentConfigMigrationItemType, JSONRPCErrorError> {
serde_json::from_value(serde_json::Value::String(item_type.clone())).map_err(|err| {
internal_error(format!(
"failed to decode import item type {item_type}: {err}"
))
})
}
fn completed_notification(
import_id: String,
item_results: &[CoreImportItemResult],
) -> ExternalAgentConfigImportCompletedNotification {
let mut protocol_type_results: Vec<ProtocolImportTypeResult> = Vec::new();
for item_result in item_results {
let item_raw_errors = item_result
.raw_errors
.iter()
.map(protocol_import_raw_error)
.collect::<Vec<_>>();
let item_successes = item_result
.successes
.iter()
.map(protocol_import_success)
.collect::<Vec<_>>();
let item_type = protocol_migration_item_type(item_result.item_type);
if let Some(type_result) = protocol_type_results
.iter_mut()
.find(|type_result| type_result.item_type == item_type)
{
type_result.successes.extend(item_successes);
type_result.failures.extend(item_raw_errors);
} else {
protocol_type_results.push(ProtocolImportTypeResult {
item_type,
successes: item_successes,
failures: item_raw_errors,
});
}
}
protocol_type_results.sort_by_key(|type_result| match type_result.item_type {
ExternalAgentConfigMigrationItemType::Config => 0,
ExternalAgentConfigMigrationItemType::Skills => 1,
ExternalAgentConfigMigrationItemType::AgentsMd => 2,
ExternalAgentConfigMigrationItemType::Plugins => 3,
ExternalAgentConfigMigrationItemType::McpServerConfig => 4,
ExternalAgentConfigMigrationItemType::Subagents => 5,
ExternalAgentConfigMigrationItemType::Hooks => 6,
ExternalAgentConfigMigrationItemType::Commands => 7,
ExternalAgentConfigMigrationItemType::Sessions => 8,
ExternalAgentConfigMigrationItemType::Memory => 9,
});
ExternalAgentConfigImportCompletedNotification {
import_id,
item_type_results: protocol_type_results,
}
}
fn protocol_import_type_result(item_result: &CoreImportItemResult) -> ProtocolImportTypeResult {
ProtocolImportTypeResult {
item_type: protocol_migration_item_type(item_result.item_type),
successes: item_result
.successes
.iter()
.map(protocol_import_success)
.collect(),
failures: item_result
.raw_errors
.iter()
.map(protocol_import_raw_error)
.collect(),
}
}
fn protocol_import_success(
success: &super::service::ExternalAgentConfigImportSuccess,
) -> ProtocolImportSuccess {
ProtocolImportSuccess {
item_type: protocol_migration_item_type(success.item_type),
cwd: success.cwd.clone(),
source: success.source.clone(),
target: success.target.clone(),
}
}
fn protocol_import_raw_error(raw_error: &CoreImportRawError) -> ProtocolImportFailure {
ProtocolImportFailure {
item_type: protocol_migration_item_type(raw_error.item_type),
error_type: raw_error.error_type.clone(),
sub_error_type: raw_error.sub_error_type.clone(),
failure_stage: raw_error.failure_stage.clone(),
message: raw_error.message.clone(),
cwd: raw_error.cwd.clone(),
source: raw_error.source.clone(),
}
}
fn protocol_migration_item_type(
item_type: CoreMigrationItemType,
) -> ExternalAgentConfigMigrationItemType {
match item_type {
CoreMigrationItemType::Config => ExternalAgentConfigMigrationItemType::Config,
CoreMigrationItemType::Skills => ExternalAgentConfigMigrationItemType::Skills,
CoreMigrationItemType::AgentsMd => ExternalAgentConfigMigrationItemType::AgentsMd,
CoreMigrationItemType::Plugins => ExternalAgentConfigMigrationItemType::Plugins,
CoreMigrationItemType::McpServerConfig => {
ExternalAgentConfigMigrationItemType::McpServerConfig
}
CoreMigrationItemType::Subagents => ExternalAgentConfigMigrationItemType::Subagents,
CoreMigrationItemType::Hooks => ExternalAgentConfigMigrationItemType::Hooks,
CoreMigrationItemType::Commands => ExternalAgentConfigMigrationItemType::Commands,
CoreMigrationItemType::Memory => ExternalAgentConfigMigrationItemType::Memory,
CoreMigrationItemType::Sessions => ExternalAgentConfigMigrationItemType::Sessions,
}
}
fn apply_plugin_outcome_to_item_result(
item_result: &mut CoreImportItemResult,
plugin_outcome: PluginImportOutcome,