mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: Use installation ID in remote enrollments (#21662)
* Pass installation ID for storage on enrollments server for deduping/grouping multiple appservers per installation * Pass installation ID in remoteControl/status/changed events
This commit is contained in:
@@ -2737,7 +2737,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"description": "Current remote-control connection status and remote identity exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
@@ -2745,11 +2745,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"installationId",
|
||||
"status"
|
||||
],
|
||||
"type": "object"
|
||||
|
||||
+5
-1
@@ -13306,7 +13306,7 @@
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"description": "Current remote-control connection status and remote identity exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
@@ -13314,11 +13314,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/v2/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"installationId",
|
||||
"status"
|
||||
],
|
||||
"title": "RemoteControlStatusChangedNotification",
|
||||
|
||||
+5
-1
@@ -9899,7 +9899,7 @@
|
||||
},
|
||||
"RemoteControlStatusChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"description": "Current remote-control connection status and remote identity exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
@@ -9907,11 +9907,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"installationId",
|
||||
"status"
|
||||
],
|
||||
"title": "RemoteControlStatusChangedNotification",
|
||||
|
||||
+5
-1
@@ -11,7 +11,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Current remote-control connection status and environment id exposed to clients.",
|
||||
"description": "Current remote-control connection status and remote identity exposed to clients.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": [
|
||||
@@ -19,11 +19,15 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"installationId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/RemoteControlConnectionStatus"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"installationId",
|
||||
"status"
|
||||
],
|
||||
"title": "RemoteControlStatusChangedNotification",
|
||||
|
||||
Generated
+2
-2
@@ -4,6 +4,6 @@
|
||||
import type { RemoteControlConnectionStatus } from "./RemoteControlConnectionStatus";
|
||||
|
||||
/**
|
||||
* Current remote-control connection status and environment id exposed to clients.
|
||||
* Current remote-control connection status and remote identity exposed to clients.
|
||||
*/
|
||||
export type RemoteControlStatusChangedNotification = { status: RemoteControlConnectionStatus, environmentId: string | null, };
|
||||
export type RemoteControlStatusChangedNotification = { status: RemoteControlConnectionStatus, installationId: string, environmentId: string | null, };
|
||||
|
||||
@@ -3,12 +3,13 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// Current remote-control connection status and environment id exposed to clients.
|
||||
/// Current remote-control connection status and remote identity exposed to clients.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct RemoteControlStatusChangedNotification {
|
||||
pub status: RemoteControlConnectionStatus,
|
||||
pub installation_id: String,
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ pub use transport::AppServerTransportParseError;
|
||||
pub use transport::CHANNEL_CAPACITY;
|
||||
pub use transport::ConnectionOrigin;
|
||||
pub use transport::RemoteControlHandle;
|
||||
pub use transport::RemoteControlStartConfig;
|
||||
pub use transport::TransportEvent;
|
||||
pub use transport::app_server_control_socket_path;
|
||||
pub use transport::auth;
|
||||
|
||||
@@ -31,6 +31,7 @@ mod unix_socket_tests;
|
||||
mod websocket;
|
||||
|
||||
pub use remote_control::RemoteControlHandle;
|
||||
pub use remote_control::RemoteControlStartConfig;
|
||||
pub use remote_control::start_remote_control;
|
||||
pub use stdio::start_stdio_connection;
|
||||
pub use unix_socket::start_control_socket_acceptor;
|
||||
|
||||
@@ -19,6 +19,7 @@ const REQUEST_ID_HEADER: &str = "x-request-id";
|
||||
const OAI_REQUEST_ID_HEADER: &str = "x-oai-request-id";
|
||||
const CF_RAY_HEADER: &str = "cf-ray";
|
||||
pub(super) const REMOTE_CONTROL_ACCOUNT_ID_HEADER: &str = "chatgpt-account-id";
|
||||
pub(super) const REMOTE_CONTROL_INSTALLATION_ID_HEADER: &str = "x-codex-installation-id";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct RemoteControlEnrollment {
|
||||
@@ -193,6 +194,7 @@ pub(crate) fn format_headers(headers: &HeaderMap) -> String {
|
||||
pub(super) async fn enroll_remote_control_server(
|
||||
remote_control_target: &RemoteControlTarget,
|
||||
auth: &RemoteControlConnectionAuth,
|
||||
installation_id: &str,
|
||||
) -> io::Result<RemoteControlEnrollment> {
|
||||
let enroll_url = &remote_control_target.enroll_url;
|
||||
let server_name = gethostname().to_string_lossy().trim().to_string();
|
||||
@@ -201,6 +203,7 @@ pub(super) async fn enroll_remote_control_server(
|
||||
os: std::env::consts::OS,
|
||||
arch: std::env::consts::ARCH,
|
||||
app_server_version: env!("CARGO_PKG_VERSION"),
|
||||
installation_id: installation_id.to_string(),
|
||||
};
|
||||
let client = build_reqwest_client();
|
||||
let mut auth_headers = HeaderMap::new();
|
||||
@@ -210,6 +213,7 @@ pub(super) async fn enroll_remote_control_server(
|
||||
.timeout(REMOTE_CONTROL_ENROLL_TIMEOUT)
|
||||
.headers(auth_headers)
|
||||
.header(REMOTE_CONTROL_ACCOUNT_ID_HEADER, &auth.account_id)
|
||||
.header(REMOTE_CONTROL_INSTALLATION_ID_HEADER, installation_id)
|
||||
.json(&request);
|
||||
|
||||
let response = http_request.send().await.map_err(|err| {
|
||||
@@ -459,6 +463,7 @@ mod tests {
|
||||
auth_provider: codex_model_provider::unauthenticated_auth_provider(),
|
||||
account_id: "account_id".to_string(),
|
||||
},
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
)
|
||||
.await
|
||||
.expect_err("invalid response should fail to parse");
|
||||
|
||||
@@ -28,6 +28,11 @@ use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::warn;
|
||||
|
||||
pub struct RemoteControlStartConfig {
|
||||
pub remote_control_url: String,
|
||||
pub installation_id: String,
|
||||
}
|
||||
|
||||
pub(super) struct QueuedServerEnvelope {
|
||||
pub(super) event: ServerEvent,
|
||||
pub(super) client_id: ClientId,
|
||||
@@ -62,7 +67,7 @@ impl RemoteControlHandle {
|
||||
}
|
||||
|
||||
pub async fn start_remote_control(
|
||||
remote_control_url: String,
|
||||
config: RemoteControlStartConfig,
|
||||
state_db: Option<Arc<StateRuntime>>,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
transport_event_tx: mpsc::Sender<TransportEvent>,
|
||||
@@ -77,7 +82,7 @@ pub async fn start_remote_control(
|
||||
warn!("remote control disabled because sqlite state db is unavailable");
|
||||
}
|
||||
let remote_control_target = if initial_enabled {
|
||||
Some(normalize_remote_control_url(&remote_control_url)?)
|
||||
Some(normalize_remote_control_url(&config.remote_control_url)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -89,14 +94,18 @@ pub async fn start_remote_control(
|
||||
} else {
|
||||
RemoteControlConnectionStatus::Disabled
|
||||
},
|
||||
installation_id: config.installation_id.clone(),
|
||||
environment_id: None,
|
||||
};
|
||||
let (status_tx, _status_rx) = watch::channel(initial_status);
|
||||
let status_publisher = RemoteControlStatusPublisher::new(status_tx.clone());
|
||||
let join_handle = tokio::spawn(async move {
|
||||
RemoteControlWebsocket::new(
|
||||
remote_control_url,
|
||||
remote_control_target,
|
||||
websocket::RemoteControlWebsocketConfig {
|
||||
remote_control_url: config.remote_control_url,
|
||||
installation_id: config.installation_id,
|
||||
remote_control_target,
|
||||
},
|
||||
state_db,
|
||||
auth_manager,
|
||||
RemoteControlChannels {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub(super) struct EnrollRemoteServerRequest {
|
||||
pub(super) os: &'static str,
|
||||
pub(super) arch: &'static str,
|
||||
pub(super) app_server_version: &'static str,
|
||||
pub(super) installation_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::enroll::REMOTE_CONTROL_ACCOUNT_ID_HEADER;
|
||||
use super::enroll::REMOTE_CONTROL_INSTALLATION_ID_HEADER;
|
||||
use super::enroll::RemoteControlEnrollment;
|
||||
use super::enroll::load_persisted_remote_control_enrollment;
|
||||
use super::enroll::update_persisted_remote_control_enrollment;
|
||||
@@ -56,6 +57,8 @@ use tokio_tungstenite::accept_hdr_async;
|
||||
use tokio_tungstenite::tungstenite;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
|
||||
|
||||
fn remote_control_auth_manager() -> Arc<AuthManager> {
|
||||
auth_manager_from_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
}
|
||||
@@ -131,6 +134,7 @@ async fn expect_remote_control_status(
|
||||
if let Some(expected_status) = expected_status {
|
||||
assert_eq!(status.status, expected_status);
|
||||
}
|
||||
assert_eq!(status.installation_id, TEST_INSTALLATION_ID);
|
||||
assert_eq!(status.environment_id.as_deref(), expected_environment_id);
|
||||
}
|
||||
|
||||
@@ -173,7 +177,10 @@ async fn remote_control_transport_manages_virtual_clients_and_routes_messages()
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -449,7 +456,10 @@ async fn remote_control_transport_reconnects_after_disconnect() {
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -528,7 +538,10 @@ async fn remote_control_start_allows_remote_control_invalid_url_when_disabled()
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, _remote_handle) = start_remote_control(
|
||||
"https://internal.example.com/backend-api/".to_string(),
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url: "https://internal.example.com/backend-api/".to_string(),
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
/*state_db*/ None,
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -564,7 +577,10 @@ async fn remote_control_start_allows_missing_auth_when_enabled() {
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, _remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
auth_manager,
|
||||
transport_event_tx,
|
||||
@@ -596,7 +612,10 @@ async fn remote_control_start_reports_missing_state_db_as_disabled_when_enabled(
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
/*state_db*/ None,
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -611,6 +630,7 @@ async fn remote_control_start_reports_missing_state_db_as_disabled_when_enabled(
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Disabled,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
}
|
||||
);
|
||||
@@ -645,7 +665,10 @@ async fn remote_control_handle_set_enabled_stops_and_restarts_connections() {
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -672,6 +695,7 @@ async fn remote_control_handle_set_enabled_stops_and_restarts_connections() {
|
||||
&mut status_rx,
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connected,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_test".to_string()),
|
||||
},
|
||||
)
|
||||
@@ -682,6 +706,7 @@ async fn remote_control_handle_set_enabled_stops_and_restarts_connections() {
|
||||
&mut status_rx,
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Disabled,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
},
|
||||
)
|
||||
@@ -698,6 +723,7 @@ async fn remote_control_handle_set_enabled_stops_and_restarts_connections() {
|
||||
&mut status_rx,
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_test".to_string()),
|
||||
},
|
||||
)
|
||||
@@ -729,7 +755,10 @@ async fn remote_control_transport_clears_outgoing_buffer_when_backend_acks() {
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -904,7 +933,10 @@ async fn remote_control_http_mode_enrolls_before_connecting() {
|
||||
let expected_server_name = gethostname().to_string_lossy().trim().to_string();
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(remote_control_state_runtime(&codex_home).await),
|
||||
remote_control_auth_manager(),
|
||||
transport_event_tx,
|
||||
@@ -929,6 +961,12 @@ async fn remote_control_http_mode_enrolls_before_connecting() {
|
||||
enroll_request.headers.get(REMOTE_CONTROL_ACCOUNT_ID_HEADER),
|
||||
Some(&"account_id".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
enroll_request
|
||||
.headers
|
||||
.get(REMOTE_CONTROL_INSTALLATION_ID_HEADER),
|
||||
Some(&TEST_INSTALLATION_ID.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<serde_json::Value>(&enroll_request.body)
|
||||
.expect("enroll body should deserialize"),
|
||||
@@ -937,6 +975,7 @@ async fn remote_control_http_mode_enrolls_before_connecting() {
|
||||
"os": std::env::consts::OS,
|
||||
"arch": std::env::consts::ARCH,
|
||||
"app_server_version": env!("CARGO_PKG_VERSION"),
|
||||
"installation_id": TEST_INSTALLATION_ID,
|
||||
})
|
||||
);
|
||||
respond_with_json(
|
||||
@@ -967,6 +1006,12 @@ async fn remote_control_http_mode_enrolls_before_connecting() {
|
||||
.get(REMOTE_CONTROL_ACCOUNT_ID_HEADER),
|
||||
Some(&"account_id".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
handshake_request
|
||||
.headers
|
||||
.get(REMOTE_CONTROL_INSTALLATION_ID_HEADER),
|
||||
Some(&TEST_INSTALLATION_ID.to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
handshake_request.headers.get("x-codex-server-id"),
|
||||
Some(&"srv_e_test".to_string())
|
||||
@@ -1128,7 +1173,10 @@ async fn remote_control_http_mode_reuses_persisted_enrollment_before_reenrolling
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, _remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(state_db.clone()),
|
||||
remote_control_auth_manager_with_home(&codex_home),
|
||||
transport_event_tx,
|
||||
@@ -1196,7 +1244,10 @@ async fn remote_control_stdio_mode_waits_for_client_name_before_connecting() {
|
||||
let (app_server_client_name_tx, app_server_client_name_rx) = oneshot::channel::<String>();
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, _remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(state_db.clone()),
|
||||
remote_control_auth_manager_with_home(&codex_home),
|
||||
transport_event_tx,
|
||||
@@ -1255,7 +1306,10 @@ async fn remote_control_waits_for_account_id_before_enrolling() {
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, _remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(state_db.clone()),
|
||||
auth_manager,
|
||||
transport_event_tx,
|
||||
@@ -1338,7 +1392,10 @@ async fn remote_control_http_mode_clears_stale_persisted_enrollment_after_404()
|
||||
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
|
||||
let shutdown_token = CancellationToken::new();
|
||||
let (remote_task, remote_handle) = start_remote_control(
|
||||
remote_control_url,
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
},
|
||||
Some(state_db.clone()),
|
||||
remote_control_auth_manager_with_home(&codex_home),
|
||||
transport_event_tx,
|
||||
|
||||
@@ -55,6 +55,7 @@ use tracing::warn;
|
||||
|
||||
pub(super) const REMOTE_CONTROL_PROTOCOL_VERSION: &str = "3";
|
||||
pub(super) const REMOTE_CONTROL_ACCOUNT_ID_HEADER: &str = "chatgpt-account-id";
|
||||
pub(super) const REMOTE_CONTROL_INSTALLATION_ID_HEADER: &str = "x-codex-installation-id";
|
||||
const REMOTE_CONTROL_SUBSCRIBE_CURSOR_HEADER: &str = "x-codex-subscribe-cursor";
|
||||
const REMOTE_CONTROL_WEBSOCKET_PING_INTERVAL: std::time::Duration =
|
||||
std::time::Duration::from_secs(10);
|
||||
@@ -214,6 +215,7 @@ impl WebsocketState {
|
||||
|
||||
pub(crate) struct RemoteControlWebsocket {
|
||||
remote_control_url: String,
|
||||
installation_id: String,
|
||||
remote_control_target: Option<RemoteControlTarget>,
|
||||
state_db: Option<Arc<StateRuntime>>,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
@@ -229,6 +231,12 @@ pub(crate) struct RemoteControlWebsocket {
|
||||
enabled_rx: watch::Receiver<bool>,
|
||||
}
|
||||
|
||||
pub(crate) struct RemoteControlWebsocketConfig {
|
||||
pub(crate) remote_control_url: String,
|
||||
pub(crate) installation_id: String,
|
||||
pub(crate) remote_control_target: Option<RemoteControlTarget>,
|
||||
}
|
||||
|
||||
enum ConnectOutcome {
|
||||
Connected(Box<WebSocketStream<MaybeTlsStream<TcpStream>>>),
|
||||
Disabled,
|
||||
@@ -254,6 +262,7 @@ impl RemoteControlStatusPublisher {
|
||||
self.tx.send_if_modified(|status| {
|
||||
let next_status = RemoteControlStatusChangedNotification {
|
||||
status: connection_status,
|
||||
installation_id: status.installation_id.clone(),
|
||||
environment_id: if connection_status == RemoteControlConnectionStatus::Disabled {
|
||||
None
|
||||
} else {
|
||||
@@ -276,6 +285,7 @@ impl RemoteControlStatusPublisher {
|
||||
}
|
||||
let next_status = RemoteControlStatusChangedNotification {
|
||||
status: status.status,
|
||||
installation_id: status.installation_id.clone(),
|
||||
environment_id,
|
||||
};
|
||||
if *status == next_status {
|
||||
@@ -290,14 +300,14 @@ impl RemoteControlStatusPublisher {
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct RemoteControlConnectOptions<'a> {
|
||||
installation_id: &'a str,
|
||||
subscribe_cursor: Option<&'a str>,
|
||||
app_server_client_name: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl RemoteControlWebsocket {
|
||||
pub(crate) fn new(
|
||||
remote_control_url: String,
|
||||
remote_control_target: Option<RemoteControlTarget>,
|
||||
config: RemoteControlWebsocketConfig,
|
||||
state_db: Option<Arc<StateRuntime>>,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
channels: RemoteControlChannels,
|
||||
@@ -315,8 +325,9 @@ impl RemoteControlWebsocket {
|
||||
let auth_recovery = auth_manager.unauthorized_recovery();
|
||||
|
||||
Self {
|
||||
remote_control_url,
|
||||
remote_control_target,
|
||||
remote_control_url: config.remote_control_url,
|
||||
installation_id: config.installation_id,
|
||||
remote_control_target: config.remote_control_target,
|
||||
state_db,
|
||||
auth_manager,
|
||||
status_publisher: channels.status_publisher,
|
||||
@@ -442,6 +453,7 @@ impl RemoteControlWebsocket {
|
||||
loop {
|
||||
let subscribe_cursor = self.state.lock().await.subscribe_cursor.clone();
|
||||
let connect_options = RemoteControlConnectOptions {
|
||||
installation_id: &self.installation_id,
|
||||
subscribe_cursor: subscribe_cursor.as_deref(),
|
||||
app_server_client_name,
|
||||
};
|
||||
@@ -918,6 +930,7 @@ fn build_remote_control_websocket_request(
|
||||
websocket_url: &str,
|
||||
enrollment: &RemoteControlEnrollment,
|
||||
auth: &RemoteControlConnectionAuth,
|
||||
installation_id: &str,
|
||||
subscribe_cursor: Option<&str>,
|
||||
) -> io::Result<tungstenite::http::Request<()>> {
|
||||
let mut request = websocket_url.into_client_request().map_err(|err| {
|
||||
@@ -942,6 +955,11 @@ fn build_remote_control_websocket_request(
|
||||
auth.auth_provider.add_auth_headers(&mut auth_headers);
|
||||
headers.extend(auth_headers);
|
||||
set_remote_control_header(headers, REMOTE_CONTROL_ACCOUNT_ID_HEADER, &auth.account_id)?;
|
||||
set_remote_control_header(
|
||||
headers,
|
||||
REMOTE_CONTROL_INSTALLATION_ID_HEADER,
|
||||
installation_id,
|
||||
)?;
|
||||
if let Some(subscribe_cursor) = subscribe_cursor {
|
||||
set_remote_control_header(
|
||||
headers,
|
||||
@@ -1066,7 +1084,12 @@ pub(super) async fn connect_remote_control_websocket(
|
||||
"creating new remote control enrollment: websocket_url={}, enroll_url={}, account_id={}",
|
||||
remote_control_target.websocket_url, remote_control_target.enroll_url, auth.account_id
|
||||
);
|
||||
let new_enrollment = match enroll_remote_control_server(remote_control_target, &auth).await
|
||||
let new_enrollment = match enroll_remote_control_server(
|
||||
remote_control_target,
|
||||
&auth,
|
||||
connect_options.installation_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(new_enrollment) => new_enrollment,
|
||||
Err(err)
|
||||
@@ -1110,6 +1133,7 @@ pub(super) async fn connect_remote_control_websocket(
|
||||
&remote_control_target.websocket_url,
|
||||
enrollment_ref,
|
||||
&auth,
|
||||
connect_options.installation_id,
|
||||
connect_options.subscribe_cursor,
|
||||
)?;
|
||||
|
||||
@@ -1247,6 +1271,7 @@ mod tests {
|
||||
const TEST_HTTP_ACCEPT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
#[cfg(not(windows))]
|
||||
const TEST_HTTP_ACCEPT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const TEST_INSTALLATION_ID: &str = "11111111-1111-4111-8111-111111111111";
|
||||
|
||||
fn remote_control_status_channel() -> (
|
||||
RemoteControlStatusPublisher,
|
||||
@@ -1254,6 +1279,7 @@ mod tests {
|
||||
) {
|
||||
let (status_tx, status_rx) = watch::channel(RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
});
|
||||
(RemoteControlStatusPublisher::new(status_tx), status_rx)
|
||||
@@ -1359,6 +1385,7 @@ mod tests {
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
RemoteControlConnectOptions {
|
||||
installation_id: TEST_INSTALLATION_ID,
|
||||
subscribe_cursor: None,
|
||||
app_server_client_name: None,
|
||||
},
|
||||
@@ -1376,6 +1403,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_test".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -1435,6 +1463,7 @@ mod tests {
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
RemoteControlConnectOptions {
|
||||
installation_id: TEST_INSTALLATION_ID,
|
||||
subscribe_cursor: None,
|
||||
app_server_client_name: None,
|
||||
},
|
||||
@@ -1448,6 +1477,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_test".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -1515,6 +1545,7 @@ mod tests {
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
RemoteControlConnectOptions {
|
||||
installation_id: TEST_INSTALLATION_ID,
|
||||
subscribe_cursor: None,
|
||||
app_server_client_name: None,
|
||||
},
|
||||
@@ -1567,6 +1598,7 @@ mod tests {
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
RemoteControlConnectOptions {
|
||||
installation_id: TEST_INSTALLATION_ID,
|
||||
subscribe_cursor: None,
|
||||
app_server_client_name: None,
|
||||
},
|
||||
@@ -1614,6 +1646,7 @@ mod tests {
|
||||
&mut auth_recovery,
|
||||
&mut enrollment,
|
||||
RemoteControlConnectOptions {
|
||||
installation_id: TEST_INSTALLATION_ID,
|
||||
subscribe_cursor: None,
|
||||
app_server_client_name: None,
|
||||
},
|
||||
@@ -1632,6 +1665,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
}
|
||||
);
|
||||
@@ -1656,8 +1690,11 @@ mod tests {
|
||||
let shutdown_token = shutdown_token.clone();
|
||||
async move {
|
||||
RemoteControlWebsocket::new(
|
||||
remote_control_url,
|
||||
Some(remote_control_target),
|
||||
RemoteControlWebsocketConfig {
|
||||
remote_control_url,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
remote_control_target: Some(remote_control_target),
|
||||
},
|
||||
/*state_db*/ None,
|
||||
remote_control_auth_manager(),
|
||||
RemoteControlChannels {
|
||||
@@ -1701,6 +1738,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connecting,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_first".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -1721,6 +1759,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connected,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: Some("env_first".to_string()),
|
||||
}
|
||||
);
|
||||
@@ -1734,6 +1773,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Connected,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
}
|
||||
);
|
||||
@@ -1748,6 +1788,7 @@ mod tests {
|
||||
status_rx.borrow().clone(),
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: RemoteControlConnectionStatus::Disabled,
|
||||
installation_id: TEST_INSTALLATION_ID.to_string(),
|
||||
environment_id: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::outgoing_message::QueuedOutgoingMessage;
|
||||
use crate::transport::CHANNEL_CAPACITY;
|
||||
use crate::transport::ConnectionState;
|
||||
use crate::transport::OutboundConnectionState;
|
||||
use crate::transport::RemoteControlStartConfig;
|
||||
use crate::transport::TransportEvent;
|
||||
use crate::transport::auth::policy_from_settings;
|
||||
use crate::transport::route_outgoing_envelope;
|
||||
@@ -686,7 +687,10 @@ pub async fn run_main_with_transport_options(
|
||||
}
|
||||
|
||||
let (remote_control_accept_handle, remote_control_handle) = start_remote_control(
|
||||
config.chatgpt_base_url.clone(),
|
||||
RemoteControlStartConfig {
|
||||
remote_control_url: config.chatgpt_base_url.clone(),
|
||||
installation_id: installation_id.clone(),
|
||||
},
|
||||
state_db.clone(),
|
||||
auth_manager.clone(),
|
||||
transport_event_tx.clone(),
|
||||
@@ -977,6 +981,7 @@ pub async fn run_main_with_transport_options(
|
||||
.send_server_notification(ServerNotification::RemoteControlStatusChanged(
|
||||
RemoteControlStatusChangedNotification {
|
||||
status: status.status,
|
||||
installation_id: status.installation_id,
|
||||
environment_id: status.environment_id,
|
||||
},
|
||||
))
|
||||
|
||||
@@ -19,6 +19,7 @@ pub(crate) use codex_app_server_transport::ConnectionOrigin;
|
||||
pub(crate) use codex_app_server_transport::OutgoingMessage;
|
||||
pub(crate) use codex_app_server_transport::QueuedOutgoingMessage;
|
||||
pub(crate) use codex_app_server_transport::RemoteControlHandle;
|
||||
pub(crate) use codex_app_server_transport::RemoteControlStartConfig;
|
||||
pub(crate) use codex_app_server_transport::TransportEvent;
|
||||
pub use codex_app_server_transport::app_server_control_socket_path;
|
||||
pub use codex_app_server_transport::auth;
|
||||
|
||||
Reference in New Issue
Block a user