mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-20 14:05:11 +00:00
chore: fix bad merge
This commit is contained in:
parent
e72eb9f611
commit
b9efe971ff
32 changed files with 3654 additions and 15543 deletions
|
|
@ -774,7 +774,6 @@ enum CredentialAgent {
|
|||
Codex,
|
||||
Opencode,
|
||||
Amp,
|
||||
Pi,
|
||||
}
|
||||
|
||||
fn credentials_to_output(credentials: ExtractedCredentials, reveal: bool) -> CredentialsOutput {
|
||||
|
|
@ -877,31 +876,6 @@ fn select_token_for_agent(
|
|||
)))
|
||||
}
|
||||
}
|
||||
CredentialAgent::Pi => {
|
||||
if let Some(provider) = provider {
|
||||
return select_token_for_provider(credentials, provider);
|
||||
}
|
||||
if let Some(openai) = credentials.openai.as_ref() {
|
||||
return Ok(openai.api_key.clone());
|
||||
}
|
||||
if let Some(anthropic) = credentials.anthropic.as_ref() {
|
||||
return Ok(anthropic.api_key.clone());
|
||||
}
|
||||
if credentials.other.len() == 1 {
|
||||
if let Some((_, cred)) = credentials.other.iter().next() {
|
||||
return Ok(cred.api_key.clone());
|
||||
}
|
||||
}
|
||||
let available = available_providers(credentials);
|
||||
if available.is_empty() {
|
||||
Err(CliError::Server("no credentials found for pi".to_string()))
|
||||
} else {
|
||||
Err(CliError::Server(format!(
|
||||
"multiple providers available for pi: {} (use --provider)",
|
||||
available.join(", ")
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
use reqwest::blocking::ClientBuilder as BlockingClientBuilder;
|
||||
use reqwest::ClientBuilder;
|
||||
|
||||
const NO_SYSTEM_PROXY_ENV: &str = "SANDBOX_AGENT_NO_SYSTEM_PROXY";
|
||||
|
||||
fn disable_system_proxy() -> bool {
|
||||
env::var(NO_SYSTEM_PROXY_ENV)
|
||||
.map(|value| matches!(value.as_str(), "1" | "true" | "TRUE" | "yes" | "YES"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn client_builder() -> ClientBuilder {
|
||||
let builder = reqwest::Client::builder();
|
||||
if disable_system_proxy() {
|
||||
builder.no_proxy()
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blocking_client_builder() -> BlockingClientBuilder {
|
||||
let builder = reqwest::blocking::Client::builder();
|
||||
if disable_system_proxy() {
|
||||
builder.no_proxy()
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
//! Sandbox agent core utilities.
|
||||
|
||||
mod acp_runtime;
|
||||
mod agent_server_logs;
|
||||
mod opencode_session_manager;
|
||||
mod universal_events;
|
||||
mod acp_proxy_runtime;
|
||||
pub mod cli;
|
||||
pub mod daemon;
|
||||
pub mod http_client;
|
||||
pub mod opencode_compat;
|
||||
pub mod router;
|
||||
pub mod server_logs;
|
||||
pub mod telemetry;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use std::str::FromStr;
|
|||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::extract::{Path, Query, State};
|
||||
|
|
@ -32,16 +31,16 @@ use crate::router::{
|
|||
is_question_tool_action, AgentModelInfo, AppState, CreateSessionRequest, PermissionReply,
|
||||
SessionInfo,
|
||||
};
|
||||
use sandbox_agent_agent_management::agents::AgentId;
|
||||
use sandbox_agent_agent_management::credentials::{
|
||||
extract_all_credentials, CredentialExtractionOptions, ExtractedCredentials,
|
||||
};
|
||||
use sandbox_agent_error::SandboxError;
|
||||
use sandbox_agent_universal_agent_schema::{
|
||||
use crate::universal_events::{
|
||||
ContentPart, FileAction, ItemDeltaData, ItemEventData, ItemKind, ItemRole, ItemStatus,
|
||||
PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, UniversalEvent,
|
||||
UniversalEventData, UniversalEventType, UniversalItem,
|
||||
};
|
||||
use sandbox_agent_agent_credentials::{
|
||||
extract_all_credentials, CredentialExtractionOptions, ExtractedCredentials,
|
||||
};
|
||||
use sandbox_agent_agent_management::agents::AgentId;
|
||||
use sandbox_agent_error::SandboxError;
|
||||
|
||||
static SESSION_COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
static MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
|
|
@ -53,7 +52,6 @@ const OPENCODE_EVENT_LOG_SIZE: usize = 4096;
|
|||
const OPENCODE_DEFAULT_MODEL_ID: &str = "mock";
|
||||
const OPENCODE_DEFAULT_PROVIDER_ID: &str = "mock";
|
||||
const OPENCODE_DEFAULT_AGENT_MODE: &str = "build";
|
||||
const OPENCODE_MODEL_CACHE_TTL: Duration = Duration::from_secs(30);
|
||||
const OPENCODE_MODEL_CHANGE_AFTER_SESSION_CREATE_ERROR: &str = "OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -153,6 +151,9 @@ impl OpenCodeSessionRecord {
|
|||
if let Some(url) = &self.share_url {
|
||||
map.insert("share".to_string(), json!({"url": url}));
|
||||
}
|
||||
if let Some(permission_mode) = &self.permission_mode {
|
||||
map.insert("permissionMode".to_string(), json!(permission_mode));
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
}
|
||||
|
|
@ -164,7 +165,7 @@ fn session_info_to_opencode_value(info: &SessionInfo, default_project_id: &str)
|
|||
.clone()
|
||||
.unwrap_or_else(|| format!("Session {}", info.session_id));
|
||||
let directory = info.directory.clone().unwrap_or_default();
|
||||
json!({
|
||||
let mut value = json!({
|
||||
"id": info.session_id,
|
||||
"slug": format!("session-{}", info.session_id),
|
||||
"projectID": default_project_id,
|
||||
|
|
@ -175,7 +176,15 @@ fn session_info_to_opencode_value(info: &SessionInfo, default_project_id: &str)
|
|||
"created": info.created_at,
|
||||
"updated": info.updated_at,
|
||||
}
|
||||
})
|
||||
});
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
obj.insert("agent".to_string(), json!(info.agent));
|
||||
obj.insert("permissionMode".to_string(), json!(info.permission_mode));
|
||||
if let Some(model) = &info.model {
|
||||
obj.insert("model".to_string(), json!(model));
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -303,8 +312,6 @@ struct OpenCodeModelCache {
|
|||
group_names: HashMap<String, String>,
|
||||
default_group: String,
|
||||
default_model: String,
|
||||
cached_at: Instant,
|
||||
had_discovery_errors: bool,
|
||||
/// Group IDs that have valid credentials available
|
||||
connected: Vec<String>,
|
||||
}
|
||||
|
|
@ -818,8 +825,6 @@ fn available_agent_ids() -> Vec<AgentId> {
|
|||
AgentId::Codex,
|
||||
AgentId::Opencode,
|
||||
AgentId::Amp,
|
||||
AgentId::Pi,
|
||||
AgentId::Cursor,
|
||||
AgentId::Mock,
|
||||
]
|
||||
}
|
||||
|
|
@ -838,30 +843,18 @@ async fn opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCache {
|
|||
// spawning duplicate provider/model fetches.
|
||||
let mut slot = state.opencode.model_cache.lock().await;
|
||||
if let Some(cache) = slot.as_ref() {
|
||||
if cache.cached_at.elapsed() < OPENCODE_MODEL_CACHE_TTL {
|
||||
info!(
|
||||
entries = cache.entries.len(),
|
||||
groups = cache.group_names.len(),
|
||||
connected = cache.connected.len(),
|
||||
"opencode model cache hit"
|
||||
);
|
||||
return cache.clone();
|
||||
}
|
||||
info!(
|
||||
entries = cache.entries.len(),
|
||||
groups = cache.group_names.len(),
|
||||
connected = cache.connected.len(),
|
||||
"opencode model cache hit"
|
||||
);
|
||||
return cache.clone();
|
||||
}
|
||||
let previous_cache = slot.clone();
|
||||
|
||||
let started = std::time::Instant::now();
|
||||
info!("opencode model cache miss; building cache");
|
||||
let mut cache = build_opencode_model_cache(state).await;
|
||||
if let Some(previous_cache) = previous_cache {
|
||||
if cache.had_discovery_errors
|
||||
&& cache.entries.is_empty()
|
||||
&& !previous_cache.entries.is_empty()
|
||||
{
|
||||
cache = previous_cache;
|
||||
cache.cached_at = Instant::now();
|
||||
}
|
||||
}
|
||||
let cache = build_opencode_model_cache(state).await;
|
||||
info!(
|
||||
elapsed_ms = started.elapsed().as_millis() as u64,
|
||||
entries = cache.entries.len(),
|
||||
|
|
@ -902,7 +895,6 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
let mut group_agents: HashMap<String, AgentId> = HashMap::new();
|
||||
let mut group_names: HashMap<String, String> = HashMap::new();
|
||||
let mut default_model: Option<String> = None;
|
||||
let mut had_discovery_errors = false;
|
||||
|
||||
let agents = available_agent_ids();
|
||||
let manager = state.inner.session_manager();
|
||||
|
|
@ -920,10 +912,6 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
let response = match response {
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
had_discovery_errors = true;
|
||||
let (group_id, group_name) = fallback_group_for_agent(agent);
|
||||
group_agents.entry(group_id.clone()).or_insert(agent);
|
||||
group_names.entry(group_id).or_insert(group_name);
|
||||
warn!(
|
||||
agent = agent.as_str(),
|
||||
elapsed_ms = elapsed.as_millis() as u64,
|
||||
|
|
@ -941,12 +929,6 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
"opencode model cache fetched agent models"
|
||||
);
|
||||
|
||||
if response.models.is_empty() {
|
||||
let (group_id, group_name) = fallback_group_for_agent(agent);
|
||||
group_agents.entry(group_id.clone()).or_insert(agent);
|
||||
group_names.entry(group_id).or_insert(group_name);
|
||||
}
|
||||
|
||||
let first_model_id = response.models.first().map(|model| model.id.clone());
|
||||
for model in response.models {
|
||||
let model_id = model.id.clone();
|
||||
|
|
@ -1031,25 +1013,10 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
}
|
||||
}
|
||||
|
||||
// Build connected list based on credential availability
|
||||
// Build connected list conservatively for deterministic compat behavior.
|
||||
let mut connected = Vec::new();
|
||||
for group_id in group_names.keys() {
|
||||
let is_connected = match group_agents.get(group_id) {
|
||||
Some(AgentId::Claude) | Some(AgentId::Amp) => has_anthropic,
|
||||
Some(AgentId::Codex) => has_openai,
|
||||
Some(AgentId::Opencode) => {
|
||||
// Check the specific provider for opencode groups (e.g., "opencode:anthropic")
|
||||
match opencode_group_provider(group_id) {
|
||||
Some("anthropic") => has_anthropic,
|
||||
Some("openai") => has_openai,
|
||||
_ => has_anthropic || has_openai,
|
||||
}
|
||||
}
|
||||
Some(AgentId::Pi) => true,
|
||||
Some(AgentId::Cursor) => true,
|
||||
Some(AgentId::Mock) => true,
|
||||
None => false,
|
||||
};
|
||||
let is_connected = matches!(group_agents.get(group_id), Some(AgentId::Mock));
|
||||
if is_connected {
|
||||
connected.push(group_id.clone());
|
||||
}
|
||||
|
|
@ -1063,8 +1030,6 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
group_names,
|
||||
default_group,
|
||||
default_model,
|
||||
cached_at: Instant::now(),
|
||||
had_discovery_errors,
|
||||
connected,
|
||||
};
|
||||
info!(
|
||||
|
|
@ -1079,19 +1044,6 @@ async fn build_opencode_model_cache(state: &OpenCodeAppState) -> OpenCodeModelCa
|
|||
cache
|
||||
}
|
||||
|
||||
fn fallback_group_for_agent(agent: AgentId) -> (String, String) {
|
||||
if agent == AgentId::Opencode {
|
||||
return (
|
||||
"opencode".to_string(),
|
||||
agent_display_name(agent).to_string(),
|
||||
);
|
||||
}
|
||||
(
|
||||
agent.as_str().to_string(),
|
||||
agent_display_name(agent).to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_agent_from_model(
|
||||
cache: &OpenCodeModelCache,
|
||||
provider_id: &str,
|
||||
|
|
@ -1205,8 +1157,6 @@ fn agent_display_name(agent: AgentId) -> &'static str {
|
|||
AgentId::Codex => "Codex",
|
||||
AgentId::Opencode => "OpenCode",
|
||||
AgentId::Amp => "Amp",
|
||||
AgentId::Pi => "Pi",
|
||||
AgentId::Cursor => "Cursor",
|
||||
AgentId::Mock => "Mock",
|
||||
}
|
||||
}
|
||||
|
|
@ -3295,9 +3245,6 @@ async fn oc_config_providers(State(state): State<Arc<OpenCodeAppState>>) -> impl
|
|||
.or_default()
|
||||
.push(entry);
|
||||
}
|
||||
for group_id in cache.group_names.keys() {
|
||||
grouped.entry(group_id.clone()).or_default();
|
||||
}
|
||||
let mut providers = Vec::new();
|
||||
let mut defaults = serde_json::Map::new();
|
||||
for (group_id, entries) in grouped {
|
||||
|
|
@ -4886,9 +4833,6 @@ async fn oc_provider_list(State(state): State<Arc<OpenCodeAppState>>) -> impl In
|
|||
.or_default()
|
||||
.push(entry);
|
||||
}
|
||||
for group_id in cache.group_names.keys() {
|
||||
grouped.entry(group_id.clone()).or_default();
|
||||
}
|
||||
let mut providers = Vec::new();
|
||||
let mut defaults = serde_json::Map::new();
|
||||
for (group_id, entries) in grouped {
|
||||
|
|
@ -5834,7 +5778,7 @@ pub struct OpenCodeApiDoc;
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sandbox_agent_universal_agent_schema::ReasoningVisibility;
|
||||
use crate::universal_events::ReasoningVisibility;
|
||||
|
||||
fn assistant_item(content: Vec<ContentPart>) -> UniversalItem {
|
||||
UniversalItem {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,6 @@ use serde::Serialize;
|
|||
use time::OffsetDateTime;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::http_client;
|
||||
static TELEMETRY_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const TELEMETRY_URL: &str = "https://tc.rivet.dev";
|
||||
|
|
@ -83,7 +82,7 @@ pub fn log_enabled_message() {
|
|||
|
||||
pub fn spawn_telemetry_task() {
|
||||
tokio::spawn(async move {
|
||||
let client = match http_client::client_builder()
|
||||
let client = match Client::builder()
|
||||
.timeout(Duration::from_millis(TELEMETRY_TIMEOUT_MS))
|
||||
.build()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,15 +3,6 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Value;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub use sandbox_agent_extracted_agent_schemas::{amp, claude, codex, opencode, pi};
|
||||
|
||||
pub mod agents;
|
||||
|
||||
pub use agents::{
|
||||
amp as convert_amp, claude as convert_claude, codex as convert_codex,
|
||||
opencode as convert_opencode, pi as convert_pi,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct UniversalEvent {
|
||||
pub event_id: String,
|
||||
|
|
@ -87,13 +78,10 @@ pub struct SessionStartedData {
|
|||
pub struct SessionEndedData {
|
||||
pub reason: SessionEndReason,
|
||||
pub terminated_by: TerminatedBy,
|
||||
/// Error message when reason is Error
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
/// Process exit code when reason is Error
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub exit_code: Option<i32>,
|
||||
/// Agent stderr output when reason is Error
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub stderr: Option<StderrOutput>,
|
||||
}
|
||||
|
|
@ -116,15 +104,11 @@ pub enum TurnPhase {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct StderrOutput {
|
||||
/// First N lines of stderr (if truncated) or full stderr (if not truncated)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub head: Option<String>,
|
||||
/// Last N lines of stderr (only present if truncated)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tail: Option<String>,
|
||||
/// Whether the output was truncated
|
||||
pub truncated: bool,
|
||||
/// Total number of lines in stderr
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub total_lines: Option<usize>,
|
||||
}
|
||||
|
|
@ -226,7 +210,7 @@ pub enum ItemKind {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ItemRole {
|
||||
User,
|
||||
|
|
@ -235,7 +219,7 @@ pub enum ItemRole {
|
|||
Tool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ItemStatus {
|
||||
InProgress,
|
||||
|
|
@ -294,93 +278,3 @@ pub enum ReasoningVisibility {
|
|||
Public,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventConversion {
|
||||
pub event_type: UniversalEventType,
|
||||
pub data: UniversalEventData,
|
||||
pub native_session_id: Option<String>,
|
||||
pub source: EventSource,
|
||||
pub synthetic: bool,
|
||||
pub raw: Option<Value>,
|
||||
}
|
||||
|
||||
impl EventConversion {
|
||||
pub fn new(event_type: UniversalEventType, data: UniversalEventData) -> Self {
|
||||
Self {
|
||||
event_type,
|
||||
data,
|
||||
native_session_id: None,
|
||||
source: EventSource::Agent,
|
||||
synthetic: false,
|
||||
raw: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_native_session(mut self, session_id: Option<String>) -> Self {
|
||||
self.native_session_id = session_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_raw(mut self, raw: Option<Value>) -> Self {
|
||||
self.raw = raw;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn synthetic(mut self) -> Self {
|
||||
self.synthetic = true;
|
||||
self.source = EventSource::Daemon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: EventSource) -> Self {
|
||||
self.source = source;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn turn_started_event(turn_id: Option<String>, metadata: Option<Value>) -> EventConversion {
|
||||
EventConversion::new(
|
||||
UniversalEventType::TurnStarted,
|
||||
UniversalEventData::Turn(TurnEventData {
|
||||
phase: TurnPhase::Started,
|
||||
turn_id,
|
||||
metadata,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn turn_ended_event(turn_id: Option<String>, metadata: Option<Value>) -> EventConversion {
|
||||
EventConversion::new(
|
||||
UniversalEventType::TurnEnded,
|
||||
UniversalEventData::Turn(TurnEventData {
|
||||
phase: TurnPhase::Ended,
|
||||
turn_id,
|
||||
metadata,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn item_from_text(role: ItemRole, text: String) -> UniversalItem {
|
||||
UniversalItem {
|
||||
item_id: String::new(),
|
||||
native_item_id: None,
|
||||
parent_id: None,
|
||||
kind: ItemKind::Message,
|
||||
role: Some(role),
|
||||
content: vec![ContentPart::Text { text }],
|
||||
status: ItemStatus::Completed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn item_from_parts(role: ItemRole, kind: ItemKind, parts: Vec<ContentPart>) -> UniversalItem {
|
||||
UniversalItem {
|
||||
item_id: String::new(),
|
||||
native_item_id: None,
|
||||
parent_id: None,
|
||||
kind,
|
||||
role: Some(role),
|
||||
content: parts,
|
||||
status: ItemStatus::Completed,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue