removed dependency on openapi type client and status

This commit is contained in:
Lifei Zhou
2026-07-01 18:05:17 +10:00
parent 222d3b1dad
commit 02efeb2eb6
3 changed files with 74 additions and 32 deletions
+36 -12
View File
@@ -1,23 +1,39 @@
import { status } from './api';
import type { Client } from './api/client';
export interface CheckServerStatusOptions {
onEvent?: (name: string, details?: Record<string, unknown>) => void;
}
export interface CheckBackendStatusParams {
baseUrl: string;
serverSecret: string;
fetch: typeof globalThis.fetch;
errorLog?: string[];
options?: CheckServerStatusOptions;
}
export const isFatalError = (line: string): boolean => {
const fatalPatterns = [/panicked at/, /RUST_BACKTRACE/, /fatal error/i];
return fatalPatterns.some((pattern) => pattern.test(line));
};
export const checkServerStatus = async (
client: Client,
errorLog: string[],
options: CheckServerStatusOptions = {}
): Promise<boolean> => {
const statusUrlFromBase = (baseUrl: string): string => {
const url = new URL(baseUrl);
url.pathname = `${url.pathname.replace(/\/+$/, '')}/status`;
url.search = '';
url.hash = '';
return url.toString();
};
export const checkBackendStatus = async ({
baseUrl,
serverSecret,
fetch,
errorLog = [],
options = {},
}: CheckBackendStatusParams): Promise<boolean> => {
const timeout = 30000;
const interval = 100;
const maxAttempts = Math.ceil(timeout / interval);
const statusUrl = statusUrlFromBase(baseUrl);
options.onEvent?.('healthcheck_start', { timeoutMs: timeout, intervalMs: interval });
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -27,12 +43,20 @@ export const checkServerStatus = async (
}
try {
await status({ client, throwOnError: true });
options.onEvent?.('healthcheck_success', { attempt });
return true;
const response = await fetch(statusUrl, {
headers: {
'X-Secret-Key': serverSecret,
},
});
if (response.ok) {
options.onEvent?.('healthcheck_success', { attempt });
return true;
}
} catch {
await new Promise((resolve) => setTimeout(resolve, interval));
// Retry until the backend is ready or the timeout expires.
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
options.onEvent?.('healthcheck_timeout', { timeoutMs: timeout });
+32 -1
View File
@@ -4,6 +4,7 @@ import os from 'node:os';
import path from 'node:path';
import { createServer } from 'net';
import { Buffer } from 'node:buffer';
import { status } from './api';
import { Client, createClient, createConfig } from './api/client';
import {
appendTail,
@@ -11,7 +12,8 @@ import {
type StartupDiagnostics,
} from './startupDiagnostics';
import { isFatalError } from './backendStatus';
export { checkServerStatus, isFatalError, type CheckServerStatusOptions } from './backendStatus';
export { isFatalError, type CheckServerStatusOptions } from './backendStatus';
import type { CheckServerStatusOptions } from './backendStatus';
export interface Logger {
info: (...args: unknown[]) => void;
@@ -36,6 +38,35 @@ export const findAvailablePort = (): Promise<number> => {
});
};
export const checkServerStatus = async (
client: Client,
errorLog: string[],
options: CheckServerStatusOptions = {}
): Promise<boolean> => {
const timeout = 30000;
const interval = 100;
const maxAttempts = Math.ceil(timeout / interval);
options.onEvent?.('healthcheck_start', { timeoutMs: timeout, intervalMs: interval });
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
if (errorLog.some(isFatalError)) {
options.onEvent?.('healthcheck_fatal_error', { attempt });
return false;
}
try {
await status({ client, throwOnError: true });
options.onEvent?.('healthcheck_success', { attempt });
return true;
} catch {
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
options.onEvent?.('healthcheck_timeout', { timeoutMs: timeout });
return false;
};
export interface FindBinaryOptions {
isPackaged?: boolean;
resourcesPath?: string;
+6 -19
View File
@@ -25,10 +25,9 @@ import path from 'node:path';
import os from 'node:os';
import { execFileSync, spawn, execFile } from 'child_process';
import 'dotenv/config';
import { checkServerStatus } from './backendStatus';
import { checkBackendStatus } from './backendStatus';
import { startGooseServe } from './gooseServe';
import { GooseServeLeaseRegistry, type GooseServeLease } from './gooseServeLeaseRegistry';
import { createClient, createConfig } from './api/client';
import { acpWebSocketUrlFromHttpBase, normalizeAcpHttpBaseUrl } from './acp/url';
import { expandTilde } from './utils/pathUtils';
import log from './utils/logger';
@@ -51,7 +50,6 @@ import {
} from './utils/autoUpdater';
import { UPDATES_ENABLED } from './updates';
import './utils/recipeHash';
import { Client } from './api/client';
import type { GooseApp } from './types/apps';
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
import { BLOCKED_PROTOCOLS, WEB_PROTOCOLS } from './utils/urlSecurity';
@@ -918,18 +916,6 @@ const getExternalBackendForCsp = (settings: Settings) => {
};
};
const createMainProcessBackendClient = (baseUrl: string, serverSecret: string): Client =>
createClient(
createConfig({
baseUrl,
fetch: net.fetch as unknown as typeof globalThis.fetch,
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': serverSecret,
},
})
);
let appConfig = {
GOOSE_DEFAULT_PROVIDER: defaultProvider,
GOOSE_DEFAULT_MODEL: defaultModel,
@@ -1045,10 +1031,11 @@ const createChat = async (
? normalizeFingerprint(externalBackend.certFingerprint)
: null;
const externalBackendReady = await checkServerStatus(
createMainProcessBackendClient(externalBaseUrl, serverSecret),
[]
);
const externalBackendReady = await checkBackendStatus({
baseUrl: externalBaseUrl,
serverSecret,
fetch: net.fetch as unknown as typeof globalThis.fetch,
});
if (!externalBackendReady) {
const canDisableExternalBackend = externalBackend.source === 'settings';
const response = dialog.showMessageBoxSync({