mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f9c0c800 | ||
|
|
a3bc7bc445 | ||
|
|
0132114285 |
@@ -1368,9 +1368,7 @@ fn map_quota_error(error: CodexCredentialQuotaError) -> ProviderAdminError {
|
||||
fn map_catalog_error(error: CodexCredentialCatalogError) -> ProviderAdminError {
|
||||
use CodexCredentialCatalogError as Error;
|
||||
provider_admin_error(match error {
|
||||
Error::InvalidCredentialData | Error::ConflictingModelFacts | Error::InvalidEtag => {
|
||||
ProviderAdminErrorKind::Invalid
|
||||
}
|
||||
Error::InvalidCredentialData | Error::InvalidEtag => ProviderAdminErrorKind::Invalid,
|
||||
Error::NoEligibleCredential => ProviderAdminErrorKind::NotFound,
|
||||
Error::ConcurrentUpdate => ProviderAdminErrorKind::Conflict,
|
||||
Error::Upstream { .. } | Error::Cache => ProviderAdminErrorKind::Unavailable,
|
||||
|
||||
@@ -138,8 +138,6 @@ pub enum CodexCredentialCatalogError {
|
||||
InvalidCredentialData,
|
||||
#[error("Codex model catalog upstream query failed: {detail}")]
|
||||
Upstream { detail: String },
|
||||
#[error("Codex model catalog contains conflicting account facts")]
|
||||
ConflictingModelFacts,
|
||||
#[error("Codex model catalog cache is unavailable")]
|
||||
Cache,
|
||||
#[error("Codex model catalog ETag is invalid")]
|
||||
@@ -359,11 +357,6 @@ impl CodexCredentialCatalogService {
|
||||
union_order.push(entry.key().clone());
|
||||
entry.insert(model.clone());
|
||||
}
|
||||
// 展示与元数据允许跨套餐漂移,union 取首见值;只有路由
|
||||
// 事实冲突才让整轮刷新失败。
|
||||
Entry::Occupied(entry) if conflicting_routing_facts(entry.get(), model) => {
|
||||
return Err(CodexCredentialCatalogError::ConflictingModelFacts);
|
||||
}
|
||||
Entry::Occupied(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -661,13 +654,6 @@ fn model_ids(models: &[CodexCatalogModel]) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 路由正确性只依赖 Responses API 支持证据(决定 Generate operation 是否
|
||||
/// 编译进 `ModelCapabilities`);display_name/limits/metadata 等展示事实
|
||||
/// 跨套餐漂移不构成冲突。
|
||||
fn conflicting_routing_facts(existing: &CodexCatalogModel, candidate: &CodexCatalogModel) -> bool {
|
||||
existing.capabilities().responses_api() != candidate.capabilities().responses_api()
|
||||
}
|
||||
|
||||
fn catalog_candidates_by_scope(
|
||||
accounts: Vec<ProviderAccount>,
|
||||
) -> Result<BTreeMap<CodexCatalogScope, Vec<ProviderAccount>>, CodexCredentialCatalogError> {
|
||||
|
||||
@@ -1492,12 +1492,10 @@ fn nonnegative_millis(value: Option<i64>) -> Option<u64> {
|
||||
}
|
||||
|
||||
fn compile_model_capabilities(model: &CodexCatalogModel) -> ProviderModelCapabilities {
|
||||
let evidence = model.capabilities();
|
||||
let mut operations = BTreeSet::new();
|
||||
if evidence.responses_api() == CodexCatalogCapabilityEvidence::DeclaredNative {
|
||||
operations.insert(OperationKind::Generate);
|
||||
}
|
||||
let capabilities = ModelCapabilities::new(operations, None).with_upstream_feature_validation();
|
||||
// Catalog membership is enough to publish Generate. `supported_in_api` is advisory;
|
||||
// the upstream response is authoritative for normal and diagnostic requests.
|
||||
let capabilities = ModelCapabilities::new(BTreeSet::from([OperationKind::Generate]), None)
|
||||
.with_upstream_feature_validation();
|
||||
ProviderModelCapabilities::new(model.request_model().clone(), capabilities)
|
||||
.with_presentation(codex_model_presentation(model))
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ async fn cross_plan_presentation_drift_unions_on_the_first_seen_model() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cross_plan_responses_api_conflict_still_fails_the_union() {
|
||||
async fn cross_plan_supported_in_api_drift_unions_on_the_first_seen_model() {
|
||||
let store = Arc::new(MemoryAccountStore::default());
|
||||
seed_account_with_plan(&store, "acct_conflict_plus", "plus").await;
|
||||
seed_account_with_plan(&store, "acct_conflict_pro", "pro").await;
|
||||
@@ -272,15 +272,12 @@ async fn cross_plan_responses_api_conflict_still_fails_the_union() {
|
||||
.await;
|
||||
let service = service_with_catalog_cache(&store, server.uri(), catalog_cache());
|
||||
|
||||
let error = service
|
||||
let snapshot = service
|
||||
.synchronize()
|
||||
.await
|
||||
.expect_err("routing facts must still agree across plans");
|
||||
.expect("supported_in_api drift must not block catalog publication");
|
||||
|
||||
assert!(matches!(
|
||||
error,
|
||||
CodexCredentialCatalogError::ConflictingModelFacts
|
||||
));
|
||||
assert_eq!(snapshot.models().len(), 1);
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ use gateway_core::engine::{
|
||||
};
|
||||
use gateway_core::error::ProviderErrorKind;
|
||||
use gateway_core::event::GatewayEvent;
|
||||
use gateway_core::operation::{GenerateRequest, Operation, ProtocolPayload, ProviderSessionState};
|
||||
use gateway_core::operation::{
|
||||
CapabilityRequirements, GenerateRequest, Operation, OperationKind, ProtocolPayload,
|
||||
ProviderSessionState,
|
||||
};
|
||||
use gateway_core::policy::ClientApiKeyId;
|
||||
use gateway_core::routing::{
|
||||
ClientRoutingScope, ConfigRevision, FrozenAccountScope, ModelCapabilities, ProviderKind,
|
||||
@@ -2424,3 +2427,36 @@ async fn provider_compiles_catalog_presentation_for_codex_models() {
|
||||
assert!(presentation.verbosity());
|
||||
assert!(!presentation.hidden());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn provider_routes_catalog_model_when_supported_in_api_is_false() {
|
||||
let store = Arc::new(MemoryAccountStore::default());
|
||||
create_account(&store, "acct_non_api_model").await;
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/codex/models"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "application/json")
|
||||
.set_body_raw(
|
||||
br#"{"models":[{"slug":"gpt-5.6-sol-wm","display_name":"GPT-5.6-Sol-WM","supported_in_api":false,"visibility":"hide"}]}"#,
|
||||
"application/json",
|
||||
),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let provider = provider_with_base_url(&store, server.uri());
|
||||
|
||||
let capabilities = provider
|
||||
.query_model_capabilities()
|
||||
.await
|
||||
.expect("catalog model capabilities");
|
||||
let model = capabilities.first().expect("catalog model");
|
||||
|
||||
assert!(
|
||||
model
|
||||
.capabilities()
|
||||
.match_requirements(&CapabilityRequirements::new(OperationKind::Generate))
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ const {
|
||||
<template #body>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<BaseTable
|
||||
class="min-h-0 flex-1"
|
||||
class="min-h-0 flex-1 [--cp-table-row-min-height:72px]"
|
||||
:columns="accountColumns"
|
||||
:rows="accounts"
|
||||
:loading="loading"
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: "3.2.6"
|
||||
version: "3.2.7"
|
||||
|
||||
Reference in New Issue
Block a user