cursor-opencode-provider 0.2.3 → 0.2.5
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.
- package/README.md +36 -17
- package/SECURITY.md +42 -0
- package/dist/activity.d.ts +15 -0
- package/dist/activity.js +76 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +19 -13
- package/dist/context/build.js +0 -7
- package/dist/context/rules.d.ts +4 -0
- package/dist/context/rules.js +53 -17
- package/dist/debug.d.ts +9 -0
- package/dist/debug.js +44 -4
- package/dist/errors.d.ts +59 -0
- package/dist/errors.js +199 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +4 -0
- package/dist/language-model.d.ts +54 -7
- package/dist/language-model.js +730 -197
- package/dist/models.d.ts +7 -0
- package/dist/models.js +259 -79
- package/dist/plugin.js +38 -11
- package/dist/protocol/exec-variants.d.ts +24 -0
- package/dist/protocol/exec-variants.js +55 -0
- package/dist/protocol/interactions.d.ts +8 -1
- package/dist/protocol/interactions.js +15 -2
- package/dist/protocol/messages.js +109 -26
- package/dist/protocol/request.d.ts +2 -4
- package/dist/protocol/request.js +0 -8
- package/dist/protocol/struct.js +1 -1
- package/dist/protocol/tool-call-bridge.js +13 -1
- package/dist/protocol/tools.d.ts +8 -1
- package/dist/protocol/tools.js +384 -39
- package/dist/session.d.ts +115 -8
- package/dist/session.js +362 -31
- package/dist/shell-timeout.d.ts +57 -0
- package/dist/shell-timeout.js +186 -0
- package/dist/transport/connect.d.ts +37 -1
- package/dist/transport/connect.js +679 -115
- package/package.json +8 -2
package/dist/errors.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/** Structured provider failure with retry and transport diagnostics. */
|
|
2
|
+
export class CursorProviderError extends Error {
|
|
3
|
+
origin;
|
|
4
|
+
transient;
|
|
5
|
+
replaySafe;
|
|
6
|
+
statusCode;
|
|
7
|
+
grpcStatus;
|
|
8
|
+
rstCode;
|
|
9
|
+
code;
|
|
10
|
+
retryAfterMs;
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
13
|
+
this.name = "CursorProviderError";
|
|
14
|
+
this.origin = options.origin;
|
|
15
|
+
this.transient = options.transient;
|
|
16
|
+
this.replaySafe = options.replaySafe;
|
|
17
|
+
this.statusCode = options.statusCode;
|
|
18
|
+
this.grpcStatus = options.grpcStatus;
|
|
19
|
+
this.rstCode = options.rstCode;
|
|
20
|
+
this.code = options.code;
|
|
21
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class CursorLocalCancellationError extends CursorProviderError {
|
|
25
|
+
constructor(message = "Cursor request cancelled locally", cause) {
|
|
26
|
+
super(message, {
|
|
27
|
+
origin: "local-cancel",
|
|
28
|
+
transient: false,
|
|
29
|
+
replaySafe: false,
|
|
30
|
+
cause,
|
|
31
|
+
});
|
|
32
|
+
this.name = "CursorLocalCancellationError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class CursorTransportError extends CursorProviderError {
|
|
36
|
+
constructor(message, options = {
|
|
37
|
+
transient: true,
|
|
38
|
+
replaySafe: true,
|
|
39
|
+
}) {
|
|
40
|
+
super(message, { ...options, origin: "transport" });
|
|
41
|
+
this.name = "CursorTransportError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class CursorServerError extends CursorProviderError {
|
|
45
|
+
constructor(message, options) {
|
|
46
|
+
super(message, { ...options, origin: "server" });
|
|
47
|
+
this.name = "CursorServerError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class CursorProtocolError extends CursorProviderError {
|
|
51
|
+
constructor(message, options = {}) {
|
|
52
|
+
super(message, {
|
|
53
|
+
...options,
|
|
54
|
+
origin: "protocol",
|
|
55
|
+
transient: false,
|
|
56
|
+
replaySafe: false,
|
|
57
|
+
});
|
|
58
|
+
this.name = "CursorProtocolError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class CursorAuthError extends CursorProviderError {
|
|
62
|
+
constructor(message = "Cursor authentication failed; reauthenticate with Cursor", options = {}) {
|
|
63
|
+
super(message, {
|
|
64
|
+
...options,
|
|
65
|
+
origin: "auth",
|
|
66
|
+
transient: false,
|
|
67
|
+
replaySafe: false,
|
|
68
|
+
});
|
|
69
|
+
this.name = "CursorAuthError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class CursorRetryExhaustedError extends CursorProviderError {
|
|
73
|
+
attempts;
|
|
74
|
+
constructor(attempts, last) {
|
|
75
|
+
super(`Cursor retry exhausted after ${attempts} attempts: ${last.message}`, {
|
|
76
|
+
origin: last.origin,
|
|
77
|
+
transient: false,
|
|
78
|
+
replaySafe: false,
|
|
79
|
+
statusCode: last.statusCode,
|
|
80
|
+
grpcStatus: last.grpcStatus,
|
|
81
|
+
rstCode: last.rstCode,
|
|
82
|
+
code: last.code,
|
|
83
|
+
retryAfterMs: last.retryAfterMs,
|
|
84
|
+
cause: last,
|
|
85
|
+
});
|
|
86
|
+
this.name = "CursorRetryExhaustedError";
|
|
87
|
+
this.attempts = attempts;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const TRANSIENT_NETWORK_CODES = new Set([
|
|
91
|
+
"ECONNABORTED",
|
|
92
|
+
"ECONNREFUSED",
|
|
93
|
+
"ECONNRESET",
|
|
94
|
+
"EHOSTUNREACH",
|
|
95
|
+
"ENETDOWN",
|
|
96
|
+
"ENETRESET",
|
|
97
|
+
"ENETUNREACH",
|
|
98
|
+
"ENOTFOUND",
|
|
99
|
+
"EPIPE",
|
|
100
|
+
"ERR_HTTP2_GOAWAY_SESSION",
|
|
101
|
+
"ERR_HTTP2_SESSION_ERROR",
|
|
102
|
+
"ERR_HTTP2_STREAM_CANCEL",
|
|
103
|
+
"ERR_HTTP2_STREAM_ERROR",
|
|
104
|
+
"ETIMEDOUT",
|
|
105
|
+
]);
|
|
106
|
+
export function isTransientGrpcStatus(status) {
|
|
107
|
+
const normalized = String(status).toLowerCase().replaceAll("-", "_");
|
|
108
|
+
return (normalized === "8" ||
|
|
109
|
+
normalized === "13" ||
|
|
110
|
+
normalized === "14" ||
|
|
111
|
+
normalized === "internal" ||
|
|
112
|
+
normalized === "resource_exhausted" ||
|
|
113
|
+
normalized === "unavailable");
|
|
114
|
+
}
|
|
115
|
+
export function isAuthGrpcStatus(status) {
|
|
116
|
+
const normalized = String(status).toLowerCase().replaceAll("-", "_");
|
|
117
|
+
return (normalized === "7" ||
|
|
118
|
+
normalized === "16" ||
|
|
119
|
+
normalized === "permission_denied" ||
|
|
120
|
+
normalized === "unauthenticated");
|
|
121
|
+
}
|
|
122
|
+
export function cursorHttpError(operation, statusCode, diagnostics = {}) {
|
|
123
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
124
|
+
return new CursorAuthError(`Cursor authentication failed (HTTP ${statusCode}); reauthenticate with Cursor`, { ...diagnostics, statusCode });
|
|
125
|
+
}
|
|
126
|
+
return new CursorServerError(`${operation} HTTP ${statusCode}`, {
|
|
127
|
+
...diagnostics,
|
|
128
|
+
statusCode,
|
|
129
|
+
transient: statusCode === 429 || statusCode >= 500,
|
|
130
|
+
replaySafe: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export function cursorGrpcError(operation, grpcStatus, diagnostics = {}) {
|
|
134
|
+
if (isAuthGrpcStatus(grpcStatus)) {
|
|
135
|
+
return new CursorAuthError(`Cursor authentication failed (gRPC ${grpcStatus}); reauthenticate with Cursor`, { ...diagnostics, grpcStatus });
|
|
136
|
+
}
|
|
137
|
+
return new CursorServerError(`${operation} gRPC status ${grpcStatus}`, {
|
|
138
|
+
...diagnostics,
|
|
139
|
+
grpcStatus,
|
|
140
|
+
transient: isTransientGrpcStatus(grpcStatus),
|
|
141
|
+
replaySafe: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function errorCode(error) {
|
|
145
|
+
if (!error || typeof error !== "object")
|
|
146
|
+
return undefined;
|
|
147
|
+
const code = error.code;
|
|
148
|
+
return typeof code === "string" ? code : undefined;
|
|
149
|
+
}
|
|
150
|
+
export function toCursorProviderError(error, options = { replaySafe: false }) {
|
|
151
|
+
if (error instanceof CursorProviderError) {
|
|
152
|
+
error.replaySafe = options.replaySafe && error.replaySafe;
|
|
153
|
+
return error;
|
|
154
|
+
}
|
|
155
|
+
const code = errorCode(error);
|
|
156
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
157
|
+
const message = error instanceof Error ? error.message : "";
|
|
158
|
+
if (name.startsWith("Auth") || /access token|api key|authentication/i.test(message)) {
|
|
159
|
+
return new CursorAuthError(undefined, { code, cause: error });
|
|
160
|
+
}
|
|
161
|
+
const httpStatus = /\bHTTP\s+(\d{3})\b/i.exec(message)?.[1];
|
|
162
|
+
if (httpStatus) {
|
|
163
|
+
const failure = cursorHttpError("Cursor request failed with", Number(httpStatus), { code });
|
|
164
|
+
failure.replaySafe = options.replaySafe && failure.replaySafe;
|
|
165
|
+
return failure;
|
|
166
|
+
}
|
|
167
|
+
if (code && TRANSIENT_NETWORK_CODES.has(code)) {
|
|
168
|
+
return new CursorTransportError(`Cursor transport failure (${code})`, {
|
|
169
|
+
transient: true,
|
|
170
|
+
replaySafe: options.replaySafe,
|
|
171
|
+
code,
|
|
172
|
+
cause: error,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (error instanceof TypeError) {
|
|
176
|
+
return new CursorProtocolError(options.fallback ?? "Invalid Cursor provider configuration", {
|
|
177
|
+
code,
|
|
178
|
+
cause: error,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return new CursorProtocolError(options.fallback ?? `Cursor provider failure (${name})`, {
|
|
182
|
+
code,
|
|
183
|
+
cause: error,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export function retrySuppressedError(cause, reason, attempt, maxAttempts) {
|
|
187
|
+
const subject = cause.message.replace(/^Cursor Run stream /, "Cursor stream ");
|
|
188
|
+
return new CursorProviderError(`${subject} ${reason}; automatic retry unsafe (attempt ${attempt}/${maxAttempts})`, {
|
|
189
|
+
origin: cause.origin,
|
|
190
|
+
transient: false,
|
|
191
|
+
replaySafe: false,
|
|
192
|
+
statusCode: cause.statusCode,
|
|
193
|
+
grpcStatus: cause.grpcStatus,
|
|
194
|
+
rstCode: cause.rstCode,
|
|
195
|
+
code: cause.code,
|
|
196
|
+
retryAfterMs: cause.retryAfterMs,
|
|
197
|
+
cause,
|
|
198
|
+
});
|
|
199
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { CursorPlugin } from "./plugin.js";
|
|
2
|
+
import type { CursorContinuationOptions } from "./session.js";
|
|
3
|
+
export type CursorRetryOptions = {
|
|
4
|
+
/** Total attempts including the initial request. Default: 3. */
|
|
5
|
+
maxAttempts?: number;
|
|
6
|
+
/** Initial full-jitter backoff ceiling. Default: 500ms. */
|
|
7
|
+
baseDelayMs?: number;
|
|
8
|
+
/** Exponential backoff ceiling. Default: 8000ms. */
|
|
9
|
+
maxDelayMs?: number;
|
|
10
|
+
};
|
|
2
11
|
export type CreateCursorOptions = {
|
|
3
12
|
name: string;
|
|
4
13
|
accessToken?: string;
|
|
@@ -14,9 +23,14 @@ export type CreateCursorOptions = {
|
|
|
14
23
|
telemetryEnabled?: boolean;
|
|
15
24
|
/** OpenCode project / worktree directory for request_context collectors. */
|
|
16
25
|
workspaceRoot?: string;
|
|
26
|
+
/** Held-stream policy. Defaults: heartbeat 5s, semantic idle 120s, tool inactivity 10m. */
|
|
27
|
+
continuation?: CursorContinuationOptions;
|
|
28
|
+
/** Fresh-turn retry policy. Defaults: 3 attempts, 500ms base, 8000ms cap. */
|
|
29
|
+
retry?: CursorRetryOptions;
|
|
17
30
|
};
|
|
18
31
|
export declare function createCursor(options: CreateCursorOptions): {
|
|
19
32
|
languageModel(modelId: string): import("@ai-sdk/provider").LanguageModelV3;
|
|
20
33
|
};
|
|
21
34
|
export { CursorPlugin };
|
|
35
|
+
export type { CursorContinuationOptions, CursorContinuationPolicy } from "./session.js";
|
|
22
36
|
export default CursorPlugin;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,10 @@ export function createCursor(options) {
|
|
|
11
11
|
}
|
|
12
12
|
export { CursorPlugin };
|
|
13
13
|
export default CursorPlugin;
|
|
14
|
+
// Keep root runtime exports plugin-safe. OpenCode's legacy plugin loader
|
|
15
|
+
// treats package-root exports as potential plugins, so extra public runtime
|
|
16
|
+
// APIs belong on subpaths such as "cursor-opencode-provider/errors".
|
|
17
|
+
//
|
|
14
18
|
// CursorPluginV2 is NOT re-exported here — see plugin-v2.ts.
|
|
15
19
|
// OpenCode's legacy plugin loader (getLegacyPlugins) iterates all exports
|
|
16
20
|
// and calls getServerPlugin on each; the v2 define() return is not a
|
package/dist/language-model.d.ts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3StreamPart } from "@ai-sdk/provider";
|
|
2
|
-
import type { CreateCursorOptions } from "./index.js";
|
|
2
|
+
import type { CreateCursorOptions, CursorRetryOptions } from "./index.js";
|
|
3
3
|
import { type OpencodeToolDef } from "./protocol/tools.js";
|
|
4
4
|
import { type CursorSession } from "./session.js";
|
|
5
|
+
import { CursorProviderError } from "./errors.js";
|
|
5
6
|
import type { SeedHistoryMessage } from "./protocol/request.js";
|
|
6
7
|
export declare const MAX_TURN_STATE_SESSIONS = 256;
|
|
8
|
+
export type CursorRetryPolicy = {
|
|
9
|
+
maxAttempts: number;
|
|
10
|
+
baseDelayMs: number;
|
|
11
|
+
maxDelayMs: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveRetryPolicy(options: CursorRetryOptions | undefined): CursorRetryPolicy;
|
|
14
|
+
export declare function connectFrameError(payload: string): CursorProviderError;
|
|
7
15
|
type V3Part = LanguageModelV3StreamPart;
|
|
8
16
|
export declare function createCursorLanguageModel(modelId: string, providerId: string, options: CreateCursorOptions): LanguageModelV3;
|
|
17
|
+
export declare function pumpWithRecovery(input: {
|
|
18
|
+
initialSession: CursorSession;
|
|
19
|
+
controller: ReadableStreamDefaultController<V3Part>;
|
|
20
|
+
abortSignal?: AbortSignal;
|
|
21
|
+
promptTokens?: number;
|
|
22
|
+
retryPolicy?: CursorRetryPolicy;
|
|
23
|
+
recover: () => Promise<CursorSession>;
|
|
24
|
+
onSession?: (session: CursorSession) => void;
|
|
25
|
+
maxRecoveries?: number;
|
|
26
|
+
}): Promise<CursorSession>;
|
|
9
27
|
/**
|
|
10
28
|
* OpenCode re-sends the full tool-result history on every continuation. Prefer
|
|
11
29
|
* the newest result that still has a live pending exec on its tagged session.
|
|
@@ -14,17 +32,27 @@ export declare function findContinuationSession(toolResults: Array<{
|
|
|
14
32
|
sessionId: string;
|
|
15
33
|
execId: number;
|
|
16
34
|
}>): CursorSession | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Deliver trailing tool results onto a live continuation session.
|
|
37
|
+
* Returns the same session when writes succeed (or only bridged results were
|
|
38
|
+
* cleared). Returns undefined after closing the session when a write fails, so
|
|
39
|
+
* the caller can rebase onto a fresh Run instead of pumping a dead stream.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deliverContinuationResults(session: CursorSession, trailingToolResults: ExtractedToolResult[]): CursorSession | undefined;
|
|
17
42
|
/**
|
|
18
43
|
* Read the held-open stream, emitting stream parts, until the turn boundary:
|
|
19
44
|
* - a tool call (exec_server_message) → emit tool-call, finish "tool-calls",
|
|
20
45
|
* and KEEP the session open for the result on the next doStream call;
|
|
21
|
-
* - turn_ended
|
|
46
|
+
* - turn_ended → finish "stop" and close the session;
|
|
47
|
+
* - transport EOF before turn_ended → throw for one fresh-Run recovery.
|
|
22
48
|
*/
|
|
23
49
|
export declare function pump(session: CursorSession, controller: ReadableStreamDefaultController<V3Part>, ids: {
|
|
24
50
|
textId: string;
|
|
25
51
|
reasoningId: string;
|
|
52
|
+
promptTokens?: number;
|
|
26
53
|
}, abortSignal?: AbortSignal): Promise<void>;
|
|
27
54
|
type ExtractedToolResult = {
|
|
55
|
+
toolCallId: string;
|
|
28
56
|
sessionId: string;
|
|
29
57
|
execId: number;
|
|
30
58
|
toolName: string;
|
|
@@ -42,15 +70,34 @@ export declare function extractTrailingToolResults(prompt: LanguageModelV3CallOp
|
|
|
42
70
|
* Redirect only to OpenCode tools that are genuinely advertised this turn;
|
|
43
71
|
* compaction keeps its dedicated summary prompt unchanged.
|
|
44
72
|
*/
|
|
45
|
-
export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean): string | undefined;
|
|
73
|
+
export declare function buildOpenCodeInteractionGuidance(tools: OpencodeToolDef[], isCompaction: boolean, workspaceRoot: string): string | undefined;
|
|
46
74
|
/** Rough char→token estimate for mid-turn usage before TurnEnded arrives. */
|
|
47
75
|
export declare function estimateTokens(chars: number): number;
|
|
76
|
+
/** Estimate current-request prompt tokens from the complete serialized V3 prompt. */
|
|
77
|
+
export declare function estimatePromptTokens(prompt: LanguageModelV3CallOptions["prompt"]): number;
|
|
78
|
+
/** Preserve cumulative Cursor counters as diagnostics, never as AI SDK request usage. */
|
|
79
|
+
export declare function cursorTurnEndedProviderMetadata(te: Record<string, unknown>): {
|
|
80
|
+
cursor: {
|
|
81
|
+
usageVersion: number;
|
|
82
|
+
inputTokensRaw: number;
|
|
83
|
+
outputTokensRaw: number;
|
|
84
|
+
cacheReadRaw: number;
|
|
85
|
+
cacheWriteRaw: number;
|
|
86
|
+
reasoningTokensRaw: number;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
48
89
|
/**
|
|
49
|
-
* Prior prompt turns for a seed ConversationStateStructure
|
|
50
|
-
*
|
|
51
|
-
* result
|
|
90
|
+
* Prior prompt turns for a seed ConversationStateStructure. Tool results must
|
|
91
|
+
* never be replayed as assistant-authored prose: that teaches the model to
|
|
92
|
+
* counterfeit `Tool result (...)` text instead of emitting a real tool call.
|
|
93
|
+
* Normal rebases omit old results; compaction can retain all results and
|
|
94
|
+
* interrupted continuations retain only the trailing live result suffix as
|
|
95
|
+
* explicit OpenCode-host observations.
|
|
52
96
|
*/
|
|
53
|
-
export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions["prompt"]
|
|
97
|
+
export declare function extractPromptHistory(prompt: LanguageModelV3CallOptions["prompt"], options?: {
|
|
98
|
+
preserveTrailingUser?: boolean;
|
|
99
|
+
toolResults?: "omit" | "all" | "trailing";
|
|
100
|
+
}): SeedHistoryMessage[];
|
|
54
101
|
/** OpenCode session id header, if present. */
|
|
55
102
|
export declare function opencodeSessionKey(callOptions: LanguageModelV3CallOptions): string | undefined;
|
|
56
103
|
/**
|