mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 09:57:47 +08:00
feat(provider): add dedicated Codex Agent Identity flow
This commit is contained in:
@@ -4,8 +4,9 @@ use crate::handlers::admin::provider::shared::support::{
|
||||
use crate::handlers::admin::request::AdminAppState;
|
||||
use crate::handlers::admin::shared::{provider_key_status_snapshot_payload, unix_secs_to_rfc3339};
|
||||
use crate::provider_key_auth::{
|
||||
provider_key_auth_config_uses_header_authorization, provider_key_auth_semantics,
|
||||
provider_key_can_refresh_oauth, provider_key_effective_api_formats,
|
||||
provider_key_auth_config_is_agent_identity, provider_key_auth_config_uses_header_authorization,
|
||||
provider_key_auth_semantics, provider_key_can_refresh_oauth,
|
||||
provider_key_effective_api_formats,
|
||||
};
|
||||
use aether_admin::provider::pool as admin_provider_pool_pure;
|
||||
use aether_admin::provider::quota as admin_provider_quota_pure;
|
||||
@@ -1156,6 +1157,8 @@ pub(super) fn build_admin_pool_key_payload(
|
||||
.unwrap_or(false);
|
||||
let oauth_header_auth = auth_semantics.oauth_managed()
|
||||
&& provider_key_auth_config_uses_header_authorization(auth_config.as_ref());
|
||||
let agent_identity =
|
||||
provider_key_auth_config_is_agent_identity(provider_type, auth_config.as_ref());
|
||||
let account_status_code = admin_pool_trimmed_string_from_map(account_snapshot, "code");
|
||||
let account_status_label =
|
||||
admin_pool_trimmed_string(account_snapshot.and_then(|item| item.get("label")));
|
||||
@@ -1220,6 +1223,7 @@ pub(super) fn build_admin_pool_key_payload(
|
||||
"oauth_managed".to_string(),
|
||||
json!(auth_semantics.oauth_managed()),
|
||||
);
|
||||
payload.insert("agent_identity".to_string(), json!(agent_identity));
|
||||
payload.insert(
|
||||
"can_refresh_oauth".to_string(),
|
||||
json!(provider_key_can_refresh_oauth(
|
||||
|
||||
+6
-2
@@ -7,8 +7,8 @@ use super::{
|
||||
};
|
||||
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
|
||||
use crate::provider_key_auth::{
|
||||
provider_key_auth_config_uses_header_authorization, provider_key_auth_semantics,
|
||||
provider_key_can_refresh_oauth,
|
||||
provider_key_auth_config_is_agent_identity, provider_key_auth_config_uses_header_authorization,
|
||||
provider_key_auth_semantics, provider_key_can_refresh_oauth,
|
||||
};
|
||||
use crate::GatewayError;
|
||||
use aether_admin::provider::pool as admin_provider_pool_pure;
|
||||
@@ -152,6 +152,10 @@ pub(super) async fn build_admin_pool_resolve_selection_response(
|
||||
"credential_kind": auth_semantics.credential_kind().as_str(),
|
||||
"runtime_auth_kind": auth_semantics.runtime_auth_kind().as_str(),
|
||||
"oauth_managed": auth_semantics.oauth_managed(),
|
||||
"agent_identity": provider_key_auth_config_is_agent_identity(
|
||||
&provider_type,
|
||||
auth_config.as_ref(),
|
||||
),
|
||||
"can_refresh_oauth": provider_key_can_refresh_oauth(auth_semantics, auth_config.as_ref()),
|
||||
"can_export_oauth": auth_semantics.can_export_oauth(),
|
||||
"can_edit_oauth": auth_semantics.can_edit_oauth(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::handlers::shared::{json_string_list, unix_secs_to_rfc3339};
|
||||
use crate::provider_key_auth::{
|
||||
provider_key_auth_config_uses_header_authorization, provider_key_auth_semantics,
|
||||
provider_key_can_refresh_oauth, provider_key_configured_api_formats,
|
||||
provider_key_inherits_provider_api_formats,
|
||||
provider_key_auth_config_is_agent_identity, provider_key_auth_config_uses_header_authorization,
|
||||
provider_key_auth_semantics, provider_key_can_refresh_oauth,
|
||||
provider_key_configured_api_formats, provider_key_inherits_provider_api_formats,
|
||||
};
|
||||
use crate::AppState;
|
||||
use aether_admin::provider::quota as admin_provider_quota_pure;
|
||||
@@ -2461,6 +2461,8 @@ pub(crate) fn build_admin_provider_key_response(
|
||||
.unwrap_or(false);
|
||||
let oauth_header_auth = auth_semantics.oauth_managed()
|
||||
&& provider_key_auth_config_uses_header_authorization(auth_config.as_ref());
|
||||
let agent_identity =
|
||||
provider_key_auth_config_is_agent_identity(provider_type, auth_config.as_ref());
|
||||
let oauth_plan_type = derive_catalog_oauth_plan_type(key, provider_type, auth_config.as_ref());
|
||||
let (
|
||||
health_score,
|
||||
@@ -2494,7 +2496,11 @@ pub(crate) fn build_admin_provider_key_response(
|
||||
);
|
||||
payload.insert(
|
||||
"api_key_masked".to_string(),
|
||||
json!(masked_catalog_api_key(state, key)),
|
||||
json!(if agent_identity {
|
||||
"[Agent Identity]".to_string()
|
||||
} else {
|
||||
masked_catalog_api_key(state, key)
|
||||
}),
|
||||
);
|
||||
payload.insert("api_key_plain".to_string(), serde_json::Value::Null);
|
||||
payload.insert("auth_type".to_string(), json!(key.auth_type));
|
||||
@@ -2518,6 +2524,7 @@ pub(crate) fn build_admin_provider_key_response(
|
||||
"oauth_managed".to_string(),
|
||||
json!(auth_semantics.oauth_managed()),
|
||||
);
|
||||
payload.insert("agent_identity".to_string(), json!(agent_identity));
|
||||
payload.insert(
|
||||
"can_refresh_oauth".to_string(),
|
||||
json!(provider_key_can_refresh_oauth(
|
||||
|
||||
@@ -112,6 +112,23 @@ pub(crate) fn provider_key_auth_config_uses_header_authorization(
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether a Codex key uses the registered Agent Identity credential shape.
|
||||
///
|
||||
/// The key itself remains `auth_type = oauth` so it can use the existing OAuth
|
||||
/// pipeline. Keep this as an explicit server-side classification rather than
|
||||
/// making the frontend infer it from refresh capabilities.
|
||||
pub(crate) fn provider_key_auth_config_is_agent_identity(
|
||||
provider_type: &str,
|
||||
auth_config: Option<&Map<String, Value>>,
|
||||
) -> bool {
|
||||
provider_type.trim().eq_ignore_ascii_case("codex")
|
||||
&& auth_config.is_some_and(|config| {
|
||||
aether_provider_transport::is_codex_agent_identity_auth_config_value(&Value::Object(
|
||||
config.clone(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn normalized_auth_type(key: &StoredProviderCatalogKey) -> String {
|
||||
key.auth_type.trim().to_ascii_lowercase()
|
||||
}
|
||||
@@ -272,11 +289,11 @@ pub(crate) fn provider_key_effective_api_formats(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
provider_active_api_formats, provider_key_auth_config_uses_header_authorization,
|
||||
provider_key_auth_semantics, provider_key_can_refresh_oauth,
|
||||
provider_key_configured_api_formats, provider_key_effective_api_formats,
|
||||
provider_key_inherits_provider_api_formats, ProviderKeyCredentialKind,
|
||||
ProviderKeyRuntimeAuthKind,
|
||||
provider_active_api_formats, provider_key_auth_config_is_agent_identity,
|
||||
provider_key_auth_config_uses_header_authorization, provider_key_auth_semantics,
|
||||
provider_key_can_refresh_oauth, provider_key_configured_api_formats,
|
||||
provider_key_effective_api_formats, provider_key_inherits_provider_api_formats,
|
||||
ProviderKeyCredentialKind, ProviderKeyRuntimeAuthKind,
|
||||
};
|
||||
use aether_data_contracts::repository::provider_catalog::{
|
||||
StoredProviderCatalogEndpoint, StoredProviderCatalogKey,
|
||||
@@ -415,6 +432,29 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_codex_agent_identity_auth_config() {
|
||||
let config = json!({
|
||||
"auth_mode": "agentIdentity",
|
||||
"agent_runtime_id": "runtime-1",
|
||||
"agent_private_key": "base64-private-key",
|
||||
"task_id": "task-1"
|
||||
});
|
||||
|
||||
assert!(provider_key_auth_config_is_agent_identity(
|
||||
"codex",
|
||||
config.as_object()
|
||||
));
|
||||
assert!(!provider_key_auth_config_is_agent_identity(
|
||||
"openai",
|
||||
config.as_object()
|
||||
));
|
||||
assert!(!provider_key_auth_config_is_agent_identity(
|
||||
"codex",
|
||||
json!({ "refresh_token": "refresh-token" }).as_object()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_legacy_kiro_bearer_key_with_auth_config_as_oauth_managed() {
|
||||
let mut key = sample_key("bearer");
|
||||
|
||||
@@ -3060,7 +3060,7 @@ async fn gateway_pool_plan_free_selector_prefers_upstream_plan_type() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_pool_keys_mark_oauth_header_auth() {
|
||||
async fn gateway_pool_keys_classify_oauth_credentials() {
|
||||
let mut provider = sample_provider("provider-codex", "codex", 10).with_transport_fields(
|
||||
true,
|
||||
false,
|
||||
@@ -3091,11 +3091,25 @@ async fn gateway_pool_keys_mark_oauth_header_auth() {
|
||||
)
|
||||
.expect("auth config should encrypt"),
|
||||
);
|
||||
let mut agent_key = sample_key(
|
||||
"key-codex-agent-identity",
|
||||
"provider-codex",
|
||||
"openai:responses",
|
||||
"",
|
||||
);
|
||||
agent_key.auth_type = "oauth".to_string();
|
||||
agent_key.encrypted_auth_config = Some(
|
||||
encrypt_python_fernet_plaintext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
r#"{"provider_type":"codex","auth_mode":"agentIdentity","agent_runtime_id":"runtime-1","agent_private_key":"base64-private-key","task_id":"task-1"}"#,
|
||||
)
|
||||
.expect("Agent Identity auth config should encrypt"),
|
||||
);
|
||||
|
||||
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||
vec![provider],
|
||||
Vec::new(),
|
||||
vec![key],
|
||||
vec![key, agent_key],
|
||||
));
|
||||
let state = AppState::new()
|
||||
.expect("gateway should build")
|
||||
@@ -3120,8 +3134,19 @@ async fn gateway_pool_keys_mark_oauth_header_auth() {
|
||||
)
|
||||
.expect("json body should parse");
|
||||
let keys = payload["keys"].as_array().expect("keys should be array");
|
||||
assert_eq!(keys.len(), 1);
|
||||
assert_eq!(keys[0]["oauth_header_auth"], true);
|
||||
assert_eq!(keys.len(), 2);
|
||||
let oauth_header_key = keys
|
||||
.iter()
|
||||
.find(|key| key["key_id"] == "key-codex-oauth-header")
|
||||
.expect("OAuth Header key should exist");
|
||||
assert_eq!(oauth_header_key["oauth_header_auth"], true);
|
||||
assert_eq!(oauth_header_key["agent_identity"], false);
|
||||
let agent_identity_key = keys
|
||||
.iter()
|
||||
.find(|key| key["key_id"] == "key-codex-agent-identity")
|
||||
.expect("Agent Identity key should exist");
|
||||
assert_eq!(agent_identity_key["oauth_header_auth"], false);
|
||||
assert_eq!(agent_identity_key["agent_identity"], true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user