Files
dbx/crates/dbx-core/src/lib.rs
T
Abeautifulsnow c300336281 feat(ai): context window management and token usage tracking
* @
feat(ai): add conversation context management and token usage tracking (Phase 4)

## Background
Phase 4 of AI agent loop upgrade: token usage extraction from LLM responses,
cost estimation display, and automatic context window management (summarization
when approaching limits).

## Changes

### Backend (Rust)
- New token_usage::TokenUsage — cross-turn token accumulation
- New agent_loop::maybe_compact — proactive context compaction: token estimation
  → cut-point selection → LLM summarization → fallback summary
  → tool-pair integrity protection → drain/insert
- New agent_loop::context_window_for_model — auto-detect context window by model
- New agent_loop::is_context_length_error — detect upstream context-length errors
- New agent_loop::compact_tool_result_for_context — JSON/text sampling for large results
- Modified agent_loop::run_agent_loop — pre-turn compaction check, retry on context error
- Modified agent_events::AgentEnd — split total_tokens into input_tokens/output_tokens
- New agent_events::ContextCompacted — compaction event with metrics
- New AiConfig.context_window — user-configurable context window size
- New AiChatMessage.kind — context summary message marker
- Modified stream_with_tools & 3 provider stream functions — extract TokenUsage
  - Claude: message_start(input) + message_delta(output)
  - OpenAI: stream_options { include_usage } → final usage chunk
  - Gemini: usageMetadata per chunk, keep last

### Frontend (TypeScript/Vue)
- AiAssistant.vue: token + cost display row, estimateCost() with 7 model families,
  contextCompacted UI step, pendingCompaction deferred apply,
  messagesForAgentHistory post-compaction history reconstruction
- EditorSettingsDialog.vue: contextWindow input (min=1000, auto-detect)
- settingsStore.ts: AiConfig.contextWindow field
- tauri.ts: AgentEvent type updates, AiChatMessage.kind
- i18n: 6 new keys in en.ts and zh-CN.ts

## Fixed Issues
- messages[0] dropped from LLM context after compaction
- estimate_text_tokens hard threshold causing 2.8x estimate jump
- context_compacted event field semantics mismatch with frontend insert position
- validate_summary English-only keywords causing fallback on non-English LLMs
- Stale agentTokens after conversation switch
@

* remove cost computation logic
2026-06-15 18:55:22 +08:00

118 lines
2.9 KiB
Rust

pub mod agent_catalog;
pub mod agent_connection;
pub mod agent_events;
pub mod agent_kv;
pub mod agent_loop;
pub mod agent_manager;
pub mod agent_runtime;
pub mod agent_service;
pub mod agent_tools;
pub mod ai;
pub mod cloud_sync;
pub mod connection;
pub mod connection_secrets;
pub mod csv_export;
pub mod data_compare;
pub mod data_grid_sql;
pub mod database_capabilities;
pub mod database_export;
pub mod database_search_sql;
pub mod db;
pub mod db_admin_sql;
pub mod driver_runtime;
pub mod external;
pub mod history;
pub mod jdbc;
pub mod models;
pub mod mongo_ops;
pub mod object_source_sql;
pub mod path_utils;
pub mod plugins;
pub mod query;
pub mod query_cancel;
pub mod query_execution_sql;
pub mod query_result_sql;
pub mod redis_ops;
pub mod saved_sql;
pub mod schema;
pub mod schema_diff;
pub mod sql;
pub mod sql_analysis;
pub mod sql_dialect;
pub mod sql_editability;
pub mod sql_file_import;
pub mod sql_risk;
pub mod sqlite_backup;
pub mod storage;
pub mod table_export;
pub mod table_import;
pub mod table_structure_sql;
pub mod text_export;
pub mod token_usage;
pub mod transfer;
pub mod types;
pub mod update;
pub mod xlsx_export;
pub const R2_CDN_BASE: &str = "https://dl.dbxio.com/";
pub fn download_candidate_urls(github_url: &str, r2_path: &str) -> Vec<String> {
vec![format!("{R2_CDN_BASE}{r2_path}"), github_url.to_string()]
}
use std::pin::Pin;
type ResponseFuture = Pin<Box<dyn std::future::Future<Output = Result<reqwest::Response, String>> + Send>>;
pub async fn race_download(
client: &reqwest::Client,
github_url: &str,
r2_path: &str,
user_agent: &str,
) -> Result<reqwest::Response, String> {
use futures::future::select_ok;
let urls = download_candidate_urls(github_url, r2_path);
let mut futs: Vec<ResponseFuture> = Vec::with_capacity(urls.len());
for url in urls {
let client = client.clone();
let ua = user_agent.to_string();
futs.push(Box::pin(async move {
client
.get(&url)
.header(reqwest::header::USER_AGENT, ua)
.send()
.await
.and_then(|r| r.error_for_status())
.map_err(|e| format!("{e}"))
}) as ResponseFuture);
}
match select_ok(futs).await {
Ok((resp, _)) => Ok(resp),
Err(last_err) => Err(last_err),
}
}
#[cfg(test)]
mod tests {
use super::download_candidate_urls;
#[test]
fn download_candidates_exclude_third_party_github_proxy() {
let urls = download_candidate_urls(
"https://github.com/t8y2/dbx/releases/latest/download/latest.json",
"releases/latest/latest.json",
);
assert_eq!(
urls,
vec![
"https://dl.dbxio.com/releases/latest/latest.json",
"https://github.com/t8y2/dbx/releases/latest/download/latest.json",
]
);
}
}