mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
feat(remote-control): add pairing start (#25675)
## Why
Remote control enrollment authorizes a desktop server, but app-server v2
did not expose the follow-up pairing operation needed to mint a
short-lived controller pairing artifact from that enrolled server.
Clients need a narrow RPC that starts pairing without exposing the
backend `serverId` or conflating pairing with websocket connection
state.
Issue: N/A; internal remote-control pairing API change.
## What Changed
Added experimental app-server v2 `remoteControl/pairing/start` with
`manualCode` input and `pairingCode`, nullable `manualPairingCode`,
`environmentId`, and Unix-seconds `expiresAt` output. The method
serializes under its own `global("remote-control-pairing")` scope and is
documented in `app-server/README.md`.
Extended the remote-control transport with private `/server/pair`
request/response types and normalized `pair_url` handling. Pairing uses
the current enrolled server bearer, refreshes that bearer when needed,
keeps backend `server_id` private, validates returned `server_id` and
`environment_id` against the current enrollment, and preserves backend
status/header/body context for failures and malformed responses.
Wired the request through `RemoteControlRequestProcessor` and
`MessageProcessor`, mapping unavailable/disabled pairing to
`invalid_request` and backend failures to internal errors.
## Verification
- `just test -p codex-app-server-transport`
- `just test -p codex-app-server
remote_control_pairing_start_returns_pairing_artifacts`
This commit is contained in:
1 parent
1ad0d7aa4b
commit
0002316687
15 files changed
+1302
-48
No files matched your search
@@ -5,7 +5,10 @@ use crate::transport::RemoteControlUnavailable;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::RemoteControlDisableResponse;
|
||||
use codex_app_server_protocol::RemoteControlEnableResponse;
|
||||
use codex_app_server_protocol::RemoteControlPairingStartParams;
|
||||
use codex_app_server_protocol::RemoteControlPairingStartResponse;
|
||||
use codex_app_server_protocol::RemoteControlStatusReadResponse;
|
||||
use std::io;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RemoteControlRequestProcessor {
|
||||
@@ -42,6 +45,16 @@ impl RemoteControlRequestProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn pairing_start(
|
||||
&self,
|
||||
params: RemoteControlPairingStartParams,
|
||||
) -> Result<RemoteControlPairingStartResponse, JSONRPCErrorError> {
|
||||
self.handle()?
|
||||
.start_pairing(params)
|
||||
.await
|
||||
.map_err(map_pairing_start_error)
|
||||
}
|
||||
|
||||
fn handle(&self) -> Result<&RemoteControlHandle, JSONRPCErrorError> {
|
||||
self.remote_control_handle
|
||||
.as_ref()
|
||||
@@ -52,3 +65,14 @@ impl RemoteControlRequestProcessor {
|
||||
fn map_unavailable(err: RemoteControlUnavailable) -> JSONRPCErrorError {
|
||||
invalid_request(err.to_string())
|
||||
}
|
||||
|
||||
fn map_pairing_start_error(err: io::Error) -> JSONRPCErrorError {
|
||||
if err.kind() == io::ErrorKind::InvalidInput {
|
||||
invalid_request(err.to_string())
|
||||
} else {
|
||||
internal_error(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod remote_control_processor_tests;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
use super::*;
|
||||
use crate::error_code::INTERNAL_ERROR_CODE;
|
||||
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn pairing_start_returns_internal_error_when_remote_control_is_unavailable() {
|
||||
let err = RemoteControlRequestProcessor::new(/*remote_control_handle*/ None)
|
||||
.pairing_start(RemoteControlPairingStartParams::default())
|
||||
.await
|
||||
.expect_err("missing remote control should fail pairing");
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
data: None,
|
||||
message: "remote control is unavailable for this app-server".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pairing_start_maps_invalid_input_to_invalid_request() {
|
||||
assert_eq!(
|
||||
map_pairing_start_error(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"remote control pairing is unavailable",
|
||||
)),
|
||||
JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
data: None,
|
||||
message: "remote control pairing is unavailable".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pairing_start_maps_backend_failures_to_internal_error() {
|
||||
assert_eq!(
|
||||
map_pairing_start_error(io::Error::other("remote control pairing failed")),
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
data: None,
|
||||
message: "remote control pairing failed".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user