| ... | ... | @@ -4,6 +4,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | 4 | import * as Effect from "effect/Effect"; |
| 5 | 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | 6 | import * as Path from "effect/Path"; |
| 7 | import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; |
| 7 | 8 | import { describe, expect, it } from "vite-plus/test"; |
| 8 | 9 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 9 | 10 | import type { CursorSettings } from "@t3tools/contracts"; |
| ... | ... | @@ -24,7 +25,11 @@ import { |
| 24 | 25 | } from "./CursorProvider.ts"; |
| 25 | 26 | |
| 26 | 27 | const runNode = <A, E>( |
| 27 | | effect: Effect.Effect<A, E, FileSystem.FileSystem | Path.Path>, |
| 28 | effect: Effect.Effect< |
| 29 | A, |
| 30 | E, |
| 31 | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path |
| 32 | >, |
| 28 | 33 | ): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer))); |
| 29 | 34 | |
| 30 | 35 | const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () { |
| ... | ... | @@ -293,6 +298,18 @@ const baseCursorSettings: CursorSettings = { |
| 293 | 298 | apiEndpoint: "", |
| 294 | 299 | customModels: [], |
| 295 | 300 | }; |
| 301 | const cursorAcpDiscoveryFailedMessage = [ |
| 302 | "Cursor ACP model discovery failed.", |
| 303 | "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.", |
| 304 | "See https://cursor.com/docs/cli/installation.", |
| 305 | "Check server logs for ACP details.", |
| 306 | ].join(" "); |
| 307 | const missingCursorBinaryPath = "/definitely/not/installed/t3-cursor-agent"; |
| 308 | const cursorCliCommandMissingMessage = [ |
| 309 | `Cursor CLI command \`${missingCursorBinaryPath}\` was not found.`, |
| 310 | `Install or enable the Cursor CLI, make sure \`${missingCursorBinaryPath}\` is on PATH, then restart T3 Code.`, |
| 311 | "See https://cursor.com/docs/cli/installation.", |
| 312 | ].join(" "); |
| 296 | 313 | |
| 297 | 314 | describe("getCursorFallbackModels", () => { |
| 298 | 315 | it("does not publish any built-in cursor models before ACP discovery", () => { |
| ... | ... | @@ -338,12 +355,11 @@ describe("buildCursorProviderSnapshot", () => { |
| 338 | 355 | auth: { status: "unauthenticated" }, |
| 339 | 356 | message: "Cursor Agent is not authenticated. Run `agent login` and try again.", |
| 340 | 357 | }, |
| 341 | | discoveryWarning: "Cursor ACP model discovery failed. Check server logs for details.", |
| 358 | discoveryWarning: cursorAcpDiscoveryFailedMessage, |
| 342 | 359 | }), |
| 343 | 360 | ).toMatchObject({ |
| 344 | 361 | status: "error", |
| 345 | | message: |
| 346 | | "Cursor Agent is not authenticated. Run `agent login` and try again. Cursor ACP model discovery failed. Check server logs for details.", |
| 362 | message: `Cursor Agent is not authenticated. Run \`agent login\` and try again. ${cursorAcpDiscoveryFailedMessage}`, |
| 347 | 363 | models: [ |
| 348 | 364 | { |
| 349 | 365 | slug: "claude-sonnet-4-6", |
| ... | ... | @@ -411,10 +427,28 @@ describe("buildCursorCapabilitiesFromConfigOptions", () => { |
| 411 | 427 | }); |
| 412 | 428 | |
| 413 | 429 | describe("checkCursorProviderStatus", () => { |
| 430 | it("reports the install docs when the Cursor CLI command is missing", async () => { |
| 431 | const provider = await runNode( |
| 432 | checkCursorProviderStatus({ |
| 433 | enabled: true, |
| 434 | binaryPath: missingCursorBinaryPath, |
| 435 | apiEndpoint: "", |
| 436 | customModels: [], |
| 437 | }), |
| 438 | ); |
| 439 | |
| 440 | expect(provider).toMatchObject({ |
| 441 | installed: false, |
| 442 | status: "error", |
| 443 | auth: { status: "unknown" }, |
| 444 | message: cursorCliCommandMissingMessage, |
| 445 | }); |
| 446 | }); |
| 447 | |
| 414 | 448 | it("passes the injected environment to ACP model discovery", async () => { |
| 415 | 449 | const { requestLogPath, wrapperPath } = await runNode(makeProviderStatusEnvFixture()); |
| 416 | 450 | |
| 417 | | const provider = await Effect.runPromise( |
| 451 | const provider = await runNode( |
| 418 | 452 | checkCursorProviderStatus( |
| 419 | 453 | { |
| 420 | 454 | enabled: true, |
| ... | ... | @@ -426,7 +460,7 @@ describe("checkCursorProviderStatus", () => { |
| 426 | 460 | ...process.env, |
| 427 | 461 | T3_ACP_REQUEST_LOG_PATH: requestLogPath, |
| 428 | 462 | }, |
| 429 | | ).pipe(Effect.provide(NodeServices.layer)), |
| 463 | ), |
| 430 | 464 | ); |
| 431 | 465 | |
| 432 | 466 | expect(provider.models.map((model) => model.slug)).toEqual([ |
| ... | ... | @@ -443,13 +477,13 @@ describe("discoverCursorModelsViaAcp", () => { |
| 443 | 477 | it("keeps the ACP probe runtime alive long enough to discover models", async () => { |
| 444 | 478 | const wrapperPath = await runNode(makeMockAgentWrapper()); |
| 445 | 479 | |
| 446 | | const models = await Effect.runPromise( |
| 480 | const models = await runNode( |
| 447 | 481 | discoverCursorModelsViaAcp({ |
| 448 | 482 | enabled: true, |
| 449 | 483 | binaryPath: wrapperPath, |
| 450 | 484 | apiEndpoint: "", |
| 451 | 485 | customModels: [], |
| 452 | | }).pipe(Effect.provide(NodeServices.layer), Effect.scoped), |
| 486 | }).pipe(Effect.scoped), |
| 453 | 487 | ); |
| 454 | 488 | |
| 455 | 489 | expect(models.map((model) => model.slug)).toEqual([ |
| ... | ... | @@ -465,13 +499,13 @@ describe("discoverCursorModelsViaAcp", () => { |
| 465 | 499 | makeExitLogFixture("cursor-provider-exit-log-"), |
| 466 | 500 | ); |
| 467 | 501 | |
| 468 | | await Effect.runPromise( |
| 502 | await runNode( |
| 469 | 503 | discoverCursorModelsViaAcp({ |
| 470 | 504 | enabled: true, |
| 471 | 505 | binaryPath: wrapperPath, |
| 472 | 506 | apiEndpoint: "", |
| 473 | 507 | customModels: [], |
| 474 | | }).pipe(Effect.provide(NodeServices.layer)), |
| 508 | }), |
| 475 | 509 | ); |
| 476 | 510 | |
| 477 | 511 | const exitLog = await runNode(waitForFileContent(exitLogPath)); |