fix: avoid embedded opencode port conflicts

This commit is contained in:
2026-08-19 16:56:25 +08:00
parent f0bf667d02
commit b33d950d2b
4 changed files with 80 additions and 7 deletions
+46 -3
View File
@@ -4,6 +4,7 @@ import {
type OpencodeClient,
} from "@opencode-ai/sdk/v2";
import { existsSync, readFileSync } from "node:fs";
import { createServer } from "node:net";
import { resolve } from "node:path";
import { config } from "../config.js";
@@ -44,6 +45,34 @@ type RuntimeMessage = {
const getRuntimeMessageRole = (message: RuntimeMessage) => message.info.role;
const getRuntimeMessageId = (message: RuntimeMessage) => message.info.id;
const isTcpPortAvailable = (hostname: string, port: number) =>
new Promise<boolean>((resolve, reject) => {
const server = createServer();
server.unref();
server.once("error", (error: NodeJS.ErrnoException) => {
if (error.code === "EADDRINUSE") {
resolve(false);
return;
}
reject(error);
});
server.listen({ host: hostname, port, exclusive: true }, () => {
server.close((error) => (error ? reject(error) : resolve(true)));
});
});
export const resolveEmbeddedOpencodePort = async (
hostname: string,
configuredPort: number,
) => {
if (configuredPort === 0) {
return 0;
}
return (await isTcpPortAvailable(hostname, configuredPort))
? configuredPort
: 0;
};
export class OpencodeRuntimeAdapter {
private clientPromise: Promise<OpencodeClient> | null = null;
private closeServer: (() => void) | null = null;
@@ -408,10 +437,24 @@ export class OpencodeRuntimeAdapter {
process.env.TJWATER_AGENT_INTERNAL_TOKEN ??
"";
const port = await resolveEmbeddedOpencodePort(
config.OPENCODE_HOSTNAME,
config.OPENCODE_PORT,
);
if (port !== config.OPENCODE_PORT) {
logger.warn(
{
hostname: config.OPENCODE_HOSTNAME,
configuredPort: config.OPENCODE_PORT,
},
"configured opencode port is occupied; falling back to a dynamic port",
);
}
logger.info(
{
hostname: config.OPENCODE_HOSTNAME,
port: config.OPENCODE_PORT,
port,
model: config.OPENCODE_MODEL,
mode: config.OPENCODE_MODE,
},
@@ -423,7 +466,7 @@ export class OpencodeRuntimeAdapter {
try {
runtime = await createOpencode({
hostname: config.OPENCODE_HOSTNAME,
port: config.OPENCODE_PORT,
port,
timeout: config.OPENCODE_TIMEOUT_MS,
config: buildOpencodeConfig(),
});
@@ -440,7 +483,7 @@ export class OpencodeRuntimeAdapter {
{
elapsedMs: Math.max(0, Date.now() - startedAt),
hostname: config.OPENCODE_HOSTNAME,
port: config.OPENCODE_PORT,
baseUrl: runtime.server.url,
mode: config.OPENCODE_MODE,
},
"opencode server started in embedded mode",