fix(api): optimize opencode api health check time and workspace switching
This commit is contained in:
Generated
+1
-1
@@ -2847,7 +2847,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openchamber-desktop"
|
name = "openchamber-desktop"
|
||||||
version = "1.3.3"
|
version = "1.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl OpenCodeManager {
|
|||||||
is_ready: Arc::new(AtomicBool::new(false)),
|
is_ready: Arc::new(AtomicBool::new(false)),
|
||||||
shutting_down: Arc::new(AtomicBool::new(false)),
|
shutting_down: Arc::new(AtomicBool::new(false)),
|
||||||
http_client: Client::builder()
|
http_client: Client::builder()
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(2))
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
@@ -406,11 +406,9 @@ impl OpenCodeManager {
|
|||||||
while tokio::time::Instant::now() < deadline {
|
while tokio::time::Instant::now() < deadline {
|
||||||
let api_prefix = self.api_prefix();
|
let api_prefix = self.api_prefix();
|
||||||
|
|
||||||
// Try /health, /config, /agent endpoints
|
// Try /config, /agent endpoints
|
||||||
match self.check_endpoints(port, &api_prefix).await {
|
match self.check_endpoints(port, &api_prefix).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// Once ready, attempt to detect and persist the API prefix for proxying
|
|
||||||
let _ = self.detect_api_prefix().await;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -431,23 +429,20 @@ impl OpenCodeManager {
|
|||||||
async fn check_endpoints(&self, port: u16, prefix: &str) -> Result<()> {
|
async fn check_endpoints(&self, port: u16, prefix: &str) -> Result<()> {
|
||||||
let base_url = format!("http://127.0.0.1:{port}{prefix}");
|
let base_url = format!("http://127.0.0.1:{port}{prefix}");
|
||||||
|
|
||||||
// Check /health
|
|
||||||
let health_url = format!("{base_url}/health");
|
|
||||||
let health_resp = self.http_client.get(&health_url).send().await?;
|
|
||||||
if !health_resp.status().is_success() {
|
|
||||||
return Err(anyhow!("/health returned {}", health_resp.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check /config
|
|
||||||
let config_url = format!("{base_url}/config");
|
let config_url = format!("{base_url}/config");
|
||||||
let config_resp = self.http_client.get(&config_url).send().await?;
|
let agent_url = format!("{base_url}/agent");
|
||||||
|
|
||||||
|
let (config_resp, agent_resp) = tokio::join!(
|
||||||
|
self.http_client.get(&config_url).send(),
|
||||||
|
self.http_client.get(&agent_url).send()
|
||||||
|
);
|
||||||
|
|
||||||
|
let config_resp = config_resp?;
|
||||||
if !config_resp.status().is_success() {
|
if !config_resp.status().is_success() {
|
||||||
return Err(anyhow!("/config returned {}", config_resp.status()));
|
return Err(anyhow!("/config returned {}", config_resp.status()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check /agent
|
let agent_resp = agent_resp?;
|
||||||
let agent_url = format!("{base_url}/agent");
|
|
||||||
let agent_resp = self.http_client.get(&agent_url).send().await?;
|
|
||||||
if !agent_resp.status().is_success() {
|
if !agent_resp.status().is_success() {
|
||||||
return Err(anyhow!("/agent returned {}", agent_resp.status()));
|
return Err(anyhow!("/agent returned {}", agent_resp.status()));
|
||||||
}
|
}
|
||||||
@@ -497,8 +492,7 @@ impl OpenCodeManager {
|
|||||||
// SIGKILL
|
// SIGKILL
|
||||||
let _ = child.kill().await;
|
let _ = child.kill().await;
|
||||||
|
|
||||||
// Wait up to 5 seconds for hard kill
|
match timeout(Duration::from_secs(2), child.wait()).await {
|
||||||
match timeout(Duration::from_secs(5), child.wait()).await {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("[desktop:opencode] exited after SIGKILL");
|
info!("[desktop:opencode] exited after SIGKILL");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,16 +182,10 @@ async function checkHealth(apiUrl: string, quick = false): Promise<boolean> {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutMs = quick ? 1500 : 3000;
|
const timeoutMs = quick ? 1500 : 3000;
|
||||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
const normalized = apiUrl.replace(/\/+$/, '');
|
const normalized = apiUrl.replace(/\/+$/, '');
|
||||||
const candidates: string[] = [`${normalized}/config`];
|
const candidates: string[] = [`${normalized}/config`];
|
||||||
|
|
||||||
// Some deployments expose a /health endpoint (not guaranteed for OpenCode).
|
|
||||||
if (!quick) {
|
|
||||||
const healthUrl = normalized.endsWith('/api') ? `${normalized.slice(0, -4)}/health` : `${normalized}/health`;
|
|
||||||
candidates.push(healthUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const target of candidates) {
|
for (const target of candidates) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(target, {
|
const response = await fetch(target, {
|
||||||
@@ -227,7 +221,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
let lastStartAt: number | null = null;
|
let lastStartAt: number | null = null;
|
||||||
let lastConnectedAt: number | null = null;
|
let lastConnectedAt: number | null = null;
|
||||||
let lastExitCode: number | null = null;
|
let lastExitCode: number | null = null;
|
||||||
|
|
||||||
// Port detection state (like desktop)
|
// Port detection state (like desktop)
|
||||||
let detectedPort: number | null = null;
|
let detectedPort: number | null = null;
|
||||||
let portWaiters: Array<(port: number) => void> = [];
|
let portWaiters: Array<(port: number) => void> = [];
|
||||||
@@ -235,12 +229,12 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
// OpenCode API prefix detection (some versions serve under /api)
|
// OpenCode API prefix detection (some versions serve under /api)
|
||||||
let apiPrefix: string = '';
|
let apiPrefix: string = '';
|
||||||
let apiPrefixDetected = false;
|
let apiPrefixDetected = false;
|
||||||
|
|
||||||
// Check if user configured a specific API URL
|
// Check if user configured a specific API URL
|
||||||
const config = vscode.workspace.getConfiguration('openchamber');
|
const config = vscode.workspace.getConfiguration('openchamber');
|
||||||
const configuredApiUrl = config.get<string>('apiUrl') || '';
|
const configuredApiUrl = config.get<string>('apiUrl') || '';
|
||||||
const useConfiguredUrl = configuredApiUrl && configuredApiUrl.trim().length > 0;
|
const useConfiguredUrl = configuredApiUrl && configuredApiUrl.trim().length > 0;
|
||||||
|
|
||||||
// Parse configured URL to extract port if specified
|
// Parse configured URL to extract port if specified
|
||||||
let configuredPort: number | null = null;
|
let configuredPort: number | null = null;
|
||||||
if (useConfiguredUrl) {
|
if (useConfiguredUrl) {
|
||||||
@@ -367,7 +361,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
function setDetectedPort(port: number) {
|
function setDetectedPort(port: number) {
|
||||||
if (detectedPort !== port) {
|
if (detectedPort !== port) {
|
||||||
detectedPort = port;
|
detectedPort = port;
|
||||||
|
|
||||||
// Notify all waiters
|
// Notify all waiters
|
||||||
const waiters = portWaiters;
|
const waiters = portWaiters;
|
||||||
portWaiters = [];
|
portWaiters = [];
|
||||||
@@ -429,7 +423,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
|
|
||||||
async function waitForReady(apiUrl: string, timeoutMs: number): Promise<boolean> {
|
async function waitForReady(apiUrl: string, timeoutMs: number): Promise<boolean> {
|
||||||
const deadline = Date.now() + timeoutMs;
|
const deadline = Date.now() + timeoutMs;
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
// Use quick health check during startup for faster response
|
// Use quick health check during startup for faster response
|
||||||
if (await checkHealth(apiUrl, true)) {
|
if (await checkHealth(apiUrl, true)) {
|
||||||
@@ -437,7 +431,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
}
|
}
|
||||||
await new Promise(r => setTimeout(r, READY_CHECK_INTERVAL_MS));
|
await new Promise(r => setTimeout(r, READY_CHECK_INTERVAL_MS));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +455,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const healthy = await checkHealth(apiUrl);
|
const healthy = await checkHealth(apiUrl);
|
||||||
if (healthy && status !== 'connected') {
|
if (healthy && status !== 'connected') {
|
||||||
setStatus('connected');
|
setStatus('connected');
|
||||||
@@ -529,7 +523,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
lastExitCode = null;
|
lastExitCode = null;
|
||||||
|
|
||||||
const spawnCwd = workingDirectory || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir();
|
const spawnCwd = workingDirectory || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir();
|
||||||
|
|
||||||
// Use port 0 for dynamic assignment unless user configured a specific port
|
// Use port 0 for dynamic assignment unless user configured a specific port
|
||||||
const portArg = configuredPort !== null ? configuredPort.toString() : '0';
|
const portArg = configuredPort !== null ? configuredPort.toString() : '0';
|
||||||
|
|
||||||
|
|||||||
@@ -1356,29 +1356,18 @@ async function waitForOpenCodeReady(timeoutMs = 20000, intervalMs = 400) {
|
|||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
try {
|
try {
|
||||||
const normalizedPrefix = normalizeApiPrefix(prefix);
|
const normalizedPrefix = normalizeApiPrefix(prefix);
|
||||||
const healthPromise = fetch(buildOpenCodeUrl('/health', normalizedPrefix), {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { Accept: 'application/json' }
|
|
||||||
}).catch((error) => error);
|
|
||||||
|
|
||||||
const configPromise = fetch(buildOpenCodeUrl('/config', normalizedPrefix), {
|
const configPromise = fetch(buildOpenCodeUrl('/config', normalizedPrefix), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { Accept: 'application/json' }
|
headers: { Accept: 'application/json' }
|
||||||
}).catch((error) => error);
|
}).catch((error) => error);
|
||||||
|
|
||||||
const [healthResult, configResult] = await Promise.all([healthPromise, configPromise]);
|
const agentPromise = fetch(buildOpenCodeUrl('/agent', normalizedPrefix), {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { Accept: 'application/json' }
|
||||||
|
}).catch((error) => error);
|
||||||
|
|
||||||
if (healthResult instanceof Error) {
|
const [configResult, agentResult] = await Promise.all([configPromise, agentPromise]);
|
||||||
lastError = healthResult;
|
|
||||||
} else if (healthResult.ok) {
|
|
||||||
const healthData = await healthResult.json().catch(() => null);
|
|
||||||
if (healthData && healthData.isOpenCodeReady === false) {
|
|
||||||
lastError = new Error('OpenCode health indicates not ready');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lastError = new Error(`OpenCode health endpoint responded with status ${healthResult.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configResult instanceof Error) {
|
if (configResult instanceof Error) {
|
||||||
lastError = configResult;
|
lastError = configResult;
|
||||||
@@ -1402,30 +1391,21 @@ async function waitForOpenCodeReady(timeoutMs = 20000, intervalMs = 400) {
|
|||||||
setDetectedOpenCodeApiPrefix(normalizedPrefix);
|
setDetectedOpenCodeApiPrefix(normalizedPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agentResult instanceof Error) {
|
||||||
|
lastError = agentResult;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agentResult.ok) {
|
||||||
|
lastError = new Error(`Agent endpoint responded with status ${agentResult.status}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await agentResult.json().catch(() => []);
|
||||||
|
|
||||||
const effectivePrefix = detectedPrefix !== null ? detectedPrefix : normalizedPrefix;
|
const effectivePrefix = detectedPrefix !== null ? detectedPrefix : normalizedPrefix;
|
||||||
|
|
||||||
const agentResponse = await fetch(
|
|
||||||
buildOpenCodeUrl('/agent', effectivePrefix),
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
headers: { Accept: 'application/json' }
|
|
||||||
}
|
|
||||||
).catch((error) => error);
|
|
||||||
|
|
||||||
if (agentResponse instanceof Error) {
|
|
||||||
lastError = agentResponse;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!agentResponse.ok) {
|
|
||||||
lastError = new Error(`Agent endpoint responded with status ${agentResponse.status}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await agentResponse.json().catch(() => []);
|
|
||||||
|
|
||||||
if (detectedPrefix === null) {
|
if (detectedPrefix === null) {
|
||||||
const agentPrefix = extractApiPrefixFromUrl(agentResponse.url, '/agent');
|
const agentPrefix = extractApiPrefixFromUrl(agentResult.url, '/agent');
|
||||||
if (agentPrefix !== null) {
|
if (agentPrefix !== null) {
|
||||||
setDetectedOpenCodeApiPrefix(agentPrefix);
|
setDetectedOpenCodeApiPrefix(agentPrefix);
|
||||||
} else if (normalizedPrefix) {
|
} else if (normalizedPrefix) {
|
||||||
|
|||||||
Reference in New Issue
Block a user