@opencode-ai/protocol 0.0.0-dev-17471

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/api.d.ts +53 -0
  2. package/dist/api.js +73 -0
  3. package/dist/client.d.ts +61 -0
  4. package/dist/client.js +47 -0
  5. package/dist/errors.d.ts +133 -0
  6. package/dist/errors.js +105 -0
  7. package/dist/groups/agent.d.ts +194 -0
  8. package/dist/groups/agent.js +30 -0
  9. package/dist/groups/command.d.ts +63 -0
  10. package/dist/groups/command.js +20 -0
  11. package/dist/groups/config.d.ts +9 -0
  12. package/dist/groups/config.js +16 -0
  13. package/dist/groups/credential.d.ts +23 -0
  14. package/dist/groups/credential.js +29 -0
  15. package/dist/groups/debug.d.ts +17 -0
  16. package/dist/groups/debug.js +23 -0
  17. package/dist/groups/event.d.ts +12882 -0
  18. package/dist/groups/event.js +42 -0
  19. package/dist/groups/form.d.ts +781 -0
  20. package/dist/groups/form.js +97 -0
  21. package/dist/groups/fs.d.ts +35 -0
  22. package/dist/groups/fs.js +51 -0
  23. package/dist/groups/generate.d.ts +38 -0
  24. package/dist/groups/generate.js +27 -0
  25. package/dist/groups/health.d.ts +27 -0
  26. package/dist/groups/health.js +34 -0
  27. package/dist/groups/integration.d.ts +1152 -0
  28. package/dist/groups/integration.js +149 -0
  29. package/dist/groups/location.d.ts +16 -0
  30. package/dist/groups/location.js +34 -0
  31. package/dist/groups/mcp.d.ts +82 -0
  32. package/dist/groups/mcp.js +78 -0
  33. package/dist/groups/message.d.ts +592 -0
  34. package/dist/groups/message.js +37 -0
  35. package/dist/groups/migration.d.ts +28 -0
  36. package/dist/groups/migration.js +21 -0
  37. package/dist/groups/model.d.ts +218 -0
  38. package/dist/groups/model.js +33 -0
  39. package/dist/groups/permission.d.ts +153 -0
  40. package/dist/groups/permission.js +99 -0
  41. package/dist/groups/plugin.d.ts +14 -0
  42. package/dist/groups/plugin.js +20 -0
  43. package/dist/groups/project.d.ts +41 -0
  44. package/dist/groups/project.js +27 -0
  45. package/dist/groups/provider.d.ts +82 -0
  46. package/dist/groups/provider.js +34 -0
  47. package/dist/groups/pty.d.ts +145 -0
  48. package/dist/groups/pty.js +112 -0
  49. package/dist/groups/reference.d.ts +29 -0
  50. package/dist/groups/reference.js +20 -0
  51. package/dist/groups/server.d.ts +5 -0
  52. package/dist/groups/server.js +11 -0
  53. package/dist/groups/session.d.ts +9247 -0
  54. package/dist/groups/session.js +515 -0
  55. package/dist/groups/shell.d.ts +152 -0
  56. package/dist/groups/shell.js +82 -0
  57. package/dist/groups/skill.d.ts +20 -0
  58. package/dist/groups/skill.js +20 -0
  59. package/dist/groups/vcs.d.ts +46 -0
  60. package/dist/groups/vcs.js +47 -0
  61. package/dist/groups/websearch.d.ts +28 -0
  62. package/dist/groups/websearch.js +34 -0
  63. package/dist/groups/worktree.d.ts +42 -0
  64. package/dist/groups/worktree.js +54 -0
  65. package/dist/middleware/authorization.d.ts +13 -0
  66. package/dist/middleware/authorization.js +6 -0
  67. package/dist/middleware/schema-error.d.ts +13 -0
  68. package/dist/middleware/schema-error.js +4 -0
  69. package/dist/simulation.d.ts +1590 -0
  70. package/dist/simulation.js +460 -0
  71. package/package.json +44 -0
@@ -0,0 +1,515 @@
1
+ import { SessionMessage } from "@opencode-ai/schema/session-message";
2
+ import { SessionTransfer } from "@opencode-ai/schema/session-transfer";
3
+ import { SessionInbox } from "@opencode-ai/schema/session-inbox";
4
+ import { PromptInput } from "@opencode-ai/schema/prompt-input";
5
+ import { Session } from "@opencode-ai/schema/session";
6
+ import { InstructionEntry } from "@opencode-ai/schema/instruction-entry";
7
+ import { Project } from "@opencode-ai/schema/project";
8
+ import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema";
9
+ import { Event } from "@opencode-ai/schema/event";
10
+ import { Workspace } from "@opencode-ai/schema/workspace";
11
+ import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect";
12
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
13
+ import { ConflictError, CommandEvaluationError, CommandNotFoundError, InvalidCursorError, InvalidRequestError, MessageNotFoundError, ServiceUnavailableError, SessionBusyError, SessionNotFoundError, SkillNotFoundError, UnknownError, } from "../errors.js";
14
+ import { Agent } from "@opencode-ai/schema/agent";
15
+ import { Skill } from "@opencode-ai/schema/skill";
16
+ import { Model } from "@opencode-ai/schema/model";
17
+ import { Location } from "@opencode-ai/schema/location";
18
+ import { SessionEvent } from "@opencode-ai/schema/session-event";
19
+ import { EventLog } from "@opencode-ai/schema/event-log";
20
+ const ParentIDFilter = Schema.Union([
21
+ Session.ID,
22
+ Schema.Null.pipe(Schema.encodeTo(Schema.Literal("null"), {
23
+ decode: SchemaGetter.transform(() => null),
24
+ encode: SchemaGetter.transform(() => "null"),
25
+ })),
26
+ ]).annotate({
27
+ description: "Filter by parent session. Use null to return only root sessions.",
28
+ });
29
+ const SessionsQueryFields = {
30
+ workspace: Workspace.ID.pipe(Schema.optional),
31
+ limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
32
+ description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
33
+ }),
34
+ order: Schema.optional(Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")])).annotate({
35
+ description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
36
+ }),
37
+ search: Schema.optional(Schema.String),
38
+ parentID: ParentIDFilter.pipe(Schema.optional),
39
+ };
40
+ const SessionsDirectoryQuery = Schema.Struct({
41
+ ...SessionsQueryFields,
42
+ directory: AbsolutePath,
43
+ });
44
+ const SessionsProjectQuery = Schema.Struct({
45
+ ...SessionsQueryFields,
46
+ project: Project.ID,
47
+ subpath: RelativePath.pipe(Schema.optional),
48
+ });
49
+ const SessionsAllQuery = Schema.Struct(SessionsQueryFields);
50
+ const withCursor = (schema) => schema.mapFields((fields) => ({
51
+ ...Struct.omit(fields, ["limit"]),
52
+ anchor: Session.ListAnchor,
53
+ }));
54
+ const SessionsCursorInput = Schema.Union([
55
+ withCursor(SessionsDirectoryQuery),
56
+ withCursor(SessionsProjectQuery),
57
+ withCursor(SessionsAllQuery),
58
+ ]);
59
+ const SessionsCursorJson = Schema.fromJsonString(SessionsCursorInput);
60
+ const encodeSessionsCursor = Schema.encodeSync(SessionsCursorJson);
61
+ const decodeSessionsCursor = Schema.decodeUnknownEffect(SessionsCursorJson);
62
+ const invalidCursor = "Invalid cursor";
63
+ export const SessionsCursor = Schema.String.pipe(Schema.brand("SessionsCursor"), statics((schema) => {
64
+ const make = schema.make.bind(schema);
65
+ return {
66
+ make: (input) => make(Encoding.encodeBase64Url(encodeSessionsCursor(input))),
67
+ parse: (input) => Effect.suspend(() => {
68
+ const result = Encoding.decodeBase64UrlString(input);
69
+ return Result.isFailure(result)
70
+ ? Effect.fail(invalidCursor)
71
+ : decodeSessionsCursor(result.success).pipe(Effect.mapError(() => invalidCursor));
72
+ }),
73
+ };
74
+ }));
75
+ const SessionActive = Schema.Struct({
76
+ type: Schema.Literal("running"),
77
+ }).annotate({ identifier: "SessionActive" });
78
+ const BooleanFromString = Schema.Literals(["true", "false"]).pipe(Schema.decodeTo(Schema.Boolean, {
79
+ decode: SchemaGetter.transform((value) => value === "true"),
80
+ encode: SchemaGetter.transform((value) => (value ? "true" : "false")),
81
+ }));
82
+ const SessionsQueryCursor = SessionsCursor.annotate({
83
+ description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
84
+ });
85
+ export const SessionsQuery = Schema.Struct({
86
+ ...SessionsQueryFields,
87
+ directory: AbsolutePath.pipe(Schema.optional),
88
+ project: Project.ID.pipe(Schema.optional),
89
+ subpath: RelativePath.pipe(Schema.optional),
90
+ cursor: SessionsQueryCursor.pipe(Schema.optional),
91
+ }).annotate({ identifier: "SessionsQuery" });
92
+ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make("server.session")
93
+ .add(HttpApiEndpoint.get("session.list", "/api/session", {
94
+ query: SessionsQuery,
95
+ success: Schema.Struct({
96
+ data: Schema.Array(Session.Info),
97
+ cursor: Schema.Struct({
98
+ previous: SessionsCursor.pipe(Schema.optional),
99
+ next: SessionsCursor.pipe(Schema.optional),
100
+ }),
101
+ }).annotate({ identifier: "SessionsResponse" }),
102
+ error: [InvalidCursorError, InvalidRequestError],
103
+ }).annotateMerge(OpenApi.annotations({
104
+ identifier: "v2.session.list",
105
+ summary: "List sessions",
106
+ description: "Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.",
107
+ })))
108
+ .add(HttpApiEndpoint.post("session.create", "/api/session", {
109
+ payload: Schema.Struct({
110
+ id: Session.ID.pipe(Schema.optional),
111
+ title: Schema.String.pipe(Schema.optional),
112
+ agent: Agent.ID.pipe(Schema.optional),
113
+ model: Model.Ref.pipe(Schema.optional),
114
+ location: Location.Ref.pipe(Schema.optional),
115
+ }),
116
+ success: Schema.Struct({ data: Session.Info }),
117
+ }).annotateMerge(OpenApi.annotations({
118
+ identifier: "v2.session.create",
119
+ summary: "Create session",
120
+ description: "Create a session at the requested location.",
121
+ })))
122
+ .add(HttpApiEndpoint.post("session.import", "/api/session/import", {
123
+ payload: Schema.Struct({
124
+ ...SessionTransfer.Data.fields,
125
+ location: Location.Ref.pipe(Schema.optional),
126
+ }),
127
+ success: Schema.Struct({ data: Session.Info }),
128
+ error: ConflictError,
129
+ }).annotateMerge(OpenApi.annotations({
130
+ identifier: "v2.session.import",
131
+ summary: "Import session",
132
+ description: "Import a projected session transcript at the requested location.",
133
+ })))
134
+ .add(HttpApiEndpoint.get("session.export", "/api/session/:sessionID/export", {
135
+ params: { sessionID: Session.ID },
136
+ query: Schema.Struct({ sanitize: BooleanFromString.pipe(Schema.optional) }),
137
+ success: Schema.Struct({ data: SessionTransfer.Data }),
138
+ error: [SessionNotFoundError, UnknownError],
139
+ }).annotateMerge(OpenApi.annotations({
140
+ identifier: "v2.session.export",
141
+ summary: "Export session",
142
+ description: "Export a complete projected session transcript.",
143
+ })))
144
+ .add(HttpApiEndpoint.get("session.active", "/api/session/active", {
145
+ success: Schema.Struct({ data: Schema.Record(Session.ID, SessionActive) }),
146
+ }).annotateMerge(OpenApi.annotations({
147
+ identifier: "v2.session.active",
148
+ summary: "List active sessions",
149
+ description: "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.",
150
+ })))
151
+ .add(HttpApiEndpoint.get("session.get", "/api/session/:sessionID", {
152
+ params: { sessionID: Session.ID },
153
+ success: Schema.Struct({ data: Session.Info }),
154
+ error: SessionNotFoundError,
155
+ }).annotateMerge(OpenApi.annotations({
156
+ identifier: "v2.session.get",
157
+ summary: "Get session",
158
+ description: "Retrieve a session by ID.",
159
+ })))
160
+ .add(HttpApiEndpoint.delete("session.remove", "/api/session/:sessionID", {
161
+ params: { sessionID: Session.ID },
162
+ success: HttpApiSchema.NoContent,
163
+ error: SessionNotFoundError,
164
+ })
165
+ .middleware(sessionLocationMiddleware)
166
+ .annotateMerge(OpenApi.annotations({
167
+ identifier: "v2.session.remove",
168
+ summary: "Delete session",
169
+ description: "Delete a session and its child sessions.",
170
+ })))
171
+ .add(HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", {
172
+ params: { sessionID: Session.ID },
173
+ payload: Schema.Struct({ boundary: Session.ForkRequestBoundary }),
174
+ success: Schema.Struct({ data: Session.Info }),
175
+ error: [SessionNotFoundError, MessageNotFoundError, InvalidRequestError],
176
+ })
177
+ .middleware(sessionLocationMiddleware)
178
+ .annotateMerge(OpenApi.annotations({
179
+ identifier: "v2.session.fork",
180
+ summary: "Fork session",
181
+ description: "Create a child session by copying projected history through or before a message boundary.",
182
+ })))
183
+ .add(HttpApiEndpoint.post("session.switchAgent", "/api/session/:sessionID/agent", {
184
+ params: { sessionID: Session.ID },
185
+ payload: Schema.Struct({ agent: Agent.ID }),
186
+ success: HttpApiSchema.NoContent,
187
+ error: SessionNotFoundError,
188
+ })
189
+ .middleware(sessionLocationMiddleware)
190
+ .annotateMerge(OpenApi.annotations({
191
+ identifier: "v2.session.switchAgent",
192
+ summary: "Switch session agent",
193
+ description: "Switch the agent used by subsequent provider turns.",
194
+ })))
195
+ .add(HttpApiEndpoint.post("session.switchModel", "/api/session/:sessionID/model", {
196
+ params: { sessionID: Session.ID },
197
+ payload: Schema.Struct({ model: Model.Ref }),
198
+ success: HttpApiSchema.NoContent,
199
+ error: SessionNotFoundError,
200
+ })
201
+ .middleware(sessionLocationMiddleware)
202
+ .annotateMerge(OpenApi.annotations({
203
+ identifier: "v2.session.switchModel",
204
+ summary: "Switch session model",
205
+ description: "Switch the model used by subsequent provider turns.",
206
+ })))
207
+ .add(HttpApiEndpoint.post("session.rename", "/api/session/:sessionID/rename", {
208
+ params: { sessionID: Session.ID },
209
+ payload: Schema.Struct({ title: Schema.String }),
210
+ success: HttpApiSchema.NoContent,
211
+ error: SessionNotFoundError,
212
+ })
213
+ .middleware(sessionLocationMiddleware)
214
+ .annotateMerge(OpenApi.annotations({
215
+ identifier: "v2.session.rename",
216
+ summary: "Rename session",
217
+ description: "Update the session title.",
218
+ })))
219
+ .add(HttpApiEndpoint.post("session.move", "/api/session/:sessionID/move", {
220
+ params: { sessionID: Session.ID },
221
+ payload: Schema.Struct({ ...Location.Ref.fields, delivery: SessionInbox.Delivery.pipe(Schema.optional) }),
222
+ success: HttpApiSchema.NoContent,
223
+ error: [SessionNotFoundError, InvalidRequestError],
224
+ }).annotateMerge(OpenApi.annotations({
225
+ identifier: "v2.session.move",
226
+ summary: "Move session",
227
+ description: "Move a session to another project directory, optionally transferring local changes.",
228
+ })))
229
+ .add(HttpApiEndpoint.post("session.prompt", "/api/session/:sessionID/prompt", {
230
+ params: { sessionID: Session.ID },
231
+ payload: Schema.Struct({
232
+ id: SessionMessage.ID.pipe(Schema.optional),
233
+ ...PromptInput.Prompt.fields,
234
+ metadata: SessionInbox.UserPayload.fields.metadata,
235
+ delivery: SessionInbox.Delivery.pipe(Schema.optional),
236
+ resume: Schema.Boolean.pipe(Schema.optional),
237
+ }),
238
+ success: Schema.Struct({ data: SessionInbox.User }),
239
+ error: [ConflictError, InvalidRequestError, SessionNotFoundError],
240
+ })
241
+ .middleware(sessionLocationMiddleware)
242
+ .annotateMerge(OpenApi.annotations({
243
+ identifier: "v2.session.prompt",
244
+ summary: "Send message",
245
+ description: "Durably admit one session input and schedule agent-loop execution unless resume is false.",
246
+ })))
247
+ .add(HttpApiEndpoint.post("session.command", "/api/session/:sessionID/command", {
248
+ params: { sessionID: Session.ID },
249
+ payload: Schema.Struct({
250
+ id: SessionMessage.ID.pipe(Schema.optional),
251
+ command: Schema.String,
252
+ arguments: Schema.String.pipe(Schema.optional),
253
+ agent: Agent.ID.pipe(Schema.optional),
254
+ model: Model.Ref.pipe(Schema.optional),
255
+ files: PromptInput.Prompt.fields.files,
256
+ agents: PromptInput.Prompt.fields.agents,
257
+ skills: PromptInput.Prompt.fields.skills,
258
+ delivery: SessionInbox.Delivery.pipe(Schema.optional),
259
+ resume: Schema.Boolean.pipe(Schema.optional),
260
+ }),
261
+ success: Schema.Struct({ data: SessionInbox.User }),
262
+ error: [ConflictError, InvalidRequestError, SessionNotFoundError, CommandNotFoundError, CommandEvaluationError],
263
+ })
264
+ .middleware(sessionLocationMiddleware)
265
+ .annotateMerge(OpenApi.annotations({
266
+ identifier: "v2.session.command",
267
+ summary: "Run command",
268
+ description: "Resolve a slash command into prompt input, admit it durably, and schedule execution unless resume is false.",
269
+ })))
270
+ .add(HttpApiEndpoint.post("session.skill", "/api/session/:sessionID/skill", {
271
+ params: { sessionID: Session.ID },
272
+ payload: Schema.Struct({
273
+ id: SessionMessage.ID.pipe(Schema.optional),
274
+ skill: Skill.ID,
275
+ resume: Schema.Boolean.pipe(Schema.optional),
276
+ }),
277
+ success: HttpApiSchema.NoContent,
278
+ error: [SessionNotFoundError, SkillNotFoundError],
279
+ })
280
+ .middleware(sessionLocationMiddleware)
281
+ .annotateMerge(OpenApi.annotations({
282
+ identifier: "v2.session.skill",
283
+ summary: "Activate skill",
284
+ description: "Activate a skill for a session by appending a skill message and resuming execution.",
285
+ })))
286
+ .add(HttpApiEndpoint.post("session.synthetic", "/api/session/:sessionID/synthetic", {
287
+ params: { sessionID: Session.ID },
288
+ payload: Schema.Struct({
289
+ id: SessionMessage.ID.pipe(Schema.optional),
290
+ text: Schema.String,
291
+ description: Schema.String.pipe(Schema.optional),
292
+ metadata: SessionMessage.Synthetic.fields.metadata,
293
+ delivery: SessionInbox.Delivery.pipe(Schema.optional),
294
+ resume: Schema.Boolean.pipe(Schema.optional),
295
+ }),
296
+ success: Schema.Struct({ data: SessionInbox.Synthetic }),
297
+ error: [ConflictError, SessionNotFoundError],
298
+ })
299
+ .middleware(sessionLocationMiddleware)
300
+ .annotateMerge(OpenApi.annotations({
301
+ identifier: "v2.session.synthetic",
302
+ summary: "Add synthetic message",
303
+ description: "Durably admit synthetic session input and schedule execution unless resume is false.",
304
+ })))
305
+ .add(HttpApiEndpoint.post("session.shell", "/api/session/:sessionID/shell", {
306
+ params: { sessionID: Session.ID },
307
+ payload: Schema.Struct({
308
+ id: Event.ID.pipe(Schema.optional),
309
+ command: Schema.String,
310
+ }),
311
+ success: HttpApiSchema.NoContent,
312
+ error: SessionNotFoundError,
313
+ })
314
+ .middleware(sessionLocationMiddleware)
315
+ .annotateMerge(OpenApi.annotations({
316
+ identifier: "v2.session.shell",
317
+ summary: "Run shell command",
318
+ description: "Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.",
319
+ })))
320
+ .add(HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", {
321
+ params: { sessionID: Session.ID },
322
+ payload: Schema.Struct({
323
+ id: SessionMessage.ID.pipe(Schema.optional),
324
+ delivery: SessionInbox.Delivery.pipe(Schema.optional),
325
+ }),
326
+ success: Schema.Struct({ data: SessionInbox.Compaction }),
327
+ error: [ConflictError, SessionNotFoundError],
328
+ })
329
+ .middleware(sessionLocationMiddleware)
330
+ .annotateMerge(OpenApi.annotations({
331
+ identifier: "v2.session.compact",
332
+ summary: "Compact session",
333
+ description: "Queue a durable session compaction request.",
334
+ })))
335
+ .add(HttpApiEndpoint.post("session.wait", "/api/session/:sessionID/wait", {
336
+ params: { sessionID: Session.ID },
337
+ success: HttpApiSchema.NoContent,
338
+ error: [SessionNotFoundError, ServiceUnavailableError],
339
+ })
340
+ .middleware(sessionLocationMiddleware)
341
+ .annotateMerge(OpenApi.annotations({
342
+ identifier: "v2.session.wait",
343
+ summary: "Wait for session",
344
+ description: "Wait for a session agent loop to become idle.",
345
+ })))
346
+ .add(HttpApiEndpoint.post("session.revert.stage", "/api/session/:sessionID/revert/stage", {
347
+ params: { sessionID: Session.ID },
348
+ payload: Schema.Struct({ messageID: SessionMessage.ID, files: Schema.Boolean.pipe(Schema.optional) }),
349
+ success: Schema.Struct({ data: Session.Revert }),
350
+ error: [MessageNotFoundError, SessionNotFoundError, SessionBusyError, UnknownError],
351
+ })
352
+ .middleware(sessionLocationMiddleware)
353
+ .annotateMerge(OpenApi.annotations({
354
+ identifier: "v2.session.revert.stage",
355
+ summary: "Stage session revert",
356
+ description: "Stage or move a reversible session boundary and optionally apply its file changes.",
357
+ })))
358
+ .add(HttpApiEndpoint.post("session.revert.clear", "/api/session/:sessionID/revert/clear", {
359
+ params: { sessionID: Session.ID },
360
+ success: HttpApiSchema.NoContent,
361
+ error: [SessionNotFoundError, SessionBusyError, UnknownError],
362
+ })
363
+ .middleware(sessionLocationMiddleware)
364
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.clear", summary: "Clear staged revert" })))
365
+ .add(HttpApiEndpoint.post("session.revert.commit", "/api/session/:sessionID/revert/commit", {
366
+ params: { sessionID: Session.ID },
367
+ success: HttpApiSchema.NoContent,
368
+ error: [SessionNotFoundError, SessionBusyError],
369
+ })
370
+ .middleware(sessionLocationMiddleware)
371
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.commit", summary: "Commit staged revert" })))
372
+ .add(HttpApiEndpoint.get("session.context", "/api/session/:sessionID/context", {
373
+ params: { sessionID: Session.ID },
374
+ success: Schema.Struct({ data: Schema.Array(SessionMessage.Info) }),
375
+ error: [SessionNotFoundError, UnknownError],
376
+ }).annotateMerge(OpenApi.annotations({
377
+ identifier: "v2.session.context",
378
+ summary: "Get session context",
379
+ description: "Retrieve the active context messages for a session (all messages after the last compaction).",
380
+ })))
381
+ .add(HttpApiEndpoint.get("session.inbox.list", "/api/session/:sessionID/inbox", {
382
+ params: { sessionID: Session.ID },
383
+ success: Schema.Struct({ data: Schema.Array(SessionInbox.Info) }),
384
+ error: SessionNotFoundError,
385
+ }).annotateMerge(OpenApi.annotations({
386
+ identifier: "v2.session.inbox.list",
387
+ summary: "List session inbox",
388
+ description: "List durable enqueued session work not yet delivered, ordered by enqueue sequence. Includes user, synthetic, compaction, and move items.",
389
+ })))
390
+ .add(HttpApiEndpoint.delete("session.inbox.cancel", "/api/session/:sessionID/inbox/:inboxID", {
391
+ params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
392
+ success: HttpApiSchema.NoContent,
393
+ error: [ConflictError, SessionNotFoundError],
394
+ }).annotateMerge(OpenApi.annotations({
395
+ identifier: "v2.session.inbox.cancel",
396
+ summary: "Cancel inbox input",
397
+ description: "Cancel an inbox item that has not yet been delivered.",
398
+ })))
399
+ .add(HttpApiEndpoint.post("session.inbox.steer", "/api/session/:sessionID/inbox/:inboxID/steer", {
400
+ params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
401
+ success: HttpApiSchema.NoContent,
402
+ error: [ConflictError, SessionNotFoundError],
403
+ }).annotateMerge(OpenApi.annotations({
404
+ identifier: "v2.session.inbox.steer",
405
+ summary: "Steer queued item",
406
+ description: "Change a queued inbox item to steer delivery and wake session execution.",
407
+ })))
408
+ .add(HttpApiEndpoint.post("session.inbox.queue", "/api/session/:sessionID/inbox/:inboxID/queue", {
409
+ params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
410
+ success: HttpApiSchema.NoContent,
411
+ error: [ConflictError, SessionNotFoundError],
412
+ }).annotateMerge(OpenApi.annotations({
413
+ identifier: "v2.session.inbox.queue",
414
+ summary: "Queue steered item",
415
+ description: "Change a steered inbox item to queued delivery.",
416
+ })))
417
+ .add(HttpApiEndpoint.get("session.instructions.entry.list", "/api/session/:sessionID/instructions/entries", {
418
+ params: { sessionID: Session.ID },
419
+ success: Schema.Struct({ data: Schema.Array(InstructionEntry.Info) }),
420
+ error: SessionNotFoundError,
421
+ })
422
+ .middleware(sessionLocationMiddleware)
423
+ .annotateMerge(OpenApi.annotations({
424
+ identifier: "v2.session.instructions.entry.list",
425
+ summary: "List instruction entries",
426
+ description: "List API-managed instruction entries attached to the session.",
427
+ })))
428
+ .add(HttpApiEndpoint.put("session.instructions.entry.put", "/api/session/:sessionID/instructions/entries/:key", {
429
+ params: { sessionID: Session.ID, key: InstructionEntry.Key },
430
+ payload: Schema.Struct({ value: Schema.Json }),
431
+ success: HttpApiSchema.NoContent,
432
+ error: [SessionNotFoundError, InstructionEntry.ValueTooLargeError],
433
+ })
434
+ .middleware(sessionLocationMiddleware)
435
+ .annotateMerge(OpenApi.annotations({
436
+ identifier: "v2.session.instructions.entry.put",
437
+ summary: "Put instruction entry",
438
+ description: "Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.",
439
+ })))
440
+ .add(HttpApiEndpoint.delete("session.instructions.entry.remove", "/api/session/:sessionID/instructions/entries/:key", {
441
+ params: { sessionID: Session.ID, key: InstructionEntry.Key },
442
+ success: HttpApiSchema.NoContent,
443
+ error: SessionNotFoundError,
444
+ })
445
+ .middleware(sessionLocationMiddleware)
446
+ .annotateMerge(OpenApi.annotations({
447
+ identifier: "v2.session.instructions.entry.remove",
448
+ summary: "Remove instruction entry",
449
+ description: "Remove one instruction entry; the removal is announced to the model at the next step boundary.",
450
+ })))
451
+ .add(HttpApiEndpoint.post("session.generate", "/api/session/:sessionID/generate", {
452
+ params: { sessionID: Session.ID },
453
+ payload: Schema.Struct({ prompt: Schema.String }),
454
+ success: Schema.Struct({
455
+ data: Schema.Struct({ text: Schema.String }),
456
+ }).annotate({ identifier: "SessionGenerateResponse" }),
457
+ error: [SessionNotFoundError, ServiceUnavailableError],
458
+ })
459
+ .middleware(sessionLocationMiddleware)
460
+ .annotateMerge(OpenApi.annotations({
461
+ identifier: "v2.session.generate",
462
+ summary: "Generate text from session context",
463
+ description: "Generate transient text from the current session context without mutating session history.",
464
+ })))
465
+ .add(HttpApiEndpoint.get("session.log", "/api/experimental/session/:sessionID/log", {
466
+ params: { sessionID: Session.ID },
467
+ query: {
468
+ after: Schema.NumberFromString.pipe(Schema.decodeTo(Event.Seq), Schema.optional),
469
+ follow: BooleanFromString.pipe(Schema.optional),
470
+ },
471
+ success: HttpApiSchema.StreamSse({
472
+ data: Schema.Union([SessionEvent.Durable, EventLog.Synced]).annotate({ identifier: "SessionLogItem" }),
473
+ }),
474
+ error: SessionNotFoundError,
475
+ }).annotateMerge(OpenApi.annotations({
476
+ identifier: "v2.session.log",
477
+ summary: "Read the session log",
478
+ description: "Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.",
479
+ })))
480
+ .add(HttpApiEndpoint.post("session.interrupt", "/api/session/:sessionID/interrupt", {
481
+ params: { sessionID: Session.ID },
482
+ query: { continue: BooleanFromString.pipe(Schema.optional) },
483
+ success: HttpApiSchema.NoContent,
484
+ error: SessionNotFoundError,
485
+ })
486
+ .middleware(sessionLocationMiddleware)
487
+ .annotateMerge(OpenApi.annotations({
488
+ identifier: "v2.session.interrupt",
489
+ summary: "Interrupt session execution",
490
+ description: "Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op. When continue=true, execution resumes pending steering input while queued work remains parked.",
491
+ })))
492
+ .add(HttpApiEndpoint.post("session.background", "/api/session/:sessionID/background", {
493
+ params: { sessionID: Session.ID },
494
+ success: HttpApiSchema.NoContent,
495
+ error: SessionNotFoundError,
496
+ })
497
+ .middleware(sessionLocationMiddleware)
498
+ .annotateMerge(OpenApi.annotations({
499
+ identifier: "v2.session.background",
500
+ summary: "Background blocking session tools",
501
+ description: "Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.",
502
+ })))
503
+ .add(HttpApiEndpoint.get("session.message", "/api/session/:sessionID/message/:messageID", {
504
+ params: { sessionID: Session.ID, messageID: SessionMessage.ID },
505
+ success: Schema.Struct({ data: SessionMessage.Info }),
506
+ error: [SessionNotFoundError, MessageNotFoundError],
507
+ }).annotateMerge(OpenApi.annotations({
508
+ identifier: "v2.session.message",
509
+ summary: "Get session message",
510
+ description: "Retrieve one projected message owned by the Session.",
511
+ })))
512
+ .annotateMerge(OpenApi.annotations({
513
+ title: "session",
514
+ description: "Experimental session routes.",
515
+ }));
@@ -0,0 +1,152 @@
1
+ import { Location } from "@opencode-ai/schema/location";
2
+ import { Schema } from "effect";
3
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi";
4
+ import { ShellNotFoundError } from "../errors.js";
5
+ export declare const ShellGroup: HttpApiGroup.HttpApiGroup<"server.shell", HttpApiEndpoint.HttpApiEndpoint<"shell.list", "GET", "/api/shell", never, Schema.toCodecStringTree<Schema.Struct<{
6
+ readonly location: Schema.optional<Schema.Struct<{
7
+ readonly directory: Schema.optional<Schema.String>;
8
+ readonly workspace: Schema.optional<Schema.String>;
9
+ }>>;
10
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
11
+ readonly location: typeof Location.Info;
12
+ readonly data: Schema.$Array<Schema.Struct<{
13
+ readonly id: Schema.brand<Schema.String, "Shell.ID"> & {
14
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
15
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
16
+ };
17
+ readonly status: Schema.Literals<readonly ["running", "exited", "timeout", "killed"]>;
18
+ readonly command: Schema.String;
19
+ readonly cwd: Schema.String;
20
+ readonly shell: Schema.String;
21
+ readonly file: Schema.String;
22
+ readonly pid: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
23
+ readonly exit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
24
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
25
+ readonly time: Schema.Struct<{
26
+ readonly started: Schema.Finite;
27
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
28
+ }>;
29
+ }>>;
30
+ }>>, never, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.create", "POST", "/api/shell", never, Schema.toCodecStringTree<Schema.Struct<{
31
+ readonly location: Schema.optional<Schema.Struct<{
32
+ readonly directory: Schema.optional<Schema.String>;
33
+ readonly workspace: Schema.optional<Schema.String>;
34
+ }>>;
35
+ }>>, Schema.toCodecJson<Schema.Struct<{
36
+ readonly command: Schema.String;
37
+ readonly cwd: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
38
+ readonly timeout: Schema.Int;
39
+ readonly metadata: Schema.decodeTo<Schema.optional<Schema.toType<Schema.$Record<Schema.String, Schema.Unknown>>>, Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>, never, never>;
40
+ }>>, never, Schema.toCodecJson<Schema.Struct<{
41
+ readonly location: typeof Location.Info;
42
+ readonly data: Schema.Struct<{
43
+ readonly id: Schema.brand<Schema.String, "Shell.ID"> & {
44
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
45
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
46
+ };
47
+ readonly status: Schema.Literals<readonly ["running", "exited", "timeout", "killed"]>;
48
+ readonly command: Schema.String;
49
+ readonly cwd: Schema.String;
50
+ readonly shell: Schema.String;
51
+ readonly file: Schema.String;
52
+ readonly pid: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
53
+ readonly exit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
54
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
55
+ readonly time: Schema.Struct<{
56
+ readonly started: Schema.Finite;
57
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
58
+ }>;
59
+ }>;
60
+ }>>, never, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.get", "GET", "/api/shell/:id", Schema.toCodecStringTree<Schema.Struct<{
61
+ id: Schema.brand<Schema.String, "Shell.ID"> & {
62
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
63
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
64
+ };
65
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
66
+ readonly location: Schema.optional<Schema.Struct<{
67
+ readonly directory: Schema.optional<Schema.String>;
68
+ readonly workspace: Schema.optional<Schema.String>;
69
+ }>>;
70
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
71
+ readonly location: typeof Location.Info;
72
+ readonly data: Schema.Struct<{
73
+ readonly id: Schema.brand<Schema.String, "Shell.ID"> & {
74
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
75
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
76
+ };
77
+ readonly status: Schema.Literals<readonly ["running", "exited", "timeout", "killed"]>;
78
+ readonly command: Schema.String;
79
+ readonly cwd: Schema.String;
80
+ readonly shell: Schema.String;
81
+ readonly file: Schema.String;
82
+ readonly pid: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
83
+ readonly exit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
84
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
85
+ readonly time: Schema.Struct<{
86
+ readonly started: Schema.Finite;
87
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
88
+ }>;
89
+ }>;
90
+ }>>, Schema.toCodecJson<typeof ShellNotFoundError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.timeout", "PATCH", "/api/shell/:id/timeout", Schema.toCodecStringTree<Schema.Struct<{
91
+ id: Schema.brand<Schema.String, "Shell.ID"> & {
92
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
93
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
94
+ };
95
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
96
+ readonly location: Schema.optional<Schema.Struct<{
97
+ readonly directory: Schema.optional<Schema.String>;
98
+ readonly workspace: Schema.optional<Schema.String>;
99
+ }>>;
100
+ }>>, Schema.toCodecJson<Schema.Struct<{
101
+ readonly timeout: Schema.Int;
102
+ }>>, never, Schema.toCodecJson<Schema.Struct<{
103
+ readonly location: typeof Location.Info;
104
+ readonly data: Schema.Struct<{
105
+ readonly id: Schema.brand<Schema.String, "Shell.ID"> & {
106
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
107
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
108
+ };
109
+ readonly status: Schema.Literals<readonly ["running", "exited", "timeout", "killed"]>;
110
+ readonly command: Schema.String;
111
+ readonly cwd: Schema.String;
112
+ readonly shell: Schema.String;
113
+ readonly file: Schema.String;
114
+ readonly pid: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
115
+ readonly exit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
116
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
117
+ readonly time: Schema.Struct<{
118
+ readonly started: Schema.Finite;
119
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Finite>>, Schema.optionalKey<Schema.Finite>, never, never>;
120
+ }>;
121
+ }>;
122
+ }>>, Schema.toCodecJson<typeof ShellNotFoundError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.output", "GET", "/api/shell/:id/output", Schema.toCodecStringTree<Schema.Struct<{
123
+ id: Schema.brand<Schema.String, "Shell.ID"> & {
124
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
125
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
126
+ };
127
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
128
+ readonly cursor: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
129
+ readonly limit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
130
+ readonly location: Schema.optional<Schema.Struct<{
131
+ readonly directory: Schema.optional<Schema.String>;
132
+ readonly workspace: Schema.optional<Schema.String>;
133
+ }>>;
134
+ }>>, never, never, Schema.toCodecJson<Schema.Struct<{
135
+ readonly location: typeof Location.Info;
136
+ readonly data: Schema.Struct<{
137
+ readonly output: Schema.String;
138
+ readonly cursor: Schema.Int;
139
+ readonly size: Schema.Int;
140
+ readonly truncated: Schema.Boolean;
141
+ }>;
142
+ }>>, Schema.toCodecJson<typeof ShellNotFoundError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.remove", "DELETE", "/api/shell/:id", Schema.toCodecStringTree<Schema.Struct<{
143
+ id: Schema.brand<Schema.String, "Shell.ID"> & {
144
+ create: () => string & import("effect/Brand").Brand<"Shell.ID">;
145
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Shell.ID">;
146
+ };
147
+ }>>, Schema.toCodecStringTree<Schema.Struct<{
148
+ readonly location: Schema.optional<Schema.Struct<{
149
+ readonly directory: Schema.optional<Schema.String>;
150
+ readonly workspace: Schema.optional<Schema.String>;
151
+ }>>;
152
+ }>>, never, never, Schema.toCodecJson<HttpApiSchema.NoContent>, Schema.toCodecJson<typeof ShellNotFoundError>, never, never>, false>;