mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
feat(usage): import Grok Build official-mode usage from session logs
Grok CLI's official OAuth mode cannot be routed through the local proxy
(empty config is the mode switch, so there is no injection point), which
left official-mode usage invisible. Add session_usage_grokbuild to
import usage from ~/.grok/sessions updates.jsonl:
- Only turn_completed events carry usage; each event is the independent
per-turn total (accumulated across inference loops within one prompt),
so events are imported at face value. Do not reintroduce differencing
of adjacent events: counters reset every turn and differencing would
massively under-record.
- Cost priority: reported costUsdTicks (1 tick = 1e-10 USD) wins when
complete, because the backfill only repairs rows with total <= 0 and
can never correct a positive mispriced value; local pricing fills the
breakdown and raises a drift warning above max(1% of reported, 1e-6).
costIsPartial marks the reported value a lower bound: prefer a full
local recompute when the model is priced, else record the lower bound.
- Idempotency key grok_session:{session}:{prompt_id}:{model} anchors on
the upstream per-turn UUID (index fallback only when prompt_id is
empty), so rewind truncation cannot shift keys and double count;
orphan rows from truncated turns are kept since the tokens were spent.
- Anti double-count vs proxy takeover: 10min settle window plus a
time-window guard over recent grokbuild proxy activity; guarded skips
never mark files as synced.
- Seed grok-4.5-build pricing 2/6/0.30, back-derived from exact
costUsdTicks samples (cache read bills at 0.30, not the listed 0.50).
- Map _grok_session to a friendly provider display name and refresh the
takeover-capability comment in services/proxy.rs.
This commit is contained in:
@@ -2246,6 +2246,10 @@ impl Database {
|
||||
("qwen3-32b", "Qwen3 32B", "0.16", "0.64", "0", "0"),
|
||||
// Grok 系列 (xAI)
|
||||
("grok-4.5", "Grok 4.5", "2", "6", "0.50", "0"),
|
||||
// Grok CLI 官方 OAuth 态 modelUsage 上报的内部别名。定价由
|
||||
// costUsdTicks(1 tick = 1e-10 USD)双轮实测反推:input/output 与
|
||||
// grok-4.5 同为 2/6,cache read 实际按 0.30 计(非 API 挂牌的 0.50)
|
||||
("grok-4.5-build", "Grok 4.5 Build", "2", "6", "0.30", "0"),
|
||||
("grok-4.3", "Grok 4.3", "1.25", "2.50", "0.20", "0"),
|
||||
(
|
||||
"grok-4.20-0309-reasoning",
|
||||
|
||||
@@ -17,6 +17,7 @@ pub mod s3_sync;
|
||||
pub mod session_usage;
|
||||
pub mod session_usage_codex;
|
||||
pub mod session_usage_gemini;
|
||||
pub mod session_usage_grokbuild;
|
||||
pub mod session_usage_opencode;
|
||||
pub mod skill;
|
||||
pub mod speedtest;
|
||||
|
||||
@@ -1505,8 +1505,11 @@ impl ProxyService {
|
||||
/// Grok Build live 是否具备可接管的自定义模型表。
|
||||
///
|
||||
/// 官方态 live(Grok CLI 自带 OAuth 登录、无 `[model.*]` 表)没有注入
|
||||
/// 占位符的落点,且官方供应商本就禁止经代理接管(封号风险),调用方
|
||||
/// 应跳过接管或直接报错。
|
||||
/// 占位符的落点:Grok CLI 以「config 是否为空」区分官方 OAuth / 自定义
|
||||
/// 供应商两种模式,表达不出「官方 OAuth + 自定义 base_url」。官方供应商
|
||||
/// 的接管能力门见 `official_provider_supports_proxy_takeover`(按应用逐个
|
||||
/// 开,目前仅 Codex),调用方应跳过接管或直接报错。官方态的用量统计由
|
||||
/// `session_usage_grokbuild` 从会话日志导入,不依赖代理。
|
||||
fn grok_live_config_supports_takeover(config: &Value) -> bool {
|
||||
config
|
||||
.get("config")
|
||||
|
||||
@@ -86,6 +86,11 @@ pub fn sync_all_unlocked(db: &Database) -> SessionSyncResult {
|
||||
"OpenCode",
|
||||
crate::services::session_usage_opencode::sync_opencode_usage(db),
|
||||
);
|
||||
merge_sync_step(
|
||||
&mut result,
|
||||
"Grok Build",
|
||||
crate::services::session_usage_grokbuild::sync_grokbuild_usage(db),
|
||||
);
|
||||
notify_sync_result(&result);
|
||||
result
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -214,6 +214,7 @@ fn provider_name_coalesce(log_alias: &str, provider_alias: &str) -> String {
|
||||
WHEN '_codex_session' THEN 'Codex (Session)' \
|
||||
WHEN '_gemini_session' THEN 'Gemini (Session)' \
|
||||
WHEN '_opencode_session' THEN 'OpenCode (Session)' \
|
||||
WHEN '_grok_session' THEN 'Grok Build (Session)' \
|
||||
ELSE {log_alias}.provider_id END)"
|
||||
)
|
||||
}
|
||||
@@ -416,6 +417,40 @@ pub(crate) fn has_matching_proxy_usage_log(
|
||||
.map_err(|e| AppError::Database(format!("查询重复代理用量日志失败: {e}")))
|
||||
}
|
||||
|
||||
/// grokbuild 会话导入的接管活动守卫:给定时刻 ±窗口内存在任何 grokbuild
|
||||
/// 代理直录行,即认为当时处于代理接管态,会话事件应整体跳过——同一请求
|
||||
/// 已由代理逐请求记账,会话侧再入账必双算。
|
||||
///
|
||||
/// 不复用 [`has_matching_proxy_usage_log`] 的指纹匹配:Grok 会话事件是
|
||||
/// 逐轮聚合值,与代理逐请求行的 token 值结构性不相等,指纹永不命中。
|
||||
/// 这里按"接管态检测"而非"行匹配"设计,故不过滤 status_code——失败的
|
||||
/// 代理请求同样证明流量正走代理。
|
||||
///
|
||||
/// 已知局限(有意取舍,方向保守只漏不双):窗口不含 session 维度,任一
|
||||
/// grokbuild 代理行会给 ±窗口内的全部会话事件投下阴影——接管/官方两态在
|
||||
/// 十分钟内交替或并行使用时,官方侧轮次会被跳过(漏记而非双算)。
|
||||
pub(crate) fn has_recent_grokbuild_proxy_activity(
|
||||
conn: &Connection,
|
||||
created_at: i64,
|
||||
) -> Result<bool, AppError> {
|
||||
let l_data_source = data_source_expr("l");
|
||||
let sql = format!(
|
||||
"SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM proxy_request_logs l
|
||||
WHERE {l_data_source} = 'proxy'
|
||||
AND l.app_type = 'grokbuild'
|
||||
AND l.created_at BETWEEN ?1 - ?2 AND ?1 + ?2
|
||||
)"
|
||||
);
|
||||
conn.query_row(
|
||||
&sql,
|
||||
params![created_at, SESSION_PROXY_DEDUP_WINDOW_SECONDS],
|
||||
|row| row.get::<_, bool>(0),
|
||||
)
|
||||
.map_err(|e| AppError::Database(format!("查询 Grok 接管活动失败: {e}")))
|
||||
}
|
||||
|
||||
pub(crate) fn has_suspected_codex_session_duplicate(
|
||||
conn: &Connection,
|
||||
request_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user