mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
feat(models): add GPT-5.3 Codex, full GPT-5.x family, Claude 4.x series, o4-mini (#197)
Fixes #184 — updates model selection, priority sort, and cost table to match current OpenAI and Anthropic model catalogs. OpenAI: GPT-5.3 Codex, GPT-5.2 Codex/Pro, GPT-5.1 Codex/Mini/Max, GPT-5/Mini/Nano, GPT-4.1/Mini/Nano, o4-mini, o3/Pro Anthropic: Claude Opus 4.6/4.5/4.1/4.0, Claude Sonnet 4.6/4.5/4.0, Claude Haiku 4.5, Claude 3.7 Sonnet, Claude 3.5 Haiku Also resolves stale merge-conflict markers in http.rs and json.rs.
This commit is contained in:
+38
-12
@@ -17,7 +17,17 @@ pub fn model_cost(model_id: &str) -> Option<(Decimal, Decimal)> {
|
||||
.unwrap_or(model_id);
|
||||
|
||||
match id {
|
||||
// OpenAI models -- prices per token (USD)
|
||||
// OpenAI — GPT-5.x / Codex
|
||||
"gpt-5.3-codex" | "gpt-5.3-codex-spark" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"gpt-5.2-codex" | "gpt-5.2-pro" | "gpt-5.2" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"gpt-5.1-codex" | "gpt-5.1-codex-max" | "gpt-5.1" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"gpt-5.1-codex-mini" => Some((dec!(0.0000003), dec!(0.0000012))),
|
||||
"gpt-5-codex" | "gpt-5-pro" | "gpt-5" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"gpt-5-mini" | "gpt-5-nano" => Some((dec!(0.0000003), dec!(0.0000012))),
|
||||
// OpenAI — GPT-4.x
|
||||
"gpt-4.1" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"gpt-4.1-mini" => Some((dec!(0.0000004), dec!(0.0000016))),
|
||||
"gpt-4.1-nano" => Some((dec!(0.0000001), dec!(0.0000004))),
|
||||
"gpt-4o" | "gpt-4o-2024-11-20" | "gpt-4o-2024-08-06" => {
|
||||
Some((dec!(0.0000025), dec!(0.00001)))
|
||||
}
|
||||
@@ -25,20 +35,36 @@ pub fn model_cost(model_id: &str) -> Option<(Decimal, Decimal)> {
|
||||
"gpt-4-turbo" | "gpt-4-turbo-2024-04-09" => Some((dec!(0.00001), dec!(0.00003))),
|
||||
"gpt-4" | "gpt-4-0613" => Some((dec!(0.00003), dec!(0.00006))),
|
||||
"gpt-3.5-turbo" | "gpt-3.5-turbo-0125" => Some((dec!(0.0000005), dec!(0.0000015))),
|
||||
// OpenAI — reasoning
|
||||
"o3" => Some((dec!(0.000002), dec!(0.000008))),
|
||||
"o3-mini" | "o3-mini-2025-01-31" => Some((dec!(0.0000011), dec!(0.0000044))),
|
||||
"o4-mini" => Some((dec!(0.0000011), dec!(0.0000044))),
|
||||
"o1" | "o1-2024-12-17" => Some((dec!(0.000015), dec!(0.00006))),
|
||||
"o1-mini" | "o1-mini-2024-09-12" => Some((dec!(0.000003), dec!(0.000012))),
|
||||
"o3-mini" | "o3-mini-2025-01-31" => Some((dec!(0.0000011), dec!(0.0000044))),
|
||||
|
||||
// Anthropic models
|
||||
"claude-3-5-sonnet-20241022" | "claude-3-5-sonnet-latest" | "claude-sonnet-4-20250514" => {
|
||||
Some((dec!(0.000003), dec!(0.000015)))
|
||||
}
|
||||
"claude-3-5-haiku-20241022" | "claude-3-5-haiku-latest" => {
|
||||
Some((dec!(0.0000008), dec!(0.000004)))
|
||||
}
|
||||
"claude-3-opus-20240229" | "claude-3-opus-latest" | "claude-opus-4-20250514" => {
|
||||
Some((dec!(0.000015), dec!(0.000075)))
|
||||
}
|
||||
// Anthropic
|
||||
"claude-opus-4-6"
|
||||
| "claude-opus-4-5"
|
||||
| "claude-opus-4-5-20251101"
|
||||
| "claude-opus-4-1"
|
||||
| "claude-opus-4-1-20250805"
|
||||
| "claude-opus-4-0"
|
||||
| "claude-opus-4-20250514"
|
||||
| "claude-3-opus-20240229"
|
||||
| "claude-3-opus-latest" => Some((dec!(0.000015), dec!(0.000075))),
|
||||
"claude-sonnet-4-6"
|
||||
| "claude-sonnet-4-5"
|
||||
| "claude-sonnet-4-5-20250929"
|
||||
| "claude-sonnet-4-0"
|
||||
| "claude-sonnet-4-20250514"
|
||||
| "claude-3-7-sonnet-20250219"
|
||||
| "claude-3-7-sonnet-latest"
|
||||
| "claude-3-5-sonnet-20241022"
|
||||
| "claude-3-5-sonnet-latest" => Some((dec!(0.000003), dec!(0.000015))),
|
||||
"claude-haiku-4-5"
|
||||
| "claude-haiku-4-5-20251001"
|
||||
| "claude-3-5-haiku-20241022"
|
||||
| "claude-3-5-haiku-latest" => Some((dec!(0.0000008), dec!(0.000004))),
|
||||
"claude-3-haiku-20240307" => Some((dec!(0.00000025), dec!(0.00000125))),
|
||||
|
||||
// Ollama / local models -- free
|
||||
|
||||
+32
-10
@@ -949,6 +949,11 @@ impl SetupWizard {
|
||||
"anthropic::claude-sonnet-4-20250514".into(),
|
||||
"Claude Sonnet 4 (best quality)".into(),
|
||||
),
|
||||
(
|
||||
"openai::gpt-5.3-codex".into(),
|
||||
"GPT-5.3 Codex (flagship)".into(),
|
||||
),
|
||||
("openai::gpt-5.2".into(), "GPT-5.2".into()),
|
||||
("openai::gpt-4o".into(), "GPT-4o".into()),
|
||||
];
|
||||
|
||||
@@ -1714,12 +1719,14 @@ fn mask_password_in_url(url: &str) -> String {
|
||||
/// Returns `(model_id, display_label)` pairs. Falls back to static defaults on error.
|
||||
async fn fetch_anthropic_models(cached_key: Option<&str>) -> Vec<(String, String)> {
|
||||
let static_defaults = vec![
|
||||
("claude-sonnet-4-20250514".into(), "Claude Sonnet 4".into()),
|
||||
("claude-opus-4-20250514".into(), "Claude Opus 4".into()),
|
||||
(
|
||||
"claude-3-5-haiku-20241022".into(),
|
||||
"Claude 3.5 Haiku (fast)".into(),
|
||||
"claude-opus-4-6".into(),
|
||||
"Claude Opus 4.6 (latest flagship)".into(),
|
||||
),
|
||||
("claude-sonnet-4-6".into(), "Claude Sonnet 4.6".into()),
|
||||
("claude-opus-4-5".into(), "Claude Opus 4.5".into()),
|
||||
("claude-sonnet-4-5".into(), "Claude Sonnet 4.5".into()),
|
||||
("claude-haiku-4-5".into(), "Claude Haiku 4.5 (fast)".into()),
|
||||
];
|
||||
|
||||
let api_key = cached_key
|
||||
@@ -1780,10 +1787,21 @@ async fn fetch_anthropic_models(cached_key: Option<&str>) -> Vec<(String, String
|
||||
/// Returns `(model_id, display_label)` pairs. Falls back to static defaults on error.
|
||||
async fn fetch_openai_models(cached_key: Option<&str>) -> Vec<(String, String)> {
|
||||
let static_defaults = vec![
|
||||
("gpt-5".into(), "GPT-5 (flagship)".into()),
|
||||
("gpt-5-mini".into(), "GPT-5 Mini (fast)".into()),
|
||||
(
|
||||
"gpt-5.3-codex".into(),
|
||||
"GPT-5.3 Codex (latest flagship)".into(),
|
||||
),
|
||||
("gpt-5.2-codex".into(), "GPT-5.2 Codex".into()),
|
||||
("gpt-5.2".into(), "GPT-5.2".into()),
|
||||
(
|
||||
"gpt-5.1-codex-mini".into(),
|
||||
"GPT-5.1 Codex Mini (fast)".into(),
|
||||
),
|
||||
("gpt-5".into(), "GPT-5".into()),
|
||||
("gpt-5-mini".into(), "GPT-5 Mini".into()),
|
||||
("gpt-4.1".into(), "GPT-4.1".into()),
|
||||
("gpt-4o".into(), "GPT-4o".into()),
|
||||
("gpt-4.1-mini".into(), "GPT-4.1 Mini".into()),
|
||||
("o4-mini".into(), "o4-mini (fast reasoning)".into()),
|
||||
("o3".into(), "o3 (reasoning)".into()),
|
||||
];
|
||||
|
||||
@@ -1864,11 +1882,15 @@ fn openai_model_priority(model_id: &str) -> usize {
|
||||
let id = model_id.to_ascii_lowercase();
|
||||
|
||||
const EXACT_PRIORITY: &[&str] = &[
|
||||
"gpt-5.3-codex",
|
||||
"gpt-5.2-codex",
|
||||
"gpt-5.2",
|
||||
"gpt-5.1-codex-mini",
|
||||
"gpt-5",
|
||||
"gpt-5-mini",
|
||||
"gpt-5-nano",
|
||||
"o3",
|
||||
"o4-mini",
|
||||
"o3",
|
||||
"o1",
|
||||
"gpt-4.1",
|
||||
"gpt-4.1-mini",
|
||||
@@ -1880,7 +1902,7 @@ fn openai_model_priority(model_id: &str) -> usize {
|
||||
}
|
||||
|
||||
const PREFIX_PRIORITY: &[&str] = &[
|
||||
"gpt-5-", "o3-", "o4-", "o1-", "gpt-4.1-", "gpt-4o-", "gpt-3.5-", "chatgpt-",
|
||||
"gpt-5.", "gpt-5-", "o3-", "o4-", "o1-", "gpt-4.1-", "gpt-4o-", "gpt-3.5-", "chatgpt-",
|
||||
];
|
||||
if let Some(pos) = PREFIX_PRIORITY
|
||||
.iter()
|
||||
@@ -2230,7 +2252,7 @@ mod tests {
|
||||
let _guard = EnvGuard::clear("OPENAI_API_KEY");
|
||||
let models = fetch_openai_models(None).await;
|
||||
assert!(!models.is_empty());
|
||||
assert_eq!(models[0].0, "gpt-5");
|
||||
assert_eq!(models[0].0, "gpt-5.3-codex");
|
||||
assert!(
|
||||
models.iter().any(|(id, _)| id.contains("gpt")),
|
||||
"static defaults should include a GPT model"
|
||||
|
||||
@@ -189,8 +189,8 @@ impl Tool for HttpTool {
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Request body. Use plain text or serialized JSON."
|
||||
"type": ["object", "array", "string", "number", "boolean", "null"],
|
||||
"description": "Request body (for POST/PUT/PATCH)"
|
||||
},
|
||||
"timeout_secs": {
|
||||
"type": "integer",
|
||||
@@ -361,13 +361,6 @@ impl Tool for HttpTool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_http_tool_schema_body_has_type() {
|
||||
let tool = HttpTool::new();
|
||||
let schema = tool.parameters_schema();
|
||||
assert_eq!(schema["properties"]["body"]["type"], "string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_tool_schema_headers_is_array() {
|
||||
let tool = HttpTool::new();
|
||||
@@ -460,4 +453,18 @@ mod tests {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_http_tool_schema_body_has_type() {
|
||||
let schema = HttpTool::new().parameters_schema();
|
||||
let body = schema
|
||||
.get("properties")
|
||||
.and_then(|p| p.get("body"))
|
||||
.expect("body schema missing");
|
||||
|
||||
assert!(
|
||||
body.get("type").is_some(),
|
||||
"body schema must include a type for OpenAI-compatible tool validation"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ impl Tool for JsonTool {
|
||||
"description": "The JSON operation to perform"
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "JSON input string. For query/stringify/validate, pass serialized JSON."
|
||||
"type": ["string", "object", "array", "number", "boolean", "null"],
|
||||
"description": "JSON input data. Pass a string for parse, any type otherwise."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
@@ -154,13 +154,6 @@ fn query_json(data: &serde_json::Value, path: &str) -> Result<serde_json::Value,
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_json_tool_schema_data_has_type() {
|
||||
let tool = JsonTool;
|
||||
let schema = tool.parameters_schema();
|
||||
assert_eq!(schema["properties"]["data"]["type"], "string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_json() {
|
||||
let data = serde_json::json!({
|
||||
@@ -197,4 +190,18 @@ mod tests {
|
||||
let err = parse_json_input(&input).unwrap_err();
|
||||
assert!(err.to_string().contains("invalid JSON input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_tool_schema_data_has_type() {
|
||||
let schema = JsonTool.parameters_schema();
|
||||
let data = schema
|
||||
.get("properties")
|
||||
.and_then(|p| p.get("data"))
|
||||
.expect("data schema missing");
|
||||
|
||||
assert!(
|
||||
data.get("type").is_some(),
|
||||
"data schema must include a type for OpenAI-compatible tool validation"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user