mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] expose Bedrock credential source in account/read (#27751)
## Why `account/read` currently reports only `type: "amazonBedrock"`, so clients cannot distinguish a Codex-managed Bedrock API key from credentials supplied by AWS. The app UI needs that distinction to render the appropriate account state without duplicating provider-auth logic. Credential-source selection belongs to the Bedrock model provider because it already owns the precedence between managed Bedrock auth and the external AWS credential path. This builds on #27443 and #27689. ## What changed - Added `AmazonBedrockCredentialSource` with `codexManaged` and `awsManaged` values. - Included the selected credential source in `ProviderAccount::AmazonBedrock` and the app-server `Account` response. - Made `AmazonBedrockModelProvider::account_state()` classify the source from its managed-auth state. - Regenerated the app-server JSON and TypeScript schemas. - Updated app-server account documentation and downstream TUI matches. `codexManaged` means the provider found a managed Bedrock API key. `awsManaged` identifies the provider's external AWS credential path; it does not assert that the AWS credential chain has been validated. ## Testing - Added model-provider coverage for Codex-managed precedence and AWS-managed fallback. - Added app-server protocol serialization coverage for both wire values. - Added app-server integration coverage for both `account/read` responses. - `just test -p codex-protocol -p codex-model-provider -p codex-app-server-protocol` (497 tests passed). After rebasing onto #27711, the `codex-app-server` test target compiled past the image-generation `PathUri` migration. Local linking was then interrupted by disk exhaustion (`No space left on device`).
This commit is contained in:
committed by
GitHub
Unverified
parent
314fa3d25b
commit
12aaeb7bf8
+15
@@ -5847,6 +5847,14 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"credentialSource": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AmazonBedrockCredentialSource"
|
||||
}
|
||||
],
|
||||
"default": "awsManaged"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
@@ -6166,6 +6174,13 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"AmazonBedrockCredentialSource": {
|
||||
"enum": [
|
||||
"codexManaged",
|
||||
"awsManaged"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AnalyticsConfig": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
|
||||
+15
@@ -49,6 +49,14 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"credentialSource": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AmazonBedrockCredentialSource"
|
||||
}
|
||||
],
|
||||
"default": "awsManaged"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
@@ -368,6 +376,13 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"AmazonBedrockCredentialSource": {
|
||||
"enum": [
|
||||
"codexManaged",
|
||||
"awsManaged"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AnalyticsConfig": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
|
||||
@@ -45,6 +45,14 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"credentialSource": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AmazonBedrockCredentialSource"
|
||||
}
|
||||
],
|
||||
"default": "awsManaged"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
@@ -61,6 +69,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"AmazonBedrockCredentialSource": {
|
||||
"enum": [
|
||||
"codexManaged",
|
||||
"awsManaged"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PlanType": {
|
||||
"enum": [
|
||||
"free",
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AmazonBedrockCredentialSource = "codexManaged" | "awsManaged";
|
||||
@@ -3,6 +3,7 @@
|
||||
export type { AbsolutePathBuf } from "./AbsolutePathBuf";
|
||||
export type { AgentMessageInputContent } from "./AgentMessageInputContent";
|
||||
export type { AgentPath } from "./AgentPath";
|
||||
export type { AmazonBedrockCredentialSource } from "./AmazonBedrockCredentialSource";
|
||||
export type { ApiPathString } from "./ApiPathString";
|
||||
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
|
||||
export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AmazonBedrockCredentialSource } from "../AmazonBedrockCredentialSource";
|
||||
import type { PlanType } from "../PlanType";
|
||||
|
||||
export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", };
|
||||
export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", credentialSource: AmazonBedrockCredentialSource, };
|
||||
|
||||
@@ -1674,6 +1674,7 @@ mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::account::AmazonBedrockCredentialSource;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
@@ -2777,6 +2778,41 @@ mod tests {
|
||||
serde_json::to_value(&chatgpt)?,
|
||||
);
|
||||
|
||||
let codex_managed_bedrock = v2::Account::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::CodexManaged,
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"type": "amazonBedrock",
|
||||
"credentialSource": "codexManaged",
|
||||
}),
|
||||
serde_json::to_value(&codex_managed_bedrock)?,
|
||||
);
|
||||
|
||||
let aws_managed_bedrock = v2::Account::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::AwsManaged,
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"type": "amazonBedrock",
|
||||
"credentialSource": "awsManaged",
|
||||
}),
|
||||
serde_json::to_value(&aws_managed_bedrock)?,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_defaults_legacy_bedrock_credential_source() -> Result<()> {
|
||||
assert_eq!(
|
||||
v2::Account::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::AwsManaged,
|
||||
},
|
||||
serde_json::from_value(json!({
|
||||
"type": "amazonBedrock",
|
||||
}))?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::protocol::common::AuthMode;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::account::AmazonBedrockCredentialSource;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::account::ProviderAccount;
|
||||
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
@@ -28,7 +29,14 @@ pub enum Account {
|
||||
|
||||
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
AmazonBedrock {},
|
||||
AmazonBedrock {
|
||||
#[serde(default = "default_bedrock_credential_source")]
|
||||
credential_source: AmazonBedrockCredentialSource,
|
||||
},
|
||||
}
|
||||
|
||||
fn default_bedrock_credential_source() -> AmazonBedrockCredentialSource {
|
||||
AmazonBedrockCredentialSource::AwsManaged
|
||||
}
|
||||
|
||||
impl From<ProviderAccount> for Account {
|
||||
@@ -36,7 +44,9 @@ impl From<ProviderAccount> for Account {
|
||||
match account {
|
||||
ProviderAccount::ApiKey => Self::ApiKey {},
|
||||
ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type },
|
||||
ProviderAccount::AmazonBedrock => Self::AmazonBedrock {},
|
||||
ProviderAccount::AmazonBedrock { credential_source } => {
|
||||
Self::AmazonBedrock { credential_source }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1902,12 +1902,15 @@ Response examples:
|
||||
{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models)
|
||||
{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true } }
|
||||
{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true } }
|
||||
{ "id": 1, "result": { "account": { "type": "amazonBedrock", "credentialSource": "codexManaged" }, "requiresOpenaiAuth": false } }
|
||||
{ "id": 1, "result": { "account": { "type": "amazonBedrock", "credentialSource": "awsManaged" }, "requiresOpenaiAuth": false } }
|
||||
```
|
||||
|
||||
Field notes:
|
||||
|
||||
- `refreshToken` (bool): set `true` to force a token refresh.
|
||||
- `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials.
|
||||
- Amazon Bedrock reports `credentialSource: "codexManaged"` when it uses a Bedrock API key managed by Codex. Otherwise it reports `credentialSource: "awsManaged"` for the external AWS credential path. This identifies the selected credential source; it does not validate that the AWS credential chain can resolve credentials.
|
||||
|
||||
### 2) Log in with an API key
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ use codex_login::AuthKeyringBackendKind;
|
||||
use codex_login::CLIENT_ID_OVERRIDE_ENV_VAR;
|
||||
use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
use codex_login::login_with_api_key;
|
||||
use codex_login::login_with_bedrock_api_key;
|
||||
use codex_protocol::account::AmazonBedrockCredentialSource;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -1725,13 +1727,60 @@ region = "us-west-2"
|
||||
let received: GetAccountResponse = to_response(resp)?;
|
||||
|
||||
let expected = GetAccountResponse {
|
||||
account: Some(Account::AmazonBedrock {}),
|
||||
account: Some(Account::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::AwsManaged,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
};
|
||||
assert_eq!(received, expected);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_account_with_managed_bedrock_provider() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(
|
||||
codex_home.path(),
|
||||
CreateConfigTomlParams {
|
||||
model_provider_id: Some("amazon-bedrock".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
login_with_bedrock_api_key(
|
||||
codex_home.path(),
|
||||
"managed-bedrock-api-key",
|
||||
"us-west-2",
|
||||
AuthCredentialsStoreMode::File,
|
||||
AuthKeyringBackendKind::default(),
|
||||
)?;
|
||||
|
||||
let mut mcp = TestAppServer::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let request_id = mcp
|
||||
.send_get_account_request(GetAccountParams {
|
||||
refresh_token: false,
|
||||
})
|
||||
.await?;
|
||||
let resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let received: GetAccountResponse = to_response(resp)?;
|
||||
|
||||
assert_eq!(
|
||||
received,
|
||||
GetAccountResponse {
|
||||
account: Some(Account::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::CodexManaged,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_account_with_chatgpt() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -15,6 +15,7 @@ use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_models_manager::manager::StaticModelsManager;
|
||||
use codex_protocol::account::AmazonBedrockCredentialSource;
|
||||
use codex_protocol::account::ProviderAccount;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -130,8 +131,13 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
}
|
||||
|
||||
fn account_state(&self) -> ProviderAccountResult {
|
||||
let credential_source = if self.managed_auth().is_some() {
|
||||
AmazonBedrockCredentialSource::CodexManaged
|
||||
} else {
|
||||
AmazonBedrockCredentialSource::AwsManaged
|
||||
};
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock),
|
||||
account: Some(ProviderAccount::AmazonBedrock { credential_source }),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
}
|
||||
@@ -209,6 +215,15 @@ mod tests {
|
||||
provider.auth().await,
|
||||
Some(CodexAuth::BedrockApiKey(managed_auth))
|
||||
);
|
||||
assert_eq!(
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::CodexManaged,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
provider
|
||||
.runtime_base_url()
|
||||
@@ -238,6 +253,15 @@ mod tests {
|
||||
|
||||
assert!(provider.auth_manager().is_none());
|
||||
assert_eq!(provider.auth().await, None);
|
||||
assert_eq!(
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::AwsManaged,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -577,7 +577,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock),
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
credential_source:
|
||||
codex_protocol::account::AmazonBedrockCredentialSource::AwsManaged,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,8 +34,21 @@ pub enum PlanType {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ProviderAccount {
|
||||
ApiKey,
|
||||
Chatgpt { email: String, plan_type: PlanType },
|
||||
AmazonBedrock,
|
||||
Chatgpt {
|
||||
email: String,
|
||||
plan_type: PlanType,
|
||||
},
|
||||
AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
pub enum AmazonBedrockCredentialSource {
|
||||
CodexManaged,
|
||||
AwsManaged,
|
||||
}
|
||||
|
||||
impl PlanType {
|
||||
|
||||
@@ -327,7 +327,7 @@ impl AppServerSession {
|
||||
true,
|
||||
)
|
||||
}
|
||||
Some(Account::AmazonBedrock {}) => {
|
||||
Some(Account::AmazonBedrock { .. }) => {
|
||||
(None, None, None, None, FeedbackAudience::External, false)
|
||||
}
|
||||
None => (None, None, None, None, FeedbackAudience::External, false),
|
||||
|
||||
@@ -1874,7 +1874,7 @@ async fn get_login_status(
|
||||
Ok(match account.account {
|
||||
Some(AppServerAccount::ApiKey {}) => LoginStatus::AuthMode(AppServerAuthMode::ApiKey),
|
||||
Some(AppServerAccount::Chatgpt { .. }) => LoginStatus::AuthMode(AppServerAuthMode::Chatgpt),
|
||||
Some(AppServerAccount::AmazonBedrock {}) => LoginStatus::NotAuthenticated,
|
||||
Some(AppServerAccount::AmazonBedrock { .. }) => LoginStatus::NotAuthenticated,
|
||||
None => LoginStatus::NotAuthenticated,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user