Limit OpenCode metadata refresh concurrency
This commit is contained in:
@@ -226,6 +226,50 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
|
|||||||
expect(openCodeClient.calls.providerList).toEqual([{ directory: cwd }]);
|
expect(openCodeClient.calls.providerList).toEqual([{ directory: cwd }]);
|
||||||
}, 60_000);
|
}, 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 () => {
|
test("available modes include build and plan", async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
const runtime = new TestOpenCodeRuntime();
|
const runtime = new TestOpenCodeRuntime();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
type TextPartInput as OpenCodeTextPartInput,
|
type TextPartInput as OpenCodeTextPartInput,
|
||||||
} from "@opencode-ai/sdk/v2/client";
|
} from "@opencode-ai/sdk/v2/client";
|
||||||
import { createPathEquivalenceMatcher } from "../../../utils/path.js";
|
import { createPathEquivalenceMatcher } from "../../../utils/path.js";
|
||||||
|
import pLimit from "p-limit";
|
||||||
import type { Logger } from "pino";
|
import type { Logger } from "pino";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -248,6 +249,8 @@ type OpenCodeMcpConfig =
|
|||||||
|
|
||||||
const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"] as const;
|
const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"] as const;
|
||||||
const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30_000;
|
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[] = [
|
const OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS: AgentSlashCommand[] = [
|
||||||
{ name: "compact", description: "Compact the current session", argumentHint: "" },
|
{ name: "compact", description: "Compact the current session", argumentHint: "" },
|
||||||
{ name: "summarize", description: "Compact the current session", argumentHint: "" },
|
{ name: "summarize", description: "Compact the current session", argumentHint: "" },
|
||||||
@@ -1336,10 +1339,12 @@ export class OpenCodeAgentClient implements AgentClient {
|
|||||||
try {
|
try {
|
||||||
// Background model discovery can be legitimately slow while OpenCode refreshes
|
// Background model discovery can be legitimately slow while OpenCode refreshes
|
||||||
// provider state, so allow longer than turn execution paths.
|
// provider state, so allow longer than turn execution paths.
|
||||||
const response = await withTimeout(
|
const response = await openCodeMetadataLimit(() =>
|
||||||
client.provider.list({ directory: options.cwd }),
|
withTimeout(
|
||||||
OPENCODE_PROVIDER_LIST_TIMEOUT_MS,
|
client.provider.list({ directory: options.cwd }),
|
||||||
`OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`,
|
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) {
|
if (response.error) {
|
||||||
@@ -1401,10 +1406,12 @@ export class OpenCodeAgentClient implements AgentClient {
|
|||||||
const client = this.runtime.createClient({ baseUrl: url, directory });
|
const client = this.runtime.createClient({ baseUrl: url, directory });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await withTimeout(
|
const response = await openCodeMetadataLimit(() =>
|
||||||
client.app.agents({ directory }),
|
withTimeout(
|
||||||
10_000,
|
client.app.agents({ directory }),
|
||||||
"OpenCode app.agents timed out after 10s",
|
10_000,
|
||||||
|
"OpenCode app.agents timed out after 10s",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.error || !response.data) {
|
if (response.error || !response.data) {
|
||||||
@@ -1561,7 +1568,7 @@ export class OpenCodeAgentClient implements AgentClient {
|
|||||||
client: OpencodeClient,
|
client: OpencodeClient,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const response = await client.provider.list({ directory: cwd });
|
const response = await openCodeMetadataLimit(() => client.provider.list({ directory: cwd }));
|
||||||
if (response.error || !response.data) {
|
if (response.error || !response.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3350,9 +3357,11 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
return this.availableModesCache;
|
return this.availableModesCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.client.app.agents({
|
const response = await openCodeMetadataLimit(() =>
|
||||||
directory: this.config.cwd,
|
this.client.app.agents({
|
||||||
});
|
directory: this.config.cwd,
|
||||||
|
}),
|
||||||
|
);
|
||||||
const agents = response.error || !response.data ? [] : response.data;
|
const agents = response.error || !response.data ? [] : response.data;
|
||||||
|
|
||||||
const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode);
|
const discoveredModes = agents.filter(isSelectableOpenCodeAgent).map(mapOpenCodeAgentToMode);
|
||||||
|
|||||||
Reference in New Issue
Block a user