Skip to content

Commit ecaeb60

Browse files
owenlin0JeffCarpenter
authored andcommitted
[app-server] add new account method API stubs (openai#5527)
These are the schema definitions for the new JSON-RPC APIs associated with accounts. These are not wired up to business logic yet and will currently throw an internal error indicating these are unimplemented.
1 parent 4979374 commit ecaeb60

File tree

4 files changed

+213
-15
lines changed

4 files changed

+213
-15
lines changed

codex-rs/app-server-protocol/src/protocol.rs

Lines changed: 147 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::JSONRPCNotification;
55
use crate::JSONRPCRequest;
66
use crate::RequestId;
77
use codex_protocol::ConversationId;
8+
use codex_protocol::account::Account;
89
use codex_protocol::config_types::ForcedLoginMethod;
910
use codex_protocol::config_types::ReasoningEffort;
1011
use codex_protocol::config_types::ReasoningSummary;
@@ -93,6 +94,43 @@ macro_rules! client_request_definitions {
9394
}
9495

9596
client_request_definitions! {
97+
/// NEW APIs
98+
#[serde(rename = "model/list")]
99+
#[ts(rename = "model/list")]
100+
ListModels {
101+
params: ListModelsParams,
102+
response: ListModelsResponse,
103+
},
104+
105+
#[serde(rename = "account/login")]
106+
#[ts(rename = "account/login")]
107+
LoginAccount {
108+
params: LoginAccountParams,
109+
response: LoginAccountResponse,
110+
},
111+
112+
#[serde(rename = "account/logout")]
113+
#[ts(rename = "account/logout")]
114+
LogoutAccount {
115+
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
116+
response: LogoutAccountResponse,
117+
},
118+
119+
#[serde(rename = "account/rateLimits/read")]
120+
#[ts(rename = "account/rateLimits/read")]
121+
GetAccountRateLimits {
122+
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
123+
response: GetAccountRateLimitsResponse,
124+
},
125+
126+
#[serde(rename = "account/read")]
127+
#[ts(rename = "account/read")]
128+
GetAccount {
129+
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
130+
response: Option<Account>,
131+
},
132+
133+
/// DEPRECATED APIs below
96134
Initialize {
97135
params: InitializeParams,
98136
response: InitializeResponse,
@@ -106,13 +144,6 @@ client_request_definitions! {
106144
params: ListConversationsParams,
107145
response: ListConversationsResponse,
108146
},
109-
#[serde(rename = "model/list")]
110-
#[ts(rename = "model/list")]
111-
/// List available Codex models along with display metadata.
112-
ListModels {
113-
params: ListModelsParams,
114-
response: ListModelsResponse,
115-
},
116147
/// Resume a recorded Codex conversation from a rollout file.
117148
ResumeConversation {
118149
params: ResumeConversationParams,
@@ -191,12 +222,6 @@ client_request_definitions! {
191222
params: ExecOneOffCommandParams,
192223
response: ExecOneOffCommandResponse,
193224
},
194-
#[serde(rename = "account/rateLimits/read")]
195-
#[ts(rename = "account/rateLimits/read")]
196-
GetAccountRateLimits {
197-
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
198-
response: GetAccountRateLimitsResponse,
199-
},
200225
}
201226

202227
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)]
@@ -352,6 +377,38 @@ pub struct ListModelsResponse {
352377
pub next_cursor: Option<String>,
353378
}
354379

380+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
381+
#[serde(tag = "type")]
382+
#[ts(tag = "type")]
383+
pub enum LoginAccountParams {
384+
#[serde(rename = "apiKey")]
385+
#[ts(rename = "apiKey")]
386+
ApiKey {
387+
#[serde(rename = "apiKey")]
388+
#[ts(rename = "apiKey")]
389+
api_key: String,
390+
},
391+
#[serde(rename = "chatgpt")]
392+
#[ts(rename = "chatgpt")]
393+
ChatGpt,
394+
}
395+
396+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
397+
#[serde(rename_all = "camelCase")]
398+
pub struct LoginAccountResponse {
399+
/// Only set if the login method is ChatGPT.
400+
#[schemars(with = "String")]
401+
pub login_id: Option<Uuid>,
402+
403+
/// URL the client should open in a browser to initiate the OAuth flow.
404+
/// Only set if the login method is ChatGPT.
405+
pub auth_url: Option<String>,
406+
}
407+
408+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
409+
#[serde(rename_all = "camelCase")]
410+
pub struct LogoutAccountResponse {}
411+
355412
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
356413
#[serde(rename_all = "camelCase")]
357414
pub struct ResumeConversationParams {
@@ -875,11 +932,13 @@ pub struct AuthStatusChangeNotification {
875932
#[serde(tag = "method", content = "params", rename_all = "camelCase")]
876933
#[strum(serialize_all = "camelCase")]
877934
pub enum ServerNotification {
935+
/// NEW NOTIFICATIONS
878936
#[serde(rename = "account/rateLimits/updated")]
879937
#[ts(rename = "account/rateLimits/updated")]
880938
#[strum(serialize = "account/rateLimits/updated")]
881939
AccountRateLimitsUpdated(RateLimitSnapshot),
882940

941+
/// DEPRECATED NOTIFICATIONS below
883942
/// Authentication status changed
884943
AuthStatusChange(AuthStatusChangeNotification),
885944

@@ -1049,16 +1108,89 @@ mod tests {
10491108
Ok(())
10501109
}
10511110

1111+
#[test]
1112+
fn serialize_account_login_api_key() -> Result<()> {
1113+
let request = ClientRequest::LoginAccount {
1114+
request_id: RequestId::Integer(2),
1115+
params: LoginAccountParams::ApiKey {
1116+
api_key: "secret".to_string(),
1117+
},
1118+
};
1119+
assert_eq!(
1120+
json!({
1121+
"method": "account/login",
1122+
"id": 2,
1123+
"params": {
1124+
"type": "apiKey",
1125+
"apiKey": "secret"
1126+
}
1127+
}),
1128+
serde_json::to_value(&request)?,
1129+
);
1130+
Ok(())
1131+
}
1132+
1133+
#[test]
1134+
fn serialize_account_login_chatgpt() -> Result<()> {
1135+
let request = ClientRequest::LoginAccount {
1136+
request_id: RequestId::Integer(3),
1137+
params: LoginAccountParams::ChatGpt,
1138+
};
1139+
assert_eq!(
1140+
json!({
1141+
"method": "account/login",
1142+
"id": 3,
1143+
"params": {
1144+
"type": "chatgpt"
1145+
}
1146+
}),
1147+
serde_json::to_value(&request)?,
1148+
);
1149+
Ok(())
1150+
}
1151+
1152+
#[test]
1153+
fn serialize_account_logout() -> Result<()> {
1154+
let request = ClientRequest::LogoutAccount {
1155+
request_id: RequestId::Integer(4),
1156+
params: None,
1157+
};
1158+
assert_eq!(
1159+
json!({
1160+
"method": "account/logout",
1161+
"id": 4,
1162+
}),
1163+
serde_json::to_value(&request)?,
1164+
);
1165+
Ok(())
1166+
}
1167+
1168+
#[test]
1169+
fn serialize_get_account() -> Result<()> {
1170+
let request = ClientRequest::GetAccount {
1171+
request_id: RequestId::Integer(5),
1172+
params: None,
1173+
};
1174+
assert_eq!(
1175+
json!({
1176+
"method": "account/read",
1177+
"id": 5,
1178+
}),
1179+
serde_json::to_value(&request)?,
1180+
);
1181+
Ok(())
1182+
}
1183+
10521184
#[test]
10531185
fn serialize_list_models() -> Result<()> {
10541186
let request = ClientRequest::ListModels {
1055-
request_id: RequestId::Integer(2),
1187+
request_id: RequestId::Integer(6),
10561188
params: ListModelsParams::default(),
10571189
};
10581190
assert_eq!(
10591191
json!({
10601192
"method": "model/list",
1061-
"id": 2,
1193+
"id": 6,
10621194
"params": {}
10631195
}),
10641196
serde_json::to_value(&request)?,

codex-rs/app-server/src/codex_message_processor.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,27 @@ impl CodexMessageProcessor {
176176
ClientRequest::ListModels { request_id, params } => {
177177
self.list_models(request_id, params).await;
178178
}
179+
ClientRequest::LoginAccount {
180+
request_id,
181+
params: _,
182+
} => {
183+
self.send_unimplemented_error(request_id, "account/login")
184+
.await;
185+
}
186+
ClientRequest::LogoutAccount {
187+
request_id,
188+
params: _,
189+
} => {
190+
self.send_unimplemented_error(request_id, "account/logout")
191+
.await;
192+
}
193+
ClientRequest::GetAccount {
194+
request_id,
195+
params: _,
196+
} => {
197+
self.send_unimplemented_error(request_id, "account/read")
198+
.await;
199+
}
179200
ClientRequest::ResumeConversation { request_id, params } => {
180201
self.handle_resume_conversation(request_id, params).await;
181202
}
@@ -257,6 +278,15 @@ impl CodexMessageProcessor {
257278
}
258279
}
259280

281+
async fn send_unimplemented_error(&self, request_id: RequestId, method: &str) {
282+
let error = JSONRPCErrorError {
283+
code: INTERNAL_ERROR_CODE,
284+
message: format!("{method} is not implemented yet"),
285+
data: None,
286+
};
287+
self.outgoing.send_error(request_id, error).await;
288+
}
289+
260290
async fn login_api_key(&mut self, request_id: RequestId, params: LoginApiKeyParams) {
261291
if matches!(
262292
self.config.forced_login_method,

codex-rs/protocol/src/account.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use schemars::JsonSchema;
2+
use serde::Deserialize;
3+
use serde::Serialize;
4+
use ts_rs::TS;
5+
6+
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
7+
#[serde(rename_all = "lowercase")]
8+
#[ts(rename_all = "lowercase")]
9+
pub enum PlanType {
10+
#[default]
11+
Free,
12+
Plus,
13+
Pro,
14+
Team,
15+
Business,
16+
Enterprise,
17+
Edu,
18+
#[serde(other)]
19+
Unknown,
20+
}
21+
22+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
23+
#[serde(tag = "type")]
24+
#[ts(tag = "type")]
25+
pub enum Account {
26+
ApiKey {
27+
api_key: String,
28+
},
29+
#[serde(rename = "chatgpt")]
30+
#[ts(rename = "chatgpt")]
31+
ChatGpt {
32+
email: Option<String>,
33+
plan_type: PlanType,
34+
},
35+
}

codex-rs/protocol/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod account;
12
mod conversation_id;
23
pub use conversation_id::ConversationId;
34
pub mod config_types;

0 commit comments

Comments
 (0)