feat: enhance OpenCodeManager with improved server readiness check and CLI availability detection
This commit is contained in:
@@ -34,6 +34,7 @@ export interface OpenCodeManager {
|
|||||||
getStatus(): ConnectionStatus;
|
getStatus(): ConnectionStatus;
|
||||||
getApiUrl(): string | null;
|
getApiUrl(): string | null;
|
||||||
getWorkingDirectory(): string;
|
getWorkingDirectory(): string;
|
||||||
|
isCliAvailable(): boolean;
|
||||||
getDebugInfo(): OpenCodeDebugInfo;
|
getDebugInfo(): OpenCodeDebugInfo;
|
||||||
onStatusChange(callback: (status: ConnectionStatus, error?: string) => void): vscode.Disposable;
|
onStatusChange(callback: (status: ConnectionStatus, error?: string) => void): vscode.Disposable;
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,29 @@ function resolvePortFromUrl(url: string): number | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForReady(url: string, timeoutMs = 5000): Promise<boolean> {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
// Increase per-request timeout to 3s to allow for cold start latency without aborting
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
||||||
|
const res = await fetch(`${url.replace(/\/+$/, '')}/config`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (res.ok) return true;
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
// Retry faster
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function inferApiPrefixFromUrl(url: string): string {
|
function inferApiPrefixFromUrl(url: string): string {
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
@@ -77,6 +101,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
let detectedPort: number | null = null;
|
let detectedPort: number | null = null;
|
||||||
let apiPrefix: string = '';
|
let apiPrefix: string = '';
|
||||||
let apiPrefixDetected = false;
|
let apiPrefixDetected = false;
|
||||||
|
let cliMissing = false;
|
||||||
|
|
||||||
const config = vscode.workspace.getConfiguration('openchamber');
|
const config = vscode.workspace.getConfiguration('openchamber');
|
||||||
const configuredApiUrl = config.get<string>('apiUrl') || '';
|
const configuredApiUrl = config.get<string>('apiUrl') || '';
|
||||||
@@ -133,6 +158,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
setStatus('connecting');
|
setStatus('connecting');
|
||||||
|
cliMissing = false; // Reset assumption on retry
|
||||||
|
|
||||||
detectedPort = null;
|
detectedPort = null;
|
||||||
apiPrefix = '';
|
apiPrefix = '';
|
||||||
@@ -149,10 +175,22 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (server && server.url) {
|
if (server && server.url) {
|
||||||
detectedPort = resolvePortFromUrl(server.url);
|
// Wait for actual HTTP readiness (stdout "listening" isn't always enough on Windows)
|
||||||
apiPrefix = inferApiPrefixFromUrl(server.url);
|
if (await waitForReady(server.url, 10000)) {
|
||||||
apiPrefixDetected = apiPrefix.length > 0;
|
detectedPort = resolvePortFromUrl(server.url);
|
||||||
setStatus('connected');
|
apiPrefix = inferApiPrefixFromUrl(server.url);
|
||||||
|
apiPrefixDetected = apiPrefix.length > 0;
|
||||||
|
setStatus('connected');
|
||||||
|
} else {
|
||||||
|
// Cleanup zombie process if health check fails
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
server = null;
|
||||||
|
throw new Error('Server started but health check failed');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Server started but URL is missing');
|
throw new Error('Server started but URL is missing');
|
||||||
}
|
}
|
||||||
@@ -161,6 +199,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
|
|
||||||
// Check for ENOENT or generic spawn failure which implies CLI missing
|
// Check for ENOENT or generic spawn failure which implies CLI missing
|
||||||
if (message.includes('ENOENT') || message.includes('spawn opencode')) {
|
if (message.includes('ENOENT') || message.includes('spawn opencode')) {
|
||||||
|
cliMissing = true;
|
||||||
setStatus('error', 'OpenCode CLI not found. Install it or ensure it\'s in PATH.');
|
setStatus('error', 'OpenCode CLI not found. Install it or ensure it\'s in PATH.');
|
||||||
vscode.window.showErrorMessage(
|
vscode.window.showErrorMessage(
|
||||||
'OpenCode CLI not found. Please install it or ensure it\'s in PATH.',
|
'OpenCode CLI not found. Please install it or ensure it\'s in PATH.',
|
||||||
@@ -220,12 +259,13 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo
|
|||||||
getStatus: () => status,
|
getStatus: () => status,
|
||||||
getApiUrl,
|
getApiUrl,
|
||||||
getWorkingDirectory: () => workingDirectory,
|
getWorkingDirectory: () => workingDirectory,
|
||||||
|
isCliAvailable: () => !cliMissing,
|
||||||
getDebugInfo: () => ({
|
getDebugInfo: () => ({
|
||||||
mode: useConfiguredUrl && configuredApiUrl ? 'external' : 'managed',
|
mode: useConfiguredUrl && configuredApiUrl ? 'external' : 'managed',
|
||||||
status,
|
status,
|
||||||
lastError,
|
lastError,
|
||||||
workingDirectory,
|
workingDirectory,
|
||||||
cliAvailable: status !== 'error' || (lastError ? !lastError.includes('CLI not found') : true), // Infer availability from status
|
cliAvailable: !cliMissing,
|
||||||
cliPath: null,
|
cliPath: null,
|
||||||
configuredApiUrl: useConfiguredUrl && configuredApiUrl ? configuredApiUrl.replace(/\/+$/, '') : null,
|
configuredApiUrl: useConfiguredUrl && configuredApiUrl ? configuredApiUrl.replace(/\/+$/, '') : null,
|
||||||
configuredPort,
|
configuredPort,
|
||||||
|
|||||||
Reference in New Issue
Block a user