mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-17 15:34:54 +00:00
A Hanzo sign-in refreshes itself, Hanzo models are known, and no stray call or link is left
The Hanzo provider asks `dev token` for its credential every ten minutes instead of reading HANZO_USER_KEY once at startup, so a session outlives the hour a sign-in token lives. A profile still on the old startup-only provider is moved to it; one the user changed is left alone. `dev token` prefers an API key, then a fresh token from the Hanzo CLI, to a sign-in token inherited from whatever launched the agent. The chat models api.hanzo.ai serves carry their real window and inputs, so enso-auto and the rest no longer run on fallback metadata. A justification without sandbox_permissions runs at the default permissions instead of failing the shell call. Featured plugins are asked for only with a ChatGPT sign-in, which is the only caller that endpoint answers. The profile links to the shared credential only when it exists, and a sign-in links first so it writes the shared one. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
//! The chat models api.hanzo.ai serves, as the agent needs to know them.
|
||||
//!
|
||||
//! The agent sizes its context, compaction and image input from a model's
|
||||
//! metadata, and a slug it cannot find runs on a 272K fallback with a warning.
|
||||
//! These are the chat slugs `GET https://api.hanzo.ai/v1/models` lists; the
|
||||
//! embedding, rerank, guard and voice models are not chat models and are absent.
|
||||
|
||||
/// What the agent needs to know about one served chat model.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Model {
|
||||
pub slug: &'static str,
|
||||
pub name: &'static str,
|
||||
/// Context window in tokens.
|
||||
pub window: i64,
|
||||
/// Accepts images as well as text.
|
||||
pub vision: bool,
|
||||
}
|
||||
|
||||
const MILLION: i64 = 1_000_000;
|
||||
|
||||
const fn model(slug: &'static str, name: &'static str, vision: bool) -> Model {
|
||||
Model {
|
||||
slug,
|
||||
name,
|
||||
window: MILLION,
|
||||
vision,
|
||||
}
|
||||
}
|
||||
|
||||
pub const MODELS: &[Model] = &[
|
||||
model("enso", "Enso", true),
|
||||
model("enso-auto", "Enso Auto", true),
|
||||
model("enso-flash", "Enso Flash", true),
|
||||
model("enso-free", "Enso Free", true),
|
||||
model("enso-pro", "Enso Pro", false),
|
||||
model("enso-ultra", "Enso Ultra", true),
|
||||
model("hanzo/enso", "Enso", true),
|
||||
model("hanzo/zen", "Zen", true),
|
||||
model("zen-free", "Zen Free", true),
|
||||
model("zen-vl", "Zen VL", true),
|
||||
model("zen5", "Zen 5", true),
|
||||
model("zen5-coder", "Zen 5 Coder", false),
|
||||
model("zen5-evo", "Zen 5 Evo", true),
|
||||
model("zen5-flash", "Zen 5 Flash", true),
|
||||
model("zen5-mini", "Zen 5 Mini", false),
|
||||
model("zen5-pro", "Zen 5 Pro", false),
|
||||
model("zen5-spark", "Zen 5 Spark", true),
|
||||
model("zen6", "Zen 6", true),
|
||||
model("zen6-flash", "Zen 6 Flash", true),
|
||||
];
|
||||
|
||||
/// The served chat model named `slug`, if there is one.
|
||||
pub fn find(slug: &str) -> Option<&'static Model> {
|
||||
MODELS.iter().find(|model| model.slug == slug)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_default_model_is_known() {
|
||||
let model = find(crate::DEFAULT_MODEL).expect("the default model is in the catalog");
|
||||
assert_eq!(model.window, 1_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_slug_is_listed_once() {
|
||||
for (index, model) in MODELS.iter().enumerate() {
|
||||
assert!(
|
||||
!MODELS[..index].iter().any(|other| other.slug == model.slug),
|
||||
"{} is listed twice",
|
||||
model.slug
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unserved_slug_is_unknown() {
|
||||
assert_eq!(find("zen-embedding"), None);
|
||||
assert_eq!(find("not-a-model"), None);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ fn the_default_profile_shares_one_account() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let home = root.path().join(".hanzo/dev");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::write(root.path().join(".hanzo/auth.json"), "{}").unwrap();
|
||||
share_credential(&home);
|
||||
let link = std::fs::read_link(home.join("auth.json")).unwrap();
|
||||
assert_eq!(link, root.path().join(".hanzo/auth.json"));
|
||||
@@ -61,6 +62,7 @@ fn sharing_the_account_again_changes_nothing() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let home = root.path().join(".hanzo/dev");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::write(root.path().join(".hanzo/auth.json"), "{}").unwrap();
|
||||
share_credential(&home);
|
||||
share_credential(&home);
|
||||
let link = std::fs::read_link(home.join("auth.json")).unwrap();
|
||||
@@ -89,3 +91,33 @@ fn an_explicit_profile_keeps_its_own_account() {
|
||||
share_credential(&home);
|
||||
assert!(std::fs::symlink_metadata(home.join("auth.json")).is_err());
|
||||
}
|
||||
|
||||
/// With no shared credential there is nothing to link to, so no link is made,
|
||||
/// and a link left pointing at nothing is removed.
|
||||
#[test]
|
||||
fn no_link_names_a_missing_credential() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let home = root.path().join(".hanzo/dev");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
share_credential(&home);
|
||||
assert!(std::fs::symlink_metadata(home.join("auth.json")).is_err());
|
||||
link(&root.path().join(".hanzo/auth.json"), &home.join("auth.json")).unwrap();
|
||||
share_credential(&home);
|
||||
assert!(std::fs::symlink_metadata(home.join("auth.json")).is_err());
|
||||
}
|
||||
|
||||
/// A sign-in about to save links first, so it writes the one shared credential.
|
||||
#[test]
|
||||
fn a_sign_in_writes_the_shared_credential() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let home = root.path().join(".hanzo/dev");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
share_for_login(&home);
|
||||
std::fs::write(home.join("auth.json"), "{\"token\":\"new\"}").unwrap();
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(root.path().join(".hanzo/auth.json")).unwrap(),
|
||||
"{\"token\":\"new\"}"
|
||||
);
|
||||
share_credential(&home);
|
||||
assert!(std::fs::symlink_metadata(home.join("auth.json")).unwrap().is_symlink());
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod catalog;
|
||||
pub mod kai;
|
||||
mod provider;
|
||||
pub use provider::activate_provider;
|
||||
@@ -30,6 +31,7 @@ pub fn initialize() -> std::io::Result<()> {
|
||||
std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")),
|
||||
)?;
|
||||
initialize_home(&home)?;
|
||||
provider::upgrade_auth(&home);
|
||||
share_credential(&home);
|
||||
provider::load_hanzo_credentials(&home);
|
||||
// This function is only invoked before upstream starts threads.
|
||||
@@ -54,7 +56,22 @@ fn product_home(explicit: Option<OsString>, user: Option<OsString>) -> std::io::
|
||||
/// One account across the Hanzo tools, so signing in to any of them signs in to
|
||||
/// all of them. Dev keeps its own sessions and settings; only the credential is
|
||||
/// shared, and only when the profile is the default one under `~/.hanzo`.
|
||||
///
|
||||
/// At startup the profile links to the shared credential only when it exists; a
|
||||
/// link to nothing is removed, so the profile never holds a dangling link.
|
||||
fn share_credential(home: &Path) {
|
||||
link_shared(home, false);
|
||||
}
|
||||
|
||||
/// Point the profile's credential at the shared one before a sign-in saves it,
|
||||
/// so the sign-in writes the one credential every Hanzo tool reads. The link may
|
||||
/// name a file the sign-in is about to create; a sign-in that does not finish
|
||||
/// leaves it for the next start to remove.
|
||||
pub fn share_for_login(home: &Path) {
|
||||
link_shared(home, true);
|
||||
}
|
||||
|
||||
fn link_shared(home: &Path, for_login: bool) {
|
||||
let (Some(hanzo), Some(name)) = (home.parent(), home.file_name()) else {
|
||||
return;
|
||||
};
|
||||
@@ -63,6 +80,13 @@ fn share_credential(home: &Path) {
|
||||
}
|
||||
let shared = hanzo.join("auth.json");
|
||||
let ours = home.join("auth.json");
|
||||
if !for_login && !shared.exists() {
|
||||
// Nothing to share yet: a link here would name a file that is not there.
|
||||
if std::fs::symlink_metadata(&ours).is_ok_and(|metadata| metadata.is_symlink()) {
|
||||
let _ = std::fs::remove_file(&ours);
|
||||
}
|
||||
return;
|
||||
}
|
||||
match std::fs::symlink_metadata(&ours) {
|
||||
// A real file here predates the shared credential; leave the user's alone.
|
||||
Ok(metadata) if !metadata.is_symlink() => return,
|
||||
@@ -110,10 +134,15 @@ model = "enso-auto"
|
||||
name = "Hanzo"
|
||||
base_url = "https://api.hanzo.ai/v1"
|
||||
wire_api = "responses"
|
||||
env_key = "HANZO_USER_KEY"
|
||||
env_key_instructions = "Run `dev login` to sign in to Hanzo or paste a Hanzo API key, or set HANZO_USER_KEY."
|
||||
requires_openai_auth = false
|
||||
|
||||
# The Hanzo sign-in is a short-lived token, so the agent asks `dev token` for it
|
||||
# again before it ages out instead of reading it once at startup.
|
||||
[model_providers.hanzo.auth]
|
||||
command = "dev"
|
||||
args = ["token"]
|
||||
refresh_interval_ms = 600000
|
||||
|
||||
[mcp_servers.hanzo]
|
||||
command = "hanzo-mcp"
|
||||
args = []
|
||||
|
||||
@@ -93,13 +93,36 @@ pub fn clear_hanzo_api_key(home: &Path) -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An IAM sign-in token rather than an API key: a JWT, which ages out within the
|
||||
/// hour. An API key never does.
|
||||
fn is_session_token(value: &str) -> bool {
|
||||
value.starts_with("eyJ") && value.split('.').count() == 3
|
||||
}
|
||||
|
||||
/// The Hanzo credential, in order: an API key set in the environment, a pasted
|
||||
/// API key, a fresh token from the signed-in Hanzo CLI, and last a sign-in token
|
||||
/// set in the environment. A sign-in token in the environment is whatever the
|
||||
/// launching process held when it started, so a fresh one from the CLI is
|
||||
/// preferred to it.
|
||||
fn credential(home: &Path) -> Option<String> {
|
||||
if let Some(key) = std::env::var("HANZO_USER_KEY")
|
||||
let inherited = std::env::var("HANZO_USER_KEY")
|
||||
.ok()
|
||||
.filter(|key| !key.is_empty())
|
||||
{
|
||||
return Some(key);
|
||||
.filter(|key| !key.is_empty());
|
||||
pick(inherited, saved_key(home), cli_token)
|
||||
}
|
||||
|
||||
fn pick(
|
||||
inherited: Option<String>,
|
||||
saved: Option<String>,
|
||||
cli: impl FnOnce() -> Option<String>,
|
||||
) -> Option<String> {
|
||||
if let Some(key) = inherited.as_ref().filter(|key| !is_session_token(key)) {
|
||||
return Some(key.clone());
|
||||
}
|
||||
saved.or_else(cli).or(inherited)
|
||||
}
|
||||
|
||||
fn saved_key(home: &Path) -> Option<String> {
|
||||
if let Ok(key) = std::fs::read_to_string(home.join("hanzo-api-key")) {
|
||||
if !key.is_empty()
|
||||
&& key.len() <= 16_384
|
||||
@@ -108,6 +131,10 @@ fn credential(home: &Path) -> Option<String> {
|
||||
return Some(key);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn cli_token() -> Option<String> {
|
||||
// The native CLI owns storage and refresh of the shared Hanzo IAM account.
|
||||
// Capture both streams so credentials never reach logs.
|
||||
let output = Command::new("hanzo")
|
||||
@@ -129,7 +156,7 @@ pub(super) fn load_hanzo_credentials(home: &Path) {
|
||||
if std::env::args_os().skip(1).any(|arg| {
|
||||
matches!(
|
||||
arg.to_str(),
|
||||
Some("--help" | "-h" | "--version" | "-V" | "login" | "logout")
|
||||
Some("--help" | "-h" | "--version" | "-V" | "login" | "logout" | "token")
|
||||
)
|
||||
}) {
|
||||
return;
|
||||
@@ -152,6 +179,46 @@ pub(super) fn load_hanzo_credentials(home: &Path) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Move a profile still on the startup-only credential to the refreshing one.
|
||||
///
|
||||
/// Earlier releases wrote the Hanzo provider with `env_key = "HANZO_USER_KEY"`,
|
||||
/// read once when the agent starts, so a session outlived its sign-in token and
|
||||
/// failed with 401 an hour in. A provider block still holding exactly that
|
||||
/// default is rewritten to ask `dev token` instead; a provider the user changed
|
||||
/// is theirs and stays as it is.
|
||||
pub(super) fn upgrade_auth(home: &Path) {
|
||||
let path = home.join("config.toml");
|
||||
let Ok(text) = std::fs::read_to_string(&path) else {
|
||||
return;
|
||||
};
|
||||
let Ok(config) = text.parse::<DocumentMut>() else {
|
||||
return;
|
||||
};
|
||||
let Some(provider) = config
|
||||
.get("model_providers")
|
||||
.and_then(|providers| providers.get("hanzo"))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if provider.get("auth").is_some()
|
||||
|| provider.get("env_key").and_then(toml_edit::Item::as_str) != Some("HANZO_USER_KEY")
|
||||
|| provider.get("base_url").and_then(toml_edit::Item::as_str) != Some(super::API_BASE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let _ = edit(home, |config| {
|
||||
let defaults = super::DEFAULT_CONFIG
|
||||
.parse::<DocumentMut>()
|
||||
.expect("valid default config");
|
||||
let Some(provider) = config["model_providers"]["hanzo"].as_table_like_mut() else {
|
||||
return;
|
||||
};
|
||||
provider.remove("env_key");
|
||||
provider.remove("env_key_instructions");
|
||||
provider.insert("auth", defaults["model_providers"]["hanzo"]["auth"].clone());
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "provider_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -31,3 +31,70 @@ fn switching_back_to_hanzo_preserves_unrelated_preferences() {
|
||||
.insert("model_reasoning_effort".to_string(), "high".into());
|
||||
assert_eq!(config, expected);
|
||||
}
|
||||
|
||||
const JWT: &str = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ6In0.c2ln";
|
||||
|
||||
#[test]
|
||||
fn an_api_key_in_the_environment_wins() {
|
||||
let chosen = pick(Some("hk-live-key".into()), Some("saved".into()), || Some(JWT.into()));
|
||||
assert_eq!(chosen.as_deref(), Some("hk-live-key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_inherited_sign_in_token_yields_to_a_fresh_one() {
|
||||
let fresh = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJmcmVzaCJ9.c2ln";
|
||||
assert_eq!(pick(Some(JWT.into()), None, || Some(fresh.into())).as_deref(), Some(fresh));
|
||||
assert_eq!(pick(Some(JWT.into()), Some("saved".into()), || None).as_deref(), Some("saved"));
|
||||
// With nothing fresher, the inherited token is still better than none.
|
||||
assert_eq!(pick(Some(JWT.into()), None, || None).as_deref(), Some(JWT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_default_provider_refreshes_its_token() {
|
||||
let config: toml::Value = toml::from_str(crate::DEFAULT_CONFIG).unwrap();
|
||||
let provider = &config["model_providers"]["hanzo"];
|
||||
assert!(provider.get("env_key").is_none(), "auth and env_key cannot both be set");
|
||||
assert_eq!(provider["auth"]["command"].as_str(), Some("dev"));
|
||||
assert_eq!(provider["auth"]["args"].as_array().unwrap()[0].as_str(), Some("token"));
|
||||
// Shorter than the hour a sign-in token lives.
|
||||
assert!(provider["auth"]["refresh_interval_ms"].as_integer().unwrap() < 3_600_000);
|
||||
}
|
||||
|
||||
const OLD_PROVIDER: &str = r#"# mine
|
||||
model_provider = "hanzo"
|
||||
model = "enso-auto"
|
||||
|
||||
[model_providers.hanzo]
|
||||
name = "Hanzo"
|
||||
base_url = "https://api.hanzo.ai/v1"
|
||||
wire_api = "responses"
|
||||
env_key = "HANZO_USER_KEY"
|
||||
env_key_instructions = "Run `dev login` to sign in to Hanzo or paste a Hanzo API key, or set HANZO_USER_KEY."
|
||||
requires_openai_auth = false
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn a_startup_only_credential_is_upgraded_to_the_refreshing_one() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let path = home.path().join("config.toml");
|
||||
std::fs::write(&path, OLD_PROVIDER).unwrap();
|
||||
upgrade_auth(home.path());
|
||||
let text = std::fs::read_to_string(&path).unwrap();
|
||||
assert!(text.starts_with("# mine\n"));
|
||||
let config: toml::Value = toml::from_str(&text).unwrap();
|
||||
let defaults: toml::Value = toml::from_str(crate::DEFAULT_CONFIG).unwrap();
|
||||
assert_eq!(config["model_providers"]["hanzo"], defaults["model_providers"]["hanzo"]);
|
||||
// Twice changes nothing more.
|
||||
upgrade_auth(home.path());
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_provider_the_user_changed_is_left_alone() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let path = home.path().join("config.toml");
|
||||
let theirs = OLD_PROVIDER.replace("env_key = \"HANZO_USER_KEY\"", "env_key = \"MY_KEY\"");
|
||||
std::fs::write(&path, &theirs).unwrap();
|
||||
upgrade_auth(home.path());
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap(), theirs);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ pub(crate) async fn run(login: Login, mut overrides: CliConfigOverrides) -> Resu
|
||||
println!("Hanzo API key saved.");
|
||||
}
|
||||
Choice::ChatGpt | Choice::OpenAiKey => {
|
||||
hanzo_config::share_for_login(&home);
|
||||
overrides
|
||||
.raw_overrides
|
||||
.push("model_provider=\"openai\"".into());
|
||||
|
||||
@@ -52,6 +52,9 @@ enum Command {
|
||||
/// Discard stored credentials.
|
||||
Logout,
|
||||
|
||||
/// Print the Hanzo credential the agent sends, fresh from the sign-in.
|
||||
Token,
|
||||
|
||||
/// Speak the agent protocol on stdin and stdout.
|
||||
Serve,
|
||||
|
||||
@@ -216,6 +219,10 @@ async fn run(paths: Arg0DispatchPaths) -> Result<()> {
|
||||
}
|
||||
Some(Command::Login(login)) => login::run(login, overrides).await?,
|
||||
Some(Command::Logout) => login::logout(overrides).await?,
|
||||
Some(Command::Token) => match hanzo_config::hanzo_credential() {
|
||||
Some(token) => println!("{token}"),
|
||||
None => anyhow::bail!("Not signed in to Hanzo. Run `dev login`."),
|
||||
},
|
||||
Some(Command::Serve) => {
|
||||
codex_app_server::run_main(paths, overrides, LoaderOverrides::default(), false, false)
|
||||
.await?;
|
||||
|
||||
@@ -268,5 +268,47 @@
|
||||
"old": " codex_guardian_v2::install(\n &mut builder,",
|
||||
"new": " // Hanzo: Kai decides in the loop; it runs only for a profile with a kai.toml.\n hanzo_kai::install(&mut builder, thread_manager.clone());\n codex_guardian_v2::install(\n &mut builder,",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "core-plugins/src/remote_legacy.rs",
|
||||
"old": " let base_url = config.chatgpt_base_url.trim_end_matches('/');\n let mut url = Url::parse(&format!(\"{base_url}/plugins/featured\"))",
|
||||
"new": " // Featured plugins are served to a ChatGPT sign-in only; any other caller is\n // refused, so it is not asked.\n let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) else {\n return Ok(Vec::new());\n };\n let base_url = config.chatgpt_base_url.trim_end_matches('/');\n let mut url = Url::parse(&format!(\"{base_url}/plugins/featured\"))",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "core-plugins/src/remote_legacy.rs",
|
||||
"old": " if let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) {\n request =\n request.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());\n }\n",
|
||||
"new": " request = request.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());\n",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "core/src/tools/handlers/mod.rs",
|
||||
"old": " if justification.is_some() && sandbox_permissions.is_none() {\n return Err(FunctionCallError::RespondToModel(\n \"`justification` requires an explicit `sandbox_permissions`; use `sandbox_permissions: \\\"require_escalated\\\"` for unsandboxed execution, or omit `justification`.\".to_string(),\n ));\n }\n\n Ok(sandbox_permissions.unwrap_or_default())",
|
||||
"new": " // A justification explains an escalation. With none asked for there is\n // nothing for it to explain, so the call runs at the default permissions\n // rather than failing.\n let _ = justification;\n Ok(sandbox_permissions.unwrap_or_default())",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "models-manager/Cargo.toml",
|
||||
"old": "[dependencies]",
|
||||
"new": "[dependencies]\nhanzo-config = { path = \"../../../../crates/hanzo-config\" }",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "models-manager/src/model_info.rs",
|
||||
"old": " warn!(\"Unknown model {slug} is used. This will use fallback model metadata.\");\n ModelInfo {\n slug: slug.to_string(),\n display_name: slug.to_string(),",
|
||||
"new": " let known = hanzo_config::catalog::find(slug);\n if known.is_none() {\n warn!(\"Unknown model {slug} is used. This will use fallback model metadata.\");\n }\n let window = known.map_or(272_000, |model| model.window);\n ModelInfo {\n slug: slug.to_string(),\n display_name: known.map_or_else(|| slug.to_string(), |model| model.name.to_string()),",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "models-manager/src/model_info.rs",
|
||||
"old": " context_window: Some(272_000),\n max_context_window: Some(272_000),",
|
||||
"new": " context_window: Some(window),\n max_context_window: Some(window),",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"file": "models-manager/src/model_info.rs",
|
||||
"old": " input_modalities: default_input_modalities(),\n used_fallback_model_metadata: true, // this is the fallback model metadata",
|
||||
"new": " input_modalities: match known {\n Some(model) if !model.vision => vec![codex_protocol::openai_models::InputModality::Text],\n _ => default_input_modalities(),\n },\n used_fallback_model_metadata: known.is_none(),",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user