Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4d25fe5
update
mzeng-openai Jan 22, 2026
9a69a97
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 22, 2026
b5a37c2
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 22, 2026
0b20959
update
mzeng-openai Jan 22, 2026
5a30030
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 23, 2026
427a5d7
update
mzeng-openai Jan 23, 2026
5092039
update
mzeng-openai Jan 23, 2026
2695137
update
mzeng-openai Jan 23, 2026
81329e2
update
mzeng-openai Jan 23, 2026
8266055
update
mzeng-openai Jan 23, 2026
94720ef
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 27, 2026
8306a4d
update
mzeng-openai Jan 27, 2026
7e399dc
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 27, 2026
b1613a3
update
mzeng-openai Jan 27, 2026
fe040d9
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 28, 2026
a68d764
update
mzeng-openai Jan 28, 2026
8e50fca
update
mzeng-openai Jan 28, 2026
aa5e9e3
update
mzeng-openai Jan 28, 2026
11d973b
update
mzeng-openai Jan 28, 2026
d56c43f
update
mzeng-openai Jan 28, 2026
3eb7ea7
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 28, 2026
82bf7b7
update
mzeng-openai Jan 28, 2026
2abe93b
update
mzeng-openai Jan 28, 2026
651ebb8
Merge branch 'main' of https://github.com/openai/codex into dev/mzeng…
mzeng-openai Jan 28, 2026
5ddd52e
update
mzeng-openai Jan 29, 2026
190dd7a
update
mzeng-openai Jan 29, 2026
c043313
update
mzeng-openai Jan 29, 2026
ec58f7d
update
mzeng-openai Jan 29, 2026
85c21f8
update
mzeng-openai Jan 29, 2026
d2fbc66
update
mzeng-openai Jan 29, 2026
6c490cc
Merge branch 'main' into dev/mzeng/connectors_codex_cli_4
mzeng-openai Jan 29, 2026
18f0eca
update
mzeng-openai Jan 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,8 @@ pub struct AppInfo {
pub name: String,
pub description: Option<String>,
pub logo_url: Option<String>,
pub logo_url_dark: Option<String>,
pub distribution_channel: Option<String>,
pub install_url: Option<String>,
#[serde(default)]
pub is_accessible: bool,
Expand Down Expand Up @@ -1897,6 +1899,10 @@ pub enum UserInput {
name: String,
path: PathBuf,
},
Mention {
name: String,
path: String,
},
}

impl UserInput {
Expand All @@ -1912,6 +1918,7 @@ impl UserInput {
UserInput::Image { url } => CoreUserInput::Image { image_url: url },
UserInput::LocalImage { path } => CoreUserInput::LocalImage { path },
UserInput::Skill { name, path } => CoreUserInput::Skill { name, path },
UserInput::Mention { name, path } => CoreUserInput::Mention { name, path },
}
}
}
Expand All @@ -1929,6 +1936,7 @@ impl From<CoreUserInput> for UserInput {
CoreUserInput::Image { image_url } => UserInput::Image { url: image_url },
CoreUserInput::LocalImage { path } => UserInput::LocalImage { path },
CoreUserInput::Skill { name, path } => UserInput::Skill { name, path },
CoreUserInput::Mention { name, path } => UserInput::Mention { name, path },
_ => unreachable!("unsupported user input variant"),
}
}
Expand Down Expand Up @@ -2732,6 +2740,10 @@ mod tests {
name: "skill-creator".to_string(),
path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"),
},
CoreUserInput::Mention {
name: "Demo App".to_string(),
path: "app://demo-app".to_string(),
},
],
});

Expand All @@ -2754,6 +2766,10 @@ mod tests {
name: "skill-creator".to_string(),
path: PathBuf::from("/repo/.codex/skills/skill-creator/SKILL.md"),
},
UserInput::Mention {
name: "Demo App".to_string(),
path: "app://demo-app".to_string(),
},
],
}
);
Expand Down
72 changes: 72 additions & 0 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Events](#events)
- [Approvals](#approvals)
- [Skills](#skills)
- [Apps](#apps)
- [Auth endpoints](#auth-endpoints)

## Protocol
Expand Down Expand Up @@ -296,6 +297,26 @@ Invoke a skill explicitly by including `$<skill-name>` in the text input and add
} } }
```

### Example: Start a turn (invoke an app)

Invoke an app by including `$<app-slug>` in the text input and adding a `mention` input item with the app id in `app://<connector-id>` form.

```json
{ "method": "turn/start", "id": 34, "params": {
"threadId": "thr_123",
"input": [
{ "type": "text", "text": "$demo-app Summarize the latest updates." },
{ "type": "mention", "name": "Demo App", "path": "app://demo-app" }
]
} }
{ "id": 34, "result": { "turn": {
"id": "turn_458",
"status": "inProgress",
"items": [],
"error": null
} } }
```

### Example: Interrupt an active turn

You can cancel a running Turn with `turn/interrupt`.
Expand Down Expand Up @@ -583,6 +604,57 @@ To enable or disable a skill by path:
}
```

## Apps

Use `app/list` to fetch available apps (connectors). Each entry includes metadata like the app `id`, display `name`, `installUrl`, and whether it is currently accessible.

```json
{ "method": "app/list", "id": 50, "params": {
"cursor": null,
"limit": 50
} }
{ "id": 50, "result": {
"data": [
{
"id": "demo-app",
"name": "Demo App",
"description": "Example connector for documentation.",
"logoUrl": "https://example.com/demo-app.png",
"logoUrlDark": null,
"distributionChannel": null,
"installUrl": "https://chatgpt.com/apps/demo-app/demo-app",
"isAccessible": true
}
],
"nextCursor": null
} }
```

Invoke an app by inserting `$<app-slug>` in the text input. The slug is derived from the app name and lowercased with non-alphanumeric characters replaced by `-` (for example, "Demo App" becomes `$demo-app`). Add a `mention` input item (recommended) so the server uses the exact `app://<connector-id>` path rather than guessing by name.

Example:

```
$demo-app Pull the latest updates from the team.
```

```json
{
"method": "turn/start",
"id": 51,
"params": {
"threadId": "thread-1",
"input": [
{
"type": "text",
"text": "$demo-app Pull the latest updates from the team."
},
{ "type": "mention", "name": "Demo App", "path": "app://demo-app" }
]
}
}
```

## Auth endpoints

The JSON-RPC auth/account surface exposes request/response methods plus server-initiated notifications (no `id`). Use these to determine auth state, start or cancel logins, logout, and inspect ChatGPT rate limits.
Expand Down
16 changes: 2 additions & 14 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use codex_app_server_protocol::AccountLoginCompletedNotification;
use codex_app_server_protocol::AccountUpdatedNotification;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
use codex_app_server_protocol::AppInfo as ApiAppInfo;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::ArchiveConversationParams;
Expand Down Expand Up @@ -3712,7 +3711,7 @@ impl CodexMessageProcessor {
}
};

if !config.features.enabled(Feature::Connectors) {
if !config.features.enabled(Feature::Apps) {
self.outgoing
.send_response(
request_id,
Expand Down Expand Up @@ -3775,18 +3774,7 @@ impl CodexMessageProcessor {
}

let end = start.saturating_add(effective_limit).min(total);
let data = connectors[start..end]
.iter()
.cloned()
.map(|connector| ApiAppInfo {
id: connector.connector_id,
name: connector.connector_name,
description: connector.connector_description,
logo_url: connector.logo_url,
install_url: connector.install_url,
is_accessible: connector.is_accessible,
})
.collect();
let data = connectors[start..end].to_vec();

let next_cursor = if end < total {
Some(end.to_string())
Expand Down
63 changes: 41 additions & 22 deletions codex-rs/app-server/tests/suite/v2/app_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ use axum::extract::State;
use axum::http::HeaderMap;
use axum::http::StatusCode;
use axum::http::header::AUTHORIZATION;
use axum::routing::post;
use axum::routing::get;
use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::connectors::ConnectorInfo;
use pretty_assertions::assert_eq;
use rmcp::handler::server::ServerHandler;
use rmcp::model::JsonObject;
Expand Down Expand Up @@ -71,19 +70,23 @@ async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {
#[tokio::test]
async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
let connectors = vec![
ConnectorInfo {
connector_id: "alpha".to_string(),
connector_name: "Alpha".to_string(),
connector_description: Some("Alpha connector".to_string()),
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
install_url: None,
is_accessible: false,
},
ConnectorInfo {
connector_id: "beta".to_string(),
connector_name: "beta".to_string(),
connector_description: None,
AppInfo {
id: "beta".to_string(),
name: "beta".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: None,
is_accessible: false,
},
Expand Down Expand Up @@ -127,6 +130,8 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
name: "Beta App".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: true,
},
Expand All @@ -135,6 +140,8 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
},
Expand All @@ -150,19 +157,23 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
#[tokio::test]
async fn list_apps_paginates_results() -> Result<()> {
let connectors = vec![
ConnectorInfo {
connector_id: "alpha".to_string(),
connector_name: "Alpha".to_string(),
connector_description: Some("Alpha connector".to_string()),
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: None,
is_accessible: false,
},
ConnectorInfo {
connector_id: "beta".to_string(),
connector_name: "beta".to_string(),
connector_description: None,
AppInfo {
id: "beta".to_string(),
name: "beta".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: None,
is_accessible: false,
},
Expand Down Expand Up @@ -206,6 +217,8 @@ async fn list_apps_paginates_results() -> Result<()> {
name: "Beta App".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: Some("https://chatgpt.com/apps/beta/beta".to_string()),
is_accessible: true,
}];
Expand Down Expand Up @@ -234,6 +247,8 @@ async fn list_apps_paginates_results() -> Result<()> {
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
}];
Expand Down Expand Up @@ -289,13 +304,13 @@ impl ServerHandler for AppListMcpServer {
}

async fn start_apps_server(
connectors: Vec<ConnectorInfo>,
connectors: Vec<AppInfo>,
tools: Vec<Tool>,
) -> Result<(String, JoinHandle<()>)> {
let state = AppsServerState {
expected_bearer: "Bearer chatgpt-token".to_string(),
expected_account_id: "account-123".to_string(),
response: json!({ "connectors": connectors }),
response: json!({ "apps": connectors, "next_token": null }),
};
let state = Arc::new(state);
let tools = Arc::new(tools);
Expand All @@ -313,7 +328,11 @@ async fn start_apps_server(
);

let router = Router::new()
.route("/aip/connectors/list_accessible", post(list_connectors))
.route("/connectors/directory/list", get(list_directory_connectors))
.route(
"/connectors/directory/list_workspace",
get(list_directory_connectors),
)
.with_state(state)
.nest_service("/api/codex/apps", mcp_service);

Expand All @@ -324,7 +343,7 @@ async fn start_apps_server(
Ok((format!("http://{addr}"), handle))
}

async fn list_connectors(
async fn list_directory_connectors(
State(state): State<Arc<AppsServerState>>,
headers: HeaderMap,
) -> Result<impl axum::response::IntoResponse, StatusCode> {
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/chatgpt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["full"] }
codex-git = { workspace = true }
urlencoding = { workspace = true }

[dev-dependencies]
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
Loading
Loading