Files
openchamber/packages/web/server/lib/opencode/network-runtime.js
T

118 lines
3.3 KiB
JavaScript

export const createOpenCodeNetworkRuntime = (deps) => {
const {
state,
getOpenCodeAuthHeaders,
configuredOpenCodeHostname = '127.0.0.1',
} = deps;
const resolveConnectHostname = () => {
const raw = typeof configuredOpenCodeHostname === 'string' ? configuredOpenCodeHostname.trim() : '';
const hostname = raw || '127.0.0.1';
if (hostname === '0.0.0.0' || hostname === '::' || hostname === '[::]') {
return '127.0.0.1';
}
if (hostname.startsWith('[') && hostname.endsWith(']')) {
return hostname;
}
return hostname.includes(':') ? `[${hostname}]` : hostname;
};
const normalizeApiPrefix = (prefix) => {
if (!prefix) {
return '';
}
if (prefix.includes('://')) {
try {
const parsed = new URL(prefix);
return normalizeApiPrefix(parsed.pathname);
} catch {
return '';
}
}
const trimmed = prefix.trim();
if (!trimmed || trimmed === '/') {
return '';
}
const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
return withLeading.endsWith('/') ? withLeading.slice(0, -1) : withLeading;
};
const waitForReady = async (url, timeoutMs = 10000) => {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
let timeout = null;
try {
const controller = new AbortController();
timeout = setTimeout(() => controller.abort(), 3000);
const response = await fetch(`${url.replace(/\/+$/, '')}/global/health`, {
method: 'GET',
headers: {
Accept: 'application/json',
...getOpenCodeAuthHeaders(),
},
signal: controller.signal,
});
clearTimeout(timeout);
timeout = null;
if (response.ok) {
const body = await response.json().catch(() => null);
if (body?.healthy === true) {
return true;
}
}
} catch {
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
return false;
};
const setDetectedOpenCodeApiPrefix = () => {
state.openCodeApiPrefix = '';
state.openCodeApiPrefixDetected = true;
if (state.openCodeApiDetectionTimer) {
clearTimeout(state.openCodeApiDetectionTimer);
state.openCodeApiDetectionTimer = null;
}
};
const buildOpenCodeUrl = (path, prefixOverride) => {
if (!state.openCodePort) {
throw new Error('OpenCode port is not available');
}
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
const prefix = normalizeApiPrefix(prefixOverride !== undefined ? prefixOverride : '');
const fullPath = `${prefix}${normalizedPath}`;
const base = state.openCodeBaseUrl ?? `http://${resolveConnectHostname()}:${state.openCodePort}`;
return `${base}${fullPath}`;
};
const detectOpenCodeApiPrefix = () => {
state.openCodeApiPrefixDetected = true;
state.openCodeApiPrefix = '';
return true;
};
const ensureOpenCodeApiPrefix = () => detectOpenCodeApiPrefix();
const scheduleOpenCodeApiDetection = () => {
return;
};
return {
waitForReady,
normalizeApiPrefix,
setDetectedOpenCodeApiPrefix,
buildOpenCodeUrl,
ensureOpenCodeApiPrefix,
scheduleOpenCodeApiDetection,
};
};