fix(agent): complete opencode warmup before serving
The previous fire-and-forget startup only created the SDK client, leaving the first chat to await project and tool initialization. Warm the real session/tool path and gate port listening on completion.
This commit is contained in:
@@ -64,6 +64,38 @@ export class OpencodeRuntimeAdapter {
|
||||
return requireData(response.data, "global.health");
|
||||
}
|
||||
|
||||
async warmup(): Promise<void> {
|
||||
const client = await this.ensureClient();
|
||||
const healthResponse = await client.global.health();
|
||||
requireData(healthResponse.data, "global.health");
|
||||
|
||||
const sessionResponse = await client.session.create({
|
||||
title: "tjwater-agent-warmup",
|
||||
});
|
||||
const session = requireData(sessionResponse.data, "session.create");
|
||||
|
||||
try {
|
||||
const [provider, model] = config.OPENCODE_MODEL.split("/");
|
||||
if (!provider || !model) {
|
||||
throw new Error(
|
||||
`invalid OPENCODE_MODEL; expected provider/model, received ${config.OPENCODE_MODEL}`,
|
||||
);
|
||||
}
|
||||
const toolsResponse = await client.tool.list({ provider, model });
|
||||
requireData(toolsResponse.data, "tool.list");
|
||||
} finally {
|
||||
await client.session.delete(
|
||||
{ sessionID: session.id },
|
||||
{ throwOnError: true },
|
||||
).catch((error) => {
|
||||
logger.warn(
|
||||
{ err: error, sessionId: session.id },
|
||||
"failed to remove opencode warmup session",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async createSession(title?: string) {
|
||||
const client = await this.ensureClient();
|
||||
const response = await client.session.create({
|
||||
|
||||
+13
-12
@@ -488,23 +488,12 @@ const bootstrap = async () => {
|
||||
resultReferenceStore.initialize(),
|
||||
sessionTranscriptStore.initialize(),
|
||||
]);
|
||||
resultReferenceStore.startCleanupLoop();
|
||||
};
|
||||
|
||||
await bootstrap();
|
||||
|
||||
const server = app.listen(config.PORT, config.HOST, () => {
|
||||
logger.info(
|
||||
{ host: config.HOST, port: config.PORT },
|
||||
"TJWaterAgent listening",
|
||||
);
|
||||
void warmupOpencodeRuntime();
|
||||
});
|
||||
|
||||
const warmupOpencodeRuntime = async () => {
|
||||
const startedAt = Date.now();
|
||||
try {
|
||||
await opencodeRuntime.ensureClient();
|
||||
await opencodeRuntime.warmup();
|
||||
logger.info(
|
||||
{
|
||||
elapsedMs: Math.max(0, Date.now() - startedAt),
|
||||
@@ -521,9 +510,21 @@ const warmupOpencodeRuntime = async () => {
|
||||
},
|
||||
"failed to warm up opencode runtime",
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
await bootstrap();
|
||||
await warmupOpencodeRuntime();
|
||||
resultReferenceStore.startCleanupLoop();
|
||||
|
||||
const server = app.listen(config.PORT, config.HOST, () => {
|
||||
logger.info(
|
||||
{ host: config.HOST, port: config.PORT },
|
||||
"TJWaterAgent listening",
|
||||
);
|
||||
});
|
||||
|
||||
const shutdown = async () => {
|
||||
logger.info("shutting down TJWaterAgent");
|
||||
server.close();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { type OpencodeClient } from "@opencode-ai/sdk/v2";
|
||||
|
||||
import { config } from "../../src/config.js";
|
||||
import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
|
||||
|
||||
const createRuntimeAdapter = (
|
||||
@@ -85,3 +86,47 @@ describe("OpencodeRuntimeAdapter.ensureClient", () => {
|
||||
expect(attempts).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OpencodeRuntimeAdapter.warmup", () => {
|
||||
it("initializes the project session and model tools before reporting ready", async () => {
|
||||
const calls: string[] = [];
|
||||
const client = {
|
||||
global: {
|
||||
health: async () => {
|
||||
calls.push("health");
|
||||
return { data: { healthy: true, version: "test" } };
|
||||
},
|
||||
},
|
||||
session: {
|
||||
create: async () => {
|
||||
calls.push("session.create");
|
||||
return { data: { id: "warmup-session" } };
|
||||
},
|
||||
delete: async ({ sessionID }: { sessionID: string }) => {
|
||||
calls.push(`session.delete:${sessionID}`);
|
||||
return { data: true };
|
||||
},
|
||||
},
|
||||
tool: {
|
||||
list: async (model: { provider: string; model: string }) => {
|
||||
calls.push(`tool.list:${model.provider}/${model.model}`);
|
||||
return { data: [] };
|
||||
},
|
||||
},
|
||||
} as unknown as OpencodeClient;
|
||||
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
|
||||
clientPromise: null,
|
||||
closeServer: null,
|
||||
ensureClient: async () => client,
|
||||
}) as OpencodeRuntimeAdapter;
|
||||
|
||||
await runtime.warmup();
|
||||
|
||||
expect(calls).toEqual([
|
||||
"health",
|
||||
"session.create",
|
||||
`tool.list:${config.OPENCODE_MODEL}`,
|
||||
"session.delete:warmup-session",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user