mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 06:04:56 +00:00
feat: log session config on create (#93)
## Summary - Adds a `session_created` telemetry event that fires when a new session is created - Logs safe session config fields: agent, agent_mode, permission_mode, model, variant - Does not include workdir or any sensitive paths - Respects existing telemetry enabled/disabled flag (`--no-telemetry`) - Sends asynchronously via spawned task to avoid blocking session creation ## Test plan - [x] `cargo check` passes - [ ] Verify event reaches telemetry endpoint with correct fields - [ ] Verify no event is sent when `--no-telemetry` is set
This commit is contained in:
parent
f09ed7cb9a
commit
5dbfde5424
2 changed files with 111 additions and 20 deletions
|
|
@ -40,6 +40,7 @@ use utoipa::{Modify, OpenApi, ToSchema};
|
||||||
|
|
||||||
use crate::agent_server_logs::AgentServerLogs;
|
use crate::agent_server_logs::AgentServerLogs;
|
||||||
use crate::opencode_compat::{build_opencode_router, OpenCodeAppState};
|
use crate::opencode_compat::{build_opencode_router, OpenCodeAppState};
|
||||||
|
use crate::telemetry;
|
||||||
use crate::ui;
|
use crate::ui;
|
||||||
use sandbox_agent_agent_management::agents::{
|
use sandbox_agent_agent_management::agents::{
|
||||||
AgentError as ManagerError, AgentId, AgentManager, InstallOptions, SpawnOptions, StreamingSpawn,
|
AgentError as ManagerError, AgentId, AgentManager, InstallOptions, SpawnOptions, StreamingSpawn,
|
||||||
|
|
@ -1622,6 +1623,9 @@ impl SessionManager {
|
||||||
session.native_session_id = Some(format!("mock-{session_id}"));
|
session.native_session_id = Some(format!("mock-{session_id}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let telemetry_agent = request.agent.clone();
|
||||||
|
let telemetry_model = request.model.clone();
|
||||||
|
let telemetry_variant = request.variant.clone();
|
||||||
let metadata = json!({
|
let metadata = json!({
|
||||||
"agent": request.agent,
|
"agent": request.agent,
|
||||||
"agentMode": session.agent_mode,
|
"agentMode": session.agent_mode,
|
||||||
|
|
@ -1651,6 +1655,8 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let native_session_id = session.native_session_id.clone();
|
let native_session_id = session.native_session_id.clone();
|
||||||
|
let telemetry_agent_mode = session.agent_mode.clone();
|
||||||
|
let telemetry_permission_mode = session.permission_mode.clone();
|
||||||
let mut sessions = self.sessions.lock().await;
|
let mut sessions = self.sessions.lock().await;
|
||||||
sessions.push(session);
|
sessions.push(session);
|
||||||
drop(sessions);
|
drop(sessions);
|
||||||
|
|
@ -1664,6 +1670,14 @@ impl SessionManager {
|
||||||
self.ensure_opencode_stream(session_id).await?;
|
self.ensure_opencode_stream(session_id).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
telemetry::log_session_created(telemetry::SessionConfig {
|
||||||
|
agent: telemetry_agent,
|
||||||
|
agent_mode: Some(telemetry_agent_mode),
|
||||||
|
permission_mode: Some(telemetry_permission_mode),
|
||||||
|
model: telemetry_model,
|
||||||
|
variant: telemetry_variant,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(CreateSessionResponse {
|
Ok(CreateSessionResponse {
|
||||||
healthy: true,
|
healthy: true,
|
||||||
error: None,
|
error: None,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
@ -10,6 +11,8 @@ use serde::Serialize;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
static TELEMETRY_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
const TELEMETRY_URL: &str = "https://tc.rivet.dev";
|
const TELEMETRY_URL: &str = "https://tc.rivet.dev";
|
||||||
const TELEMETRY_ENV_DEBUG: &str = "SANDBOX_AGENT_TELEMETRY_DEBUG";
|
const TELEMETRY_ENV_DEBUG: &str = "SANDBOX_AGENT_TELEMETRY_DEBUG";
|
||||||
const TELEMETRY_ID_FILE: &str = "telemetry_id";
|
const TELEMETRY_ID_FILE: &str = "telemetry_id";
|
||||||
|
|
@ -19,7 +22,7 @@ const TELEMETRY_INTERVAL_SECS: u64 = 300;
|
||||||
const TELEMETRY_MIN_GAP_SECS: i64 = 300;
|
const TELEMETRY_MIN_GAP_SECS: i64 = 300;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct TelemetryEvent {
|
struct TelemetryEvent<D: Serialize> {
|
||||||
// p = project identifier
|
// p = project identifier
|
||||||
p: String,
|
p: String,
|
||||||
// dt = unix timestamp (seconds)
|
// dt = unix timestamp (seconds)
|
||||||
|
|
@ -31,13 +34,13 @@ struct TelemetryEvent {
|
||||||
// ev = event name
|
// ev = event name
|
||||||
ev: String,
|
ev: String,
|
||||||
// d = data payload
|
// d = data payload
|
||||||
d: TelemetryData,
|
d: D,
|
||||||
// v = schema version
|
// v = schema version
|
||||||
v: u8,
|
v: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct TelemetryData {
|
struct BeaconData {
|
||||||
version: String,
|
version: String,
|
||||||
os: OsInfo,
|
os: OsInfo,
|
||||||
provider: ProviderInfo,
|
provider: ProviderInfo,
|
||||||
|
|
@ -60,15 +63,17 @@ struct ProviderInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn telemetry_enabled(no_telemetry: bool) -> bool {
|
pub fn telemetry_enabled(no_telemetry: bool) -> bool {
|
||||||
if no_telemetry {
|
let enabled = if no_telemetry {
|
||||||
return false;
|
false
|
||||||
}
|
} else if cfg!(debug_assertions) {
|
||||||
if cfg!(debug_assertions) {
|
env::var(TELEMETRY_ENV_DEBUG)
|
||||||
return env::var(TELEMETRY_ENV_DEBUG)
|
|
||||||
.map(|value| matches!(value.as_str(), "1" | "true" | "TRUE"))
|
.map(|value| matches!(value.as_str(), "1" | "true" | "TRUE"))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false)
|
||||||
}
|
} else {
|
||||||
true
|
true
|
||||||
|
};
|
||||||
|
TELEMETRY_ENABLED.store(enabled, Ordering::Relaxed);
|
||||||
|
enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_enabled_message() {
|
pub fn log_enabled_message() {
|
||||||
|
|
@ -105,7 +110,7 @@ async fn attempt_send(client: &Client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let event = build_event(dt);
|
let event = build_beacon_event(dt);
|
||||||
if let Err(err) = client.post(TELEMETRY_URL).json(&event).send().await {
|
if let Err(err) = client.post(TELEMETRY_URL).json(&event).send().await {
|
||||||
tracing::debug!(error = %err, "telemetry request failed");
|
tracing::debug!(error = %err, "telemetry request failed");
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,15 +118,12 @@ async fn attempt_send(client: &Client) {
|
||||||
write_last_sent(dt);
|
write_last_sent(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_event(dt: i64) -> TelemetryEvent {
|
fn build_beacon_event(dt: i64) -> TelemetryEvent<BeaconData> {
|
||||||
let eid = load_or_create_id();
|
new_event(
|
||||||
TelemetryEvent {
|
|
||||||
p: "sandbox-agent".to_string(),
|
|
||||||
dt,
|
dt,
|
||||||
et: "sandbox".to_string(),
|
"sandbox",
|
||||||
eid,
|
"entity_beacon",
|
||||||
ev: "entity_beacon".to_string(),
|
BeaconData {
|
||||||
d: TelemetryData {
|
|
||||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
os: OsInfo {
|
os: OsInfo {
|
||||||
name: std::env::consts::OS.to_string(),
|
name: std::env::consts::OS.to_string(),
|
||||||
|
|
@ -130,6 +132,18 @@ fn build_event(dt: i64) -> TelemetryEvent {
|
||||||
},
|
},
|
||||||
provider: detect_provider(),
|
provider: detect_provider(),
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_event<D: Serialize>(dt: i64, entity_type: &str, event_name: &str, data: D) -> TelemetryEvent<D> {
|
||||||
|
let eid = load_or_create_id();
|
||||||
|
TelemetryEvent {
|
||||||
|
p: "sandbox-agent".to_string(),
|
||||||
|
dt,
|
||||||
|
et: entity_type.to_string(),
|
||||||
|
eid,
|
||||||
|
ev: event_name.to_string(),
|
||||||
|
d: data,
|
||||||
v: 1,
|
v: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,3 +445,66 @@ fn metadata_or_none(
|
||||||
Some(map)
|
Some(map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct SessionCreatedData {
|
||||||
|
version: String,
|
||||||
|
agent: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
agent_mode: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
permission_mode: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
model: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
variant: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionConfig {
|
||||||
|
pub agent: String,
|
||||||
|
pub agent_mode: Option<String>,
|
||||||
|
pub permission_mode: Option<String>,
|
||||||
|
pub model: Option<String>,
|
||||||
|
pub variant: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_session_created(config: SessionConfig) {
|
||||||
|
if !TELEMETRY_ENABLED.load(Ordering::Relaxed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = new_event(
|
||||||
|
OffsetDateTime::now_utc().unix_timestamp(),
|
||||||
|
"session",
|
||||||
|
"session_created",
|
||||||
|
SessionCreatedData {
|
||||||
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
agent: config.agent,
|
||||||
|
agent_mode: config.agent_mode,
|
||||||
|
permission_mode: config.permission_mode,
|
||||||
|
model: config.model,
|
||||||
|
variant: config.variant,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
spawn_send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_send<D: Serialize + Send + 'static>(event: TelemetryEvent<D>) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let client = match Client::builder()
|
||||||
|
.timeout(Duration::from_millis(TELEMETRY_TIMEOUT_MS))
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!(error = %err, "failed to build telemetry client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = client.post(TELEMETRY_URL).json(&event).send().await {
|
||||||
|
tracing::debug!(error = %err, "telemetry send failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue