Add OpenAI GPT-5.6 models (#566)
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run

## Summary

- add GPT-5.6 Sol, Terra, and Luna to the built-in OpenAI model catalog
with current capabilities, limits, aliases, and pricing
- route all three models through the OpenAI Responses API and keep the
Codex-safe 272K context policy
- make GPT-5.6 Sol the OpenAI default and update the model docs and Ask
Fabro API contract

## Testing

- cargo nextest run -p fabro-model
- cargo nextest run -p fabro-llm builtin_catalog_route_equivalence_table
- cargo nextest run -p fabro-server list_models
- cargo nextest run -p fabro-server --features test-support
run_responses_include_ask_fabro_affordance
- cargo +nightly-2026-04-14 fmt --check --all
- cargo +nightly-2026-04-14 clippy -p fabro-model -p fabro-llm
--all-targets -- -D warnings
- git diff --check
This commit is contained in:
Bryan Helmkamp 2026-07-10 10:46:30 -04:00 committed by GitHub
parent 96372c5a3c
commit 18d4628291
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 110 additions and 15 deletions

View file

@ -20,6 +20,9 @@ No single model is best at everything. Fabro lets you assign the right model to
| `claude-sonnet-4-6` | anthropic | `sonnet`, `claude-sonnet` | 200K | $3.00 / $15.00 | 50 tok/s |
| `claude-sonnet-4-5` | anthropic | | 200K | $3.00 / $15.00 | 50 tok/s |
| `claude-haiku-4-5` | anthropic | `haiku`, `claude-haiku` | 200K | $0.80 / $4.00 | 100 tok/s |
| `gpt-5.6-sol` | openai | `gpt-5.6`, `gpt56-sol` | 272K | $5.00 / $30.00 | n/a |
| `gpt-5.6-terra` | openai | `gpt56-terra` | 272K | $2.50 / $15.00 | n/a |
| `gpt-5.6-luna` | openai | `gpt56-luna` | 272K | $1.00 / $6.00 | n/a |
| `gpt-5.4` | openai | `gpt54`, `gpt5`, `codex` | 272K | $2.50 / $15.00 | 70 tok/s |
| `gpt-5.5` | openai | `gpt55` | 272K | $5.00 / $30.00 | 70 tok/s |
| `gpt-5.5-pro` | openai | `gpt55-pro` | 1M | $30.00 / $180.00 | 20 tok/s |
@ -169,7 +172,7 @@ When no model or provider is specified, Fabro checks configured provider credent
| Provider | Default model |
|---|---|
| `anthropic` | `claude-sonnet-4-6` |
| `openai` | `gpt-5.5` |
| `openai` | `gpt-5.6-sol` |
| `gemini` | `gemini-3.5-flash` |
| `kimi` | `kimi-k2.5` |
| `zai` | `glm-4.7` |

View file

@ -100,6 +100,13 @@ fn normalize_attach_json_progress_event(mut event: Value) -> Value {
);
}
}
if let Some(model_name) = event.pointer_mut("/properties/settings/run/model/name") {
assert!(
model_name.is_string(),
"default model should serialize as a string"
);
*model_name = Value::String("[DEFAULT_MODEL]".to_string());
}
event
}
@ -991,7 +998,7 @@ fn attach_json_errors_without_prompting_for_human_input() {
"speed": null
},
"fallbacks": [],
"name": "gpt-5.5",
"name": "[DEFAULT_MODEL]",
"provider": "openai"
},
"notifications": {},

View file

@ -306,6 +306,9 @@ mod tests {
("gpt-5.4-pro", "gpt-5.4-pro", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("gpt-5.5", "gpt-5.5", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("gpt-5.5-pro", "gpt-5.5-pro", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("gpt-5.6-luna", "gpt-5.6-luna", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("gpt-5.6-sol", "gpt-5.6-sol", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("gpt-5.6-terra", "gpt-5.6-terra", T::OpenAi, C::OpenAiResponses, B::OpenAi, P::OpenAi),
("kimi-k2.5", "kimi-k2.5", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("mercury-2", "mercury-2", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),
("minimax-m2.5", "minimax-m2.5", T::OpenAiCompatible, C::OpenAiCompatible, B::OpenAi, P::OpenAi),

View file

@ -2137,7 +2137,8 @@ enabled = true
let m = Catalog::builtin()
.default_for_provider(&ProviderId::openai())
.unwrap();
assert_eq!(m.id, "gpt-5.5");
assert_eq!(m.provider, ProviderId::openai());
assert!(m.default);
let m = Catalog::builtin()
.default_for_provider(&ProviderId::gemini())
@ -2910,12 +2911,10 @@ probe = false
))
.expect("sparse built-in model override should build");
let openai = ProviderId::openai();
assert_eq!(
catalog
.probe_for_provider(&ProviderId::openai())
.unwrap()
.id,
"gpt-5.5"
catalog.probe_for_provider(&openai).unwrap().id,
catalog.default_for_provider(&openai).unwrap().id
);
}
@ -3319,12 +3318,10 @@ small_default = false
))
.expect("sparse built-in model override should build");
let openai = ProviderId::openai();
assert_eq!(
catalog
.small_default_for_provider(&ProviderId::openai())
.unwrap()
.id,
"gpt-5.5"
catalog.small_default_for_provider(&openai).unwrap().id,
catalog.default_for_provider(&openai).unwrap().id
);
}
@ -4447,6 +4444,9 @@ sampling_params = false
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.5",
"gpt-5.6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
] {
assert_eq!(
catalog.get(model).unwrap().context_window(),

View file

@ -8,6 +8,82 @@ priority = 90
[providers.openai.auth]
credentials = ["env:OPENAI_API_KEY", "vault:OPENAI_API_KEY", "vault:OPENAI_CODEX"]
[models."gpt-5.6-sol"]
provider = "openai"
api_id = "gpt-5.6-sol"
display_name = "GPT-5.6 Sol"
family = "gpt-5"
training = "2026-02-16"
knowledge_cutoff = "February 16, 2026"
default = true
aliases = ["gpt56-sol", "gpt-56-sol", "gpt-5.6", "gpt56", "gpt-56"]
[models."gpt-5.6-sol".limits]
context_window = 272000
max_output = 128000
[models."gpt-5.6-sol".features]
tools = true
vision = true
reasoning = true
reasoning_effort = "levels"
prompt_cache = true
[models."gpt-5.6-sol".costs]
input_cost_per_mtok = 5.0
output_cost_per_mtok = 30.0
cache_input_cost_per_mtok = 0.5
[models."gpt-5.6-terra"]
provider = "openai"
api_id = "gpt-5.6-terra"
display_name = "GPT-5.6 Terra"
family = "gpt-5"
training = "2026-02-16"
knowledge_cutoff = "February 16, 2026"
aliases = ["gpt56-terra", "gpt-56-terra"]
[models."gpt-5.6-terra".limits]
context_window = 272000
max_output = 128000
[models."gpt-5.6-terra".features]
tools = true
vision = true
reasoning = true
reasoning_effort = "levels"
prompt_cache = true
[models."gpt-5.6-terra".costs]
input_cost_per_mtok = 2.5
output_cost_per_mtok = 15.0
cache_input_cost_per_mtok = 0.25
[models."gpt-5.6-luna"]
provider = "openai"
api_id = "gpt-5.6-luna"
display_name = "GPT-5.6 Luna"
family = "gpt-5"
training = "2026-02-16"
knowledge_cutoff = "February 16, 2026"
aliases = ["gpt56-luna", "gpt-56-luna"]
[models."gpt-5.6-luna".limits]
context_window = 272000
max_output = 128000
[models."gpt-5.6-luna".features]
tools = true
vision = true
reasoning = true
reasoning_effort = "levels"
prompt_cache = true
[models."gpt-5.6-luna".costs]
input_cost_per_mtok = 1.0
output_cost_per_mtok = 6.0
cache_input_cost_per_mtok = 0.1
[models."gpt-5.4"]
provider = "openai"
api_id = "gpt-5.4"
@ -40,7 +116,6 @@ display_name = "GPT-5.5"
family = "gpt-5"
training = "2025-12-01"
knowledge_cutoff = "December 2025"
default = true
estimated_output_tps = 70
aliases = ["gpt55", "gpt-55"]

View file

@ -1,5 +1,6 @@
use axum::body::Body;
use axum::http::{Request, StatusCode};
use fabro_model::{Catalog, ProviderId};
use fabro_types::settings::run::EnvironmentProvider;
use tower::ServiceExt;
@ -160,7 +161,13 @@ _version = 1
created["ask_fabro"]["unavailable_reason"],
"sandbox_not_ready"
);
assert_eq!(created["ask_fabro"]["default_model"], "gpt-5.5");
let default_openai_model = Catalog::builtin()
.default_for_provider(&ProviderId::openai())
.expect("the built-in OpenAI provider should have a default model");
assert_eq!(
created["ask_fabro"]["default_model"].as_str(),
Some(default_openai_model.id())
);
let get_request = Request::builder()
.method("GET")