From 1c2416b6deb1eee856d1fddbf08300cf851a19fc Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 20 Feb 2026 12:18:39 +0800 Subject: [PATCH] desktop: don't spawn sidecar if default is localhost server --- packages/desktop/src-tauri/src/lib.rs | 19 ++++++++++++--- packages/desktop/src-tauri/src/server.rs | 6 ++++- packages/desktop/src/bindings.ts | 2 ++ packages/desktop/src/index.tsx | 31 ++++++++++++------------ 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/desktop/src-tauri/src/lib.rs b/packages/desktop/src-tauri/src/lib.rs index c6a7d13e6..7ea3aaa8a 100644 --- a/packages/desktop/src-tauri/src/lib.rs +++ b/packages/desktop/src-tauri/src/lib.rs @@ -40,7 +40,9 @@ use crate::windows::{LoadingWindow, MainWindow}; #[derive(Clone, serde::Serialize, specta::Type, Debug)] struct ServerReadyData { url: String, + username: Option, password: Option, + is_sidecar: bool } #[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)] @@ -605,6 +607,7 @@ async fn initialize(app: AppHandle) { child, health_check, url, + username, password, } => { let app = app.clone(); @@ -631,7 +634,7 @@ async fn initialize(app: AppHandle) { app.state::().set_child(Some(child)); - Ok(ServerReadyData { url, password }) + Ok(ServerReadyData { url, username,password, is_sidecar: true }) } .map(move |res| { let _ = server_ready_tx.send(res); @@ -641,7 +644,9 @@ async fn initialize(app: AppHandle) { ServerConnection::Existing { url } => { let _ = server_ready_tx.send(Ok(ServerReadyData { url: url.to_string(), + username: None, password: None, + is_sidecar: false, })); None } @@ -719,6 +724,7 @@ enum ServerConnection { }, CLI { url: String, + username: Option, password: Option, child: CommandChild, health_check: server::HealthCheck, @@ -730,11 +736,15 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection { tracing::info!(?custom_url, "Attempting server connection"); - if let Some(url) = custom_url - && server::check_health_or_ask_retry(&app, &url).await + if let Some(url) = &custom_url + && server::check_health_or_ask_retry(&app, url).await { tracing::info!(%url, "Connected to custom server"); - return ServerConnection::Existing { url: url.clone() }; + // If the default server is already local, no need to also spawn a sidecar + if server::is_localhost_url(url) { + return ServerConnection::Existing { url: url.clone() }; + } + // Remote default server: fall through and also spawn a local sidecar } let local_port = get_sidecar_port(); @@ -755,6 +765,7 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection { ServerConnection::CLI { url: local_url, + username: Some("opencode".to_string()), password: Some(password), child, health_check, diff --git a/packages/desktop/src-tauri/src/server.rs b/packages/desktop/src-tauri/src/server.rs index a13b450bb..2c43c1cc8 100644 --- a/packages/desktop/src-tauri/src/server.rs +++ b/packages/desktop/src-tauri/src/server.rs @@ -150,7 +150,7 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool { return false; }; - let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(3)); + let mut builder = reqwest::Client::builder().timeout(Duration::from_secs(7)); if url_is_localhost(&url) { // Some environments set proxy variables (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) without @@ -178,6 +178,10 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool { .unwrap_or(false) } +pub fn is_localhost_url(url: &str) -> bool { + reqwest::Url::parse(url).is_ok_and(|u| url_is_localhost(&u)) +} + fn url_is_localhost(url: &reqwest::Url) -> bool { url.host_str().is_some_and(|host| { host.eq_ignore_ascii_case("localhost") diff --git a/packages/desktop/src/bindings.ts b/packages/desktop/src/bindings.ts index 67816ad41..6d05bfc56 100644 --- a/packages/desktop/src/bindings.ts +++ b/packages/desktop/src/bindings.ts @@ -35,7 +35,9 @@ export type LoadingWindowComplete = null; export type ServerReadyData = { url: string, + username: string | null, password: string | null, + is_sidecar: boolean, }; export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" }; diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 98af589dd..4a28e1b49 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -23,7 +23,7 @@ import { relaunch } from "@tauri-apps/plugin-process" import { open as shellOpen } from "@tauri-apps/plugin-shell" import { Store } from "@tauri-apps/plugin-store" import { check, type Update } from "@tauri-apps/plugin-updater" -import { type Accessor, createResource, type JSX, onCleanup, onMount, Show } from "solid-js" +import { createResource, type JSX, onCleanup, onMount, Show } from "solid-js" import { render } from "solid-js/web" import pkg from "../package.json" import { initI18n, t } from "./i18n" @@ -31,7 +31,7 @@ import { UPDATER_ENABLED } from "./updater" import { webviewZoom } from "./webview-zoom" import "./styles.css" import { Channel } from "@tauri-apps/api/core" -import { commands, type InitStep } from "./bindings" +import { commands, ServerReadyData, type InitStep } from "./bindings" import { createMenu } from "./menu" const root = document.getElementById("root") @@ -452,16 +452,19 @@ render(() => { {(data) => { - const server: ServerConnection.Sidecar = { - displayName: "Local Server", - type: "sidecar", - variant: "base", - http: { - url: data().url, - username: "opencode", - password: data().password ?? undefined, - }, + const http = { + url: data.url, + username: data.username ?? undefined, + password: data.password ?? undefined, } + const server: ServerConnection.Any = data.is_sidecar + ? { + displayName: "Local Server", + type: "sidecar", + variant: "base", + http, + } + : { type: "http", http } function Inner() { const cmd = useCommand() @@ -485,10 +488,8 @@ render(() => { ) }, root!) -type ServerReadyData = { url: string; password: string | null } - // Gate component that waits for the server to be ready -function ServerGate(props: { children: (data: Accessor) => JSX.Element }) { +function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element }) { const [serverData] = createResource(() => commands.awaitInitialization(new Channel() as any)) return ( @@ -516,7 +517,7 @@ function ServerGate(props: { children: (data: Accessor) => JSX. } > - {(data) => props.children(data)} + {(data) => props.children(data())} )