103 lines · 4.5 KB · TypeScript
RawDownload
// The toolkit-connect performance bug, reproduced with the REAL opencode// binary in a REAL terminal. A user adds a dozen integrations to their workspace,// makes a toolkit, and points OpenCode at /mcp/toolkits/<slug>. OpenCode runs// its own OAuth (discovery, DCR, PKCE) and then `mcp list`. The whole session// runs in one recorded PTY: the run's terminal.cast replays exactly what a// user at a shell sees.//// Before the fix, building the toolkit session walks the WHOLE catalog with a// per-tool policy resolution (an N+1 that scales with total tools, not toolkit// size), so `mcp list` blows past OpenCode's connect timeout and the server// shows "failed". After the fix the walk is two batched reads and the same// command shows "connected". The catalog is seeded over the public API exactly// as a user would build it.import { join } from "node:path"; import { expect } from "@effect/vitest";import { Effect } from "effect"; import { scenario } from "../src/scenario";import { Api, Cli, OpenCode, RunDir, Target } from "../src/services";import { catalogApi, seedLargeCatalog } from "../scenarios/support/large-catalog"; const SERVER_NAME = "executor"; scenario(  "Toolkits · the real OpenCode binary connects to a toolkit over a large catalog",  { timeout: 240_000 },  Effect.scoped(    Effect.gen(function* () {      const target = yield* Target;      const opencode = yield* OpenCode;      const runDir = yield* RunDir;      const cli = yield* Cli;      const { client: makeClient } = yield* Api;       const identity = yield* target.newIdentity();      const email = identity.credentials?.email ?? identity.label;      const client = yield* makeClient(catalogApi, identity);       // Build a production-shaped workspace: one real spec + ten more integrations,      // a toolkit scoped to one of them. ~3,300 tools across 11 integrations — large      // enough that the pre-fix per-tool policy N+1 pushes the toolkit connect      // past OpenCode's client timeout.      const seeded = yield* seedLargeCatalog(client);       yield* Effect.gen(function* () {        const toolkitUrl = new URL(          `/mcp/toolkits/${seeded.toolkitSlug}`,          target.baseUrl,        ).toString();        const home = opencode.makeHome(SERVER_NAME, toolkitUrl);        // First-run database migration happens off camera.        yield* Effect.sync(() => opencode.warmUp(home));         yield* cli.session(          ["bash", "--norc"],          async (term) => {            await term.screen.waitForText("$", { timeoutMs: 10_000 });             const outputAfter = (text: string, line: string): string | null => {              const echoed = text.lastIndexOf(line);              if (echoed === -1) return null;              const after = text.slice(echoed + line.length);              return after.trimEnd().endsWith("\n$") ? after : null;            };            const sh = async (line: string, timeoutMs: number) => {              await term.keyboard.type(line);              await term.keyboard.press("Enter");              const snapshot = await term.screen.waitUntil(                (current) => outputAfter(current.text, line) !== null,                { timeoutMs },              );              return outputAfter(snapshot.text, line) ?? "";            };             // OpenCode completes MCP OAuth for real: discovery, DCR, PKCE, its            // own scope request, its own token store.            const consent = opencode.completeOAuthConsent(home, email, home.openedUrls().length);            const auth = await sh(`opencode mcp auth ${SERVER_NAME}`, 90_000);            await consent;            expect(auth, "opencode mcp auth completes").not.toContain("failed");             // The load-bearing line: listing the toolkit server forces OpenCode            // to establish the session, which builds the execute-tool            // description over the whole catalog. Pre-fix this is where the            // per-tool N+1 times the client out; post-fix it returns promptly.            const listed = await sh("opencode mcp list", 120_000);            expect(              listed,              "OpenCode connects to the toolkit even with a large catalog behind it",            ).toContain("connected");          },          {            cwd: home.projectDir,            env: { ...home.env, PS1: "$ ", BASH_SILENCE_DEPRECATION_WARNING: "1" },            record: join(runDir, "terminal.cast"),            viewport: { cols: 100, rows: 40 },          },        );      }).pipe(Effect.ensuring(seeded.cleanup));    }),  ),);