Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/provider-cursor/src/server/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
InteractionUpdate,
McpServerConfig,
RunResult,
SDKModel,
SDKUserMessage,
SettingSource,
ToolCall,
Expand Down Expand Up @@ -300,11 +301,12 @@ export function makeCursorAgentOptions(input: {
readonly threadId: ThreadId;
readonly mcpSession: McpProviderSession.McpProviderSessionConfig | undefined;
readonly platform: NodeJS.Platform;
readonly sdkModels?: ReadonlyArray<Pick<SDKModel, "id" | "variants">>;
}): AgentOptions {
const policy = cursorRuntimeAgentPolicy(input.runtimePolicy, input.platform);
const mcpServers = cursorMcpServers(input.mcpSession);
return {
model: cursorSdkModelSelection(input.modelSelection),
model: cursorSdkModelSelection(input.modelSelection, input.sdkModels),
name: `T3 Code ${input.threadId}`,
mode: input.runtimePolicy.interactionMode === "plan" ? "plan" : "agent",
...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
Expand Down Expand Up @@ -961,6 +963,7 @@ export interface CursorAdapterV2Options {
readonly instanceId: ProviderInstanceId;
readonly environment: NodeJS.ProcessEnv;
readonly getModels?: () => ReadonlyArray<ServerProviderModel>;
readonly getSdkModels?: () => ReadonlyArray<Pick<SDKModel, "id" | "variants">>;
}

export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
Expand Down Expand Up @@ -2249,6 +2252,7 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
platform,
threadId: openInput.threadId,
mcpSession: yield* mcpSessions.read(openInput.threadId),
sdkModels: adapterOptions.getSdkModels?.() ?? [],
});
if (
platform === "win32" &&
Expand Down Expand Up @@ -2405,7 +2409,10 @@ export const makeCursorAdapterV2 = Effect.fn("makeCursorAdapterV2")(function* (
message,
summarize: compacted,
options: {
model: cursorSdkModelSelection(turnInput.modelSelection),
model: cursorSdkModelSelection(
turnInput.modelSelection,
adapterOptions.getSdkModels?.() ?? [],
),
mode: turnInput.runtimePolicy.interactionMode === "plan" ? "plan" : "agent",
...(mcpServers === undefined ? {} : { mcpServers }),
},
Expand Down
3 changes: 2 additions & 1 deletion packages/provider-cursor/src/server/driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ it.layer(layerTest)("CursorDriver", (it) => {
{
displayName: "Claude Opus 5.5",
isDefault: true,
isMaxMode: true,
params: [{ id: "context", value: "1m" }],
},
],
Expand Down Expand Up @@ -389,7 +390,7 @@ it.layer(layerTest)("CursorDriver", (it) => {
allowance: DEFAULT_HANDOFF_TOKEN_CAP,
},
catalogDefault: {
model: { id: "claude-opus-5-5" },
model: { id: "claude-opus-5-5", maxMode: true },
window: 1_000_000,
allowance: DEFAULT_HANDOFF_TOKEN_CAP,
},
Expand Down
43 changes: 30 additions & 13 deletions packages/provider-cursor/src/server/driver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SDKModel } from "@cursor/sdk";
import {
ProviderDriverKind,
ProviderSetupError,
Expand Down Expand Up @@ -162,10 +163,12 @@ export const CursorDriver: ProviderDriver<
});

let catalogModels: ReadonlyArray<ServerProviderModel> = [];
let catalogSdkModels: ReadonlyArray<SDKModel> = [];
const orchestrationAdapter = yield* makeCursorAdapterV2({
instanceId,
environment: processEnv,
getModels: () => catalogModels,
getSdkModels: () => catalogSdkModels,
}).pipe(
Effect.provideService(CursorAgentSdk.CursorAgentSdkRunner, {
...sdkRunner,
Expand Down Expand Up @@ -221,32 +224,46 @@ export const CursorDriver: ProviderDriver<
},
auth.usesApiKey ? "api-key" : "browser",
).pipe(
Effect.filterOrElse(
(snapshot) =>
!(
effectiveConfig.enabled &&
snapshot.installed &&
snapshot.auth.status === "authenticated"
Effect.flatMap(({ provider: statusProvider, sdkModels }) =>
Effect.succeed(statusProvider).pipe(
Effect.filterOrElse(
(snapshot) =>
!(
effectiveConfig.enabled &&
snapshot.installed &&
snapshot.auth.status === "authenticated"
),
(snapshot) =>
readCursorUsageLimits(effectiveConfig, {
...processEnv,
CURSOR_API_KEY: apiKey,
}).pipe(Effect.map((usageLimits) => ({ ...snapshot, usageLimits }))),
),
(snapshot) =>
readCursorUsageLimits(effectiveConfig, {
...processEnv,
CURSOR_API_KEY: apiKey,
}).pipe(Effect.map((usageLimits) => ({ ...snapshot, usageLimits }))),
Effect.map((provider) => ({ provider, sdkModels })),
),
),
),
),
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, path),
Effect.map(stampSnapshot),
Effect.tap((provider) =>
Effect.map(({ provider, sdkModels }) => ({
provider: stampSnapshot(provider),
sdkModels,
})),
Effect.tap(({ provider, sdkModels }) =>
Effect.sync(() => {
// A failed check lists only custom models, so keep the last loaded catalog.
if (provider.models.some((model) => !model.isCustom)) {
catalogModels = provider.models;
}
// Reuse the timed status probe — a second unbounded catalog.read would
// defeat the probe timeout when the first request stalled.
if (sdkModels !== undefined) {
catalogSdkModels = sdkModels;
}
}),
),
Effect.map(({ provider }) => provider),
Effect.provide(CursorSdkCatalog.layer),
Effect.provideService(HttpClient.HttpClient, httpClient),
);
Expand Down
195 changes: 195 additions & 0 deletions packages/provider-cursor/src/server/sdkModel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { describe, expect, it } from "@effect/vitest";
import { ProviderInstanceId } from "@t3tools/contracts";
import {
CURSOR_SDK_MAX_MODE_OPTION_ID,
cursorSdkMaxModeForParams,
cursorSdkModelOffersOptionalMaxMode,
cursorSdkModelSelection,
} from "./sdkModel.ts";

describe("cursorSdkModelSelection", () => {
const instanceId = ProviderInstanceId.make("cursor");

it("sets maxMode when every matching catalog variant requires it", () => {
const selection = cursorSdkModelSelection(
{
instanceId,
model: "claude-opus-4-8",
options: [{ id: "contextWindow", value: "1m" }],
},
[
{
id: "claude-opus-4-8",
variants: [
{
displayName: "1M",
isMaxMode: true,
params: [{ id: "context", value: "1m" }],
},
],
},
],
);
expect(selection).toEqual({
id: "claude-opus-4-8",
params: [{ id: "context", value: "1m" }],
maxMode: true,
});
});

it("omits maxMode when a matching variant can run without it", () => {
expect(
cursorSdkMaxModeForParams(
{
variants: [
{
displayName: "1M",
isMaxMode: true,
params: [{ id: "context", value: "1m" }],
},
{
displayName: "1M standard",
params: [{ id: "context", value: "1m" }],
},
],
},
[{ id: "context", value: "1m" }],
),
).toBe(false);
});

it("sets maxMode from the composer option without forwarding it as an SDK param", () => {
const catalog = [
{
id: "composer-2",
variants: [
{
displayName: "Composer",
isDefault: true,
params: [{ id: "fast", value: "false" }],
},
{
displayName: "Composer Max",
isMaxMode: true,
params: [{ id: "fast", value: "false" }],
},
{
displayName: "Composer Fast",
params: [{ id: "fast", value: "true" }],
},
],
},
];
expect(
cursorSdkModelSelection(
{
instanceId,
model: "composer-2",
options: [
{ id: "fastMode", value: false },
{ id: CURSOR_SDK_MAX_MODE_OPTION_ID, value: true },
],
},
catalog,
),
).toEqual({
id: "composer-2",
params: [{ id: "fast", value: "false" }],
maxMode: true,
});
expect(
cursorSdkModelSelection(
{
instanceId,
model: "composer-2",
options: [
{ id: "fastMode", value: false },
{ id: CURSOR_SDK_MAX_MODE_OPTION_ID, value: false },
],
},
catalog,
),
).toEqual({
id: "composer-2",
params: [{ id: "fast", value: "false" }],
});
expect(
cursorSdkModelSelection(
{
instanceId,
model: "composer-2",
options: [
{ id: "fastMode", value: true },
{ id: CURSOR_SDK_MAX_MODE_OPTION_ID, value: true },
],
},
catalog,
),
).toEqual({
id: "composer-2",
params: [{ id: "fast", value: "true" }],
});
expect(
cursorSdkModelSelection(
{
instanceId,
model: "composer-2",
options: [{ id: CURSOR_SDK_MAX_MODE_OPTION_ID, value: true }],
},
catalog,
),
).toEqual({
id: "composer-2",
maxMode: true,
});
});
});

describe("cursorSdkModelOffersOptionalMaxMode", () => {
it("is true when the same params have both max and non-max variants", () => {
expect(
cursorSdkModelOffersOptionalMaxMode({
variants: [
{
displayName: "Standard",
isDefault: true,
params: [{ id: "fast", value: "false" }],
},
{
displayName: "Max",
isMaxMode: true,
params: [{ id: "fast", value: "false" }],
},
],
}),
).toBe(true);
});

it("is false when max mode is forced for every matching selection", () => {
expect(
cursorSdkModelOffersOptionalMaxMode({
variants: [
{
displayName: "1M",
isMaxMode: true,
params: [{ id: "context", value: "1m" }],
},
],
}),
).toBe(false);
});

it("is false when no variants use max mode", () => {
expect(
cursorSdkModelOffersOptionalMaxMode({
variants: [
{
displayName: "Default",
isDefault: true,
params: [{ id: "fast", value: "false" }],
},
],
}),
).toBe(false);
});
});
Loading
Loading