desktop: don't spawn sidecar if default is localhost server

This commit is contained in:
Brendan Allan
2026-02-20 12:18:39 +08:00
parent d86c10816d
commit 1c2416b6de
4 changed files with 38 additions and 20 deletions

View File

@@ -40,7 +40,9 @@ use crate::windows::{LoadingWindow, MainWindow};
#[derive(Clone, serde::Serialize, specta::Type, Debug)] #[derive(Clone, serde::Serialize, specta::Type, Debug)]
struct ServerReadyData { struct ServerReadyData {
url: String, url: String,
username: Option<String>,
password: Option<String>, password: Option<String>,
is_sidecar: bool
} }
#[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)] #[derive(Clone, Copy, serde::Serialize, specta::Type, Debug)]
@@ -605,6 +607,7 @@ async fn initialize(app: AppHandle) {
child, child,
health_check, health_check,
url, url,
username,
password, password,
} => { } => {
let app = app.clone(); let app = app.clone();
@@ -631,7 +634,7 @@ async fn initialize(app: AppHandle) {
app.state::<ServerState>().set_child(Some(child)); app.state::<ServerState>().set_child(Some(child));
Ok(ServerReadyData { url, password }) Ok(ServerReadyData { url, username,password, is_sidecar: true })
} }
.map(move |res| { .map(move |res| {
let _ = server_ready_tx.send(res); let _ = server_ready_tx.send(res);
@@ -641,7 +644,9 @@ async fn initialize(app: AppHandle) {
ServerConnection::Existing { url } => { ServerConnection::Existing { url } => {
let _ = server_ready_tx.send(Ok(ServerReadyData { let _ = server_ready_tx.send(Ok(ServerReadyData {
url: url.to_string(), url: url.to_string(),
username: None,
password: None, password: None,
is_sidecar: false,
})); }));
None None
} }
@@ -719,6 +724,7 @@ enum ServerConnection {
}, },
CLI { CLI {
url: String, url: String,
username: Option<String>,
password: Option<String>, password: Option<String>,
child: CommandChild, child: CommandChild,
health_check: server::HealthCheck, health_check: server::HealthCheck,
@@ -730,11 +736,15 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection {
tracing::info!(?custom_url, "Attempting server connection"); tracing::info!(?custom_url, "Attempting server connection");
if let Some(url) = custom_url if let Some(url) = &custom_url
&& server::check_health_or_ask_retry(&app, &url).await && server::check_health_or_ask_retry(&app, url).await
{ {
tracing::info!(%url, "Connected to custom server"); 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(); let local_port = get_sidecar_port();
@@ -755,6 +765,7 @@ async fn setup_server_connection(app: AppHandle) -> ServerConnection {
ServerConnection::CLI { ServerConnection::CLI {
url: local_url, url: local_url,
username: Some("opencode".to_string()),
password: Some(password), password: Some(password),
child, child,
health_check, health_check,

View File

@@ -150,7 +150,7 @@ pub async fn check_health(url: &str, password: Option<&str>) -> bool {
return false; 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) { if url_is_localhost(&url) {
// Some environments set proxy variables (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY) without // 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) .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 { fn url_is_localhost(url: &reqwest::Url) -> bool {
url.host_str().is_some_and(|host| { url.host_str().is_some_and(|host| {
host.eq_ignore_ascii_case("localhost") host.eq_ignore_ascii_case("localhost")

View File

@@ -35,7 +35,9 @@ export type LoadingWindowComplete = null;
export type ServerReadyData = { export type ServerReadyData = {
url: string, url: string,
username: string | null,
password: string | null, password: string | null,
is_sidecar: boolean,
}; };
export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" }; export type SqliteMigrationProgress = { type: "InProgress"; value: number } | { type: "Done" };

View File

@@ -23,7 +23,7 @@ import { relaunch } from "@tauri-apps/plugin-process"
import { open as shellOpen } from "@tauri-apps/plugin-shell" import { open as shellOpen } from "@tauri-apps/plugin-shell"
import { Store } from "@tauri-apps/plugin-store" import { Store } from "@tauri-apps/plugin-store"
import { check, type Update } from "@tauri-apps/plugin-updater" 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 { render } from "solid-js/web"
import pkg from "../package.json" import pkg from "../package.json"
import { initI18n, t } from "./i18n" import { initI18n, t } from "./i18n"
@@ -31,7 +31,7 @@ import { UPDATER_ENABLED } from "./updater"
import { webviewZoom } from "./webview-zoom" import { webviewZoom } from "./webview-zoom"
import "./styles.css" import "./styles.css"
import { Channel } from "@tauri-apps/api/core" import { Channel } from "@tauri-apps/api/core"
import { commands, type InitStep } from "./bindings" import { commands, ServerReadyData, type InitStep } from "./bindings"
import { createMenu } from "./menu" import { createMenu } from "./menu"
const root = document.getElementById("root") const root = document.getElementById("root")
@@ -452,16 +452,19 @@ render(() => {
<AppBaseProviders> <AppBaseProviders>
<ServerGate> <ServerGate>
{(data) => { {(data) => {
const server: ServerConnection.Sidecar = { const http = {
displayName: "Local Server", url: data.url,
type: "sidecar", username: data.username ?? undefined,
variant: "base", password: data.password ?? undefined,
http: {
url: data().url,
username: "opencode",
password: data().password ?? undefined,
},
} }
const server: ServerConnection.Any = data.is_sidecar
? {
displayName: "Local Server",
type: "sidecar",
variant: "base",
http,
}
: { type: "http", http }
function Inner() { function Inner() {
const cmd = useCommand() const cmd = useCommand()
@@ -485,10 +488,8 @@ render(() => {
) )
}, root!) }, root!)
type ServerReadyData = { url: string; password: string | null }
// Gate component that waits for the server to be ready // Gate component that waits for the server to be ready
function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) { function ServerGate(props: { children: (data: ServerReadyData) => JSX.Element }) {
const [serverData] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any)) const [serverData] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any))
return ( return (
@@ -516,7 +517,7 @@ function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.
</div> </div>
} }
> >
{(data) => props.children(data)} {(data) => props.children(data())}
</Show> </Show>
</Show> </Show>
) )