Add terminal client, memory, model fallback, version and install improvements
- codecraft: full-screen terminal client (ratatui) – local tools and files, models via the CodeCraft server gateway or own providers (standalone with a guided setup), slash commands, @files, permission dialogs, resume, update - Static Linux CLI builds (x86_64/aarch64, cargo-zigbuild) served by the server: /cli/install.sh and /cli/download/<target> - Project memory (remember tool, .codecraft/memory.md) and personal memory - Model fallback when a host is unreachable or a budget is used up - Version/commit in UI and health, daily release check, "What's new" dialog - PWA: install button, maskable icon, manifest id and screenshots - Case-insensitive detection of connection errors for retries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa1f8cf016
commit
6dffcb501a
52 files changed
+6477
-765
No files matched your search
+25
-3
@@ -300,7 +300,18 @@ pub async fn run_session(app: Arc<App>, session_id: &str, cancel: CancellationTo
|
||||
reasoning: true,
|
||||
model: model.clone(),
|
||||
};
|
||||
let step = stream_step(&app, &session, &agent.name, &model_spec, provider, req, &cancel).await?;
|
||||
let step = match stream_step(&app, &session, &agent.name, &model_spec, provider, req.clone(), &cancel).await {
|
||||
Ok(step) => step,
|
||||
// Host down or budget used up: switch to the model's fallback for this step.
|
||||
Err(e) if model.fallback.is_some() && !cancel.is_cancelled() && needs_fallback(&e) => {
|
||||
let fb = model.fallback.clone().unwrap_or_default();
|
||||
let (fb_provider, fb_model) = app.registry().resolve(&fb)?;
|
||||
app.emit(Event::Error { session_id: session_id.into(), message: format!("{model_spec} is not available ({}) – using the fallback model {fb}", short_error(&e)) });
|
||||
let req = ChatRequest { model: fb_model.clone(), max_tokens: fb_model.max_output.unwrap_or(16_000), ..req };
|
||||
stream_step(&app, &session, &agent.name, &fb, fb_provider, req, &cancel).await?
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
final_text = step
|
||||
.message
|
||||
.parts
|
||||
@@ -747,9 +758,20 @@ fn estimate_prompt_tokens(req: &ChatRequest) -> u64 {
|
||||
(chars as f64 / 3.8) as u64 + images * 1000
|
||||
}
|
||||
|
||||
/// Errors after which the fallback model takes over: the host is unreachable
|
||||
/// (connection, timeout, gateway errors) or the provider's budget is used up.
|
||||
fn needs_fallback(e: &anyhow::Error) -> bool {
|
||||
let s = format!("{e:#}").to_lowercase();
|
||||
["connect", "timed out", "timeout", "http 502", "http 503", "http 504", "dns", "budget"].iter().any(|k| s.contains(k))
|
||||
}
|
||||
|
||||
fn short_error(e: &anyhow::Error) -> String {
|
||||
format!("{e:#}").lines().next().unwrap_or("").chars().take(120).collect()
|
||||
}
|
||||
|
||||
fn is_retryable(e: &anyhow::Error) -> bool {
|
||||
let s = format!("{e:#}");
|
||||
s.contains("HTTP 429") || s.contains("HTTP 5") || s.contains("overloaded") || s.contains("connection") || s.contains("timed out")
|
||||
let s = format!("{e:#}").to_lowercase();
|
||||
["http 429", "http 5", "overloaded", "connect", "timed out", "timeout"].iter().any(|k| s.contains(k))
|
||||
}
|
||||
|
||||
/// One model request with streaming to the UI, usage recording and retries.
|
||||
|
||||
Reference in new issue
Block a user