Stats history with year filter, folding and XLSX/CSV export; snippets, read-only links, commit suggestions, weekly review, offline copies, server speech-to-text, planning model, job reports; fal.ai health fix
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c08c6c7436
commit
388ca96419
31 files changed
+1518
-82
No files matched your search
@@ -1248,6 +1248,58 @@ async fn side_task(app: &App, session: &Session, kind: &str, system: &str, input
|
||||
Ok(strip_think(&text))
|
||||
}
|
||||
|
||||
/// A commit message for staged changes (small model; the diff is cut to its context window).
|
||||
/// `recent` are the latest commit subjects, so the style matches the repository.
|
||||
pub async fn suggest_commit_message(app: &App, user_id: &str, diff: &str, recent: &str) -> Result<String> {
|
||||
let cfg = app.config();
|
||||
let spec = if !cfg.server.small_model.is_empty() && app.registry().resolve(&cfg.server.small_model).is_ok() {
|
||||
cfg.server.small_model.clone()
|
||||
} else {
|
||||
app.registry().first_model_excluding(crate::claude_code::PROVIDER).ok_or_else(|| anyhow!("no model available"))?
|
||||
};
|
||||
let (provider, mut model) = app.registry().resolve(&spec)?;
|
||||
model.effort = Some("low".into());
|
||||
let max_out = 200u64;
|
||||
let system = "You write a git commit message for the staged changes. Reply with the message only: a subject line of at most 72 characters in the imperative mood, then optionally a blank line and up to 4 short bullet points on what changed and why. No quotes, no code fences. Match the style of the recent commit subjects (language, prefixes).";
|
||||
let ctx = model.context.unwrap_or(32_000);
|
||||
let room = ctx.saturating_sub(max_out + system.len() as u64 / 3 + 500).max(600) as usize * 5 / 2;
|
||||
let input = format!("<recent-commits>\n{}\n</recent-commits>\n<staged-diff>\n{}\n</staged-diff>", fit(recent, 600), fit(diff, room.saturating_sub(700).clamp(500, 40_000)));
|
||||
let req = ChatRequest {
|
||||
system: system.into(),
|
||||
messages: vec![Message::new(Role::User, vec![Part::Text { text: input }])],
|
||||
tools: vec![],
|
||||
max_tokens: max_out,
|
||||
reasoning: false,
|
||||
session_id: None,
|
||||
model: model.clone(),
|
||||
};
|
||||
let (text, usage) = complete_text(app, &spec, provider, req, CancellationToken::new()).await?;
|
||||
let (p, m) = spec.split_once('/').unwrap_or(("", spec.as_str()));
|
||||
let _ = app.store.add_usage(&UsageRecord {
|
||||
ts: now_ms(),
|
||||
session_id: "cli:git-commit".into(),
|
||||
root_session_id: "cli:git-commit".into(),
|
||||
provider: p.into(),
|
||||
model: m.into(),
|
||||
agent: "commit-message".into(),
|
||||
input_tokens: usage.input_tokens,
|
||||
output_tokens: usage.output_tokens,
|
||||
cache_read_tokens: usage.cache_read_tokens,
|
||||
cache_write_tokens: usage.cache_write_tokens,
|
||||
ttft_ms: 0,
|
||||
duration_ms: 0,
|
||||
cost: model.cost(&usage),
|
||||
error: false,
|
||||
user_id: Some(user_id.to_string()),
|
||||
allowance: usage.allowance,
|
||||
});
|
||||
let msg = strip_think(&text).trim().trim_matches('`').trim().to_string();
|
||||
if msg.is_empty() {
|
||||
bail!("the model returned no message");
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn message_text(m: &Message) -> String {
|
||||
m.parts.iter().filter_map(|p| if let Part::Text { text } = p { Some(text.as_str()) } else { None }).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user