mirror of
https://github.com/openai/codex.git
synced 2026-11-07 08:34:29 +00:00
Expose model upgrade retirement times (#38449)
## What changed - Parse the optional `retirement_at` RFC 3339 value from model upgrade metadata, treating missing, null, or malformed values as unknown. - Expose known retirement times from `model/list` as nullable Unix timestamps in `upgradeInfo.retirementAt` and preserve them when converting app-server models back into model presets. - Update the generated protocol schemas and app-server documentation. ## Testing - Cover absent, null, valid, and malformed catalog values. - Verify `model/list` serialization and app-server model conversion for known, missing, and out-of-range timestamps. GitOrigin-RevId: 969baf9f5b012997f460fb4611ba63c963ee658c
This commit is contained in:
1 parent
1da59ad257
commit
5cc65ecb98
14 files changed
+309
-42
No files matched your search
@@ -40,6 +40,10 @@ fn model_from_preset(preset: &ModelPreset) -> Model {
|
||||
upgrade_copy: upgrade.upgrade_copy.clone(),
|
||||
model_link: upgrade.model_link.clone(),
|
||||
migration_markdown: upgrade.migration_markdown.clone(),
|
||||
retirement_at: upgrade
|
||||
.retirement_at
|
||||
.as_ref()
|
||||
.map(chrono::DateTime::timestamp),
|
||||
}),
|
||||
availability_nux: preset.availability_nux.clone().map(Into::into),
|
||||
display_name: preset.display_name.clone(),
|
||||
@@ -155,38 +159,48 @@ async fn list_models_includes_hidden_models() -> Result<()> {
|
||||
#[tokio::test]
|
||||
async fn list_models_uses_chatgpt_remote_catalog_as_source_of_truth() -> Result<()> {
|
||||
let server = MockServer::start().await;
|
||||
let remote_model: ModelInfo = serde_json::from_value(json!({
|
||||
"slug": "chatgpt-remote-only",
|
||||
"display_name": "ChatGPT Remote Only",
|
||||
"description": "Remote-only model for app-server model/list coverage",
|
||||
"model_specialty": MODEL_SPECIALTY_CYBER,
|
||||
"default_reasoning_level": "max",
|
||||
"supported_reasoning_levels": [
|
||||
{"effort": "max", "description": "Maximum"},
|
||||
{"effort": "low", "description": "Low"},
|
||||
{"effort": "focused", "description": "Focused"}
|
||||
],
|
||||
"shell_type": "shell_command",
|
||||
"visibility": "list",
|
||||
"minimal_client_version": [0, 1, 0],
|
||||
"supported_in_api": true,
|
||||
"priority": 0,
|
||||
"upgrade": null,
|
||||
"support_verbosity": false,
|
||||
"default_verbosity": null,
|
||||
"apply_patch_tool_type": null,
|
||||
"truncation_policy": {"mode": "bytes", "limit": 10_000},
|
||||
"supports_parallel_tool_calls": false,
|
||||
"supports_image_detail_original": false,
|
||||
"multi_agent_version": "v2",
|
||||
"context_window": 272_000,
|
||||
"max_context_window": 272_000,
|
||||
"experimental_supported_tools": [],
|
||||
}))?;
|
||||
let remote_models = [json!("2030-01-01T00:00:00Z"), serde_json::Value::Null]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(priority, retirement_at)| {
|
||||
serde_json::from_value::<ModelInfo>(json!({
|
||||
"slug": format!("chatgpt-remote-only-{priority}"),
|
||||
"display_name": "ChatGPT Remote Only",
|
||||
"description": "Remote-only model for app-server model/list coverage",
|
||||
"model_specialty": MODEL_SPECIALTY_CYBER,
|
||||
"default_reasoning_level": "max",
|
||||
"supported_reasoning_levels": [
|
||||
{"effort": "max", "description": "Maximum"},
|
||||
{"effort": "low", "description": "Low"},
|
||||
{"effort": "focused", "description": "Focused"}
|
||||
],
|
||||
"shell_type": "shell_command",
|
||||
"visibility": "list",
|
||||
"minimal_client_version": [0, 1, 0],
|
||||
"supported_in_api": true,
|
||||
"priority": priority,
|
||||
"upgrade": {
|
||||
"model": "replacement-model",
|
||||
"migration_markdown": "Use the replacement model.",
|
||||
"retirement_at": retirement_at,
|
||||
},
|
||||
"support_verbosity": false,
|
||||
"default_verbosity": null,
|
||||
"apply_patch_tool_type": null,
|
||||
"truncation_policy": {"mode": "bytes", "limit": 10_000},
|
||||
"supports_parallel_tool_calls": false,
|
||||
"supports_image_detail_original": false,
|
||||
"multi_agent_version": "v2",
|
||||
"context_window": 272_000,
|
||||
"max_context_window": 272_000,
|
||||
"experimental_supported_tools": [],
|
||||
}))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let models_mock = mount_models_once(
|
||||
&server,
|
||||
ModelsResponse {
|
||||
models: vec![remote_model.clone()],
|
||||
models: remote_models.clone(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -216,20 +230,30 @@ openai_base_url = "{server_uri}/v1"
|
||||
.with_env_overrides(&[("OPENAI_API_KEY", None)])
|
||||
.build_initialized()
|
||||
.await?;
|
||||
let request_id = mcp
|
||||
.send_list_models_request(ModelListParams {
|
||||
limit: Some(100),
|
||||
cursor: None,
|
||||
include_hidden: None,
|
||||
})
|
||||
.await?;
|
||||
let response = mcp
|
||||
.read_stream_until_response_message(RequestId::Integer(request_id))
|
||||
.await?;
|
||||
assert_eq!(
|
||||
response.result["data"][0]["upgradeInfo"]["retirementAt"],
|
||||
json!(1_893_456_000)
|
||||
);
|
||||
assert_eq!(
|
||||
response.result["data"][1]["upgradeInfo"]["retirementAt"],
|
||||
serde_json::Value::Null
|
||||
);
|
||||
let ModelListResponse {
|
||||
data: items,
|
||||
next_cursor,
|
||||
} = mcp
|
||||
.request(|request_id| ClientRequest::ModelList {
|
||||
request_id,
|
||||
params: ModelListParams {
|
||||
limit: Some(100),
|
||||
cursor: None,
|
||||
include_hidden: None,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
let mut expected_presets: Vec<ModelPreset> = vec![remote_model.into()];
|
||||
} = serde_json::from_value(response.result)?;
|
||||
let mut expected_presets: Vec<ModelPreset> =
|
||||
remote_models.into_iter().map(Into::into).collect();
|
||||
ModelPreset::mark_default_by_picker_visibility(&mut expected_presets);
|
||||
let mut expected_items = expected_presets
|
||||
.iter()
|
||||
|
||||
Reference in new issue
Block a user