feat: customize opencode branding (#103)

This commit is contained in:
Nathan Flurry 2026-02-06 03:05:23 -08:00 committed by GitHub
parent dc2a2b1687
commit b824a2c839
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 700 additions and 20 deletions

View file

@ -15,8 +15,8 @@ use reqwest::blocking::Client as HttpClient;
use reqwest::Method;
use crate::router::{build_router_with_state, shutdown_servers};
use crate::router::{
AgentInstallRequest, AppState, AuthConfig, CreateSessionRequest, MessageRequest,
PermissionReply, PermissionReplyRequest, QuestionReplyRequest,
AgentInstallRequest, AppState, AuthConfig, BrandingMode, CreateSessionRequest,
MessageRequest, PermissionReply, PermissionReplyRequest, QuestionReplyRequest,
};
use crate::router::{
AgentListResponse, AgentModelsResponse, AgentModesResponse, CreateSessionResponse,
@ -502,9 +502,14 @@ fn run_server(cli: &CliConfig, server: &ServerArgs) -> Result<(), CliError> {
AuthConfig::disabled()
};
let branding = if cli.gigacode {
BrandingMode::Gigacode
} else {
BrandingMode::SandboxAgent
};
let agent_manager = AgentManager::new(default_install_dir())
.map_err(|err| CliError::Server(err.to_string()))?;
let state = Arc::new(AppState::new(auth, agent_manager));
let state = Arc::new(AppState::with_branding(auth, agent_manager, branding));
let (mut router, state) = build_router_with_state(state);
let cors = build_cors_layer(server)?;
@ -581,7 +586,7 @@ fn run_api(command: &ApiCommand, cli: &CliConfig) -> Result<(), CliError> {
fn run_opencode(cli: &CliConfig, args: &OpencodeArgs) -> Result<(), CliError> {
let name = if cli.gigacode { "Gigacode" } else { "OpenCode command" };
write_stderr_line(&format!("EXPERIMENTAL: Please report bugs to:\n- GitHub: https://github.com/rivet-dev/sandbox-agent/issues\n- Discord: https://rivet.dev/discord\n\n{name} is powered by:- OpenCode (TUI): https://opencode.ai/\n- Sandbox Agent SDK (multi-agent compatibility): https://sandboxagent.dev/\n\n"))?;
write_stderr_line(&format!("\nEXPERIMENTAL: Please report bugs to:\n- GitHub: https://github.com/rivet-dev/sandbox-agent/issues\n- Discord: https://rivet.dev/discord\n\n{name} is powered by:\n- OpenCode (TUI): https://opencode.ai/\n- Sandbox Agent SDK (multi-agent compatibility): https://sandboxagent.dev/\n\n"))?;
let token = cli.token.clone();

View file

@ -2573,9 +2573,10 @@ pub fn build_opencode_router(state: Arc<OpenCodeAppState>) -> Router {
tag = "opencode"
)]
async fn oc_agent_list(State(state): State<Arc<OpenCodeAppState>>) -> impl IntoResponse {
let name = state.inner.branding.product_name();
let agent = json!({
"name": "Sandbox Agent",
"description": "Sandbox Agent compatibility layer",
"name": name,
"description": format!("{name} compatibility layer"),
"mode": "all",
"native": false,
"hidden": false,

View file

@ -55,15 +55,43 @@ static USER_MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1);
const ANTHROPIC_MODELS_URL: &str = "https://api.anthropic.com/v1/models?beta=true";
const ANTHROPIC_VERSION: &str = "2023-06-01";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BrandingMode {
#[default]
SandboxAgent,
Gigacode,
}
impl BrandingMode {
pub fn product_name(&self) -> &'static str {
match self {
BrandingMode::SandboxAgent => "Sandbox Agent",
BrandingMode::Gigacode => "Gigacode",
}
}
pub fn docs_url(&self) -> &'static str {
match self {
BrandingMode::SandboxAgent => "https://sandboxagent.dev",
BrandingMode::Gigacode => "https://gigacode.dev",
}
}
}
#[derive(Debug)]
pub struct AppState {
auth: AuthConfig,
agent_manager: Arc<AgentManager>,
session_manager: Arc<SessionManager>,
pub branding: BrandingMode,
}
impl AppState {
pub fn new(auth: AuthConfig, agent_manager: AgentManager) -> Self {
Self::with_branding(auth, agent_manager, BrandingMode::default())
}
pub fn with_branding(auth: AuthConfig, agent_manager: AgentManager, branding: BrandingMode) -> Self {
let agent_manager = Arc::new(agent_manager);
let session_manager = Arc::new(SessionManager::new(agent_manager.clone()));
session_manager
@ -73,6 +101,7 @@ impl AppState {
auth,
agent_manager,
session_manager,
branding,
}
}
@ -152,12 +181,15 @@ pub fn build_router_with_state(shared: Arc<AppState>) -> (Router, Arc<AppState>)
));
}
let mut router = Router::new()
let root_router = Router::new()
.route("/", get(get_root))
.fallback(not_found)
.with_state(shared.clone());
let mut router = root_router
.nest("/v1", v1_router)
.nest("/opencode", opencode_router)
.merge(opencode_root_router)
.fallback(not_found);
.merge(opencode_root_router);
if ui::is_enabled() {
router = router.merge(ui::router());
@ -4068,21 +4100,26 @@ async fn get_agent_models(
Ok(Json(models))
}
const SERVER_INFO: &str = "\
This is a Sandbox Agent server. Available endpoints:\n\
- GET / - Server info\n\
- GET /v1/health - Health check\n\
- GET /ui/ - Inspector UI\n\n\
See https://sandboxagent.dev for API documentation.";
async fn get_root() -> &'static str {
SERVER_INFO
fn server_info(branding: BrandingMode) -> String {
format!(
"This is a {} server. Available endpoints:\n\
\x20 - GET / - Server info\n\
\x20 - GET /v1/health - Health check\n\
\x20 - GET /ui/ - Inspector UI\n\n\
See {} for API documentation.",
branding.product_name(),
branding.docs_url(),
)
}
async fn not_found() -> (StatusCode, String) {
async fn get_root(State(state): State<Arc<AppState>>) -> String {
server_info(state.branding)
}
async fn not_found(State(state): State<Arc<AppState>>) -> (StatusCode, String) {
(
StatusCode::NOT_FOUND,
format!("404 Not Found\n\n{SERVER_INFO}"),
format!("404 Not Found\n\n{}", server_info(state.branding)),
)
}

View file

@ -0,0 +1,6 @@
---
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
assertion_line: 145
expression: snapshot_status(status)
---
status: 204

View file

@ -0,0 +1,131 @@
---
source: server/packages/sandbox-agent/tests/sessions/multi_turn.rs
assertion_line: 15
expression: value
---
first:
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 7
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 8
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 9
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 10
type: item.delta
second:
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 1
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 2
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 3
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 4
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 5
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 7
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 8
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 9
type: item.delta

View file

@ -0,0 +1,237 @@
---
source: server/packages/sandbox-agent/tests/sessions/permissions.rs
assertion_line: 12
expression: value
---
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 7
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 8
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 9
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 10
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 11
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 12
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 13
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 14
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 15
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 16
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 17
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 18
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 19
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 20
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 21
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 22
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 23
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 24
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 25
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 26
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 27
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 28
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 29
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 30
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 31
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 32
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 33
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 34
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 35
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 36
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 37
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 38
type: item.delta

View file

@ -0,0 +1,105 @@
---
source: server/packages/sandbox-agent/tests/sessions/questions.rs
assertion_line: 12
expression: value
---
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 7
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 8
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 9
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 10
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 11
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 12
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 13
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 14
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 15
type: item.delta
- question:
id: "<redacted>"
options: 4
status: requested
seq: 16
type: question.requested

View file

@ -0,0 +1,105 @@
---
source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs
assertion_line: 12
expression: value
---
session_a:
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 7
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 8
type: item.delta
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 9
type: item.delta
session_b:
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 6
type: item.delta

View file

@ -0,0 +1,7 @@
---
source: server/packages/sandbox-agent/tests/sessions/session_lifecycle.rs
assertion_line: 12
expression: value
---
healthy: true
nativeSessionId: "<redacted>"

View file

@ -0,0 +1,46 @@
---
source: server/packages/sandbox-agent/tests/sessions/../common/http.rs
assertion_line: 1001
expression: normalized
---
- metadata: true
seq: 1
session: started
type: session.started
- item:
content_types:
- text
kind: message
role: user
status: in_progress
seq: 2
type: item.started
- delta:
delta: "<redacted>"
item_id: "<redacted>"
native_item_id: "<redacted>"
seq: 3
type: item.delta
- item:
content_types:
- text
kind: message
role: user
status: completed
seq: 4
type: item.completed
- item:
content_types:
- text
kind: message
role: assistant
status: in_progress
seq: 5
type: item.started
- item:
content_types: []
kind: message
role: assistant
status: completed
seq: 6
type: item.completed