Files
codex/codex-rs/protocol/src/account.rs
Celia Chen 432771c5fd feat: expose AWS account state from account/read (#19048)
## Why

AWS/Bedrock mode currently reports `account: null` with
`requiresOpenaiAuth: false` from `account/read`. That suppresses the
OpenAI-auth requirement, but it does not let app clients distinguish AWS
auth from any other non-OpenAI custom provider. For the prototype AWS
provider UX, clients need a simple provider-derived signal so they can
suppress ChatGPT/API-key login and token-refresh paths without
hardcoding Bedrock checks.

## What changed

- Adds an `aws` variant to the v2 `Account` protocol union.
- Adds `ProviderAccountKind` to `codex-model-provider` so the runtime
provider owns the app-visible account classification.
- Makes Amazon Bedrock return `ProviderAccountKind::Aws` from the
model-provider layer.
- Updates app-server `account/read` to map `ProviderAccountKind` to the
existing `GetAccountResponse` wire shape.
- Preserves the existing `account: null, requiresOpenaiAuth: false`
behavior for other non-OpenAI providers.
- Regenerates the app-server protocol schema fixtures.
- Adds coverage for provider account classification and for the Amazon
Bedrock `account/read` response.

## Testing

- `cargo test -p codex-model-provider`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server get_account_with_aws_provider`

## Notes

I attempted `just bazel-lock-update` and `just bazel-lock-check`, but
both are blocked in my local environment because `bazel` is not
installed.
2026-04-24 01:53:13 +00:00

125 lines
4.0 KiB
Rust

use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
#[serde(rename_all = "lowercase")]
#[ts(rename_all = "lowercase")]
pub enum PlanType {
#[default]
Free,
Go,
Plus,
Pro,
ProLite,
Team,
#[serde(rename = "self_serve_business_usage_based")]
#[ts(rename = "self_serve_business_usage_based")]
SelfServeBusinessUsageBased,
Business,
#[serde(rename = "enterprise_cbp_usage_based")]
#[ts(rename = "enterprise_cbp_usage_based")]
EnterpriseCbpUsageBased,
Enterprise,
Edu,
#[serde(other)]
Unknown,
}
/// Account state returned by a model provider before it is adapted to an app-facing wire type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProviderAccount {
ApiKey,
Chatgpt { email: String, plan_type: PlanType },
AmazonBedrock,
}
impl PlanType {
pub fn is_team_like(self) -> bool {
matches!(self, Self::Team | Self::SelfServeBusinessUsageBased)
}
pub fn is_business_like(self) -> bool {
matches!(self, Self::Business | Self::EnterpriseCbpUsageBased)
}
pub fn is_workspace_account(self) -> bool {
matches!(
self,
Self::Team
| Self::SelfServeBusinessUsageBased
| Self::Business
| Self::EnterpriseCbpUsageBased
| Self::Enterprise
| Self::Edu
)
}
}
#[cfg(test)]
mod tests {
use super::PlanType;
use pretty_assertions::assert_eq;
#[test]
fn usage_based_plan_types_use_expected_wire_names() {
assert_eq!(
serde_json::to_string(&PlanType::SelfServeBusinessUsageBased)
.expect("self-serve business usage based should serialize"),
"\"self_serve_business_usage_based\""
);
assert_eq!(
serde_json::to_string(&PlanType::EnterpriseCbpUsageBased)
.expect("enterprise cbp usage based should serialize"),
"\"enterprise_cbp_usage_based\""
);
assert_eq!(
serde_json::to_string(&PlanType::ProLite).expect("prolite should serialize"),
"\"prolite\""
);
assert_eq!(
serde_json::from_str::<PlanType>("\"self_serve_business_usage_based\"")
.expect("self-serve business usage based should deserialize"),
PlanType::SelfServeBusinessUsageBased
);
assert_eq!(
serde_json::from_str::<PlanType>("\"prolite\"").expect("prolite should deserialize"),
PlanType::ProLite
);
assert_eq!(
serde_json::from_str::<PlanType>("\"enterprise_cbp_usage_based\"")
.expect("enterprise cbp usage based should deserialize"),
PlanType::EnterpriseCbpUsageBased
);
}
#[test]
fn plan_family_helpers_group_usage_based_variants_with_existing_plans() {
assert_eq!(PlanType::Team.is_team_like(), true);
assert_eq!(PlanType::SelfServeBusinessUsageBased.is_team_like(), true);
assert_eq!(PlanType::Business.is_team_like(), false);
assert_eq!(PlanType::Business.is_business_like(), true);
assert_eq!(PlanType::EnterpriseCbpUsageBased.is_business_like(), true);
assert_eq!(PlanType::Team.is_business_like(), false);
}
#[test]
fn workspace_account_helper_includes_usage_based_workspace_plans() {
assert_eq!(PlanType::Team.is_workspace_account(), true);
assert_eq!(
PlanType::SelfServeBusinessUsageBased.is_workspace_account(),
true
);
assert_eq!(PlanType::Business.is_workspace_account(), true);
assert_eq!(
PlanType::EnterpriseCbpUsageBased.is_workspace_account(),
true
);
assert_eq!(PlanType::Enterprise.is_workspace_account(), true);
assert_eq!(PlanType::Edu.is_workspace_account(), true);
assert_eq!(PlanType::Pro.is_workspace_account(), false);
}
}