authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-05 14:56:43-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 14:56:43-04:00
log0d8a91a25a35618c7cae1e02791e491afa1f0a00
tree085fa6f4380cb0f1dba93483dd1264dc14bc7ab6
parent6349a0e68a958cc51b7b5198683c1d1db88b8d28
signature Signed by PGP key B5690EEEBB952194

fix(cursor): cache successful model discovery between refreshes (#9918)


3 files changed, 68 insertions(+), 4 deletions(-)

apps/server/src/provider/Drivers/CursorDriver.ts+7-1
......@@ -29,6 +29,7 @@ import { makeCursorAdapter } from "../Layers/CursorAdapter.ts";
2929import {
3030 buildInitialCursorProviderSnapshot,
3131 checkCursorProviderStatus,
32 makeCursorModelDiscovery,
3233 enrichCursorSnapshot,
3334} from "../Layers/CursorProvider.ts";
3435import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
......@@ -136,7 +137,12 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = {
136137 });
137138 const textGeneration = yield* makeCursorTextGeneration(effectiveConfig, processEnv);
138139
139 const checkProvider = checkCursorProviderStatus(effectiveConfig, processEnv).pipe(
140 const discoverModels = yield* makeCursorModelDiscovery(effectiveConfig, processEnv);
141 const checkProvider = checkCursorProviderStatus(
142 effectiveConfig,
143 processEnv,
144 discoverModels,
145 ).pipe(
140146 Effect.map(stampIdentity),
141147 Effect.provideService(Crypto.Crypto, crypto),
142148 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
apps/server/src/provider/Layers/CursorProvider.test.ts+37
......@@ -16,6 +16,7 @@ import {
1616 buildCursorCapabilitiesFromConfigOptions,
1717 checkCursorProviderStatus,
1818 discoverCursorModelsViaAcp,
19 makeCursorModelDiscovery,
1920 getCursorParameterizedModelPickerUnsupportedMessage,
2021 parseCursorAboutOutput,
2122 parseCursorCliConfigChannel,
......@@ -627,6 +628,42 @@ describe("checkCursorProviderStatus", () => {
627628});
628629
629630describe("discoverCursorModelsViaAcp", () => {
631 it("reuses successful discovery until the CLI version or account changes", async () => {
632 await runNode(
633 Effect.gen(function* () {
634 const { requestLogPath, wrapperPath } = yield* makeProviderStatusEnvFixture();
635 const fileSystem = yield* FileSystem.FileSystem;
636 const settings = {
637 enabled: true,
638 binaryPath: wrapperPath,
639 apiEndpoint: "",
640 customModels: [],
641 };
642 const discover = yield* makeCursorModelDiscovery(settings, {
643 ...process.env,
644 T3_ACP_REQUEST_LOG_PATH: requestLogPath,
645 });
646 const about = {
647 version: "2026.08.11",
648 auth: { status: "authenticated" as const, label: "first@example.test" },
649 };
650 const first = yield* discover(about);
651 expect(first.length).toBeGreaterThan(0);
652 yield* fileSystem.writeFileString(requestLogPath, "");
653 expect(yield* discover(about)).toEqual(first);
654 expect(yield* fileSystem.readFileString(requestLogPath)).toBe("");
655 yield* discover({ ...about, version: "2026.08.12" });
656 expect(yield* fileSystem.readFileString(requestLogPath)).toContain("initialize");
657 yield* fileSystem.writeFileString(requestLogPath, "");
658 yield* discover({
659 version: "2026.08.12",
660 auth: { ...about.auth, label: "second@example.test" },
661 });
662 expect(yield* fileSystem.readFileString(requestLogPath)).toContain("initialize");
663 }),
664 );
665 });
666
630667 it("keeps the ACP probe runtime alive long enough to discover models", async () => {
631668 const wrapperPath = await runNode(makeMockAgentWrapper());
632669
apps/server/src/provider/Layers/CursorProvider.ts+24-3
......@@ -10,6 +10,8 @@ import type {
1010} from "@t3tools/contracts";
1111import type * as EffectAcpSchema from "effect-acp/schema";
1212import { causeErrorTag } from "@t3tools/shared/observability";
13import * as Cache from "effect/Cache";
14import * as Duration from "effect/Duration";
1315import * as Crypto from "effect/Crypto";
1416import * as DateTime from "effect/DateTime";
1517import * as Effect from "effect/Effect";
......@@ -572,6 +574,23 @@ export const discoverCursorModelsViaAcp = (
572574 environment?: NodeJS.ProcessEnv,
573575) => discoverCursorModelsViaListAvailableModels(cursorSettings, environment);
574576
577// Each driver instance owns its cache; version and account changes invalidate it.
578export const makeCursorModelDiscovery = Effect.fn("makeCursorModelDiscovery")(function* (
579 cursorSettings: CursorSettings,
580 environment?: NodeJS.ProcessEnv,
581) {
582 const cache = yield* Cache.makeWith(
583 (_key: string) => discoverCursorModelsViaAcp(cursorSettings, environment),
584 {
585 capacity: 1,
586 timeToLive: (exit) =>
587 Exit.isSuccess(exit) && exit.value.length > 0 ? Duration.minutes(30) : Duration.zero,
588 },
589 );
590 return (about: Pick<CursorAboutResult, "version" | "auth">) =>
591 Cache.get(cache, JSON.stringify([about.version, about.auth]));
592});
593
575594function getCursorFallbackModels(
576595 cursorSettings: Pick<CursorSettings, "customModels">,
577596): ReadonlyArray<ServerProviderModel> {
......@@ -989,6 +1008,7 @@ const runCursorAboutCommand = (cursorSettings: CursorSettings, environment?: Nod
9891008export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(function* (
9901009 cursorSettings: CursorSettings,
9911010 environment?: NodeJS.ProcessEnv,
1011 discoverModels?: (about: CursorAboutResult) => ReturnType<typeof discoverCursorModelsViaAcp>,
9921012): Effect.fn.Return<
9931013 ServerProviderDraft,
9941014 never,
......@@ -1086,9 +1106,10 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
10861106 let discoveryWarning: string | undefined;
10871107 if (parsed.auth.status !== "unauthenticated") {
10881108 const discoveryExit = yield* Effect.exit(
1089 discoverCursorModelsViaAcp(cursorSettings, environment).pipe(
1090 Effect.timeoutOption(CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS),
1091 ),
1109 (discoverModels
1110 ? discoverModels(parsed)
1111 : discoverCursorModelsViaAcp(cursorSettings, environment)
1112 ).pipe(Effect.timeoutOption(CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS)),
10921113 );
10931114 if (Exit.isFailure(discoveryExit)) {
10941115 yield* Effect.logWarning("Cursor ACP model discovery failed", {