diff --git a/src/commands/checkpoint_agent/presets/github_copilot/ide.rs b/src/commands/checkpoint_agent/presets/github_copilot/ide.rs
index 0ac23ec946..54d5b3aa19 100644
--- a/src/commands/checkpoint_agent/presets/github_copilot/ide.rs
+++ b/src/commands/checkpoint_agent/presets/github_copilot/ide.rs
@@ -230,14 +230,13 @@ pub(super) fn parse_vscode_native_hooks(
}
_ => crate::streams::sweep::StreamFormat::CopilotSessionJson,
};
- model_extraction::extract_model(path, sweep_format, None)
- .ok()
- .flatten()
- .or_else(|| {
- model_extraction::extract_model_from_copilot_models_json(path)
- .ok()
- .flatten()
- })
+ model_extraction::extract_model_from_copilot_vscode_transcript(
+ path,
+ sweep_format,
+ &session_id,
+ )
+ .ok()
+ .flatten()
})
.unwrap_or_else(|| "unknown".to_string()),
},
@@ -606,6 +605,79 @@ mod tests {
}
}
+ #[test]
+ fn test_copilot_native_model_prefers_otel_selected_model_over_models_json_default() {
+ let dir = tempfile::tempdir().unwrap();
+ let user_dir = dir.path().join("User");
+ let transcript_path = user_dir
+ .join("workspaceStorage")
+ .join("workspace-1")
+ .join("GitHub.copilot-chat")
+ .join("transcripts")
+ .join("session-abc.jsonl");
+ std::fs::create_dir_all(transcript_path.parent().unwrap()).unwrap();
+ std::fs::write(
+ &transcript_path,
+ r#"{"type":"session.start","data":{"sessionId":"session-abc"}}"#,
+ )
+ .unwrap();
+
+ let models_path = user_dir
+ .join("workspaceStorage")
+ .join("workspace-1")
+ .join("GitHub.copilot-chat")
+ .join("debug-logs")
+ .join("session-abc")
+ .join("models.json");
+ std::fs::create_dir_all(models_path.parent().unwrap()).unwrap();
+ std::fs::write(&models_path, r#"[{"id":"gpt-4.1","is_chat_default":true}]"#).unwrap();
+
+ let otel_db_path = user_dir
+ .join("globalStorage")
+ .join("github.copilot-chat")
+ .join("agent-traces.db");
+ std::fs::create_dir_all(otel_db_path.parent().unwrap()).unwrap();
+ let conn = rusqlite::Connection::open(&otel_db_path).unwrap();
+ conn.execute_batch(
+ "CREATE TABLE spans (
+ span_id TEXT PRIMARY KEY,
+ chat_session_id TEXT,
+ request_model TEXT,
+ response_model TEXT,
+ end_time_ms REAL NOT NULL
+ );",
+ )
+ .unwrap();
+ conn.execute(
+ "INSERT INTO spans (span_id, chat_session_id, request_model, response_model, end_time_ms)
+ VALUES ('span-1', 'session-abc', 'claude-sonnet-4', 'claude-sonnet-4-20250514', 1000)",
+ [],
+ )
+ .unwrap();
+ drop(conn);
+
+ let input = json!({
+ "hook_event_name": "PostToolUse",
+ "cwd": "/home/user/project",
+ "tool_name": "create_file",
+ "session_id": "session-abc",
+ "tool_use_id": "tu-2",
+ "tool_input": {"file_path": "/home/user/project/src/new.rs"},
+ "transcript_path": transcript_path
+ })
+ .to_string();
+ let events = GithubCopilotPreset
+ .parse(&input, "t_test123456789a")
+ .unwrap();
+
+ match &events[0] {
+ ParsedHookEvent::PostFileEdit(e) => {
+ assert_eq!(e.context.agent_id.model, "claude-sonnet-4");
+ }
+ _ => panic!("Expected PostFileEdit"),
+ }
+ }
+
#[test]
fn test_copilot_native_pre_bash_call() {
let input = json!({
diff --git a/src/streams/model_extraction.rs b/src/streams/model_extraction.rs
index e3763c09b6..7ecc521e81 100644
--- a/src/streams/model_extraction.rs
+++ b/src/streams/model_extraction.rs
@@ -2,7 +2,7 @@ use crate::streams::sweep::StreamFormat;
use crate::streams::types::StreamError;
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
-use std::path::Path;
+use std::path::{Path, PathBuf};
pub fn extract_model(
path: &Path,
@@ -16,6 +16,7 @@ pub fn extract_model(
StreamFormat::CopilotSessionJson => extract_model_from_copilot_session_json(path),
StreamFormat::AmpThreadJson => extract_model_from_amp_thread_json(path),
StreamFormat::OpenCodeSqlite => extract_model_from_opencode_sqlite(path, session_id),
+ StreamFormat::CopilotOtelSqlite => extract_model_from_copilot_otel_sqlite(path, session_id),
// Droid uses extract_model_from_droid_settings() with the settings path instead
_ => Ok(None),
}
@@ -175,6 +176,94 @@ pub fn extract_model_from_copilot_models_json(
Ok(model)
}
+pub fn extract_model_from_copilot_vscode_transcript(
+ stream_path: &Path,
+ format: StreamFormat,
+ chat_session_id: &str,
+) -> Result