effect-cursor-sdk 0.1.1
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/LICENSE +21 -0
- package/README.md +275 -0
- package/dist/index.d.ts +840 -0
- package/dist/index.js +1186 -0
- package/dist/index.js.map +1 -0
- package/package.json +85 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1186 @@
|
|
|
1
|
+
import { Config, ConfigProvider, Context, Data, Effect, Layer, ManagedRuntime, Match, Metric, Option, Redacted, Schema, Stream } from "effect";
|
|
2
|
+
import { Agent, AuthenticationError, AuthenticationError as AuthenticationError$1, ConfigurationError, ConfigurationError as ConfigurationError$1, Cursor, CursorAgentError, CursorAgentError as CursorAgentError$1, CursorAgentPlatform, IntegrationNotConnectedError, IntegrationNotConnectedError as IntegrationNotConnectedError$1, NetworkError, NetworkError as NetworkError$1, RateLimitError, RateLimitError as RateLimitError$1, UnknownAgentError, UnknownAgentError as UnknownAgentError$1, UnsupportedRunOperationError, UnsupportedRunOperationError as UnsupportedRunOperationError$1, createAgentPlatform, createInMemoryRunEventNotifier, createLocalRunEventNotifier, createSdkMessageRunStreamEvent, decodeLocalRunStreamEvent, decodeSdkMessageRunStreamEvent, getTurnType, isTerminalLocalRunStreamEvent, localRunStreamEventToSdkMessage, startLocalRunEventNotifierServer } from "@cursor/sdk";
|
|
3
|
+
//#region src/cursor-error.ts
|
|
4
|
+
/**
|
|
5
|
+
* Authentication or permission failure reported by the Cursor SDK.
|
|
6
|
+
*
|
|
7
|
+
* @see {@link mapCursorError}
|
|
8
|
+
* @category errors
|
|
9
|
+
*/
|
|
10
|
+
var CursorAuthenticationError = class extends Data.TaggedError("CursorAuthenticationError") {};
|
|
11
|
+
/**
|
|
12
|
+
* Cursor rate limit or usage-limit failure.
|
|
13
|
+
*
|
|
14
|
+
* @see {@link mapCursorError}
|
|
15
|
+
* @category errors
|
|
16
|
+
*/
|
|
17
|
+
var CursorRateLimitError = class extends Data.TaggedError("CursorRateLimitError") {};
|
|
18
|
+
/**
|
|
19
|
+
* Invalid configuration, model, prompt, repository, or request options.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link AgentOptions}
|
|
22
|
+
* @see {@link mapCursorError}
|
|
23
|
+
* @category errors
|
|
24
|
+
*/
|
|
25
|
+
var CursorConfigurationError = class extends Data.TaggedError("CursorConfigurationError") {};
|
|
26
|
+
/**
|
|
27
|
+
* SCM integration is not connected for a requested cloud repository.
|
|
28
|
+
*
|
|
29
|
+
* @see {@link mapCursorError}
|
|
30
|
+
* @category errors
|
|
31
|
+
*/
|
|
32
|
+
var CursorIntegrationNotConnectedError = class extends Data.TaggedError("CursorIntegrationNotConnectedError") {};
|
|
33
|
+
/**
|
|
34
|
+
* Network, service availability, timeout, or backend failure.
|
|
35
|
+
*
|
|
36
|
+
* @see {@link mapCursorError}
|
|
37
|
+
* @category errors
|
|
38
|
+
*/
|
|
39
|
+
var CursorNetworkError = class extends Data.TaggedError("CursorNetworkError") {};
|
|
40
|
+
/**
|
|
41
|
+
* A run operation is unavailable for the current runtime or run state.
|
|
42
|
+
*
|
|
43
|
+
* @see {@link RunOperation}
|
|
44
|
+
* @see {@link mapCursorError}
|
|
45
|
+
* @category errors
|
|
46
|
+
*/
|
|
47
|
+
var CursorUnsupportedOperationError = class extends Data.TaggedError("CursorUnsupportedOperationError") {};
|
|
48
|
+
/**
|
|
49
|
+
* A run reached an error terminal state or failed to produce a usable result.
|
|
50
|
+
*
|
|
51
|
+
* @see {@link RunResult}
|
|
52
|
+
* @category errors
|
|
53
|
+
*/
|
|
54
|
+
var CursorRunFailedError = class extends Data.TaggedError("CursorRunFailedError") {};
|
|
55
|
+
/**
|
|
56
|
+
* Stream creation or iteration failed.
|
|
57
|
+
*
|
|
58
|
+
* @see {@link CursorRunService}
|
|
59
|
+
* @see {@link SDKMessage}
|
|
60
|
+
* @category errors
|
|
61
|
+
*/
|
|
62
|
+
var CursorStreamError = class extends Data.TaggedError("CursorStreamError") {};
|
|
63
|
+
/**
|
|
64
|
+
* Fallback for unknown SDK or JavaScript failures.
|
|
65
|
+
*
|
|
66
|
+
* @see {@link mapCursorError}
|
|
67
|
+
* @category errors
|
|
68
|
+
*/
|
|
69
|
+
var CursorUnknownError = class extends Data.TaggedError("CursorUnknownError") {};
|
|
70
|
+
const messageFrom = (cause) => {
|
|
71
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
72
|
+
};
|
|
73
|
+
const retryableFrom = (cause) => {
|
|
74
|
+
return cause instanceof CursorAgentError$1 ? cause.isRetryable : false;
|
|
75
|
+
};
|
|
76
|
+
function mapCursorError(cause, context) {
|
|
77
|
+
const fields = {
|
|
78
|
+
...context,
|
|
79
|
+
message: messageFrom(cause),
|
|
80
|
+
cause,
|
|
81
|
+
isRetryable: retryableFrom(cause)
|
|
82
|
+
};
|
|
83
|
+
if (context.operation === "run.stream") return new CursorStreamError(fields);
|
|
84
|
+
return Match.value(cause).pipe(Match.when(Match.instanceOf(AuthenticationError$1), () => {
|
|
85
|
+
return new CursorAuthenticationError(fields);
|
|
86
|
+
}), Match.when(Match.instanceOf(RateLimitError$1), () => {
|
|
87
|
+
return new CursorRateLimitError(fields);
|
|
88
|
+
}), Match.when(Match.instanceOf(IntegrationNotConnectedError$1), (error) => {
|
|
89
|
+
return new CursorIntegrationNotConnectedError({
|
|
90
|
+
...fields,
|
|
91
|
+
provider: error.provider,
|
|
92
|
+
helpUrl: error.helpUrl
|
|
93
|
+
});
|
|
94
|
+
}), Match.when(Match.instanceOf(UnsupportedRunOperationError$1), (error) => {
|
|
95
|
+
return new CursorUnsupportedOperationError({
|
|
96
|
+
...fields,
|
|
97
|
+
sdkOperation: error.operation
|
|
98
|
+
});
|
|
99
|
+
}), Match.when(Match.instanceOf(ConfigurationError$1), () => {
|
|
100
|
+
return new CursorConfigurationError(fields);
|
|
101
|
+
}), Match.when(Match.instanceOf(NetworkError$1), () => {
|
|
102
|
+
return new CursorNetworkError(fields);
|
|
103
|
+
}), Match.when(Match.instanceOf(UnknownAgentError$1), () => {
|
|
104
|
+
return new CursorUnknownError(fields);
|
|
105
|
+
}), Match.when(Match.instanceOf(CursorAgentError$1), () => {
|
|
106
|
+
return new CursorUnknownError(fields);
|
|
107
|
+
}), Match.orElse(() => {
|
|
108
|
+
return new CursorUnknownError(fields);
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/cursor-sdk-factory.ts
|
|
113
|
+
/**
|
|
114
|
+
* Context service that provides the live `@cursor/sdk` static API boundary.
|
|
115
|
+
*
|
|
116
|
+
* Use {@link CursorSdkFactory.Live} in production layers and override the
|
|
117
|
+
* service in tests with {@link makeMockSdkFactoryLayer} or a custom layer.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* const program = Effect.gen(function*() {
|
|
122
|
+
* const sdk = yield* CursorSdkFactory
|
|
123
|
+
* return sdk.create({ model: { id: "composer-2" }, local: { cwd: process.cwd() } })
|
|
124
|
+
* })
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @see {@link CursorSdkFactoryShape}
|
|
128
|
+
* @see {@link liveLayer}
|
|
129
|
+
* @category services
|
|
130
|
+
*/
|
|
131
|
+
var CursorSdkFactory = class CursorSdkFactory extends Context.Service()("effect-cursor-sdk/cursor-sdk-factory/CursorSdkFactory") {
|
|
132
|
+
static Live = Layer.succeed(CursorSdkFactory)(CursorSdkFactory.of({
|
|
133
|
+
create: (options) => {
|
|
134
|
+
return Agent.create(options);
|
|
135
|
+
},
|
|
136
|
+
resume: (agentId, options) => {
|
|
137
|
+
return Agent.resume(agentId, options);
|
|
138
|
+
},
|
|
139
|
+
prompt: (message, options) => {
|
|
140
|
+
return Agent.prompt(message, options);
|
|
141
|
+
},
|
|
142
|
+
listAgents: (options) => {
|
|
143
|
+
return Agent.list(options);
|
|
144
|
+
},
|
|
145
|
+
listRuns: (agentId, options) => {
|
|
146
|
+
return Agent.listRuns(agentId, options);
|
|
147
|
+
},
|
|
148
|
+
getRun: (runId, options) => {
|
|
149
|
+
return Agent.getRun(runId, options);
|
|
150
|
+
},
|
|
151
|
+
getAgent: (agentId, options) => {
|
|
152
|
+
return Agent.get(agentId, options);
|
|
153
|
+
},
|
|
154
|
+
archiveAgent: (agentId, options) => {
|
|
155
|
+
return Agent.archive(agentId, options);
|
|
156
|
+
},
|
|
157
|
+
unarchiveAgent: (agentId, options) => {
|
|
158
|
+
return Agent.unarchive(agentId, options);
|
|
159
|
+
},
|
|
160
|
+
deleteAgent: (agentId, options) => {
|
|
161
|
+
return Agent.delete(agentId, options);
|
|
162
|
+
},
|
|
163
|
+
listMessages: (agentId, options) => {
|
|
164
|
+
return Agent.messages.list(agentId, options);
|
|
165
|
+
},
|
|
166
|
+
me: (options) => {
|
|
167
|
+
return Cursor.me(options);
|
|
168
|
+
},
|
|
169
|
+
listModels: (options) => {
|
|
170
|
+
return Cursor.models.list(options);
|
|
171
|
+
},
|
|
172
|
+
listRepositories: (options) => {
|
|
173
|
+
return Cursor.repositories.list(options);
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
};
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/cursor-telemetry.ts
|
|
179
|
+
/**
|
|
180
|
+
* Telemetry helpers for the Effect Cursor SDK boundary.
|
|
181
|
+
*
|
|
182
|
+
* Services wrap SDK calls with {@link instrument}, which records Effect metrics
|
|
183
|
+
* and opens an OpenTelemetry-style span per {@link CursorOperation}. Named
|
|
184
|
+
* `Metric.counter` values are exported so applications can wire them into a
|
|
185
|
+
* metrics backend via Effect's metrics layer.
|
|
186
|
+
*
|
|
187
|
+
* {@link redact} is a small helper for scrubbing structured metadata before it
|
|
188
|
+
* leaves a trust boundary (for example log attributes or debug payloads).
|
|
189
|
+
*
|
|
190
|
+
* @see {@link CursorOperation}
|
|
191
|
+
* @see {@link CursorAgentService}
|
|
192
|
+
* @see {@link CursorRunService}
|
|
193
|
+
* @see {@link CursorInspectionService}
|
|
194
|
+
* @see {@link CursorArtifactService}
|
|
195
|
+
*
|
|
196
|
+
* @module
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Increments once when an instrumented SDK effect **starts** execution.
|
|
200
|
+
*
|
|
201
|
+
* Bound to the metric key `cursor_operations_started`. Used by
|
|
202
|
+
* {@link instrument} on every wrapped call; pair with
|
|
203
|
+
* {@link cursorOperationsFailed} to compute failure rates per operation when
|
|
204
|
+
* both counters are exported to your metrics stack.
|
|
205
|
+
*
|
|
206
|
+
* @see {@link instrument}
|
|
207
|
+
* @see {@link cursorOperationsFailed}
|
|
208
|
+
*
|
|
209
|
+
* @category telemetry
|
|
210
|
+
*/
|
|
211
|
+
const cursorOperationsStarted = Metric.counter("cursor_operations_started");
|
|
212
|
+
/**
|
|
213
|
+
* Increments once when an instrumented SDK effect **fails** (any error channel
|
|
214
|
+
* value), after {@link cursorOperationsStarted} has already been recorded for
|
|
215
|
+
* that run.
|
|
216
|
+
*
|
|
217
|
+
* Bound to the metric key `cursor_operations_failed`. Failures are tracked in
|
|
218
|
+
* `Effect.tapError` so the effect still fails with the original error; this
|
|
219
|
+
* counter is observability-only.
|
|
220
|
+
*
|
|
221
|
+
* @see {@link instrument}
|
|
222
|
+
* @see {@link cursorOperationsStarted}
|
|
223
|
+
*
|
|
224
|
+
* @category telemetry
|
|
225
|
+
*/
|
|
226
|
+
const cursorOperationsFailed = Metric.counter("cursor_operations_failed");
|
|
227
|
+
/**
|
|
228
|
+
* Counter reserved for **per-message** or **per-chunk** stream throughput.
|
|
229
|
+
*
|
|
230
|
+
* Bound to the metric key `cursor_stream_events`. The service wrappers do not
|
|
231
|
+
* attach this metric automatically to {@link CursorRunService.streamEvents};
|
|
232
|
+
* export it from your app if you want to `Metric.track` inside a
|
|
233
|
+
* `Stream.tapEffect` (or similar) when consuming SDK stream payloads.
|
|
234
|
+
*
|
|
235
|
+
* @see {@link CursorRunService}
|
|
236
|
+
*
|
|
237
|
+
* @category telemetry
|
|
238
|
+
*/
|
|
239
|
+
const cursorStreamEvents = Metric.counter("cursor_stream_events");
|
|
240
|
+
/** String substituted for values under sensitive-looking keys. */
|
|
241
|
+
const REDACTED_MARKER = "[redacted]";
|
|
242
|
+
/** Replaces non-plain objects (for example `Date`, `Map`) so they are not mistaken for empty `{}`. */
|
|
243
|
+
const OPAQUE_MARKER = "[opaque]";
|
|
244
|
+
/** Replaces a value when the same object appears on the recursion stack (true cycles only). */
|
|
245
|
+
const CIRCULAR_MARKER = "[circular]";
|
|
246
|
+
/** Replaces nested values when recursion depth exceeds this limit (prevents stack overflow). */
|
|
247
|
+
const MAX_REDACT_DEPTH = 64;
|
|
248
|
+
const isPlainObject = (value) => {
|
|
249
|
+
const proto = Object.getPrototypeOf(value);
|
|
250
|
+
return proto === null || proto === Object.prototype;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Lower-cased key substring / equality checks. Substrings such as `key` also
|
|
254
|
+
* match `apiKey` (and unfortunately unrelated keys like `monkey`); that is an
|
|
255
|
+
* intentional tradeoff for simple log scrubbing.
|
|
256
|
+
*/
|
|
257
|
+
const isSensitiveKeyName = (normalized) => {
|
|
258
|
+
if (normalized === "authorization" || normalized === "data") return true;
|
|
259
|
+
return normalized.includes("key") || normalized.includes("token") || normalized.includes("secret") || normalized.includes("password") || normalized.includes("passwd") || normalized.includes("credential") || normalized.includes("cookie") || normalized.includes("jwt") || normalized.includes("bearer");
|
|
260
|
+
};
|
|
261
|
+
const redactInner = (value, path, depth) => {
|
|
262
|
+
if (depth > MAX_REDACT_DEPTH) return OPAQUE_MARKER;
|
|
263
|
+
if (Array.isArray(value)) {
|
|
264
|
+
if (path.has(value)) return CIRCULAR_MARKER;
|
|
265
|
+
path.add(value);
|
|
266
|
+
const out = value.map((item) => {
|
|
267
|
+
return redactInner(item, path, depth + 1);
|
|
268
|
+
});
|
|
269
|
+
path.delete(value);
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
if (value === null || typeof value !== "object") return value;
|
|
273
|
+
if (!isPlainObject(value)) return OPAQUE_MARKER;
|
|
274
|
+
if (path.has(value)) return CIRCULAR_MARKER;
|
|
275
|
+
path.add(value);
|
|
276
|
+
const out = Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
277
|
+
if (isSensitiveKeyName(key.toLowerCase())) return [key, REDACTED_MARKER];
|
|
278
|
+
return [key, redactInner(item, path, depth + 1)];
|
|
279
|
+
}));
|
|
280
|
+
path.delete(value);
|
|
281
|
+
return out;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Deep-clones **plain** objects and arrays while replacing values under keys that
|
|
285
|
+
* often carry secrets, bearer material, or large opaque blobs (for example API
|
|
286
|
+
* keys, tokens, passwords, cookies, `Authorization`, or a generic `data` field).
|
|
287
|
+
*
|
|
288
|
+
* Matching is **substring-based on the lower-cased key name** (for example
|
|
289
|
+
* `apiKey`, `CURSOR_API_TOKEN`, `client_secret`, `setCookie` all match). Primitives
|
|
290
|
+
* and `null` pass through unchanged. Non-plain objects (for example `Date`, `Map`,
|
|
291
|
+
* class instances) are replaced by `"[opaque]"` so they are not serialized as empty
|
|
292
|
+
* objects. True circular references in the input are replaced by `"[circular]"`.
|
|
293
|
+
* Recursion deeper than 64 levels falls back to `"[opaque]"` for the overflow branch.
|
|
294
|
+
*
|
|
295
|
+
* This is a best-effort redactor for logs and attributes—not a cryptographic
|
|
296
|
+
* guarantee; do not rely on it for compliance redaction without review.
|
|
297
|
+
*
|
|
298
|
+
* @param value - Arbitrary JSON-like value to sanitize.
|
|
299
|
+
* @returns A structure of the same shape with sensitive-looking entries replaced
|
|
300
|
+
* by the string `"[redacted]"`.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```ts
|
|
304
|
+
* redact({ apiKey: "secret", nested: { token: "x" }, safe: 1 })
|
|
305
|
+
* // => { apiKey: "[redacted]", nested: { token: "[redacted]" }, safe: 1 }
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @category telemetry
|
|
309
|
+
*/
|
|
310
|
+
const redact = (value) => {
|
|
311
|
+
return redactInner(value, /* @__PURE__ */ new Set(), 0);
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Wraps an SDK-backed {@link Effect.Effect | Effect} with consistent
|
|
315
|
+
* observability: increments {@link cursorOperationsStarted} at execution start,
|
|
316
|
+
* increments {@link cursorOperationsFailed} on the error channel, and attaches
|
|
317
|
+
* a span named `cursor.<operation>` (for example `cursor.agent.list`).
|
|
318
|
+
*
|
|
319
|
+
* The span name is stable and includes the {@link CursorOperation} tag space
|
|
320
|
+
* so traces can be filtered consistently across agent, run, artifact, and
|
|
321
|
+
* inspection APIs.
|
|
322
|
+
*
|
|
323
|
+
* @param operation - Logical SDK operation; must match {@link CursorOperation}
|
|
324
|
+
* for typed error mapping and trace taxonomy.
|
|
325
|
+
* @param effect - The effect produced by `Effect.tryPromise` (or similar)
|
|
326
|
+
* around a single SDK call.
|
|
327
|
+
* @returns The same effect type `Effect<A, E, R>` with metrics and span
|
|
328
|
+
* attached; success and failure values are unchanged.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* import { Effect } from "effect"
|
|
333
|
+
* import { instrument } from "./cursor-telemetry"
|
|
334
|
+
*
|
|
335
|
+
* const eff = instrument(
|
|
336
|
+
* "agent.get",
|
|
337
|
+
* Effect.tryPromise({
|
|
338
|
+
* try: () => sdk.getAgent(id),
|
|
339
|
+
* catch: (e) => e,
|
|
340
|
+
* }),
|
|
341
|
+
* )
|
|
342
|
+
* ```
|
|
343
|
+
*
|
|
344
|
+
* @see {@link cursorOperationsStarted}
|
|
345
|
+
* @see {@link cursorOperationsFailed}
|
|
346
|
+
* @see {@link CursorOperation}
|
|
347
|
+
*
|
|
348
|
+
* @category telemetry
|
|
349
|
+
*/
|
|
350
|
+
const instrument = (operation, effect) => {
|
|
351
|
+
return effect.pipe(Effect.track(cursorOperationsStarted.pipe(Metric.withConstantInput(1))), Effect.tapError(() => {
|
|
352
|
+
return Effect.track(Effect.void, cursorOperationsFailed.pipe(Metric.withConstantInput(1)));
|
|
353
|
+
}), Effect.withSpan(`cursor.${operation}`));
|
|
354
|
+
};
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/cursor-agent.ts
|
|
357
|
+
/**
|
|
358
|
+
* Effect-native agent lifecycle and prompt service.
|
|
359
|
+
*
|
|
360
|
+
* The service wraps `Agent.create`, `Agent.resume`, `Agent.prompt`, and
|
|
361
|
+
* instance lifecycle methods while preserving SDK-owned option and result
|
|
362
|
+
* types.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* import { CursorAgentService, liveLayer } from "effect-cursor-sdk"
|
|
367
|
+
* import { Effect } from "effect"
|
|
368
|
+
*
|
|
369
|
+
* const program = Effect.gen(function*() {
|
|
370
|
+
* const agents = yield* CursorAgentService
|
|
371
|
+
* const agent = yield* agents.create({ model: { id: "composer-2" }, local: { cwd: process.cwd() } })
|
|
372
|
+
* return yield* agents.send(agent, "Summarize this repository")
|
|
373
|
+
* }).pipe(Effect.provide(liveLayer))
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @see {@link CursorRunService} for operations on returned `Run` handles.
|
|
377
|
+
* @see {@link CursorSdkFactory} for replacing SDK construction in tests.
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* Inherits the **API boundary notice** on {@link CursorAgentServiceShape} about
|
|
381
|
+
* a possible future switch from raw {@link AgentOptions} to
|
|
382
|
+
* {@link loadCursorConfig} / {@link agentOptionsFromConfig}.
|
|
383
|
+
*
|
|
384
|
+
* @category services
|
|
385
|
+
*/
|
|
386
|
+
var CursorAgentService = class CursorAgentService extends Context.Service()("effect-cursor-sdk/cursor-agent/CursorAgentService") {
|
|
387
|
+
static Live = Layer.effect(CursorAgentService)(Effect.gen(function* () {
|
|
388
|
+
const sdk = yield* CursorSdkFactory;
|
|
389
|
+
const create = (options) => {
|
|
390
|
+
return instrument("agent.create", Effect.tryPromise({
|
|
391
|
+
try: () => sdk.create(options),
|
|
392
|
+
catch: (cause) => {
|
|
393
|
+
return mapCursorError(cause, { operation: "agent.create" });
|
|
394
|
+
}
|
|
395
|
+
}));
|
|
396
|
+
};
|
|
397
|
+
const resume = (agentId, options) => {
|
|
398
|
+
return instrument("agent.resume", Effect.tryPromise({
|
|
399
|
+
try: () => sdk.resume(agentId, options),
|
|
400
|
+
catch: (cause) => {
|
|
401
|
+
return mapCursorError(cause, {
|
|
402
|
+
operation: "agent.resume",
|
|
403
|
+
agentId
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
};
|
|
408
|
+
const prompt = (message, options) => {
|
|
409
|
+
return instrument("agent.prompt", Effect.tryPromise({
|
|
410
|
+
try: () => sdk.prompt(message, options),
|
|
411
|
+
catch: (cause) => {
|
|
412
|
+
return mapCursorError(cause, { operation: "agent.prompt" });
|
|
413
|
+
}
|
|
414
|
+
}));
|
|
415
|
+
};
|
|
416
|
+
const send = (agent, message, options) => {
|
|
417
|
+
return instrument("agent.send", Effect.tryPromise({
|
|
418
|
+
try: () => agent.send(message, options),
|
|
419
|
+
catch: (cause) => {
|
|
420
|
+
return mapCursorError(cause, {
|
|
421
|
+
operation: "agent.send",
|
|
422
|
+
agentId: agent.agentId
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}));
|
|
426
|
+
};
|
|
427
|
+
const reload = (agent) => {
|
|
428
|
+
return instrument("agent.reload", Effect.tryPromise({
|
|
429
|
+
try: () => agent.reload(),
|
|
430
|
+
catch: (cause) => {
|
|
431
|
+
return mapCursorError(cause, {
|
|
432
|
+
operation: "agent.reload",
|
|
433
|
+
agentId: agent.agentId
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}));
|
|
437
|
+
};
|
|
438
|
+
const close = (agent) => {
|
|
439
|
+
return instrument("agent.close", Effect.sync(() => agent.close()));
|
|
440
|
+
};
|
|
441
|
+
const dispose = (agent) => {
|
|
442
|
+
return instrument("agent.dispose", Effect.tryPromise({
|
|
443
|
+
try: () => agent[Symbol.asyncDispose](),
|
|
444
|
+
catch: (cause) => {
|
|
445
|
+
return mapCursorError(cause, {
|
|
446
|
+
operation: "agent.dispose",
|
|
447
|
+
agentId: agent.agentId
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}));
|
|
451
|
+
};
|
|
452
|
+
const scoped = (options) => {
|
|
453
|
+
return Effect.acquireRelease(create(options), (agent) => {
|
|
454
|
+
return Effect.ignore(dispose(agent));
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
return {
|
|
458
|
+
create,
|
|
459
|
+
resume,
|
|
460
|
+
prompt,
|
|
461
|
+
send,
|
|
462
|
+
reload,
|
|
463
|
+
close,
|
|
464
|
+
dispose,
|
|
465
|
+
scoped
|
|
466
|
+
};
|
|
467
|
+
}));
|
|
468
|
+
};
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/cursor-artifacts.ts
|
|
471
|
+
/**
|
|
472
|
+
* Effect-native wrappers for agent artifacts.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* const artifacts = yield* CursorArtifactService
|
|
477
|
+
* const items = yield* artifacts.listArtifacts(agent)
|
|
478
|
+
* const bytes = yield* artifacts.downloadArtifact(agent, items[0]!.path)
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* @see {@link SDKArtifact}
|
|
482
|
+
*
|
|
483
|
+
* @category services
|
|
484
|
+
*/
|
|
485
|
+
var CursorArtifactService = class CursorArtifactService extends Context.Service()("effect-cursor-sdk/cursor-artifacts/CursorArtifactService") {
|
|
486
|
+
static Live = Layer.succeed(CursorArtifactService)(CursorArtifactService.of({
|
|
487
|
+
listArtifacts: (agent) => {
|
|
488
|
+
return instrument("artifacts.list", Effect.tryPromise({
|
|
489
|
+
try: () => agent.listArtifacts(),
|
|
490
|
+
catch: (cause) => {
|
|
491
|
+
return mapCursorError(cause, {
|
|
492
|
+
operation: "artifacts.list",
|
|
493
|
+
agentId: agent.agentId
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}));
|
|
497
|
+
},
|
|
498
|
+
downloadArtifact: (agent, path) => {
|
|
499
|
+
return instrument("artifacts.download", Effect.tryPromise({
|
|
500
|
+
try: () => agent.downloadArtifact(path),
|
|
501
|
+
catch: (cause) => {
|
|
502
|
+
return mapCursorError(cause, {
|
|
503
|
+
operation: "artifacts.download",
|
|
504
|
+
agentId: agent.agentId
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
}));
|
|
510
|
+
};
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/cursor-config.ts
|
|
513
|
+
/**
|
|
514
|
+
* Branded secret material for the Cursor API key.
|
|
515
|
+
*
|
|
516
|
+
* Values are held inside {@link Redacted} on {@link CursorConfig}; this schema
|
|
517
|
+
* only brands the decoded plaintext type so it cannot be confused with other
|
|
518
|
+
* strings at the type level.
|
|
519
|
+
*/
|
|
520
|
+
const CursorApiKey = Schema.Redacted(Schema.String).pipe(Schema.brand("CursorApiKey"));
|
|
521
|
+
/**
|
|
522
|
+
* Branded model identifier from wrapper config.
|
|
523
|
+
*
|
|
524
|
+
* Populated from the `CURSOR_MODEL` environment variable when using
|
|
525
|
+
* {@link loadCursorConfig}.
|
|
526
|
+
*/
|
|
527
|
+
const CursorModelId = Schema.String.pipe(Schema.brand("CursorModelId"));
|
|
528
|
+
/**
|
|
529
|
+
* Branded local working directory for agent runs.
|
|
530
|
+
*
|
|
531
|
+
* Populated from `CURSOR_LOCAL_CWD` when using {@link loadCursorConfig}.
|
|
532
|
+
*/
|
|
533
|
+
const CursorLocalCwd = Schema.String.pipe(Schema.brand("CursorLocalCwd"));
|
|
534
|
+
/**
|
|
535
|
+
* Minimal configuration owned by this wrapper.
|
|
536
|
+
*
|
|
537
|
+
* The SDK's `AgentOptions` type remains the source of truth for complete
|
|
538
|
+
* runtime options. This schema only models environment-derived defaults.
|
|
539
|
+
* Use {@link agentOptionsFromConfig} to merge those defaults into SDK-owned
|
|
540
|
+
* {@link AgentOptions}.
|
|
541
|
+
*
|
|
542
|
+
* @remarks
|
|
543
|
+
* **Forward path:** A future major version of this package may require all
|
|
544
|
+
* agent-related options to be supplied through this module (for example
|
|
545
|
+
* {@link loadCursorConfig} plus {@link agentOptionsFromConfig}) instead of raw
|
|
546
|
+
* {@link AgentOptions} on {@link CursorAgentService}.
|
|
547
|
+
* Prefer this path for new code.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```ts
|
|
551
|
+
* const config = new CursorConfig({
|
|
552
|
+
* apiKey: Redacted.make(CursorApiKey.make(process.env.CURSOR_API_KEY!)),
|
|
553
|
+
* modelId: CursorModelId.make("composer-2"),
|
|
554
|
+
* cwd: CursorLocalCwd.make(process.cwd())
|
|
555
|
+
* })
|
|
556
|
+
* ```
|
|
557
|
+
*
|
|
558
|
+
* @see {@link cursorConfig}
|
|
559
|
+
* @see {@link loadCursorConfig}
|
|
560
|
+
*
|
|
561
|
+
* @category config
|
|
562
|
+
*/
|
|
563
|
+
var CursorConfig = class extends Schema.Class("CursorConfig")({
|
|
564
|
+
apiKey: Schema.optional(CursorApiKey),
|
|
565
|
+
modelId: Schema.optional(CursorModelId),
|
|
566
|
+
cwd: Schema.optional(CursorLocalCwd)
|
|
567
|
+
}) {};
|
|
568
|
+
/**
|
|
569
|
+
* Effect Config descriptors for common Cursor environment variables.
|
|
570
|
+
*
|
|
571
|
+
* Reads `CURSOR_API_KEY`, `CURSOR_MODEL`, and `CURSOR_LOCAL_CWD` from the active
|
|
572
|
+
* Effect {@link ConfigProvider.ConfigProvider}. All fields are optional so
|
|
573
|
+
* callers can still pass complete SDK options explicitly.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* const config = yield* loadCursorConfig
|
|
578
|
+
* const options = agentOptionsFromConfig(config)
|
|
579
|
+
* ```
|
|
580
|
+
*
|
|
581
|
+
* @see {@link Config}
|
|
582
|
+
* @see {@link loadCursorConfig}
|
|
583
|
+
*
|
|
584
|
+
* @category config
|
|
585
|
+
*/
|
|
586
|
+
const cursorConfig = Config.all({
|
|
587
|
+
apiKey: Config.redacted("CURSOR_API_KEY").pipe(Config.option),
|
|
588
|
+
modelId: Config.string("CURSOR_MODEL").pipe(Config.option),
|
|
589
|
+
cwd: Config.string("CURSOR_LOCAL_CWD").pipe(Config.option)
|
|
590
|
+
});
|
|
591
|
+
/**
|
|
592
|
+
* Build SDK `AgentOptions` from wrapper config and optional overrides.
|
|
593
|
+
*
|
|
594
|
+
* Explicit override values always win over environment-derived defaults.
|
|
595
|
+
*
|
|
596
|
+
* @param config - Wrapper-owned environment defaults.
|
|
597
|
+
* @param overrides - Complete or partial SDK-owned options to merge over the defaults.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* const options = agentOptionsFromConfig(config, {
|
|
602
|
+
* cloud: {
|
|
603
|
+
* repos: [{ url: "https://github.com/acme/app" }],
|
|
604
|
+
* autoCreatePR: true
|
|
605
|
+
* }
|
|
606
|
+
* })
|
|
607
|
+
* ```
|
|
608
|
+
*
|
|
609
|
+
* @see {@link CursorConfig}
|
|
610
|
+
* @see {@link AgentOptions}
|
|
611
|
+
*
|
|
612
|
+
* @remarks
|
|
613
|
+
* Same **forward path** notice as {@link CursorConfig}: this merge is the
|
|
614
|
+
* intended boundary for redacted keys and env defaults ahead of a possible
|
|
615
|
+
* requirement to use it for all agent entry points.
|
|
616
|
+
*
|
|
617
|
+
* @category config
|
|
618
|
+
*/
|
|
619
|
+
const agentOptionsFromConfig = (config, overrides = {}) => {
|
|
620
|
+
const model = overrides.model ?? (config.modelId ? { id: config.modelId } : void 0);
|
|
621
|
+
return {
|
|
622
|
+
...overrides,
|
|
623
|
+
apiKey: overrides.apiKey ?? (config.apiKey ? Redacted.value(config.apiKey) : void 0),
|
|
624
|
+
model,
|
|
625
|
+
local: overrides.local ?? config.cwd ? {
|
|
626
|
+
cwd: config.cwd,
|
|
627
|
+
...overrides.local
|
|
628
|
+
} : void 0
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Load environment-derived defaults as a schema-backed value.
|
|
633
|
+
*
|
|
634
|
+
* @example
|
|
635
|
+
* ```ts
|
|
636
|
+
* const config = yield* loadCursorConfig
|
|
637
|
+
* const options = agentOptionsFromConfig(config, { local: { cwd: process.cwd() } })
|
|
638
|
+
* ```
|
|
639
|
+
*
|
|
640
|
+
* @see {@link cursorConfig}
|
|
641
|
+
* @see {@link agentOptionsFromConfig}
|
|
642
|
+
*
|
|
643
|
+
* @remarks
|
|
644
|
+
* Same **forward path** notice as {@link CursorConfig}: this effect is the
|
|
645
|
+
* intended entry for redacted env defaults ahead of a possible requirement to
|
|
646
|
+
* use it (with {@link agentOptionsFromConfig}) for all agent entry points.
|
|
647
|
+
*
|
|
648
|
+
* @category config
|
|
649
|
+
*/
|
|
650
|
+
const loadCursorConfig = Effect.gen(function* () {
|
|
651
|
+
const provider = yield* ConfigProvider.ConfigProvider;
|
|
652
|
+
const raw = yield* cursorConfig.parse(provider);
|
|
653
|
+
const apiKey = Option.getOrUndefined(raw.apiKey);
|
|
654
|
+
const modelId = Option.getOrUndefined(raw.modelId);
|
|
655
|
+
const cwd = Option.getOrUndefined(raw.cwd);
|
|
656
|
+
return new CursorConfig({
|
|
657
|
+
apiKey: apiKey ? CursorApiKey.make(apiKey) : void 0,
|
|
658
|
+
modelId: modelId !== void 0 ? CursorModelId.make(modelId) : void 0,
|
|
659
|
+
cwd: cwd !== void 0 ? CursorLocalCwd.make(cwd) : void 0
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
//#endregion
|
|
663
|
+
//#region src/cursor-inspection.ts
|
|
664
|
+
/**
|
|
665
|
+
* Effect-native wrappers for SDK inspection, lifecycle, and account catalog APIs.
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* ```ts
|
|
669
|
+
* const inspection = yield* CursorInspectionService
|
|
670
|
+
* const agents = yield* inspection.listAgents({ runtime: "cloud", includeArchived: true })
|
|
671
|
+
* const models = yield* inspection.listModels()
|
|
672
|
+
* ```
|
|
673
|
+
*
|
|
674
|
+
* @see {@link CursorSdkFactory}
|
|
675
|
+
* @see {@link SDKAgentInfo}
|
|
676
|
+
*
|
|
677
|
+
* @category services
|
|
678
|
+
*/
|
|
679
|
+
var CursorInspectionService = class CursorInspectionService extends Context.Service()("effect-cursor-sdk/cursor-inspection/CursorInspectionService") {
|
|
680
|
+
static Live = Layer.effect(CursorInspectionService)(Effect.gen(function* () {
|
|
681
|
+
const sdk = yield* CursorSdkFactory;
|
|
682
|
+
return {
|
|
683
|
+
listAgents: (options) => {
|
|
684
|
+
return instrument("agent.list", Effect.tryPromise({
|
|
685
|
+
try: () => sdk.listAgents(options),
|
|
686
|
+
catch: (cause) => {
|
|
687
|
+
return mapCursorError(cause, { operation: "agent.list" });
|
|
688
|
+
}
|
|
689
|
+
}));
|
|
690
|
+
},
|
|
691
|
+
getAgent: (agentId, options) => {
|
|
692
|
+
return instrument("agent.get", Effect.tryPromise({
|
|
693
|
+
try: () => sdk.getAgent(agentId, options),
|
|
694
|
+
catch: (cause) => {
|
|
695
|
+
return mapCursorError(cause, {
|
|
696
|
+
operation: "agent.get",
|
|
697
|
+
agentId
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}));
|
|
701
|
+
},
|
|
702
|
+
archiveAgent: (agentId, options) => {
|
|
703
|
+
return instrument("agent.archive", Effect.tryPromise({
|
|
704
|
+
try: () => sdk.archiveAgent(agentId, options),
|
|
705
|
+
catch: (cause) => {
|
|
706
|
+
return mapCursorError(cause, {
|
|
707
|
+
operation: "agent.archive",
|
|
708
|
+
agentId
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}));
|
|
712
|
+
},
|
|
713
|
+
unarchiveAgent: (agentId, options) => {
|
|
714
|
+
return instrument("agent.unarchive", Effect.tryPromise({
|
|
715
|
+
try: () => sdk.unarchiveAgent(agentId, options),
|
|
716
|
+
catch: (cause) => {
|
|
717
|
+
return mapCursorError(cause, {
|
|
718
|
+
operation: "agent.unarchive",
|
|
719
|
+
agentId
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}));
|
|
723
|
+
},
|
|
724
|
+
deleteAgent: (agentId, options) => {
|
|
725
|
+
return instrument("agent.delete", Effect.tryPromise({
|
|
726
|
+
try: () => sdk.deleteAgent(agentId, options),
|
|
727
|
+
catch: (cause) => {
|
|
728
|
+
return mapCursorError(cause, {
|
|
729
|
+
operation: "agent.delete",
|
|
730
|
+
agentId
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}));
|
|
734
|
+
},
|
|
735
|
+
listRuns: (agentId, options) => {
|
|
736
|
+
return instrument("run.list", Effect.tryPromise({
|
|
737
|
+
try: () => sdk.listRuns(agentId, options),
|
|
738
|
+
catch: (cause) => {
|
|
739
|
+
return mapCursorError(cause, {
|
|
740
|
+
operation: "run.list",
|
|
741
|
+
agentId
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}));
|
|
745
|
+
},
|
|
746
|
+
getRun: (runId, options) => {
|
|
747
|
+
return instrument("run.get", Effect.tryPromise({
|
|
748
|
+
try: () => sdk.getRun(runId, options),
|
|
749
|
+
catch: (cause) => {
|
|
750
|
+
return mapCursorError(cause, {
|
|
751
|
+
operation: "run.get",
|
|
752
|
+
runId
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}));
|
|
756
|
+
},
|
|
757
|
+
listMessages: (agentId, options) => {
|
|
758
|
+
return instrument("messages.list", Effect.tryPromise({
|
|
759
|
+
try: () => sdk.listMessages(agentId, options),
|
|
760
|
+
catch: (cause) => {
|
|
761
|
+
return mapCursorError(cause, {
|
|
762
|
+
operation: "messages.list",
|
|
763
|
+
agentId
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}));
|
|
767
|
+
},
|
|
768
|
+
me: (options) => {
|
|
769
|
+
return instrument("cursor.me", Effect.tryPromise({
|
|
770
|
+
try: () => sdk.me(options),
|
|
771
|
+
catch: (cause) => {
|
|
772
|
+
return mapCursorError(cause, { operation: "cursor.me" });
|
|
773
|
+
}
|
|
774
|
+
}));
|
|
775
|
+
},
|
|
776
|
+
listModels: (options) => {
|
|
777
|
+
return instrument("cursor.models.list", Effect.tryPromise({
|
|
778
|
+
try: () => sdk.listModels(options),
|
|
779
|
+
catch: (cause) => {
|
|
780
|
+
return mapCursorError(cause, { operation: "cursor.models.list" });
|
|
781
|
+
}
|
|
782
|
+
}));
|
|
783
|
+
},
|
|
784
|
+
listRepositories: (options) => {
|
|
785
|
+
return instrument("cursor.repositories.list", Effect.tryPromise({
|
|
786
|
+
try: () => sdk.listRepositories(options),
|
|
787
|
+
catch: (cause) => {
|
|
788
|
+
return mapCursorError(cause, { operation: "cursor.repositories.list" });
|
|
789
|
+
}
|
|
790
|
+
}));
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}));
|
|
794
|
+
};
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/cursor-mock.ts
|
|
797
|
+
/**
|
|
798
|
+
* Deterministic SDK `Run` implementation for tests.
|
|
799
|
+
*
|
|
800
|
+
* @param streamEvents - Events yielded by {@link MockCursorRun.stream}.
|
|
801
|
+
* @param waitResult - Result returned by {@link MockCursorRun.wait}.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* const run = new MockCursorRun([assistantEvent], {
|
|
806
|
+
* id: "run-1",
|
|
807
|
+
* status: "finished",
|
|
808
|
+
* result: "Done"
|
|
809
|
+
* })
|
|
810
|
+
* ```
|
|
811
|
+
*
|
|
812
|
+
* @see {@link makeMockRun}
|
|
813
|
+
* @category testing
|
|
814
|
+
*/
|
|
815
|
+
var MockCursorRun = class {
|
|
816
|
+
id;
|
|
817
|
+
agentId;
|
|
818
|
+
createdAt = Date.now();
|
|
819
|
+
#status;
|
|
820
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
821
|
+
constructor(streamEvents, waitResult) {
|
|
822
|
+
this.streamEvents = streamEvents;
|
|
823
|
+
this.waitResult = waitResult;
|
|
824
|
+
this.id = waitResult.id;
|
|
825
|
+
this.agentId = streamEvents[0]?.agent_id ?? "mock-agent";
|
|
826
|
+
this.#status = waitResult.status;
|
|
827
|
+
}
|
|
828
|
+
get status() {
|
|
829
|
+
return this.#status;
|
|
830
|
+
}
|
|
831
|
+
get result() {
|
|
832
|
+
return this.waitResult.result;
|
|
833
|
+
}
|
|
834
|
+
get model() {
|
|
835
|
+
return this.waitResult.model;
|
|
836
|
+
}
|
|
837
|
+
get durationMs() {
|
|
838
|
+
return this.waitResult.durationMs;
|
|
839
|
+
}
|
|
840
|
+
get git() {
|
|
841
|
+
return this.waitResult.git;
|
|
842
|
+
}
|
|
843
|
+
supports(_operation) {
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
unsupportedReason(_operation) {}
|
|
847
|
+
async *stream() {
|
|
848
|
+
yield* this.streamEvents;
|
|
849
|
+
}
|
|
850
|
+
async conversation() {
|
|
851
|
+
return [];
|
|
852
|
+
}
|
|
853
|
+
async wait() {
|
|
854
|
+
return this.waitResult;
|
|
855
|
+
}
|
|
856
|
+
async cancel() {
|
|
857
|
+
this.#status = "cancelled";
|
|
858
|
+
for (const listener of this.#listeners) listener(this.#status);
|
|
859
|
+
}
|
|
860
|
+
onDidChangeStatus(listener) {
|
|
861
|
+
this.#listeners.add(listener);
|
|
862
|
+
return () => {
|
|
863
|
+
this.#listeners.delete(listener);
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
/**
|
|
868
|
+
* Deterministic SDK `Agent` implementation for tests.
|
|
869
|
+
*
|
|
870
|
+
* @param fixtures - Static data used by `send`, artifact methods, and metadata methods.
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```ts
|
|
874
|
+
* const agent = new MockCursorAgent({ result: { id: "run-1", status: "finished" } })
|
|
875
|
+
* const run = await agent.send("hello")
|
|
876
|
+
* ```
|
|
877
|
+
*
|
|
878
|
+
* @see {@link makeMockAgent}
|
|
879
|
+
* @see {@link CursorMockFixtures}
|
|
880
|
+
* @category testing
|
|
881
|
+
*/
|
|
882
|
+
var MockCursorAgent = class {
|
|
883
|
+
agentId;
|
|
884
|
+
runs = [];
|
|
885
|
+
closed = false;
|
|
886
|
+
constructor(fixtures = {}) {
|
|
887
|
+
this.fixtures = fixtures;
|
|
888
|
+
this.agentId = fixtures.agentId ?? "mock-agent";
|
|
889
|
+
}
|
|
890
|
+
get model() {
|
|
891
|
+
return this.fixtures.result?.model;
|
|
892
|
+
}
|
|
893
|
+
async send(_message, _options) {
|
|
894
|
+
const run = makeMockRun(this.fixtures);
|
|
895
|
+
this.runs.push(run);
|
|
896
|
+
return run;
|
|
897
|
+
}
|
|
898
|
+
close() {
|
|
899
|
+
this.closed = true;
|
|
900
|
+
}
|
|
901
|
+
async reload() {}
|
|
902
|
+
async [Symbol.asyncDispose]() {
|
|
903
|
+
this.closed = true;
|
|
904
|
+
}
|
|
905
|
+
async listArtifacts() {
|
|
906
|
+
return [...this.fixtures.artifacts ?? []];
|
|
907
|
+
}
|
|
908
|
+
async downloadArtifact(_path) {
|
|
909
|
+
return this.fixtures.artifactData ?? Buffer.from("");
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
/**
|
|
913
|
+
* Create a mock run from fixtures.
|
|
914
|
+
*
|
|
915
|
+
* @param fixtures - Optional mock run and stream data.
|
|
916
|
+
*
|
|
917
|
+
* @see {@link MockCursorRun}
|
|
918
|
+
* @category testing
|
|
919
|
+
*/
|
|
920
|
+
const makeMockRun = (fixtures = {}) => {
|
|
921
|
+
const runId = fixtures.runId ?? "mock-run";
|
|
922
|
+
return new MockCursorRun(fixtures.stream ?? [], fixtures.result ?? {
|
|
923
|
+
id: runId,
|
|
924
|
+
status: "finished",
|
|
925
|
+
result: ""
|
|
926
|
+
});
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
929
|
+
* Create a mock agent from fixtures.
|
|
930
|
+
*
|
|
931
|
+
* @param fixtures - Optional mock agent, run, artifact, and metadata data.
|
|
932
|
+
*
|
|
933
|
+
* @see {@link MockCursorAgent}
|
|
934
|
+
* @category testing
|
|
935
|
+
*/
|
|
936
|
+
const makeMockAgent = (fixtures = {}) => {
|
|
937
|
+
return new MockCursorAgent(fixtures);
|
|
938
|
+
};
|
|
939
|
+
/**
|
|
940
|
+
* Layer replacing the SDK factory with deterministic mock behavior.
|
|
941
|
+
*
|
|
942
|
+
* This is the lowest-level mock entry point. Prefer {@link mockLayer} when you
|
|
943
|
+
* want all higher-level services wired together for tests.
|
|
944
|
+
*
|
|
945
|
+
* @param fixtures - Static SDK responses returned by the factory methods.
|
|
946
|
+
*
|
|
947
|
+
* @example
|
|
948
|
+
* ```ts
|
|
949
|
+
* const layer = makeMockSdkFactoryLayer({
|
|
950
|
+
* agents: [{ agentId: "mock-agent", name: "Mock", summary: "Test", lastModified: 0 }]
|
|
951
|
+
* })
|
|
952
|
+
* ```
|
|
953
|
+
*
|
|
954
|
+
* @see {@link CursorSdkFactory}
|
|
955
|
+
* @see {@link mockLayer}
|
|
956
|
+
* @category testing
|
|
957
|
+
*/
|
|
958
|
+
const makeMockSdkFactoryLayer = (fixtures = {}) => {
|
|
959
|
+
return Layer.succeed(CursorSdkFactory, CursorSdkFactory.of({
|
|
960
|
+
create: (_options) => {
|
|
961
|
+
return Promise.resolve(makeMockAgent(fixtures));
|
|
962
|
+
},
|
|
963
|
+
resume: (_agentId, _options) => {
|
|
964
|
+
return Promise.resolve(makeMockAgent(fixtures));
|
|
965
|
+
},
|
|
966
|
+
prompt: async (_message, _options) => {
|
|
967
|
+
return fixtures.result ?? {
|
|
968
|
+
id: fixtures.runId ?? "mock-run",
|
|
969
|
+
status: "finished",
|
|
970
|
+
result: ""
|
|
971
|
+
};
|
|
972
|
+
},
|
|
973
|
+
listAgents: async (_options) => {
|
|
974
|
+
return { items: [...fixtures.agents ?? []] };
|
|
975
|
+
},
|
|
976
|
+
listRuns: async (_agentId, _options) => {
|
|
977
|
+
return { items: [makeMockRun(fixtures)] };
|
|
978
|
+
},
|
|
979
|
+
getRun: async (_runId, _options) => {
|
|
980
|
+
return makeMockRun(fixtures);
|
|
981
|
+
},
|
|
982
|
+
getAgent: async (_agentId, _options) => {
|
|
983
|
+
return fixtures.agents?.[0] ?? {
|
|
984
|
+
agentId: fixtures.agentId ?? "mock-agent",
|
|
985
|
+
name: "Mock Agent",
|
|
986
|
+
summary: "Deterministic mock agent",
|
|
987
|
+
lastModified: 0
|
|
988
|
+
};
|
|
989
|
+
},
|
|
990
|
+
archiveAgent: async (_agentId, _options) => {},
|
|
991
|
+
unarchiveAgent: async (_agentId, _options) => {},
|
|
992
|
+
deleteAgent: async (_agentId, _options) => {},
|
|
993
|
+
listMessages: async (_agentId, _options) => {
|
|
994
|
+
return [...fixtures.messages ?? []];
|
|
995
|
+
},
|
|
996
|
+
me: async (_options) => {
|
|
997
|
+
return fixtures.user ?? {
|
|
998
|
+
apiKeyName: "mock",
|
|
999
|
+
createdAt: "1970-01-01T00:00:00.000Z"
|
|
1000
|
+
};
|
|
1001
|
+
},
|
|
1002
|
+
listModels: async (_options) => {
|
|
1003
|
+
return [...fixtures.models ?? []];
|
|
1004
|
+
},
|
|
1005
|
+
listRepositories: async (_options) => {
|
|
1006
|
+
return [...fixtures.repositories ?? []];
|
|
1007
|
+
}
|
|
1008
|
+
}));
|
|
1009
|
+
};
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region src/cursor-run.ts
|
|
1012
|
+
/**
|
|
1013
|
+
* Effect-native helpers for SDK run handles.
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
* ```ts
|
|
1017
|
+
* const run = yield* agents.send(agent, "Refactor auth")
|
|
1018
|
+
* const text = yield* runs.collectText(run)
|
|
1019
|
+
* ```
|
|
1020
|
+
*
|
|
1021
|
+
* @see {@link CursorAgentService} for creating and sending runs.
|
|
1022
|
+
* @see {@link SDKMessage} for the SDK-owned stream event shape.
|
|
1023
|
+
*
|
|
1024
|
+
* @category services
|
|
1025
|
+
*/
|
|
1026
|
+
var CursorRunService = class CursorRunService extends Context.Service()("effect-cursor-sdk/cursor-run/CursorRunService") {
|
|
1027
|
+
static Live = Layer.succeed(CursorRunService)(CursorRunService.of({
|
|
1028
|
+
supports: (run, operation) => {
|
|
1029
|
+
return run.supports(operation);
|
|
1030
|
+
},
|
|
1031
|
+
unsupportedReason: (run, operation) => {
|
|
1032
|
+
return run.unsupportedReason(operation);
|
|
1033
|
+
},
|
|
1034
|
+
wait: (run) => {
|
|
1035
|
+
return instrument("run.wait", Effect.tryPromise({
|
|
1036
|
+
try: () => run.wait(),
|
|
1037
|
+
catch: (cause) => {
|
|
1038
|
+
return mapCursorError(cause, {
|
|
1039
|
+
operation: "run.wait",
|
|
1040
|
+
agentId: run.agentId,
|
|
1041
|
+
runId: run.id
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}));
|
|
1045
|
+
},
|
|
1046
|
+
cancel: (run) => {
|
|
1047
|
+
return instrument("run.cancel", Effect.tryPromise({
|
|
1048
|
+
try: () => run.cancel(),
|
|
1049
|
+
catch: (cause) => {
|
|
1050
|
+
return mapCursorError(cause, {
|
|
1051
|
+
operation: "run.cancel",
|
|
1052
|
+
agentId: run.agentId,
|
|
1053
|
+
runId: run.id
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}));
|
|
1057
|
+
},
|
|
1058
|
+
conversation: (run) => {
|
|
1059
|
+
return instrument("run.conversation", Effect.tryPromise({
|
|
1060
|
+
try: () => run.conversation(),
|
|
1061
|
+
catch: (cause) => {
|
|
1062
|
+
return mapCursorError(cause, {
|
|
1063
|
+
operation: "run.conversation",
|
|
1064
|
+
agentId: run.agentId,
|
|
1065
|
+
runId: run.id
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
}));
|
|
1069
|
+
},
|
|
1070
|
+
streamEvents: (run) => {
|
|
1071
|
+
return Stream.fromAsyncIterable(run.stream(), (cause) => {
|
|
1072
|
+
return mapCursorError(cause, {
|
|
1073
|
+
operation: "run.stream",
|
|
1074
|
+
agentId: run.agentId,
|
|
1075
|
+
runId: run.id
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
},
|
|
1079
|
+
onDidChangeStatus: (run, listener) => {
|
|
1080
|
+
return Effect.sync(() => run.onDidChangeStatus(listener));
|
|
1081
|
+
},
|
|
1082
|
+
collectText: (run) => {
|
|
1083
|
+
return Stream.fromAsyncIterable(run.stream(), (cause) => {
|
|
1084
|
+
return mapCursorError(cause, {
|
|
1085
|
+
operation: "run.stream",
|
|
1086
|
+
agentId: run.agentId,
|
|
1087
|
+
runId: run.id
|
|
1088
|
+
});
|
|
1089
|
+
}).pipe(Stream.runFold(() => "", (text, event) => {
|
|
1090
|
+
if (event.type !== "assistant") return text;
|
|
1091
|
+
return text + event.message.content.filter((block) => {
|
|
1092
|
+
return block.type === "text";
|
|
1093
|
+
}).map((block) => {
|
|
1094
|
+
return block.text;
|
|
1095
|
+
}).join("");
|
|
1096
|
+
}));
|
|
1097
|
+
}
|
|
1098
|
+
}));
|
|
1099
|
+
};
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/cursor-runtime.ts
|
|
1102
|
+
/**
|
|
1103
|
+
* Live service layer for the SDK-backed Effect wrapper.
|
|
1104
|
+
*
|
|
1105
|
+
* Provides {@link CursorAgentService}, {@link CursorRunService},
|
|
1106
|
+
* {@link CursorArtifactService}, and {@link CursorInspectionService} using
|
|
1107
|
+
* {@link CursorSdkFactory.Live}.
|
|
1108
|
+
*
|
|
1109
|
+
* @example
|
|
1110
|
+
* ```ts
|
|
1111
|
+
* const result = yield* program.pipe(Effect.provide(liveLayer))
|
|
1112
|
+
* ```
|
|
1113
|
+
*
|
|
1114
|
+
* @see {@link CursorSdkFactory}
|
|
1115
|
+
* @see {@link liveRuntime}
|
|
1116
|
+
*
|
|
1117
|
+
* @category layers
|
|
1118
|
+
*/
|
|
1119
|
+
const liveLayer = Layer.mergeAll(CursorAgentService.Live, CursorRunService.Live, CursorArtifactService.Live, CursorInspectionService.Live).pipe(Layer.provideMerge(CursorSdkFactory.Live));
|
|
1120
|
+
/**
|
|
1121
|
+
* Deterministic mock layer for tests and examples.
|
|
1122
|
+
*
|
|
1123
|
+
* @param fixtures - Static SDK responses returned by the mock factory.
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* const layer = mockLayer({
|
|
1128
|
+
* result: { id: "run-1", status: "finished", result: "ok" }
|
|
1129
|
+
* })
|
|
1130
|
+
* ```
|
|
1131
|
+
*
|
|
1132
|
+
* @see {@link CursorMockFixtures}
|
|
1133
|
+
* @see {@link makeMockSdkFactoryLayer}
|
|
1134
|
+
*
|
|
1135
|
+
* @category layers
|
|
1136
|
+
*/
|
|
1137
|
+
const mockLayer = (fixtures = {}) => {
|
|
1138
|
+
return Layer.mergeAll(CursorAgentService.Live, CursorRunService.Live, CursorArtifactService.Live, CursorInspectionService.Live).pipe(Layer.provideMerge(makeMockSdkFactoryLayer(fixtures)));
|
|
1139
|
+
};
|
|
1140
|
+
/**
|
|
1141
|
+
* Ready-made live runtime.
|
|
1142
|
+
*
|
|
1143
|
+
* Use this for small scripts that prefer `ManagedRuntime.runPromise` over
|
|
1144
|
+
* manually providing {@link liveLayer}.
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```ts
|
|
1148
|
+
* const value = await liveRuntime.runPromise(program)
|
|
1149
|
+
* ```
|
|
1150
|
+
*
|
|
1151
|
+
* @see {@link liveLayer}
|
|
1152
|
+
*
|
|
1153
|
+
* @category runtimes
|
|
1154
|
+
*/
|
|
1155
|
+
const liveRuntime = ManagedRuntime.make(liveLayer);
|
|
1156
|
+
/**
|
|
1157
|
+
* Ready-made mock runtime.
|
|
1158
|
+
*
|
|
1159
|
+
* @param fixtures - Static SDK responses returned by the mock services.
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```ts
|
|
1163
|
+
* const runtime = makeMockRuntime({ result: { id: "run-1", status: "finished" } })
|
|
1164
|
+
* const value = await runtime.runPromise(program)
|
|
1165
|
+
* ```
|
|
1166
|
+
*
|
|
1167
|
+
* @see {@link mockLayer}
|
|
1168
|
+
* @see {@link CursorMockFixtures}
|
|
1169
|
+
*
|
|
1170
|
+
* @category runtimes
|
|
1171
|
+
*/
|
|
1172
|
+
const makeMockRuntime = (fixtures = {}) => {
|
|
1173
|
+
return ManagedRuntime.make(mockLayer(fixtures));
|
|
1174
|
+
};
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region src/index.ts
|
|
1177
|
+
/**
|
|
1178
|
+
* Public package name.
|
|
1179
|
+
*
|
|
1180
|
+
* @category metadata
|
|
1181
|
+
*/
|
|
1182
|
+
const packageName = "effect-cursor-sdk";
|
|
1183
|
+
//#endregion
|
|
1184
|
+
export { AuthenticationError, ConfigurationError, CursorAgentError, CursorAgentPlatform, CursorAgentService, CursorApiKey, CursorArtifactService, CursorAuthenticationError, CursorConfig, CursorConfigurationError, CursorInspectionService, CursorIntegrationNotConnectedError, CursorLocalCwd, CursorModelId, CursorNetworkError, CursorRateLimitError, CursorRunFailedError, CursorRunService, CursorSdkFactory, CursorStreamError, CursorUnknownError, CursorUnsupportedOperationError, IntegrationNotConnectedError, MockCursorAgent, MockCursorRun, NetworkError, RateLimitError, UnknownAgentError, UnsupportedRunOperationError, agentOptionsFromConfig, createAgentPlatform, createInMemoryRunEventNotifier, createLocalRunEventNotifier, createSdkMessageRunStreamEvent, cursorConfig, cursorOperationsFailed, cursorOperationsStarted, cursorStreamEvents, decodeLocalRunStreamEvent, decodeSdkMessageRunStreamEvent, getTurnType, instrument, isTerminalLocalRunStreamEvent, liveLayer, liveRuntime, loadCursorConfig, localRunStreamEventToSdkMessage, makeMockAgent, makeMockRun, makeMockRuntime, makeMockSdkFactoryLayer, mapCursorError, mockLayer, packageName, redact, startLocalRunEventNotifierServer };
|
|
1185
|
+
|
|
1186
|
+
//# sourceMappingURL=index.js.map
|