mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Refresh live thread tools through app/installed (#43039)
## Why Calling `app/installed` with `threadId` and `forceRefresh: true` refreshed a separate runtime snapshot without updating the thread's tools for subsequent turns. ## What changed Use the thread's current configuration and refresh its live app tools when a thread is specified. Account for the refreshed snapshot's model-visible tools when reporting `callable`. Requests without `threadId` continue to use a separate runtime for refreshes. ## Testing Add integration coverage verifying that refreshed tools replace previous tools in subsequent model requests and can be called, and that a failed refresh preserves the last working tools. Extend thread configuration coverage to include forced refreshes. GitOrigin-RevId: fc13e28a59da00af470cf25c0cde356504e1575b
This commit is contained in:
@@ -47,25 +47,40 @@ impl AppsRequestProcessor {
|
||||
let mut snapshot_age = None;
|
||||
let mut snapshot_tool_count = 0;
|
||||
let result = async {
|
||||
let config = self
|
||||
.load_apps_config(params.thread_id.as_deref())
|
||||
.await?;
|
||||
let (config, thread) = match params.thread_id.as_deref() {
|
||||
Some(thread_id) => {
|
||||
let (_, thread) = self.load_thread(thread_id).await?;
|
||||
let config = thread.config().await;
|
||||
(config.as_ref().clone(), Some(thread))
|
||||
}
|
||||
None => (
|
||||
self.load_latest_config(/*fallback_cwd*/ None).await?,
|
||||
None,
|
||||
),
|
||||
};
|
||||
let auth = self.auth_manager.auth().await;
|
||||
let runtime_enabled = config
|
||||
.features
|
||||
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend));
|
||||
|
||||
let mcp_manager = self.thread_manager.mcp_manager();
|
||||
let mcp_config = mcp_manager.runtime_config(&config).await;
|
||||
let mut mcp_servers = effective_mcp_servers(&mcp_config, auth.as_ref());
|
||||
mcp_servers.retain(|name, _| name == CODEX_APPS_MCP_SERVER_NAME);
|
||||
let mcp_config = Arc::new(mcp_config.for_threadless_operations(&mcp_servers));
|
||||
let cache_key = connector_runtime_context_key(auth.as_ref());
|
||||
let previous_snapshot = mcp_manager
|
||||
.codex_apps_tools_cache()
|
||||
.current_snapshot(config.codex_home.to_path_buf(), cache_key.clone());
|
||||
let snapshot = if force_refresh && runtime_enabled {
|
||||
let mut model_visible_tool_names = None;
|
||||
let tools = if force_refresh && runtime_enabled {
|
||||
let refresh_result = async {
|
||||
if let Some(thread) = thread {
|
||||
let snapshot = thread.refresh_codex_apps_tools().await?;
|
||||
model_visible_tool_names = Some(snapshot.model_visible_tool_names);
|
||||
snapshot_age = Some(Duration::ZERO);
|
||||
return Ok(snapshot.tools);
|
||||
}
|
||||
let mcp_config = mcp_manager.runtime_config(&config).await;
|
||||
let mut mcp_servers = effective_mcp_servers(&mcp_config, auth.as_ref());
|
||||
mcp_servers.retain(|name, _| name == CODEX_APPS_MCP_SERVER_NAME);
|
||||
let mcp_config = Arc::new(mcp_config.for_threadless_operations(&mcp_servers));
|
||||
anyhow::ensure!(
|
||||
!mcp_servers.is_empty(),
|
||||
"host-owned MCP server '{CODEX_APPS_MCP_SERVER_NAME}' is not enabled"
|
||||
@@ -125,14 +140,17 @@ impl AppsRequestProcessor {
|
||||
};
|
||||
cancellation_token.cancel();
|
||||
runtime.shutdown().await;
|
||||
result
|
||||
result.map(|snapshot| {
|
||||
snapshot_age = Some(snapshot.age());
|
||||
snapshot.tools().to_vec()
|
||||
})
|
||||
}
|
||||
.await;
|
||||
|
||||
match refresh_result {
|
||||
Ok(snapshot) => {
|
||||
Ok(tools) => {
|
||||
refresh_disposition = "success";
|
||||
Some(snapshot)
|
||||
tools
|
||||
}
|
||||
Err(err) => {
|
||||
refresh_disposition = "error";
|
||||
@@ -147,17 +165,22 @@ impl AppsRequestProcessor {
|
||||
refresh_disposition = "skipped_apps_disabled";
|
||||
retained_previous_snapshot = previous_snapshot.is_some();
|
||||
}
|
||||
previous_snapshot
|
||||
};
|
||||
let Some(snapshot) = snapshot else {
|
||||
return Ok(AppsInstalledResponse { apps: Vec::new() });
|
||||
previous_snapshot.map_or_else(Vec::new, |snapshot| {
|
||||
snapshot_age = Some(snapshot.age());
|
||||
snapshot.tools().to_vec()
|
||||
})
|
||||
};
|
||||
|
||||
snapshot_age = Some(snapshot.age());
|
||||
snapshot_tool_count = snapshot.tools().len();
|
||||
snapshot_tool_count = tools.len();
|
||||
let apps = installed_connector_runtime(
|
||||
&config.config_layer_stack,
|
||||
snapshot.tools().iter().map(connector_runtime_tool),
|
||||
tools.iter().map(|tool| {
|
||||
let mut runtime_tool = connector_runtime_tool(tool);
|
||||
if let Some(names) = &model_visible_tool_names {
|
||||
runtime_tool.model_visible &= names.contains(tool.tool.name.as_ref());
|
||||
}
|
||||
runtime_tool
|
||||
}),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|app| InstalledApp {
|
||||
|
||||
Reference in New Issue
Block a user