From 873c169c2c7fa80dce5666d5835cec12411e0d3a Mon Sep 17 00:00:00 2001 From: Huarch Date: Mon, 8 Jun 2026 20:19:46 +0800 Subject: [PATCH] fix(agent): warm up opencode on startup --- src/runtime/opencode.ts | 5 ++++- src/server.ts | 24 ++++++++++++++++++++++++ tests/runtime/opencode.test.ts | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/runtime/opencode.ts b/src/runtime/opencode.ts index d63185e..e8f7dc1 100644 --- a/src/runtime/opencode.ts +++ b/src/runtime/opencode.ts @@ -50,7 +50,10 @@ export class OpencodeRuntimeAdapter { async ensureClient(): Promise { if (!this.clientPromise) { - this.clientPromise = this.bootstrapClient(); + this.clientPromise = this.bootstrapClient().catch((error) => { + this.clientPromise = null; + throw error; + }); } return this.clientPromise; } diff --git a/src/server.ts b/src/server.ts index f67696e..9e39cdb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -283,8 +283,32 @@ const server = app.listen(config.PORT, config.HOST, () => { { host: config.HOST, port: config.PORT }, "TJWaterAgent listening", ); + void warmupOpencodeRuntime(); }); +const warmupOpencodeRuntime = async () => { + const startedAt = Date.now(); + try { + await opencodeRuntime.ensureClient(); + logger.info( + { + elapsedMs: Math.max(0, Date.now() - startedAt), + mode: config.OPENCODE_MODE, + }, + "opencode runtime warmed up", + ); + } catch (error) { + logger.error( + { + err: error, + elapsedMs: Math.max(0, Date.now() - startedAt), + mode: config.OPENCODE_MODE, + }, + "failed to warm up opencode runtime", + ); + } +}; + const shutdown = async () => { logger.info("shutting down TJWaterAgent"); server.close(); diff --git a/tests/runtime/opencode.test.ts b/tests/runtime/opencode.test.ts index ffd9a0e..60e75a3 100644 --- a/tests/runtime/opencode.test.ts +++ b/tests/runtime/opencode.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "bun:test"; +import { type OpencodeClient } from "@opencode-ai/sdk/v2"; import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js"; @@ -60,3 +61,27 @@ describe("OpencodeRuntimeAdapter.revertToUserMessage", () => { }); }); }); + +describe("OpencodeRuntimeAdapter.ensureClient", () => { + it("retries bootstrap after a failed startup attempt", async () => { + let attempts = 0; + const client = { + global: { health: async () => ({ data: { healthy: true } }) }, + } as unknown as OpencodeClient; + const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), { + clientPromise: null, + closeServer: null, + bootstrapClient: async () => { + attempts += 1; + if (attempts === 1) { + throw new Error("startup failed"); + } + return client; + }, + }) as OpencodeRuntimeAdapter; + + await expect(runtime.ensureClient()).rejects.toThrow("startup failed"); + await expect(runtime.ensureClient()).resolves.toBe(client); + expect(attempts).toBe(2); + }); +});