authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 10:02:21-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
log04a931fc7992152b388faef5cda28e64a0887303
tree02088fdb26458b26615e651fd320c825f460c81f
parent75354bbd962d9fc38a1ba40ee78520822b349016

Add Cursor CLI stream event schemas and decoding tests

- define `CursorAdapter` service contract and Cursor stream-json schema types - add `CursorCliStreamEvent` decoding tests for system/thinking/tool/result/retry events - add implementation plan for Cursor provider integration Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 656 insertions(+), 0 deletions(-)

.plans/18-cursor-agent-provider.md created+310
......@@ -0,0 +1,310 @@
1# Plan: Cursor CLI (`agent`) Provider Integration
2
3## Goal
4
5Add Cursor as a first-class provider in T3 Code using Cursor CLI stream JSON mode (`agent -p --output-format stream-json`), with robust session lifecycle handling and canonical `ProviderRuntimeEvent` projection.
6
7---
8
9## 1) Exploration Findings (from live CLI runs)
10
11### 1.1 Core invocation shape
12
131. Binary is `agent` on PATH (`2026.02.27-e7d2ef6` observed).
142. Non-interactive streaming mode:
15 - `agent -p --trust --output-format stream-json --stream-partial-output ...`
163. Session continuity:
17 - `agent create-chat` returns a chat UUID.
18 - `--resume <chatId>` and `--continue` preserve history.
19 - Stream `session_id` equals chat UUID.
20
21### 1.2 Stream JSON event families observed
22
231. `system/init`
242. `user`
253. `thinking/delta`, `thinking/completed`
264. `assistant` (partial and final)
275. `tool_call/started`, `tool_call/completed`
286. `result/success` (`is_error: false` in all successful process exits)
297. Previously observed under load/rate-limit:
30 - `connection/reconnecting`, `connection/reconnected`
31 - `retry/starting`, `retry/resuming`
32 - followed by non-JSON terminal line `v: [resource_exhausted] Error` and process exit code `1`
33
34### 1.3 Tool call variants observed
35
361. `shellToolCall`
372. `readToolCall`
383. `editToolCall`
394. `grepToolCall`
405. `globToolCall`
41
42Tool completion outcomes observed:
43
441. `result.success`
452. `result.failure` (non-zero exit, stderr)
463. `result.rejected` (permission denied by Cursor approval layer)
47
48### 1.4 Critical protocol quirks
49
501. `tool_call.call_id` contains embedded newline characters (must sanitize for IDs).
512. Startup failures are plain text, not JSON:
52 - invalid model -> plain error + exit `1`
53 - invalid API key -> plain warning + exit `1`
543. `result.subtype` can be `success` even when tool calls failed/rejected inside the turn.
554. Running multiple `agent` commands concurrently can race on `~/.cursor/cli-config.json` writes (`ENOENT rename ...cli-config.json.tmp` observed).
56
57Fixtures recorded in repo:
58
591. `.tmp/cursor-observations/*.ndjson`
602. `.tmp/cursor-observations/startup-invalid-*.txt`
61
62---
63
64## 2) Integration Constraints for T3
65
661. T3 requires adapter contract support for:
67 - `startSession`, `sendTurn`, `interruptTurn`, `respondToRequest`, `readThread`, `rollbackThread`, `stopSession`, `listSessions`, `hasSession`, `stopAll`, `streamEvents`.
682. Orchestration depends on canonical runtime events (`ProviderRuntimeEvent`) only.
693. Current `ProviderCommandReactor` ignores `thread.turn-start-requested.payload.provider` and prefers existing provider/session; this must be fixed for reliable provider selection (including Cursor).
704. Cursor CLI currently has no external approval callback API; approvals are internal to CLI behavior.
71
72---
73
74## 3) Proposed Architecture
75
76## 3.1 New server components
77
781. `apps/server/src/provider/Services/CursorAdapter.ts` (service contract/tag only for DI parity)
792. `apps/server/src/provider/Layers/CursorAdapter.ts` (single implementation unit; owns Cursor process lifecycle + stream parsing + runtime projection)
803. No separate `cursorCliManager.ts` abstraction in v1.
81
82### 3.2 Session model
83
841. T3 `ProviderSessionId` = synthetic UUID managed by adapter (stable for T3 APIs).
852. Cursor chat UUID stored as:
86 - `ProviderSession.resumeCursor = { chatId: string }`
87 - `ProviderSession.threadId = ProviderThreadId(chatId)` for adapter contract compatibility.
883. Adapter keeps runtime map:
89 - `sessionId -> { chatId, cwd, model, activeChildProcess?, activeTurnId?, turnLog }`
90
91### 3.3 Command strategy
92
931. `startSession`:
94 - call `agent create-chat` unless `resumeCursor.chatId` exists.
95 - validate provider is `cursor` when provided.
962. `sendTurn`:
97 - spawn `agent -p --trust --output-format stream-json --stream-partial-output --resume <chatId>`.
98 - add `--model` when provided.
99 - map runtime mode:
100 - `approvalPolicy = never` -> include `--force`
101 - otherwise omit `--force` (commands may be rejected by CLI).
102 - use `--workspace <cwd>` when available.
1033. `interruptTurn`:
104 - terminate active child process for session (`SIGINT` then hard kill fallback).
105
106### 3.4 Effect-first implementation style (required)
107
1081. Keep adapter logic inside `CursorAdapterLive` layer constructor (no manager indirection).
1092. Use Effect process primitives first:
110 - `ChildProcessSpawner` + `ChildProcess.make` from `effect/unstable/process` for `agent` execution.
1113. Use Effect concurrency/state primitives:
112 - `Queue` for adapter event queue, `Stream.fromQueue` for `streamEvents`
113 - `Ref` / `Ref.Synchronized` for session maps and active-turn process handles
114 - `Effect.scoped` + `Effect.forkScoped` for worker fibers and cleanup
1154. Use Effect stream parsing path for stdout/stderr:
116 - decode bytes -> line buffer -> JSON parse -> typed projection
117 - keep non-JSON lines on a fallback branch that emits `runtime.error`
1185. Keep errors in typed adapter error algebra (`ProviderAdapter*Error`) via `Effect.mapError` boundaries, not ad-hoc exceptions.
119
120---
121
122## 4) Canonical Event Mapping Plan
123
124For each parsed NDJSON line, emit zero or more `ProviderRuntimeEvent`s:
125
1261. `system/init`
127 - emit `session.started` (once per adapter session lifecycle)
128 - emit `thread.started` if thread not yet emitted
1292. `assistant` partial chunks
130 - emit `message.delta` with stable per-turn synthetic `itemId`
1313. final `assistant` message
132 - emit `message.completed`
1334. `tool_call/started`
134 - emit `tool.started`
135 - map kind:
136 - shell -> `command`
137 - edit -> `file-change`
138 - read/grep/glob -> `other`
1395. `tool_call/completed`
140 - emit `tool.completed`
141 - include summary detail from nested payload (`command`, `path`, stderr fragment, rejection reason)
1426. `thinking/*`
143 - initial version: ignore for canonical events (optional later: map to `tool.*` “Thinking”)
1447. `result/success`
145 - emit `turn.completed` with `status: completed` unless unrecoverable parse/process error already set failure
1468. non-JSON stdout/stderr protocol line
147 - emit `runtime.error`
148 - emit `turn.completed` with `status: failed`
1499. `connection/*` + `retry/*` (when present)
150 - emit `tool.started/completed` with `toolKind: other`, title like “Connection retry”
151 - if final failure follows, also emit `runtime.error`
152
153Synthetic IDs:
154
1551. `turnId`: adapter-generated UUID per `sendTurn`.
1562. `itemId`: `${turnId}:assistant` for assistant stream; `${turnId}:${sanitizedCallId}` for tool calls.
1573. `call_id` sanitization: replace whitespace (including newline) with `_`.
158
159---
160
161## 5) Approval and Checkpoint Behavior (explicit limitations)
162
163### 5.1 Approvals
164
1651. Cursor CLI approval is internal; adapter cannot accept/reject mid-turn via `respondToRequest`.
1662. Plan for v1:
167 - `respondToRequest` returns `ProviderAdapterRequestError` (“Cursor CLI does not expose external approval response API”).
168 - approval-required runtime uses no `--force`, so dangerous commands become `tool_call.result.rejected`.
1693. Future:
170 - if Cursor exposes approval RPC/hooks, implement `approval.requested` + `approval.resolved` fully.
171
172### 5.2 Rollback / thread read
173
1741. Cursor CLI has no observed equivalent to `thread/read` or `thread/rollback`.
1752. Plan for v1:
176 - `readThread` returns adapter-maintained in-memory turn snapshot.
177 - `rollbackThread` returns `ProviderAdapterRequestError` unsupported.
1783. Product guard:
179 - disable checkpoint revert for Cursor threads until rollback is implemented.
180
181---
182
183## 6) Required Contract and Runtime Changes
184
185## 6.1 Contracts
186
1871. `packages/contracts/src/orchestration.ts`
188 - add `cursor` to `ProviderKind`.
1892. `packages/contracts/src/provider.ts`
190 - add `CursorProviderStartOptions` under `providerOptions.cursor`:
191 - `binaryPath?`
192 - `apiKey?` (optional; default login auth)
193 - `trust?` (default true for headless)
1943. `packages/contracts/src/model.ts`
195 - add `cursor` provider model options baseline.
196 - initial set should mirror observed supported IDs that are stable enough for UX.
1974. Update related contract tests.
198
199## 6.2 Server orchestration and registry
200
2011. Add `CursorAdapter` to provider registry and server layer wiring.
2022. Update provider-kind decoding in:
203 - `apps/server/src/provider/Layers/ProviderSessionDirectory.ts`
204 - persistence schemas that currently literal-match provider kinds.
2053. Fix provider selection in `ProviderCommandReactor`:
206 - honor `thread.turn-start-requested.payload.provider` as highest precedence.
207 - fallback to existing session provider, then default.
208
209## 6.3 Web
210
2111. Enable Cursor in provider selector (`apps/web/src/session-logic.ts`).
2122. Add Cursor label/icon treatment.
2133. Add optional Cursor settings fields if using per-user overrides (binary path/api key).
2144. Ensure thread/provider display and legacy mapping code handles `cursor`.
215
216---
217
218## 7) Implementation Phases
219
220### Phase A: Protocol-safe adapter skeleton
221
2221. Implement `CursorAdapter` service/layer with all runtime logic in `CursorAdapterLive` (no CLI manager), plus session map + event queue.
2232. Implement robust NDJSON parser with:
224 - line buffering
225 - tolerant non-JSON handling
226 - explicit process-exit mapping.
2273. Wire into registry and server layers.
228
229### Phase B: Event projection completeness
230
2311. Map assistant/tool/result events to canonical runtime events.
2322. Add synthetic ID strategy and item lifecycle consistency.
2333. Add runtime error and retry handling.
234
235### Phase C: Provider selection and UX
236
2371. Fix `ProviderCommandReactor` provider precedence.
2382. Enable Cursor provider in web UI.
2393. Add Cursor model defaults/options.
240
241### Phase D: Safeguards and unsupported surfaces
242
2431. Explicit unsupported errors for `respondToRequest` and `rollbackThread` (v1).
2442. Guard checkpoint-revert path for Cursor threads.
2453. Document limitations in UI/help text.
246
247---
248
249## 8) Test Plan
250
251Follow project rule: backend external-service integrations tested via layered fakes, not by mocking core business logic.
252
253### 8.1 Unit tests (`CursorAdapter`)
254
2551. stream JSON parse and mapping:
256 - assistant partial/final
257 - each tool type
258 - shell success/failure/rejected
259 - non-JSON line failure
2602. process lifecycle:
261 - start/send/interrupt/stop
262 - stale session errors
2633. ID sanitization:
264 - `call_id` with newline.
265
266### 8.2 Provider service/routing tests
267
2681. Registry resolves `cursor`.
2692. Session directory persistence reads/writes `cursor` provider.
2703. ProviderService fan-out/order with Cursor events.
271
272### 8.3 Orchestration tests
273
2741. `thread.turn.start` with `provider: cursor` routes to Cursor adapter.
2752. checkpoint revert on Cursor thread returns controlled failure activity.
2763. approval response command on Cursor session surfaces “unsupported approval API”.
277
278### 8.4 Smoke/integration harness
279
2801. Optional local smoke behind env flag (`CURSOR_SMOKE=1`) to run real `agent`:
281 - create session
282 - send simple turn
283 - verify message delta/completion + turn completion.
284
285---
286
287## 9) Operational Notes
288
2891. Do not run concurrent `agent` processes per workspace by default; serialize per adapter session to avoid config races.
2902. Keep native event NDJSON logs for Cursor similar to Codex adapter logs.
2913. Treat startup plain-text failures as process-level errors and surface full message.
292
293---
294
295## 10) Open Questions
296
2971. Should Cursor provider expose full model list dynamically (`agent --list-models`) or use curated static list in contracts?
2982. Should T3 keep strict approval UX parity with Codex, or accept Cursor’s current non-interactive approval limitation in v1?
2993. Is checkpoint revert required for initial Cursor release, or can it ship as explicitly unsupported?
300
301---
302
303## 11) Delivery Checklist
304
3051. Contracts updated (`ProviderKind`, provider start options, models).
3062. Cursor adapter + layer implemented and registered.
3073. Provider selection precedence fixed in `ProviderCommandReactor`.
3084. Cursor enabled in web provider picker.
3095. Tests added for adapter mapping, routing, and orchestration behavior.
3106. Lint + test suite green.
apps/server/src/provider/Services/CursorAdapter.test.ts created+132
......@@ -0,0 +1,132 @@
1import { Schema } from "effect";
2import { describe, expect, it } from "vitest";
3
4import { CursorCliStreamEvent } from "./CursorAdapter.ts";
5
6describe("CursorCliStreamEvent", () => {
7 it("decodes system/init events", () => {
8 const decoded = Schema.decodeUnknownSync(CursorCliStreamEvent)({
9 type: "system",
10 subtype: "init",
11 session_id: "sess-1",
12 apiKeySource: "login",
13 cwd: "/tmp/project",
14 model: "Auto",
15 permissionMode: "default",
16 });
17
18 expect(decoded.type).toBe("system");
19 if (decoded.type !== "system") {
20 return;
21 }
22 expect(decoded.subtype).toBe("init");
23 expect(decoded.session_id).toBe("sess-1");
24 });
25
26 it("decodes thinking delta/completed events", () => {
27 const delta = Schema.decodeUnknownSync(CursorCliStreamEvent)({
28 type: "thinking",
29 subtype: "delta",
30 text: "draft",
31 session_id: "sess-1",
32 timestamp_ms: 123,
33 });
34
35 expect(delta.type).toBe("thinking");
36 if (delta.type !== "thinking") {
37 return;
38 }
39 expect(delta.subtype).toBe("delta");
40
41 const completed = Schema.decodeUnknownSync(CursorCliStreamEvent)({
42 type: "thinking",
43 subtype: "completed",
44 session_id: "sess-1",
45 });
46
47 expect(completed.type).toBe("thinking");
48 if (completed.type !== "thinking") {
49 return;
50 }
51 expect(completed.subtype).toBe("completed");
52 });
53
54 it("decodes tool_call completed events with rejected results", () => {
55 const decoded = Schema.decodeUnknownSync(CursorCliStreamEvent)({
56 type: "tool_call",
57 subtype: "completed",
58 call_id: "tool-1\nline-2",
59 session_id: "sess-1",
60 tool_call: {
61 shellToolCall: {
62 args: {
63 command: "rm -rf /tmp/nope",
64 },
65 result: {
66 rejected: {
67 reason: "approval denied",
68 },
69 },
70 },
71 },
72 });
73
74 expect(decoded.type).toBe("tool_call");
75 if (decoded.type !== "tool_call") {
76 return;
77 }
78 expect(decoded.subtype).toBe("completed");
79 expect(decoded.call_id).toContain("\n");
80 });
81
82 it("decodes result success events with usage payload", () => {
83 const decoded = Schema.decodeUnknownSync(CursorCliStreamEvent)({
84 type: "result",
85 subtype: "success",
86 duration_ms: 100,
87 duration_api_ms: 90,
88 is_error: false,
89 result: "OK",
90 session_id: "sess-1",
91 request_id: "req-1",
92 usage: {
93 inputTokens: 10,
94 outputTokens: 5,
95 cacheReadTokens: 2,
96 cacheWriteTokens: 1,
97 },
98 });
99
100 expect(decoded.type).toBe("result");
101 if (decoded.type !== "result") {
102 return;
103 }
104 expect(decoded.subtype).toBe("success");
105 expect(decoded.is_error).toBe(false);
106 });
107
108 it("decodes connection/retry lifecycle events", () => {
109 const reconnecting = Schema.decodeUnknownSync(CursorCliStreamEvent)({
110 type: "connection",
111 subtype: "reconnecting",
112 session_id: "sess-1",
113 });
114 expect(reconnecting.type).toBe("connection");
115
116 const retry = Schema.decodeUnknownSync(CursorCliStreamEvent)({
117 type: "retry",
118 subtype: "resuming",
119 session_id: "sess-1",
120 });
121 expect(retry.type).toBe("retry");
122 });
123
124 it("rejects unsupported stream event types", () => {
125 expect(() =>
126 Schema.decodeUnknownSync(CursorCliStreamEvent)({
127 type: "bogus",
128 session_id: "sess-1",
129 }),
130 ).toThrow();
131 });
132});
apps/server/src/provider/Services/CursorAdapter.ts created+214
......@@ -0,0 +1,214 @@
1/**
2 * CursorAdapter - Cursor CLI implementation of the generic provider adapter contract.
3 *
4 * This service will own Cursor CLI (`agent`) process / stream-json semantics and emit
5 * canonical provider runtime events via the shared provider adapter contract.
6 *
7 * This file intentionally defines raw Cursor stream-json schemas up front so the future
8 * layer implementation can decode/validate NDJSON lines in a single place.
9 *
10 * @module CursorAdapter
11 */
12import { Schema, ServiceMap } from "effect";
13
14import type { ProviderAdapterError } from "../Errors.ts";
15import type { ProviderAdapterShape } from "./ProviderAdapter.ts";
16
17export const CursorCliSessionId = Schema.String.check(Schema.isNonEmpty());
18export type CursorCliSessionId = typeof CursorCliSessionId.Type;
19
20export const CursorCliTextContentPart = Schema.Struct({
21 type: Schema.Literal("text"),
22 text: Schema.String,
23});
24export type CursorCliTextContentPart = typeof CursorCliTextContentPart.Type;
25
26export const CursorCliContentPart = Schema.Union([
27 CursorCliTextContentPart,
28 Schema.Struct({
29 type: Schema.String,
30 }),
31]);
32export type CursorCliContentPart = typeof CursorCliContentPart.Type;
33
34export const CursorCliUserMessage = Schema.Struct({
35 role: Schema.Literal("user"),
36 content: Schema.Array(CursorCliContentPart),
37});
38export type CursorCliUserMessage = typeof CursorCliUserMessage.Type;
39
40export const CursorCliAssistantMessage = Schema.Struct({
41 role: Schema.Literal("assistant"),
42 content: Schema.Array(CursorCliContentPart),
43});
44export type CursorCliAssistantMessage = typeof CursorCliAssistantMessage.Type;
45
46export const CursorCliToolCallResult = Schema.Struct({
47 success: Schema.optional(Schema.Unknown),
48 failure: Schema.optional(Schema.Unknown),
49 rejected: Schema.optional(Schema.Unknown),
50});
51export type CursorCliToolCallResult = typeof CursorCliToolCallResult.Type;
52
53export const CursorCliToolCallEntry = Schema.Struct({
54 args: Schema.optional(Schema.Unknown),
55 result: Schema.optional(CursorCliToolCallResult),
56});
57export type CursorCliToolCallEntry = typeof CursorCliToolCallEntry.Type;
58
59export const CursorCliFunctionToolCall = Schema.Struct({
60 name: Schema.String,
61 arguments: Schema.String,
62 result: Schema.optional(CursorCliToolCallResult),
63});
64export type CursorCliFunctionToolCall = typeof CursorCliFunctionToolCall.Type;
65
66export const CursorCliToolCallPayload = Schema.Struct({
67 readToolCall: Schema.optional(CursorCliToolCallEntry),
68 writeToolCall: Schema.optional(CursorCliToolCallEntry),
69 editToolCall: Schema.optional(CursorCliToolCallEntry),
70 shellToolCall: Schema.optional(CursorCliToolCallEntry),
71 grepToolCall: Schema.optional(CursorCliToolCallEntry),
72 globToolCall: Schema.optional(CursorCliToolCallEntry),
73 function: Schema.optional(CursorCliFunctionToolCall),
74});
75export type CursorCliToolCallPayload = typeof CursorCliToolCallPayload.Type;
76
77export const CursorCliTokenUsage = Schema.Struct({
78 inputTokens: Schema.optional(Schema.Number),
79 outputTokens: Schema.optional(Schema.Number),
80 cacheReadTokens: Schema.optional(Schema.Number),
81 cacheWriteTokens: Schema.optional(Schema.Number),
82});
83export type CursorCliTokenUsage = typeof CursorCliTokenUsage.Type;
84
85const CursorCliTimestampMs = {
86 timestamp_ms: Schema.optional(Schema.Number),
87} as const;
88
89export const CursorCliSystemInitEvent = Schema.Struct({
90 type: Schema.Literal("system"),
91 subtype: Schema.Literal("init"),
92 session_id: CursorCliSessionId,
93 apiKeySource: Schema.optional(Schema.String),
94 cwd: Schema.optional(Schema.String),
95 model: Schema.optional(Schema.String),
96 permissionMode: Schema.optional(Schema.String),
97 ...CursorCliTimestampMs,
98});
99export type CursorCliSystemInitEvent = typeof CursorCliSystemInitEvent.Type;
100
101export const CursorCliUserEvent = Schema.Struct({
102 type: Schema.Literal("user"),
103 message: CursorCliUserMessage,
104 session_id: CursorCliSessionId,
105 ...CursorCliTimestampMs,
106});
107export type CursorCliUserEvent = typeof CursorCliUserEvent.Type;
108
109export const CursorCliAssistantEvent = Schema.Struct({
110 type: Schema.Literal("assistant"),
111 message: CursorCliAssistantMessage,
112 session_id: CursorCliSessionId,
113 ...CursorCliTimestampMs,
114});
115export type CursorCliAssistantEvent = typeof CursorCliAssistantEvent.Type;
116
117export const CursorCliThinkingDeltaEvent = Schema.Struct({
118 type: Schema.Literal("thinking"),
119 subtype: Schema.Literal("delta"),
120 text: Schema.String,
121 session_id: CursorCliSessionId,
122 ...CursorCliTimestampMs,
123});
124export type CursorCliThinkingDeltaEvent = typeof CursorCliThinkingDeltaEvent.Type;
125
126export const CursorCliThinkingCompletedEvent = Schema.Struct({
127 type: Schema.Literal("thinking"),
128 subtype: Schema.Literal("completed"),
129 session_id: CursorCliSessionId,
130 ...CursorCliTimestampMs,
131});
132export type CursorCliThinkingCompletedEvent = typeof CursorCliThinkingCompletedEvent.Type;
133
134export const CursorCliToolCallStartedEvent = Schema.Struct({
135 type: Schema.Literal("tool_call"),
136 subtype: Schema.Literal("started"),
137 call_id: Schema.String,
138 tool_call: CursorCliToolCallPayload,
139 session_id: CursorCliSessionId,
140 ...CursorCliTimestampMs,
141});
142export type CursorCliToolCallStartedEvent = typeof CursorCliToolCallStartedEvent.Type;
143
144export const CursorCliToolCallCompletedEvent = Schema.Struct({
145 type: Schema.Literal("tool_call"),
146 subtype: Schema.Literal("completed"),
147 call_id: Schema.String,
148 tool_call: CursorCliToolCallPayload,
149 session_id: CursorCliSessionId,
150 ...CursorCliTimestampMs,
151});
152export type CursorCliToolCallCompletedEvent = typeof CursorCliToolCallCompletedEvent.Type;
153
154export const CursorCliResultSuccessEvent = Schema.Struct({
155 type: Schema.Literal("result"),
156 subtype: Schema.Literal("success"),
157 duration_ms: Schema.Number,
158 duration_api_ms: Schema.optional(Schema.Number),
159 is_error: Schema.Boolean,
160 result: Schema.String,
161 session_id: CursorCliSessionId,
162 request_id: Schema.optional(Schema.String),
163 usage: Schema.optional(CursorCliTokenUsage),
164 ...CursorCliTimestampMs,
165});
166export type CursorCliResultSuccessEvent = typeof CursorCliResultSuccessEvent.Type;
167
168export const CursorCliConnectionEvent = Schema.Struct({
169 type: Schema.Literal("connection"),
170 subtype: Schema.Literals(["reconnecting", "reconnected"]),
171 session_id: Schema.optional(CursorCliSessionId),
172 ...CursorCliTimestampMs,
173});
174export type CursorCliConnectionEvent = typeof CursorCliConnectionEvent.Type;
175
176export const CursorCliRetryEvent = Schema.Struct({
177 type: Schema.Literal("retry"),
178 subtype: Schema.Literals(["starting", "resuming"]),
179 session_id: Schema.optional(CursorCliSessionId),
180 ...CursorCliTimestampMs,
181});
182export type CursorCliRetryEvent = typeof CursorCliRetryEvent.Type;
183
184export const CursorCliStreamEvent = Schema.Union([
185 CursorCliSystemInitEvent,
186 CursorCliUserEvent,
187 CursorCliAssistantEvent,
188 CursorCliThinkingDeltaEvent,
189 CursorCliThinkingCompletedEvent,
190 CursorCliToolCallStartedEvent,
191 CursorCliToolCallCompletedEvent,
192 CursorCliResultSuccessEvent,
193 CursorCliConnectionEvent,
194 CursorCliRetryEvent,
195]);
196export type CursorCliStreamEvent = typeof CursorCliStreamEvent.Type;
197
198/**
199 * CursorAdapterShape - Service API for the Cursor provider adapter.
200 *
201 * `provider` is intentionally narrowed to `"cursor"` here. Until contracts add
202 * Cursor to `ProviderKind`, this shape is defined via `Omit<...,"provider">`.
203 */
204export interface CursorAdapterShape
205 extends Omit<ProviderAdapterShape<ProviderAdapterError>, "provider"> {
206 readonly provider: "cursor";
207}
208
209/**
210 * CursorAdapter - Service tag for Cursor provider adapter operations.
211 */
212export class CursorAdapter extends ServiceMap.Service<CursorAdapter, CursorAdapterShape>()(
213 "t3/provider/Services/CursorAdapter",
214) {}