Limit OpenCode metadata refresh concurrency

This commit is contained in:
Mohamed Boudra
2026-06-04 13:32:51 +07:00
parent f9791d7ab6
commit e381117b1c
2 changed files with 65 additions and 12 deletions
@@ -226,6 +226,50 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
expect(openCodeClient.calls.providerList).toEqual([{ directory: cwd }]);
}, 60_000);
test("limits concurrent OpenCode metadata requests across clients", async () => {
const runtime = new TestOpenCodeRuntime();
let activeProviderListCalls = 0;
let maxActiveProviderListCalls = 0;
const response = {
data: {
connected: ["opencode"],
all: [
{
id: "opencode",
name: "OpenCode",
source: "api",
models: {
"big-pickle": {
name: "Big Pickle",
},
},
},
],
},
};
for (let index = 0; index < 12; index += 1) {
const openCodeClient = new TestOpenCodeClient();
openCodeClient.providerListImplementation = async () => {
activeProviderListCalls += 1;
maxActiveProviderListCalls = Math.max(maxActiveProviderListCalls, activeProviderListCalls);
await new Promise((resolve) => setTimeout(resolve, 20));
activeProviderListCalls -= 1;
return response;
};
runtime.enqueueClient(openCodeClient);
}
const client = new OpenCodeAgentClient(logger, undefined, { runtime });
await Promise.all(
Array.from({ length: 12 }, (_, index) =>
client.listModels({ cwd: path.join(os.tmpdir(), `opencode-cwd-${index}`), force: false }),
),
);
expect(maxActiveProviderListCalls).toBeLessThanOrEqual(4);
});
test("available modes include build and plan", async () => {
const cwd = tmpCwd();
const runtime = new TestOpenCodeRuntime();
@@ -11,6 +11,7 @@ import {
type TextPartInput as OpenCodeTextPartInput,
} from "@opencode-ai/sdk/v2/client";
import { createPathEquivalenceMatcher } from "../../../utils/path.js";
import pLimit from "p-limit";
import type { Logger } from "pino";
import { z } from "zod";
@@ -248,6 +249,8 @@ type OpenCodeMcpConfig =
const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"] as const;
const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30_000;
const OPENCODE_METADATA_CONCURRENCY = 4;
const openCodeMetadataLimit = pLimit(OPENCODE_METADATA_CONCURRENCY);
const OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS: AgentSlashCommand[] = [
{ name: "compact", description: "Compact the current session", argumentHint: "" },
{ name: "summarize", description: "Compact the current session", argumentHint: "" },
@@ -1336,10 +1339,12 @@ export class OpenCodeAgentClient implements AgentClient {
try {
// Background model discovery can be legitimately slow while OpenCode refreshes
// provider state, so allow longer than turn execution paths.
const response = await withTimeout(
client.provider.list({ directory: options.cwd }),
OPENCODE_PROVIDER_LIST_TIMEOUT_MS,
`OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`,
const response = await openCodeMetadataLimit(() =>
withTimeout(
client.provider.list({ directory: options.cwd }),
OPENCODE_PROVIDER_LIST_TIMEOUT_MS,
`OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`,
),
);
if (response.error) {
@@ -1401,10 +1406,12 @@ export class OpenCodeAgentClient implements AgentClient {
const client = this.runtime.createClient({ baseUrl: url, directory });
try {
const response = await withTimeout(
client.app.agents({ directory }),
10_000,
"OpenCode app.agents timed out after 10s",
const response = await openCodeMetadataLimit(() =>
withTimeout(
client.app.agents({ directory }),
10_000,
"OpenCode app.agents timed out after 10s",
),
);
if (response.error || !response.data) {
@@ -1561,7 +1568,7 @@ export class OpenCodeAgentClient implements AgentClient {
client: OpencodeClient,
cwd: string,
): Promise<void> {
const response = await client.provider.list({ directory: cwd });
const response = await openCodeMetadataLimit(() => client.provider.list({ directory: cwd }));
if (response.error || !response.data) {
return;
}
@@ -3350,9 +3357,11 @@ class OpenCodeAgentSession implements AgentSession {
return this.availableModesCache;
}
const response = await this.client.app.agents({
directory: this.config.cwd,
});
const response = await openCodeMetadataLimit(() =>
this.client.app.agents({
directory: this.config.cwd,
}),
);
const agents = response.error || !response.data ? [] : response.data;
const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode);