Skip to content

Commit 5e421d5

Browse files
committed
feat: customize opencode branding
1 parent b7fa0f7 commit 5e421d5

10 files changed

+699
-19
lines changed

server/packages/sandbox-agent/src/cli.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use reqwest::blocking::Client as HttpClient;
1515
use reqwest::Method;
1616
use crate::router::{build_router_with_state, shutdown_servers};
1717
use crate::router::{
18-
AgentInstallRequest, AppState, AuthConfig, CreateSessionRequest, MessageRequest,
19-
PermissionReply, PermissionReplyRequest, QuestionReplyRequest,
18+
AgentInstallRequest, AppState, AuthConfig, BrandingMode, CreateSessionRequest,
19+
MessageRequest, PermissionReply, PermissionReplyRequest, QuestionReplyRequest,
2020
};
2121
use crate::router::{
2222
AgentListResponse, AgentModelsResponse, AgentModesResponse, CreateSessionResponse,
@@ -502,9 +502,14 @@ fn run_server(cli: &CliConfig, server: &ServerArgs) -> Result<(), CliError> {
502502
AuthConfig::disabled()
503503
};
504504

505+
let branding = if cli.gigacode {
506+
BrandingMode::Gigacode
507+
} else {
508+
BrandingMode::SandboxAgent
509+
};
505510
let agent_manager = AgentManager::new(default_install_dir())
506511
.map_err(|err| CliError::Server(err.to_string()))?;
507-
let state = Arc::new(AppState::new(auth, agent_manager));
512+
let state = Arc::new(AppState::with_branding(auth, agent_manager, branding));
508513
let (mut router, state) = build_router_with_state(state);
509514

510515
let cors = build_cors_layer(server)?;
@@ -581,7 +586,7 @@ fn run_api(command: &ApiCommand, cli: &CliConfig) -> Result<(), CliError> {
581586

582587
fn run_opencode(cli: &CliConfig, args: &OpencodeArgs) -> Result<(), CliError> {
583588
let name = if cli.gigacode { "Gigacode" } else { "OpenCode command" };
584-
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"))?;
589+
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"))?;
585590

586591
let token = cli.token.clone();
587592

server/packages/sandbox-agent/src/opencode_compat.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,9 +2573,10 @@ pub fn build_opencode_router(state: Arc<OpenCodeAppState>) -> Router {
25732573
tag = "opencode"
25742574
)]
25752575
async fn oc_agent_list(State(state): State<Arc<OpenCodeAppState>>) -> impl IntoResponse {
2576+
let name = state.inner.branding.product_name();
25762577
let agent = json!({
2577-
"name": "Sandbox Agent",
2578-
"description": "Sandbox Agent compatibility layer",
2578+
"name": name,
2579+
"description": format!("{name} compatibility layer"),
25792580
"mode": "all",
25802581
"native": false,
25812582
"hidden": false,

server/packages/sandbox-agent/src/router.rs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,43 @@ static USER_MESSAGE_COUNTER: AtomicU64 = AtomicU64::new(1);
5555
const ANTHROPIC_MODELS_URL: &str = "https://api.anthropic.com/v1/models?beta=true";
5656
const ANTHROPIC_VERSION: &str = "2023-06-01";
5757

58+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59+
pub enum BrandingMode {
60+
#[default]
61+
SandboxAgent,
62+
Gigacode,
63+
}
64+
65+
impl BrandingMode {
66+
pub fn product_name(&self) -> &'static str {
67+
match self {
68+
BrandingMode::SandboxAgent => "Sandbox Agent",
69+
BrandingMode::Gigacode => "Gigacode",
70+
}
71+
}
72+
73+
pub fn docs_url(&self) -> &'static str {
74+
match self {
75+
BrandingMode::SandboxAgent => "https://sandboxagent.dev",
76+
BrandingMode::Gigacode => "https://gigacode.dev",
77+
}
78+
}
79+
}
80+
5881
#[derive(Debug)]
5982
pub struct AppState {
6083
auth: AuthConfig,
6184
agent_manager: Arc<AgentManager>,
6285
session_manager: Arc<SessionManager>,
86+
pub branding: BrandingMode,
6387
}
6488

6589
impl AppState {
6690
pub fn new(auth: AuthConfig, agent_manager: AgentManager) -> Self {
91+
Self::with_branding(auth, agent_manager, BrandingMode::default())
92+
}
93+
94+
pub fn with_branding(auth: AuthConfig, agent_manager: AgentManager, branding: BrandingMode) -> Self {
6795
let agent_manager = Arc::new(agent_manager);
6896
let session_manager = Arc::new(SessionManager::new(agent_manager.clone()));
6997
session_manager
@@ -73,6 +101,7 @@ impl AppState {
73101
auth,
74102
agent_manager,
75103
session_manager,
104+
branding,
76105
}
77106
}
78107

@@ -152,12 +181,15 @@ pub fn build_router_with_state(shared: Arc<AppState>) -> (Router, Arc<AppState>)
152181
));
153182
}
154183

155-
let mut router = Router::new()
184+
let root_router = Router::new()
156185
.route("/", get(get_root))
186+
.fallback(not_found)
187+
.with_state(shared.clone());
188+
189+
let mut router = root_router
157190
.nest("/v1", v1_router)
158191
.nest("/opencode", opencode_router)
159-
.merge(opencode_root_router)
160-
.fallback(not_found);
192+
.merge(opencode_root_router);
161193

162194
if ui::is_enabled() {
163195
router = router.merge(ui::router());
@@ -4068,21 +4100,26 @@ async fn get_agent_models(
40684100
Ok(Json(models))
40694101
}
40704102

4071-
const SERVER_INFO: &str = "\
4072-
This is a Sandbox Agent server. Available endpoints:\n\
4073-
- GET / - Server info\n\
4074-
- GET /v1/health - Health check\n\
4075-
- GET /ui/ - Inspector UI\n\n\
4076-
See https://sandboxagent.dev for API documentation.";
4103+
fn server_info(branding: BrandingMode) -> String {
4104+
format!(
4105+
"This is a {} server. Available endpoints:\n\
4106+
\x20 - GET / - Server info\n\
4107+
\x20 - GET /v1/health - Health check\n\
4108+
\x20 - GET /ui/ - Inspector UI\n\n\
4109+
See {} for API documentation.",
4110+
branding.product_name(),
4111+
branding.docs_url(),
4112+
)
4113+
}
40774114

4078-
async fn get_root() -> &'static str {
4079-
SERVER_INFO
4115+
async fn get_root(State(state): State<Arc<AppState>>) -> String {
4116+
server_info(state.branding)
40804117
}
40814118

4082-
async fn not_found() -> (StatusCode, String) {
4119+
async fn not_found(State(state): State<Arc<AppState>>) -> (StatusCode, String) {
40834120
(
40844121
StatusCode::NOT_FOUND,
4085-
format!("404 Not Found\n\n{SERVER_INFO}"),
4122+
format!("404 Not Found\n\n{}", server_info(state.branding)),
40864123
)
40874124
}
40884125

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
source: server/packages/sandbox-agent/tests/http/agent_endpoints.rs
3+
assertion_line: 145
4+
expression: snapshot_status(status)
5+
---
6+
status: 204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
source: server/packages/sandbox-agent/tests/sessions/multi_turn.rs
3+
assertion_line: 15
4+
expression: value
5+
---
6+
first:
7+
- metadata: true
8+
seq: 1
9+
session: started
10+
type: session.started
11+
- item:
12+
content_types:
13+
- text
14+
kind: message
15+
role: user
16+
status: in_progress
17+
seq: 2
18+
type: item.started
19+
- delta:
20+
delta: "<redacted>"
21+
item_id: "<redacted>"
22+
native_item_id: "<redacted>"
23+
seq: 3
24+
type: item.delta
25+
- item:
26+
content_types:
27+
- text
28+
kind: message
29+
role: user
30+
status: completed
31+
seq: 4
32+
type: item.completed
33+
- item:
34+
content_types:
35+
- text
36+
kind: message
37+
role: assistant
38+
status: in_progress
39+
seq: 5
40+
type: item.started
41+
- delta:
42+
delta: "<redacted>"
43+
item_id: "<redacted>"
44+
native_item_id: "<redacted>"
45+
seq: 6
46+
type: item.delta
47+
- delta:
48+
delta: "<redacted>"
49+
item_id: "<redacted>"
50+
native_item_id: "<redacted>"
51+
seq: 7
52+
type: item.delta
53+
- delta:
54+
delta: "<redacted>"
55+
item_id: "<redacted>"
56+
native_item_id: "<redacted>"
57+
seq: 8
58+
type: item.delta
59+
- delta:
60+
delta: "<redacted>"
61+
item_id: "<redacted>"
62+
native_item_id: "<redacted>"
63+
seq: 9
64+
type: item.delta
65+
- delta:
66+
delta: "<redacted>"
67+
item_id: "<redacted>"
68+
native_item_id: "<redacted>"
69+
seq: 10
70+
type: item.delta
71+
second:
72+
- item:
73+
content_types:
74+
- text
75+
kind: message
76+
role: user
77+
status: in_progress
78+
seq: 1
79+
type: item.started
80+
- delta:
81+
delta: "<redacted>"
82+
item_id: "<redacted>"
83+
native_item_id: "<redacted>"
84+
seq: 2
85+
type: item.delta
86+
- item:
87+
content_types:
88+
- text
89+
kind: message
90+
role: user
91+
status: completed
92+
seq: 3
93+
type: item.completed
94+
- item:
95+
content_types:
96+
- text
97+
kind: message
98+
role: assistant
99+
status: in_progress
100+
seq: 4
101+
type: item.started
102+
- delta:
103+
delta: "<redacted>"
104+
item_id: "<redacted>"
105+
native_item_id: "<redacted>"
106+
seq: 5
107+
type: item.delta
108+
- delta:
109+
delta: "<redacted>"
110+
item_id: "<redacted>"
111+
native_item_id: "<redacted>"
112+
seq: 6
113+
type: item.delta
114+
- delta:
115+
delta: "<redacted>"
116+
item_id: "<redacted>"
117+
native_item_id: "<redacted>"
118+
seq: 7
119+
type: item.delta
120+
- delta:
121+
delta: "<redacted>"
122+
item_id: "<redacted>"
123+
native_item_id: "<redacted>"
124+
seq: 8
125+
type: item.delta
126+
- delta:
127+
delta: "<redacted>"
128+
item_id: "<redacted>"
129+
native_item_id: "<redacted>"
130+
seq: 9
131+
type: item.delta

0 commit comments

Comments
 (0)