opencode-cursor-provider 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +55 -0
  2. package/README.md +112 -0
  3. package/dist/auth/credential.d.ts +44 -0
  4. package/dist/auth/credential.js +85 -0
  5. package/dist/auth/link.d.ts +14 -0
  6. package/dist/auth/link.js +120 -0
  7. package/dist/auth/state.d.ts +54 -0
  8. package/dist/auth/state.js +54 -0
  9. package/dist/bridge/agent.d.ts +17 -0
  10. package/dist/bridge/agent.js +39 -0
  11. package/dist/bridge/binding.d.ts +35 -0
  12. package/dist/bridge/binding.js +59 -0
  13. package/dist/bridge/bridge.d.ts +16 -0
  14. package/dist/bridge/bridge.js +21 -0
  15. package/dist/bridge/conversation.d.ts +64 -0
  16. package/dist/bridge/conversation.js +86 -0
  17. package/dist/bridge/correlation.d.ts +10 -0
  18. package/dist/bridge/correlation.js +52 -0
  19. package/dist/bridge/lock.d.ts +4 -0
  20. package/dist/bridge/lock.js +24 -0
  21. package/dist/bridge/response-journal.d.ts +7 -0
  22. package/dist/bridge/response-journal.js +49 -0
  23. package/dist/bridge/translate.d.ts +60 -0
  24. package/dist/bridge/translate.js +188 -0
  25. package/dist/bridge/turn.d.ts +29 -0
  26. package/dist/bridge/turn.js +219 -0
  27. package/dist/catalog/catalog.d.ts +20 -0
  28. package/dist/catalog/catalog.js +192 -0
  29. package/dist/catalog/source.d.ts +8 -0
  30. package/dist/catalog/source.js +30 -0
  31. package/dist/errors.d.ts +35 -0
  32. package/dist/errors.js +36 -0
  33. package/dist/ids.d.ts +33 -0
  34. package/dist/ids.js +52 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.js +3 -0
  37. package/dist/model/language-model.d.ts +10 -0
  38. package/dist/model/language-model.js +539 -0
  39. package/dist/model/provider-options.d.ts +19 -0
  40. package/dist/model/provider-options.js +84 -0
  41. package/dist/plugin.d.ts +2 -0
  42. package/dist/plugin.js +127 -0
  43. package/dist/runtime.d.ts +8 -0
  44. package/dist/runtime.js +25 -0
  45. package/package.json +33 -0
@@ -0,0 +1,219 @@
1
+ import { AuthenticationError, CursorAgentError, } from "@cursor/sdk";
2
+ import { resolveWireId } from "../catalog/catalog.js";
3
+ import { nowMs } from "../ids.js";
4
+ import { isAgentLost, openCursorAgent } from "./agent.js";
5
+ import { route } from "./binding.js";
6
+ import { checkpointOf, cursorMessage, resumeTurn, } from "./conversation.js";
7
+ import { createResponseJournal } from "./response-journal.js";
8
+ import { createMessageTranslator } from "./translate.js";
9
+ export function createTurnRunner(ctx) {
10
+ return (request) => runTurn(ctx, request);
11
+ }
12
+ async function* runTurn(ctx, request) {
13
+ const lockKey = request.scope?.sessionID ?? "one-shot";
14
+ const release = await ctx.lock.acquire(lockKey);
15
+ try {
16
+ const apiKey = await ctx.link.resolve();
17
+ if (apiKey === undefined) {
18
+ yield { type: "failed", error: { kind: "not-linked" } };
19
+ return;
20
+ }
21
+ const wireID = resolveWireId(ctx.models(), request.modelID);
22
+ if (wireID === undefined) {
23
+ yield { type: "failed", error: { kind: "model-unknown", modelID: request.modelID } };
24
+ return;
25
+ }
26
+ let plan = planTurn(request, apiKey, wireID, ctx.bindings);
27
+ if (typeof plan.prompt === "string" && plan.prompt.trim().length === 0) {
28
+ yield { type: "done", reason: "stop" };
29
+ return;
30
+ }
31
+ let retryLost = true;
32
+ while (true) {
33
+ let session;
34
+ let emitted = false;
35
+ try {
36
+ if (request.signal?.aborted) {
37
+ yield { type: "failed", error: { kind: "cancelled" } };
38
+ return;
39
+ }
40
+ session = await (ctx.openAgent ?? openCursorAgent)({
41
+ apiKey: plan.apiKey,
42
+ wireID: plan.wireID,
43
+ params: plan.params,
44
+ cwd: request.scope?.cwd ?? process.cwd(),
45
+ resume: plan.kind === "RESUME" ? plan.binding?.agentID : undefined,
46
+ agentOptions: request.agentOptions,
47
+ });
48
+ if (request.signal?.aborted) {
49
+ yield { type: "failed", error: { kind: "cancelled" } };
50
+ return;
51
+ }
52
+ const run = await session.send(plan.prompt, request.mode === undefined ? undefined : { mode: request.mode });
53
+ emitted = true;
54
+ yield {
55
+ type: "response-metadata",
56
+ id: run.id,
57
+ ...(run.createdAt === undefined ? {} : { timestamp: run.createdAt }),
58
+ modelId: run.model?.id ?? plan.wireID,
59
+ };
60
+ let cancelPromise;
61
+ const cancel = () => {
62
+ if (cancelPromise !== undefined || !run.supports("cancel"))
63
+ return;
64
+ cancelPromise = run.cancel().catch(() => { });
65
+ };
66
+ request.signal?.addEventListener("abort", cancel);
67
+ if (request.signal?.aborted)
68
+ cancel();
69
+ let sawUsage = false;
70
+ const response = createResponseJournal();
71
+ const translate = createMessageTranslator();
72
+ try {
73
+ for await (const message of run.stream()) {
74
+ if (request.signal?.aborted) {
75
+ yield { type: "failed", error: { kind: "cancelled" } };
76
+ return;
77
+ }
78
+ if (request.includeRawChunks === true)
79
+ yield { type: "raw", value: message };
80
+ for (const candidate of translate(message)) {
81
+ const event = response.accept(candidate);
82
+ if (event === undefined)
83
+ continue;
84
+ if (event.type === "usage")
85
+ sawUsage = true;
86
+ yield event;
87
+ }
88
+ }
89
+ if (request.signal?.aborted) {
90
+ yield { type: "failed", error: { kind: "cancelled" } };
91
+ return;
92
+ }
93
+ const result = await run.wait();
94
+ if (result.status === "cancelled") {
95
+ yield { type: "failed", error: { kind: "cancelled" } };
96
+ return;
97
+ }
98
+ if (!sawUsage && result.usage !== undefined)
99
+ yield usageEvent(result.usage);
100
+ if (result.status === "error") {
101
+ yield {
102
+ type: "failed",
103
+ error: { kind: "agent-run-failed", detail: result.error?.message ?? result.id },
104
+ };
105
+ return;
106
+ }
107
+ if (request.scope && plan.kind !== "ONE_SHOT") {
108
+ ctx.bindings.put({
109
+ sessionID: request.scope.sessionID,
110
+ agentID: session.id,
111
+ modelID: request.modelID,
112
+ cwd: request.scope.cwd,
113
+ checkpoint: checkpointOf(withResponse(request.conversation, response.parts())),
114
+ params: request.params,
115
+ mode: request.mode,
116
+ agentOptions: request.agentOptions,
117
+ lastUsedAt: nowMs(ctx.clock),
118
+ });
119
+ }
120
+ yield {
121
+ type: "done",
122
+ reason: "stop",
123
+ metadata: {
124
+ runId: result.id,
125
+ ...(result.requestId === undefined ? {} : { requestId: result.requestId }),
126
+ ...(result.durationMs === undefined ? {} : { durationMs: result.durationMs }),
127
+ ...(result.model === undefined ? {} : { modelId: result.model.id }),
128
+ },
129
+ };
130
+ return;
131
+ }
132
+ finally {
133
+ request.signal?.removeEventListener("abort", cancel);
134
+ await cancelPromise;
135
+ }
136
+ }
137
+ catch (error) {
138
+ if (retryLost && !emitted && plan.kind === "RESUME" && request.scope && isAgentLost(error)) {
139
+ retryLost = false;
140
+ ctx.bindings.drop(request.scope.sessionID);
141
+ plan = {
142
+ ...plan,
143
+ kind: "FRESH",
144
+ binding: undefined,
145
+ prompt: promptFor("FRESH", request.conversation, undefined),
146
+ };
147
+ continue;
148
+ }
149
+ yield failedFromCaught(error, request.signal, ctx.link.reject);
150
+ return;
151
+ }
152
+ finally {
153
+ await session?.dispose();
154
+ }
155
+ }
156
+ }
157
+ finally {
158
+ release();
159
+ }
160
+ }
161
+ function withResponse(conversation, parts) {
162
+ if (parts.length === 0)
163
+ return conversation;
164
+ return { ...conversation, turns: [...conversation.turns, { role: "assistant", parts }] };
165
+ }
166
+ function planTurn(request, apiKey, wireID, bindings) {
167
+ const binding = request.scope ? bindings.get(request.scope.sessionID) : undefined;
168
+ const kind = route({
169
+ scope: request.scope,
170
+ binding,
171
+ modelID: request.modelID,
172
+ conversation: request.conversation,
173
+ params: request.params,
174
+ mode: request.mode,
175
+ agentOptions: request.agentOptions,
176
+ });
177
+ return {
178
+ apiKey,
179
+ wireID,
180
+ params: request.params,
181
+ kind,
182
+ binding,
183
+ prompt: promptFor(kind, request.conversation, binding),
184
+ };
185
+ }
186
+ function usageEvent(usage) {
187
+ return {
188
+ type: "usage",
189
+ input: usage.inputTokens,
190
+ output: usage.outputTokens,
191
+ cacheRead: usage.cacheReadTokens,
192
+ cacheWrite: usage.cacheWriteTokens,
193
+ reasoning: usage.reasoningTokens ?? 0,
194
+ total: usage.totalTokens,
195
+ };
196
+ }
197
+ function promptFor(kind, conversation, binding) {
198
+ if (kind === "RESUME" && binding) {
199
+ const turn = resumeTurn(conversation, binding.checkpoint);
200
+ if (turn !== undefined)
201
+ return cursorMessage([], [turn]);
202
+ }
203
+ return cursorMessage(conversation.system, conversation.turns);
204
+ }
205
+ function failedFromCaught(error, signal, unlink) {
206
+ if (signal?.aborted)
207
+ return { type: "failed", error: { kind: "cancelled" } };
208
+ if (error instanceof AuthenticationError) {
209
+ unlink();
210
+ return { type: "failed", error: { kind: "not-linked" } };
211
+ }
212
+ if (error instanceof CursorAgentError) {
213
+ return { type: "failed", error: { kind: "agent-start-failed", detail: error.message } };
214
+ }
215
+ return {
216
+ type: "failed",
217
+ error: { kind: "agent-start-failed", detail: error instanceof Error ? error.message : "unknown error" },
218
+ };
219
+ }
@@ -0,0 +1,20 @@
1
+ import type { ModelParameterDefinition, ModelParameterValue, ModelVariant } from "@cursor/sdk";
2
+ import { Model } from "@opencode-ai/plugin";
3
+ import type { CatalogEditor } from "@opencode-ai/plugin/promise/catalog";
4
+ import { type CatalogModelID, type CursorModelID } from "../ids.ts";
5
+ export interface CursorModelDescriptor {
6
+ readonly catalogID: CatalogModelID;
7
+ readonly wireID: CursorModelID;
8
+ readonly name: string;
9
+ readonly parameters: readonly ModelParameterDefinition[];
10
+ readonly variants: readonly ModelVariant[];
11
+ }
12
+ export declare const CURSOR_MODEL_PARAMS = "cursorModelParams";
13
+ export declare const SEED_MODELS: readonly CursorModelDescriptor[];
14
+ export declare function providerId(): string & import("effect/Brand").Brand<"Provider.ID">;
15
+ export declare function applyProvider(draft: CatalogEditor): void;
16
+ export declare function applyModels(draft: CatalogEditor, models: readonly CursorModelDescriptor[]): void;
17
+ export declare function parseListedModels(raw: unknown): CursorModelDescriptor[];
18
+ export declare function toCatalogVariants(model: CursorModelDescriptor | undefined): Model.Variant[];
19
+ export declare function modelParamsFromOptions(options: unknown): readonly ModelParameterValue[] | undefined;
20
+ export declare function resolveWireId(models: readonly CursorModelDescriptor[], catalogID: string): CursorModelID | undefined;
@@ -0,0 +1,192 @@
1
+ import { Integration, Model, Provider } from "@opencode-ai/plugin";
2
+ import { PACKAGE_SPEC, PROVIDER_ID, asCatalogModelID, asCursorModelID } from "../ids.js";
3
+ export const CURSOR_MODEL_PARAMS = "cursorModelParams";
4
+ export const SEED_MODELS = [
5
+ {
6
+ catalogID: asCatalogModelID("composer-2.5"),
7
+ wireID: asCursorModelID("composer-2.5"),
8
+ name: "Composer 2.5",
9
+ parameters: [],
10
+ variants: [],
11
+ },
12
+ {
13
+ catalogID: asCatalogModelID("auto"),
14
+ wireID: asCursorModelID("auto"),
15
+ name: "Cursor Auto",
16
+ parameters: [],
17
+ variants: [],
18
+ },
19
+ ];
20
+ export function providerId() {
21
+ return Provider.ID.make(PROVIDER_ID);
22
+ }
23
+ export function applyProvider(draft) {
24
+ draft.provider.update(PROVIDER_ID, (provider) => {
25
+ provider.id = providerId();
26
+ provider.name = "Cursor";
27
+ provider.package = PACKAGE_SPEC;
28
+ provider.integrationID = Integration.ID.make(PROVIDER_ID);
29
+ provider.activation = "auto";
30
+ dropBaseURL(provider.settings);
31
+ });
32
+ }
33
+ export function applyModels(draft, models) {
34
+ for (const model of models) {
35
+ writeModel(draft, model.catalogID, model);
36
+ }
37
+ const preferred = models[0];
38
+ if (preferred) {
39
+ writeModel(draft, "default", preferred);
40
+ draft.model.default.set(PROVIDER_ID, preferred.catalogID);
41
+ }
42
+ }
43
+ function writeModel(draft, catalogID, model) {
44
+ draft.model.update(PROVIDER_ID, catalogID, (entry) => {
45
+ const defaults = Model.Info.default(providerId(), Model.ID.make(catalogID));
46
+ entry.id = defaults.id;
47
+ entry.modelID = Model.ID.make(model.wireID);
48
+ entry.providerID = defaults.providerID;
49
+ entry.name = model.name;
50
+ entry.capabilities = { tools: false, input: ["text", "image"], output: ["text"] };
51
+ entry.variants = toCatalogVariants(model);
52
+ entry.time = defaults.time;
53
+ entry.cost = defaults.cost;
54
+ entry.status = "active";
55
+ entry.enabled = true;
56
+ entry.limit = defaults.limit;
57
+ entry.package = PACKAGE_SPEC;
58
+ dropBaseURL(entry.settings);
59
+ });
60
+ }
61
+ export function parseListedModels(raw) {
62
+ const listed = [];
63
+ const seen = new Set();
64
+ for (const item of listedItems(raw)) {
65
+ const parsed = parseListedItem(item);
66
+ if (parsed === undefined)
67
+ continue;
68
+ if (seen.has(parsed.catalogID))
69
+ continue;
70
+ seen.add(parsed.catalogID);
71
+ listed.push(parsed);
72
+ }
73
+ if (listed.length === 0)
74
+ return [...SEED_MODELS];
75
+ for (const seed of SEED_MODELS) {
76
+ if (!seen.has(seed.catalogID))
77
+ listed.push(seed);
78
+ }
79
+ return listed;
80
+ }
81
+ function isRecord(value) {
82
+ return typeof value === "object" && value !== null;
83
+ }
84
+ function listedItems(raw) {
85
+ if (Array.isArray(raw))
86
+ return raw;
87
+ if (isRecord(raw) && Array.isArray(raw.items))
88
+ return raw.items;
89
+ return [];
90
+ }
91
+ function parseListedItem(item) {
92
+ if (!isRecord(item))
93
+ return undefined;
94
+ const nested = isRecord(item.model) ? item.model : undefined;
95
+ const rawId = typeof item.id === "string" ? item.id : typeof nested?.id === "string" ? nested.id : undefined;
96
+ if (rawId === undefined)
97
+ return undefined;
98
+ const id = rawId.trim();
99
+ if (id.length === 0)
100
+ return undefined;
101
+ const rawName = typeof item.displayName === "string" ? item.displayName : undefined;
102
+ const name = rawName && rawName.length > 0 ? rawName : id;
103
+ return {
104
+ catalogID: asCatalogModelID(id),
105
+ wireID: asCursorModelID(id),
106
+ name,
107
+ parameters: parseParameters(item.parameters),
108
+ variants: parseVariants(item.variants),
109
+ };
110
+ }
111
+ function parseParameters(value) {
112
+ if (!Array.isArray(value))
113
+ return [];
114
+ const definitions = [];
115
+ for (const item of value) {
116
+ if (!isRecord(item) || typeof item.id !== "string" || !Array.isArray(item.values))
117
+ continue;
118
+ const values = [];
119
+ for (const candidate of item.values) {
120
+ if (!isRecord(candidate) || typeof candidate.value !== "string")
121
+ continue;
122
+ values.push({
123
+ value: candidate.value,
124
+ ...(typeof candidate.displayName === "string" ? { displayName: candidate.displayName } : {}),
125
+ });
126
+ }
127
+ definitions.push({
128
+ id: item.id,
129
+ values,
130
+ ...(typeof item.displayName === "string" ? { displayName: item.displayName } : {}),
131
+ });
132
+ }
133
+ return definitions;
134
+ }
135
+ function parseVariants(value) {
136
+ if (!Array.isArray(value))
137
+ return [];
138
+ const variants = [];
139
+ for (const item of value) {
140
+ if (!isRecord(item) || typeof item.displayName !== "string")
141
+ continue;
142
+ const params = parseParams(item.params);
143
+ if (params === undefined)
144
+ continue;
145
+ variants.push({
146
+ displayName: item.displayName,
147
+ params,
148
+ ...(typeof item.description === "string" ? { description: item.description } : {}),
149
+ ...(typeof item.isDefault === "boolean" ? { isDefault: item.isDefault } : {}),
150
+ });
151
+ }
152
+ return variants;
153
+ }
154
+ function parseParams(value) {
155
+ if (!Array.isArray(value))
156
+ return undefined;
157
+ const params = [];
158
+ for (const item of value) {
159
+ if (!isRecord(item) || typeof item.id !== "string" || typeof item.value !== "string")
160
+ return undefined;
161
+ params.push({ id: item.id, value: item.value });
162
+ }
163
+ return params;
164
+ }
165
+ export function toCatalogVariants(model) {
166
+ if (model === undefined)
167
+ return [];
168
+ const seen = new Map();
169
+ return model.variants.map((variant) => {
170
+ const count = (seen.get(variant.displayName) ?? 0) + 1;
171
+ seen.set(variant.displayName, count);
172
+ const id = count === 1 ? variant.displayName : `${variant.displayName}-${count}`;
173
+ return {
174
+ id: Model.VariantID.make(id),
175
+ body: { [CURSOR_MODEL_PARAMS]: variant.params.map((param) => ({ ...param })) },
176
+ };
177
+ });
178
+ }
179
+ export function modelParamsFromOptions(options) {
180
+ if (!isRecord(options))
181
+ return undefined;
182
+ return parseParams(options[CURSOR_MODEL_PARAMS]);
183
+ }
184
+ function dropBaseURL(settings) {
185
+ if (settings === undefined)
186
+ return;
187
+ delete settings.baseURL;
188
+ }
189
+ export function resolveWireId(models, catalogID) {
190
+ const match = models.find((model) => model.catalogID === catalogID);
191
+ return match?.wireID;
192
+ }
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import type { CursorLink } from "../auth/link.ts";
3
+ import { type CursorModelDescriptor } from "./catalog.ts";
4
+ export interface ModelSource {
5
+ list(): readonly CursorModelDescriptor[];
6
+ refresh(ctx: Plugin.Context): Promise<void>;
7
+ }
8
+ export declare function createModelSource(link: CursorLink): ModelSource;
@@ -0,0 +1,30 @@
1
+ import { AuthenticationError, Cursor } from "@cursor/sdk";
2
+ import { parseListedModels, SEED_MODELS } from "./catalog.js";
3
+ export function createModelSource(link) {
4
+ let models = [...SEED_MODELS];
5
+ return {
6
+ list() {
7
+ return models;
8
+ },
9
+ async refresh(ctx) {
10
+ await link.restore(ctx);
11
+ const apiKey = await link.resolve();
12
+ if (apiKey === undefined)
13
+ return;
14
+ try {
15
+ models = parseListedModels(await Cursor.models.list({ apiKey }));
16
+ }
17
+ catch (error) {
18
+ if (error instanceof AuthenticationError)
19
+ link.reject();
20
+ return;
21
+ }
22
+ try {
23
+ await ctx.catalog.reload();
24
+ }
25
+ catch {
26
+ // Keep the listed models. The next credential or poll can replay the catalog.
27
+ }
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,35 @@
1
+ import type { CursorAgentID, EpochMs } from "./ids.ts";
2
+ export type UnsupportedReason = "tools-requested" | "tool-choice" | "structured-output" | "multiple-completions" | "file-input" | "tool-result-content" | "provider-option";
3
+ export type CursorPluginError = {
4
+ readonly kind: "not-linked";
5
+ } | {
6
+ readonly kind: "credential-expired";
7
+ readonly expiredAt: EpochMs;
8
+ } | {
9
+ readonly kind: "login-cancelled";
10
+ } | {
11
+ readonly kind: "login-failed";
12
+ readonly detail: string;
13
+ } | {
14
+ readonly kind: "unsupported-request";
15
+ readonly reason: UnsupportedReason;
16
+ } | {
17
+ readonly kind: "agent-start-failed";
18
+ readonly detail: string;
19
+ } | {
20
+ readonly kind: "agent-run-failed";
21
+ readonly detail: string;
22
+ } | {
23
+ readonly kind: "agent-lost";
24
+ readonly agentID: CursorAgentID;
25
+ } | {
26
+ readonly kind: "model-unknown";
27
+ readonly modelID: string;
28
+ } | {
29
+ readonly kind: "cancelled";
30
+ };
31
+ export declare class CursorPluginFailure extends Error {
32
+ readonly cause: CursorPluginError;
33
+ constructor(cause: CursorPluginError);
34
+ static message(cause: CursorPluginError): string;
35
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,36 @@
1
+ export class CursorPluginFailure extends Error {
2
+ cause;
3
+ constructor(cause) {
4
+ super(CursorPluginFailure.message(cause));
5
+ this.name = "CursorPluginFailure";
6
+ this.cause = cause;
7
+ }
8
+ static message(cause) {
9
+ switch (cause.kind) {
10
+ case "not-linked":
11
+ return "Cursor is not connected. Run /connect and choose Cursor.";
12
+ case "credential-expired":
13
+ return "The Cursor login has expired. Run /connect again.";
14
+ case "login-cancelled":
15
+ return "Cursor sign-in was cancelled.";
16
+ case "login-failed":
17
+ return `Cursor sign-in failed: ${cause.detail}`;
18
+ case "unsupported-request":
19
+ return `Cursor cannot honor this request (${cause.reason}).`;
20
+ case "agent-start-failed":
21
+ return `Cursor did not start: ${cause.detail}`;
22
+ case "agent-run-failed":
23
+ return `Cursor failed mid-run: ${cause.detail}`;
24
+ case "agent-lost":
25
+ return `The Cursor agent ${cause.agentID} is gone. Starting a new one.`;
26
+ case "model-unknown":
27
+ return `Unknown Cursor model: ${cause.modelID}`;
28
+ case "cancelled":
29
+ return "The Cursor run was cancelled.";
30
+ default: {
31
+ const _exhaustive = cause;
32
+ return _exhaustive;
33
+ }
34
+ }
35
+ }
36
+ }
package/dist/ids.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ export type Brand<T, B extends string> = T & {
2
+ readonly __brand: B;
3
+ };
4
+ export type CursorApiKey = Brand<string, "CursorApiKey">;
5
+ export type CursorAgentID = Brand<string, "CursorAgentID">;
6
+ export type CursorModelID = Brand<string, "CursorModelID">;
7
+ export type CatalogModelID = Brand<string, "CatalogModelID">;
8
+ export type OpencodeSessionID = Brand<string, "OpencodeSessionID">;
9
+ export type EpochMs = Brand<number, "EpochMs">;
10
+ export declare const PROVIDER_ID = "cursor";
11
+ export declare const INTEGRATION_ID = "cursor";
12
+ export declare const OAUTH_METHOD_ID = "oauth";
13
+ export declare const PACKAGE_SPEC = "aisdk:@ai-sdk/openai-compatible";
14
+ export declare const ENV_NAME = "CURSOR_API_KEY";
15
+ export type Parsed<T> = {
16
+ readonly ok: true;
17
+ readonly value: T;
18
+ } | {
19
+ readonly ok: false;
20
+ readonly issue: string;
21
+ };
22
+ export declare function ok<T>(value: T): Parsed<T>;
23
+ export declare function fail<T>(issue: string): Parsed<T>;
24
+ export declare function asApiKey(value: string): CursorApiKey;
25
+ export declare function asAgentID(value: string): CursorAgentID;
26
+ export declare function asCatalogModelID(value: string): CatalogModelID;
27
+ export declare function asCursorModelID(value: string): CursorModelID;
28
+ export declare function asSessionID(value: string): OpencodeSessionID;
29
+ export declare function asEpochMs(value: number): EpochMs;
30
+ export declare function parseNonEmpty(value: unknown, label: string): Parsed<string>;
31
+ export declare function parseApiKey(value: unknown): Parsed<CursorApiKey>;
32
+ export declare function parseAgentID(value: unknown): Parsed<CursorAgentID>;
33
+ export declare function nowMs(clock?: () => number): EpochMs;
package/dist/ids.js ADDED
@@ -0,0 +1,52 @@
1
+ export const PROVIDER_ID = "cursor";
2
+ export const INTEGRATION_ID = "cursor";
3
+ export const OAUTH_METHOD_ID = "oauth";
4
+ export const PACKAGE_SPEC = "aisdk:@ai-sdk/openai-compatible";
5
+ export const ENV_NAME = "CURSOR_API_KEY";
6
+ export function ok(value) {
7
+ return { ok: true, value };
8
+ }
9
+ export function fail(issue) {
10
+ return { ok: false, issue };
11
+ }
12
+ export function asApiKey(value) {
13
+ return value;
14
+ }
15
+ export function asAgentID(value) {
16
+ return value;
17
+ }
18
+ export function asCatalogModelID(value) {
19
+ return value;
20
+ }
21
+ export function asCursorModelID(value) {
22
+ return value;
23
+ }
24
+ export function asSessionID(value) {
25
+ return value;
26
+ }
27
+ export function asEpochMs(value) {
28
+ return value;
29
+ }
30
+ export function parseNonEmpty(value, label) {
31
+ if (typeof value !== "string")
32
+ return fail(`${label} must be a string`);
33
+ const trimmed = value.trim();
34
+ if (trimmed.length === 0)
35
+ return fail(`${label} must not be empty`);
36
+ return ok(trimmed);
37
+ }
38
+ export function parseApiKey(value) {
39
+ const parsed = parseNonEmpty(value, "api key");
40
+ if (!parsed.ok)
41
+ return parsed;
42
+ return ok(asApiKey(parsed.value));
43
+ }
44
+ export function parseAgentID(value) {
45
+ const parsed = parseNonEmpty(value, "agent id");
46
+ if (!parsed.ok)
47
+ return parsed;
48
+ return ok(asAgentID(parsed.value));
49
+ }
50
+ export function nowMs(clock = Date.now) {
51
+ return asEpochMs(clock());
52
+ }
@@ -0,0 +1,4 @@
1
+ import { plugin } from "./plugin.ts";
2
+ export { model } from "./runtime.ts";
3
+ export type { CursorProviderOptions } from "./model/provider-options.ts";
4
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { plugin } from "./plugin.js";
2
+ export { model } from "./runtime.js";
3
+ export default plugin;
@@ -0,0 +1,10 @@
1
+ import type { LanguageModelV3 } from "@ai-sdk/provider";
2
+ import type { ModelParameterValue } from "@cursor/sdk";
3
+ import type { SessionAgentBridge } from "../bridge/bridge.ts";
4
+ import type { CatalogModelID } from "../ids.ts";
5
+ export declare function toLanguageModel(input: {
6
+ bridge: SessionAgentBridge;
7
+ modelID: CatalogModelID;
8
+ wireID: string;
9
+ params?: readonly ModelParameterValue[];
10
+ }): LanguageModelV3;