feat: add managed Bedrock login API (#31327)

## Why

App-server clients can report whether Amazon Bedrock is using
AWS-managed credentials or a Codex-managed API key, but they do not have
a matching API for creating the managed login. This PR defines that
experimental wire contract independently from its implementation.

Managed Bedrock API keys are already a primary `CodexAuth` mode. The API
therefore describes a normal Codex login that replaces the current
stored auth rather than introducing provider-scoped credential storage.

## What changed

- Add the experimental `amazonBedrock` variant to `account/login/start`.
- Accept an API key and AWS region and return a matching discriminated
response.
- Gate the request behind the app-server `experimentalApi` capability.
- Regenerate the JSON and TypeScript protocol schemas.
- Document the login contract, notifications, primary-auth replacement
semantics, restart boundary, and non-transactional durable writes.

## Impact

This PR defines the API shape but does not implement login behavior. The
next PR adds validation, persistence through the existing Codex auth
lifecycle, provider selection, and notifications.

## Validation

- `just test -p codex-app-server-protocol`

## Stack

1. **#31327 Managed Bedrock experimental API** — base: `main`
2. #31326 Managed Bedrock login — base: `codex/managed-bedrock-api`
3. #31325 Managed Bedrock logout — base:
`codex/managed-bedrock-login-v2`
This commit is contained in:
Celia Chen
2026-07-09 17:05:37 -07:00
committed by GitHub
parent ac3da4fb1a
commit b45fef0774
11 changed files with 216 additions and 4 deletions
@@ -1543,6 +1543,31 @@
],
"title": "ChatgptAuthTokensLoginAccountParams",
"type": "object"
},
{
"description": "[UNSTABLE] Managed Amazon Bedrock login is experimental.",
"properties": {
"apiKey": {
"type": "string"
},
"region": {
"type": "string"
},
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockLoginAccountParamsType",
"type": "string"
}
},
"required": [
"apiKey",
"region",
"type"
],
"title": "AmazonBedrockLoginAccountParams",
"type": "object"
}
]
},
@@ -11591,6 +11591,31 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountParams",
"type": "object"
},
{
"description": "[UNSTABLE] Managed Amazon Bedrock login is experimental.",
"properties": {
"apiKey": {
"type": "string"
},
"region": {
"type": "string"
},
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountParamsType",
"type": "string"
}
},
"required": [
"apiKey",
"region",
"type"
],
"title": "AmazonBedrockv2::LoginAccountParams",
"type": "object"
}
],
"title": "LoginAccountParams"
@@ -11684,6 +11709,22 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountResponseType",
"type": "string"
}
},
"required": [
"type"
],
"title": "AmazonBedrockv2::LoginAccountResponse",
"type": "object"
}
],
"title": "LoginAccountResponse"
@@ -7995,6 +7995,31 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountParams",
"type": "object"
},
{
"description": "[UNSTABLE] Managed Amazon Bedrock login is experimental.",
"properties": {
"apiKey": {
"type": "string"
},
"region": {
"type": "string"
},
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountParamsType",
"type": "string"
}
},
"required": [
"apiKey",
"region",
"type"
],
"title": "AmazonBedrockv2::LoginAccountParams",
"type": "object"
}
],
"title": "LoginAccountParams"
@@ -8088,6 +8113,22 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountResponseType",
"type": "string"
}
},
"required": [
"type"
],
"title": "AmazonBedrockv2::LoginAccountResponse",
"type": "object"
}
],
"title": "LoginAccountResponse"
@@ -112,6 +112,31 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountParams",
"type": "object"
},
{
"description": "[UNSTABLE] Managed Amazon Bedrock login is experimental.",
"properties": {
"apiKey": {
"type": "string"
},
"region": {
"type": "string"
},
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountParamsType",
"type": "string"
}
},
"required": [
"apiKey",
"region",
"type"
],
"title": "AmazonBedrockv2::LoginAccountParams",
"type": "object"
}
],
"title": "LoginAccountParams"
@@ -87,6 +87,22 @@
],
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"amazonBedrock"
],
"title": "AmazonBedrockv2::LoginAccountResponseType",
"type": "string"
}
},
"required": [
"type"
],
"title": "AmazonBedrockv2::LoginAccountResponse",
"type": "object"
}
],
"title": "LoginAccountResponse"
@@ -19,4 +19,4 @@ chatgptAccountId: string,
* When `null`, Codex attempts to derive the plan type from access-token
* claims. If unavailable, the plan defaults to `unknown`.
*/
chatgptPlanType?: string | null, };
chatgptPlanType?: string | null, } | { "type": "amazonBedrock", apiKey: string, region: string, };
@@ -14,4 +14,4 @@ verificationUrl: string,
/**
* One-time code the user must enter after signing in.
*/
userCode: string, } | { "type": "chatgptAuthTokens", };
userCode: string, } | { "type": "chatgptAuthTokens", } | { "type": "amazonBedrock", };
@@ -2722,6 +2722,34 @@ mod tests {
Ok(())
}
#[test]
fn serialize_account_login_amazon_bedrock() -> Result<()> {
let request = ClientRequest::LoginAccount {
request_id: RequestId::Integer(2),
params: v2::LoginAccountParams::AmazonBedrock {
api_key: "secret".to_string(),
region: "us-west-2".to_string(),
},
};
assert_eq!(
json!({
"method": "account/login/start",
"id": 2,
"params": {
"type": "amazonBedrock",
"apiKey": "secret",
"region": "us-west-2"
}
}),
serde_json::to_value(&request)?,
);
assert_eq!(
json!({"type": "amazonBedrock"}),
serde_json::to_value(v2::LoginAccountResponse::AmazonBedrock {})?,
);
Ok(())
}
#[test]
fn serialize_account_login_chatgpt() -> Result<()> {
let request = ClientRequest::LoginAccount {
@@ -105,6 +105,11 @@ pub enum LoginAccountParams {
#[ts(optional = nullable)]
chatgpt_plan_type: Option<String>,
},
/// [UNSTABLE] Managed Amazon Bedrock login is experimental.
#[experimental("account/login/start.amazonBedrock")]
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
AmazonBedrock { api_key: String, region: String },
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
@@ -148,6 +153,9 @@ pub enum LoginAccountResponse {
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
ChatgptAuthTokens {},
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
AmazonBedrock {},
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
+27 -2
View File
@@ -1921,16 +1921,17 @@ Codex supports these authentication modes. The current mode is surfaced in `acco
- **API key (`apiKey`)**: Caller supplies an OpenAI API key via `account/login/start` with `type: "apiKey"`. The API key is saved and used for API requests.
- **ChatGPT managed (`chatgpt`)** (recommended): Codex owns the ChatGPT OAuth flow and refresh tokens. Start via `account/login/start` with `type: "chatgpt"` for the browser flow or `type: "chatgptDeviceCode"` for device code; Codex persists tokens to disk and refreshes them automatically.
- **Codex managed Amazon Bedrock auth (`amazonBedrock`, experimental)**: Caller supplies an Amazon Bedrock API key and region via `account/login/start` with `type: "amazonBedrock"`. The client must enable the `experimentalApi` initialization capability for Codex-managed Amazon Bedrock login. Codex replaces the current primary auth with the Bedrock credential and writes `model_provider = "amazon-bedrock"` to the user config.
- **Personal access token (`personalAccessToken`)**: Codex uses a ChatGPT-backed personal access token loaded outside the app-server login RPCs, such as with `codex login --with-access-token` or `CODEX_ACCESS_TOKEN`.
### API Overview
- `account/read` — fetch current account info; optionally refresh tokens.
- `account/login/start` — begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`).
- `account/login/start` — begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`, `amazonBedrock`).
- `account/login/completed` (notify) — emitted when a login attempt finishes (success or error).
- `account/login/cancel` — cancel a pending managed ChatGPT login by `loginId`.
- `account/logout` — sign out; triggers `account/updated`.
- `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, `personalAccessToken`, or `null`) and includes the current ChatGPT `planType` when available.
- `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `bedrockApiKey`, `chatgpt`, `personalAccessToken`, or `null`) and includes the current ChatGPT `planType` when available.
- `account/rateLimits/read` — fetch ChatGPT rate limits, an optional effective monthly credit limit, and the earned rate-limit resets currently available, including expiry details when provided by the backend. Rate-limit updates arrive via `account/rateLimits/updated` (notify); reset-credit data is snapshot-only.
- `account/rateLimitResetCredit/consume` — consume one earned reset using a caller-provided idempotency key, optionally selecting a reset-credit ID returned by `account/rateLimits/read`.
- `account/usage/read` — fetch ChatGPT account token-activity summary and daily buckets.
@@ -2006,6 +2007,30 @@ Field notes:
{ "method": "account/updated", "params": { "authMode": "chatgpt", "planType": "plus" } }
```
### 3) Log in with an Amazon Bedrock API key
This experimental flow requires the client to initialize with `experimentalApi: true`.
1. Send:
```json
{
"method": "account/login/start",
"id": 3,
"params": { "type": "amazonBedrock", "apiKey": "…", "region": "us-west-2" }
}
```
2. Expect:
```json
{ "id": 3, "result": { "type": "amazonBedrock" } }
```
3. Notifications:
```json
{ "method": "account/login/completed", "params": { "loginId": null, "success": true, "error": null } }
{ "method": "account/updated", "params": { "authMode": "bedrockApiKey", "planType": null } }
```
Codex stores the key and region as the primary Codex auth, replacing any previously stored login, and writes `model_provider = "amazon-bedrock"` to the active user config. Existing loaded sessions keep their current provider selection, so clients should restart the app-server before sending more model requests. This limitation will be addressed in a follow-up.
### 4) Log in with ChatGPT (device code flow)
1. Start:
@@ -293,6 +293,9 @@ impl AccountRequestProcessor {
)
.await;
}
LoginAccountParams::AmazonBedrock { .. } => {
return Err(invalid_request("Amazon Bedrock login is not implemented"));
}
}
Ok(())
}