Expose available access programs in model discovery (#44893)

## What changed

Carry optional `available_access_programs` metadata through model information, presets, caches, and the TUI, and expose it as `availableAccessPrograms` in app-server `model/list` responses. Update the generated JSON schemas, TypeScript types, and Python models.

Preserve the distinction between missing metadata and an empty `cyber` list. Ignore unknown cyber program names when reading the catalog so new server programs do not prevent older clients from loading it. Discovery metadata does not grant access; inference still enforces authorization.

## Testing

Add coverage for absent, null, empty, and populated metadata, unknown program names, and app-server serialization. Extend cache tests and verify that online refreshes persist changed access metadata even when the catalog ETag stays unchanged.

GitOrigin-RevId: b3ef5805c1c61b8d64b9b896c9c0a79120143667
This commit is contained in:
faizan-oai authored and copyberry committed 2026-09-11 19:13:04 +00:00
1 parent 4dcce4f0c4
commit e3a52b87b2
31 files changed
+405 -44

No files matched your search

@@ -1,7 +1,9 @@
use super::shared::v2_enum_from_core;
use super::turn::CyberAccessProgram;
use crate::JsonSchema;
use crate::TS;
use codex_protocol::openai_models::InputModality;
use codex_protocol::openai_models::ModelAccessPrograms as CoreModelAccessPrograms;
use codex_protocol::openai_models::ModelAvailabilityNux as CoreModelAvailabilityNux;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::default_input_modalities;
@@ -86,6 +88,31 @@ pub struct ModelServiceTier {
pub description: String,
}
/// Caller-specific explicit access programs advertised by model discovery.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ModelAccessPrograms {
/// Accepted explicit selections.
pub cyber: Vec<CyberAccessProgram>,
}
impl From<CoreModelAccessPrograms> for ModelAccessPrograms {
fn from(value: CoreModelAccessPrograms) -> Self {
Self {
cyber: value.cyber.into_iter().map(Into::into).collect(),
}
}
}
impl From<ModelAccessPrograms> for CoreModelAccessPrograms {
fn from(value: ModelAccessPrograms) -> Self {
Self {
cyber: value.cyber.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -116,6 +143,9 @@ pub struct Model {
/// Catalog default service tier id for this model, when one is configured.
#[serde(default)]
pub default_service_tier: Option<String>,
/// Null when the catalog does not provide access-program metadata.
#[serde(default)]
pub available_access_programs: Option<ModelAccessPrograms>,
// Only one model should be marked as default.
pub is_default: bool,
}
@@ -139,6 +139,16 @@ impl From<CyberAccessProgram> for CoreCyberAccessProgram {
}
}
impl From<CoreCyberAccessProgram> for CyberAccessProgram {
fn from(value: CoreCyberAccessProgram) -> Self {
match value {
CoreCyberAccessProgram::Standard => Self::Standard,
CoreCyberAccessProgram::DaybreakBlue => Self::DaybreakBlue,
CoreCyberAccessProgram::DaybreakRed => Self::DaybreakRed,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]