From 622651ec35a52eee90ba477287004b1e83f15604 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Fri, 30 Oct 2026 18:43:48 +0800 Subject: [PATCH] Capture OpenCode model variants --- bt-daemon/src/translate/opencode.rs | 9 ++++++++- bt-daemon/tests/opencode_translator.rs | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bt-daemon/src/translate/opencode.rs b/bt-daemon/src/translate/opencode.rs index d4be4955..c487b0cf 100644 --- a/bt-daemon/src/translate/opencode.rs +++ b/bt-daemon/src/translate/opencode.rs @@ -289,6 +289,8 @@ struct MessageInfo { #[serde(default, rename = "modelID")] model_id: Option, #[serde(default)] + variant: Option, + #[serde(default)] time: Option, #[serde(default)] tokens: Option, @@ -987,6 +989,11 @@ impl OpenCodeTranslator { }; let provider = info.provider_id.as_deref().unwrap_or("unknown"); let model = info.model_id.as_deref().unwrap_or("unknown"); + // Allowlist the selected OpenCode variant; do not copy arbitrary model options. + let mut metadata = json!({"model":model,"provider":provider,"message_id":mid}); + if let Some(variant) = &info.variant { + metadata["variant"] = json!(variant); + } vec![SpanOp::Insert(SpanRow { span_id: ids::span_id(&self.daemon_session_id, &format!("llm:{sid}:{mid}")), root_span_id: state.effective_root_span_id.clone(), @@ -1011,7 +1018,7 @@ impl OpenCodeTranslator { .or(Some(ts_ms)), input: Some(Value::Array(input)), output: Some(Value::Array(vec![assistant])), - metadata: Some(json!({"model":model,"provider":provider,"message_id":mid})), + metadata: Some(metadata), metrics: Some( json!({"prompt_tokens":prompt,"completion_tokens":completion,"tokens":prompt+completion+reasoning,"prompt_cached_tokens":cache_read,"prompt_cache_creation_tokens":cache_write,"reasoning_tokens":reasoning}), ), diff --git a/bt-daemon/tests/opencode_translator.rs b/bt-daemon/tests/opencode_translator.rs index 255399d1..6e4935f8 100644 --- a/bt-daemon/tests/opencode_translator.rs +++ b/bt-daemon/tests/opencode_translator.rs @@ -91,7 +91,7 @@ fn opencode_builds_turn_llm_tool_and_closes_the_session() { event( "message.updated", 5, - json!({"properties":{"info":{"id":"m1","sessionID":"native","role":"assistant","providerID":"openai","modelID":"gpt-5","time":{"created":2,"completed":5},"tokens":{"input":5,"output":2,"reasoning":1,"cache":{"read":3,"write":4}}}}}), + json!({"properties":{"info":{"id":"m1","sessionID":"native","role":"assistant","providerID":"openai","modelID":"gpt-5","variant":"high","time":{"created":2,"completed":5},"tokens":{"input":5,"output":2,"reasoning":1,"cache":{"read":3,"write":4}}}}}), ), event( "tool.execute.after", @@ -120,6 +120,7 @@ fn opencode_builds_turn_llm_tool_and_closes_the_session() { .values() .find(|r| r.span_type == SpanType::Llm) .unwrap(); + assert_eq!(llm.metadata.as_ref().unwrap()["variant"], "high"); assert_eq!(llm.metrics.as_ref().unwrap()["prompt_tokens"], 12); assert_eq!(llm.output.as_ref().unwrap()[0]["content"], "world"); let tool = rows @@ -260,6 +261,10 @@ fn opencode_idle_updates_a_resumable_session_root() { .count(); assert_eq!(reopened_roots, 1); let rows = reduce(ops); + assert!(rows + .values() + .filter(|row| row.span_type == SpanType::Llm) + .all(|row| row.metadata.as_ref().unwrap().get("variant").is_none())); let root = rows.values().find(|row| row.name == "OpenCode").unwrap(); assert_eq!(root.metadata.as_ref().unwrap()["total_turns"], 2); assert_eq!(root.end_ms, Some(13));