@opencode/protocol 2.0.2 → 2.0.4

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 (69) hide show
  1. package/dist/api.d.ts +4 -6
  2. package/dist/api.js +4 -8
  3. package/dist/client.d.ts +0 -2
  4. package/dist/client.js +0 -2
  5. package/dist/errors.d.ts +20 -0
  6. package/dist/errors.js +17 -0
  7. package/dist/groups/agent.d.ts +6 -5
  8. package/dist/groups/agent.js +2 -2
  9. package/dist/groups/command.d.ts +3 -3
  10. package/dist/groups/command.js +1 -1
  11. package/dist/groups/config.d.ts +5 -13
  12. package/dist/groups/config.js +11 -18
  13. package/dist/groups/credential.d.ts +3 -18
  14. package/dist/groups/credential.js +3 -10
  15. package/dist/groups/debug.d.ts +0 -8
  16. package/dist/groups/debug.js +3 -3
  17. package/dist/groups/event.d.ts +99 -74
  18. package/dist/groups/event.js +3 -3
  19. package/dist/groups/form.d.ts +6 -654
  20. package/dist/groups/form.js +6 -83
  21. package/dist/groups/fs.d.ts +6 -6
  22. package/dist/groups/fs.js +3 -3
  23. package/dist/groups/generate.d.ts +1 -1
  24. package/dist/groups/generate.js +2 -2
  25. package/dist/groups/integration.d.ts +27 -27
  26. package/dist/groups/integration.js +19 -16
  27. package/dist/groups/location.d.ts +10 -4
  28. package/dist/groups/location.js +2 -3
  29. package/dist/groups/mcp.d.ts +11 -13
  30. package/dist/groups/mcp.js +10 -10
  31. package/dist/groups/message.d.ts +710 -41
  32. package/dist/groups/message.js +26 -3
  33. package/dist/groups/migration.js +1 -1
  34. package/dist/groups/model.d.ts +12 -9
  35. package/dist/groups/model.js +2 -2
  36. package/dist/groups/permission.d.ts +9 -16
  37. package/dist/groups/permission.js +8 -20
  38. package/dist/groups/persistent-pty.js +1 -1
  39. package/dist/groups/plugin.d.ts +7 -12
  40. package/dist/groups/plugin.js +3 -13
  41. package/dist/groups/project.d.ts +1 -14
  42. package/dist/groups/project.js +2 -13
  43. package/dist/groups/provider.d.ts +8 -7
  44. package/dist/groups/provider.js +2 -2
  45. package/dist/groups/pty.d.ts +15 -12
  46. package/dist/groups/pty.js +8 -8
  47. package/dist/groups/reference.d.ts +8 -12
  48. package/dist/groups/reference.js +16 -3
  49. package/dist/groups/rpc.d.ts +0 -1
  50. package/dist/groups/rpc.js +1 -1
  51. package/dist/groups/server.d.ts +9 -1
  52. package/dist/groups/server.js +11 -5
  53. package/dist/groups/session.d.ts +1146 -787
  54. package/dist/groups/session.js +192 -93
  55. package/dist/groups/shell.d.ts +13 -43
  56. package/dist/groups/shell.js +6 -24
  57. package/dist/groups/skill.d.ts +4 -5
  58. package/dist/groups/skill.js +1 -1
  59. package/dist/groups/vcs.d.ts +17 -12
  60. package/dist/groups/vcs.js +6 -6
  61. package/dist/groups/websearch.d.ts +6 -5
  62. package/dist/groups/websearch.js +2 -2
  63. package/dist/groups/worktree.d.ts +17 -23
  64. package/dist/groups/worktree.js +21 -30
  65. package/package.json +2 -2
  66. package/dist/groups/health.d.ts +0 -15
  67. package/dist/groups/health.js +0 -20
  68. package/dist/groups/workspace.d.ts +0 -22
  69. package/dist/groups/workspace.js +0 -27
@@ -1,17 +1,15 @@
1
1
  import { SessionMessage } from "@opencode/schema/session-message";
2
- import { SessionTransfer } from "@opencode/schema/session-transfer";
3
2
  import { SessionInbox } from "@opencode/schema/session-inbox";
4
3
  import { PromptInput } from "@opencode/schema/prompt-input";
5
4
  import { Session } from "@opencode/schema/session";
6
5
  import { SessionStats } from "@opencode/schema/session-stats";
7
6
  import { InstructionEntry } from "@opencode/schema/instruction-entry";
8
7
  import { Project } from "@opencode/schema/project";
9
- import { AbsolutePath, NonNegativeInt, PositiveInt, RelativePath, statics } from "@opencode/schema/schema";
8
+ import { AbsolutePath, DateTimeUtcFromMillis, NonNegativeInt, PositiveInt, RelativePath, statics, } from "@opencode/schema/schema";
10
9
  import { Event } from "@opencode/schema/event";
11
- import { Workspace } from "@opencode/schema/workspace";
12
10
  import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect";
13
11
  import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
14
- import { ConflictError, CommandExecutionError, CommandNotFoundError, InvalidCursorError, InvalidRequestError, MessageNotFoundError, ServiceUnavailableError, SessionBusyError, SessionNotFoundError, SkillNotFoundError, UnknownError, } from "../errors.js";
12
+ import { ConflictError, CommandExecutionError, CommandNotFoundError, FormAlreadySettledError, FormInvalidAnswerError, FormNotFoundError, InvalidCursorError, InvalidRequestError, MessageNotFoundError, ServiceUnavailableError, SessionBusyError, SessionNotFoundError, SkillNotFoundError, UnknownError, } from "../errors.js";
15
13
  import { Agent } from "@opencode/schema/agent";
16
14
  import { Skill } from "@opencode/schema/skill";
17
15
  import { Model } from "@opencode/schema/model";
@@ -19,6 +17,9 @@ import { Permission } from "@opencode/schema/permission";
19
17
  import { Location } from "@opencode/schema/location";
20
18
  import { SessionEvent } from "@opencode/schema/session-event";
21
19
  import { EventLog } from "@opencode/schema/event-log";
20
+ import { FileDiff } from "@opencode/schema/file-diff";
21
+ import { Form } from "@opencode/schema/form";
22
+ import { PublicSessionMessage } from "./message.js";
22
23
  const ParentIDFilter = Schema.Union([
23
24
  Session.ID,
24
25
  Schema.Null.pipe(Schema.encodeTo(Schema.Literal("null"), {
@@ -29,7 +30,6 @@ const ParentIDFilter = Schema.Union([
29
30
  description: "Filter by parent session. Use null to return only root sessions.",
30
31
  });
31
32
  const SessionsQueryFields = {
32
- workspace: Workspace.ID.pipe(Schema.optional),
33
33
  limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
34
34
  description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
35
35
  }),
@@ -77,6 +77,34 @@ export const SessionsCursor = Schema.String.pipe(Schema.brand("SessionsCursor"),
77
77
  const SessionActive = Schema.Struct({
78
78
  type: Schema.Literal("running"),
79
79
  }).annotate({ identifier: "SessionActive" });
80
+ const PublicSessionInfo = Schema.Struct({
81
+ ...Struct.omit(Session.Info.fields, ["location"]),
82
+ location: Location.PublicRef,
83
+ }).annotate({ identifier: "Session.Info" });
84
+ const PublicSessionTransfer = Schema.Struct({
85
+ info: PublicSessionInfo,
86
+ messages: Schema.Array(PublicSessionMessage),
87
+ }).annotate({ identifier: "SessionTransfer.Data" });
88
+ const PublicMovePayload = Schema.Struct({
89
+ ...Struct.omit(SessionInbox.MovePayload.fields, ["location"]),
90
+ location: Location.PublicRef,
91
+ }).annotate({ identifier: "Session.Inbox.MovePayload" });
92
+ const PublicMove = Schema.Struct({
93
+ ...Struct.omit(SessionInbox.Move.fields, ["payload"]),
94
+ payload: PublicMovePayload,
95
+ }).annotate({ identifier: "Session.Inbox.Move" });
96
+ const PublicInboxInfo = Schema.Union([
97
+ SessionInbox.User,
98
+ SessionInbox.Synthetic,
99
+ SessionInbox.Compaction,
100
+ PublicMove,
101
+ ]).annotate({ identifier: "Session.Inbox.Info" });
102
+ const FormCreatePayload = Schema.Struct({
103
+ id: Form.ID.pipe(Schema.optional),
104
+ title: Form.Info.fields.title,
105
+ metadata: Form.Info.fields.metadata,
106
+ fields: Form.Info.fields.fields,
107
+ }).annotate({ identifier: "Form.CreatePayload" });
80
108
  const BooleanFromString = Schema.Literals(["true", "false"]).pipe(Schema.decodeTo(Schema.Boolean, {
81
109
  decode: SchemaGetter.transform((value) => value === "true"),
82
110
  encode: SchemaGetter.transform((value) => (value ? "true" : "false")),
@@ -91,11 +119,11 @@ export const SessionsQuery = Schema.Struct({
91
119
  subpath: RelativePath.pipe(Schema.optional),
92
120
  cursor: SessionsQueryCursor.pipe(Schema.optional),
93
121
  }).annotate({ identifier: "SessionsQuery" });
94
- export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make("server.session")
122
+ export const makeSessionGroup = (sessionLocationMiddleware, formLocationMiddleware) => HttpApiGroup.make("server.session")
95
123
  .add(HttpApiEndpoint.get("session.list", "/api/session", {
96
124
  query: SessionsQuery,
97
125
  success: Schema.Struct({
98
- data: Schema.Array(Session.Info),
126
+ data: Schema.Array(PublicSessionInfo),
99
127
  cursor: Schema.Struct({
100
128
  previous: SessionsCursor.pipe(Schema.optional),
101
129
  next: SessionsCursor.pipe(Schema.optional),
@@ -103,11 +131,11 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
103
131
  }).annotate({ identifier: "SessionsResponse" }),
104
132
  error: [InvalidCursorError, InvalidRequestError],
105
133
  }).annotateMerge(OpenApi.annotations({
106
- identifier: "v2.session.list",
134
+ identifier: "session.list",
107
135
  summary: "List sessions",
108
136
  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.",
109
137
  })))
110
- .add(HttpApiEndpoint.get("session.stats", "/api/session/stats", {
138
+ .add(HttpApiEndpoint.get("session.stats", "/api/experimental/session/stats", {
111
139
  query: Schema.Struct({
112
140
  from: Schema.NumberFromString.pipe(Schema.optional),
113
141
  to: Schema.NumberFromString.pipe(Schema.optional),
@@ -118,7 +146,7 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
118
146
  success: Schema.Struct({ data: SessionStats.Info }),
119
147
  error: InvalidRequestError,
120
148
  }).annotateMerge(OpenApi.annotations({
121
- identifier: "v2.session.stats",
149
+ identifier: "experimental.session.stats",
122
150
  summary: "Get session statistics",
123
151
  description: "Aggregate local session activity, usage, and tool reliability for a time range.",
124
152
  })))
@@ -128,51 +156,51 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
128
156
  title: Schema.String.pipe(Schema.optional),
129
157
  agent: Agent.ID.pipe(Schema.optional),
130
158
  model: Model.Ref.pipe(Schema.optional),
131
- location: Location.Ref.pipe(Schema.optional),
159
+ location: Location.PublicRef.pipe(Schema.optional),
132
160
  metadata: Session.Metadata.pipe(Schema.optional),
133
161
  permissions: Permission.Ruleset.pipe(Schema.optional),
134
162
  }),
135
- success: Schema.Struct({ data: Session.Info }),
163
+ success: Schema.Struct({ data: PublicSessionInfo }),
136
164
  }).annotateMerge(OpenApi.annotations({
137
- identifier: "v2.session.create",
165
+ identifier: "session.create",
138
166
  summary: "Create session",
139
167
  description: "Create a session at the requested location.",
140
168
  })))
141
- .add(HttpApiEndpoint.post("session.import", "/api/session/import", {
169
+ .add(HttpApiEndpoint.post("session.import", "/api/experimental/session/import", {
142
170
  payload: Schema.Struct({
143
- ...SessionTransfer.Data.fields,
144
- location: Location.Ref.pipe(Schema.optional),
171
+ ...PublicSessionTransfer.fields,
172
+ location: Location.PublicRef.pipe(Schema.optional),
145
173
  }),
146
- success: Schema.Struct({ data: Session.Info }),
174
+ success: Schema.Struct({ data: PublicSessionInfo }),
147
175
  error: [ConflictError, SessionNotFoundError],
148
176
  }).annotateMerge(OpenApi.annotations({
149
- identifier: "v2.session.import",
177
+ identifier: "experimental.session.import",
150
178
  summary: "Import session",
151
179
  description: "Import a projected session transcript at the requested location. If parentID is supplied, the parent session must already exist; import parents before children.",
152
180
  })))
153
- .add(HttpApiEndpoint.get("session.export", "/api/session/:sessionID/export", {
181
+ .add(HttpApiEndpoint.get("session.export", "/api/experimental/session/:sessionID/export", {
154
182
  params: { sessionID: Session.ID },
155
183
  query: Schema.Struct({ sanitize: BooleanFromString.pipe(Schema.optional) }),
156
- success: Schema.Struct({ data: SessionTransfer.Data }),
184
+ success: Schema.Struct({ data: PublicSessionTransfer }),
157
185
  error: [SessionNotFoundError, UnknownError],
158
186
  }).annotateMerge(OpenApi.annotations({
159
- identifier: "v2.session.export",
187
+ identifier: "experimental.session.export",
160
188
  summary: "Export session",
161
189
  description: "Export a complete projected session transcript.",
162
190
  })))
163
191
  .add(HttpApiEndpoint.get("session.active", "/api/session/active", {
164
192
  success: Schema.Struct({ data: Schema.Record(Session.ID, SessionActive) }),
165
193
  }).annotateMerge(OpenApi.annotations({
166
- identifier: "v2.session.active",
194
+ identifier: "session.active",
167
195
  summary: "List active sessions",
168
196
  description: "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.",
169
197
  })))
170
198
  .add(HttpApiEndpoint.get("session.get", "/api/session/:sessionID", {
171
199
  params: { sessionID: Session.ID },
172
- success: Schema.Struct({ data: Session.Info }),
200
+ success: Schema.Struct({ data: PublicSessionInfo }),
173
201
  error: SessionNotFoundError,
174
202
  }).annotateMerge(OpenApi.annotations({
175
- identifier: "v2.session.get",
203
+ identifier: "session.get",
176
204
  summary: "Get session",
177
205
  description: "Retrieve a session by ID.",
178
206
  })))
@@ -181,21 +209,21 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
181
209
  success: HttpApiSchema.NoContent,
182
210
  error: SessionNotFoundError,
183
211
  }).annotateMerge(OpenApi.annotations({
184
- identifier: "v2.session.remove",
212
+ identifier: "session.remove",
185
213
  summary: "Delete session",
186
214
  description: "Delete a session and its child sessions.",
187
215
  })))
188
216
  .add(HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", {
189
217
  params: { sessionID: Session.ID },
190
- payload: Schema.Struct({ boundary: Session.ForkRequestBoundary }),
191
- success: Schema.Struct({ data: Session.Info }),
218
+ payload: Schema.Struct({ before: SessionMessage.ID.pipe(Schema.optional) }),
219
+ success: Schema.Struct({ data: PublicSessionInfo }),
192
220
  error: [SessionNotFoundError, MessageNotFoundError, InvalidRequestError],
193
221
  })
194
222
  .middleware(sessionLocationMiddleware)
195
223
  .annotateMerge(OpenApi.annotations({
196
- identifier: "v2.session.fork",
224
+ identifier: "session.fork",
197
225
  summary: "Fork session",
198
- description: "Create a child session by copying projected history through or before a message boundary.",
226
+ description: "Create a child session by copying projected history before a message. Omit before to copy the full history.",
199
227
  })))
200
228
  .add(HttpApiEndpoint.post("session.switchAgent", "/api/session/:sessionID/agent", {
201
229
  params: { sessionID: Session.ID },
@@ -205,7 +233,7 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
205
233
  })
206
234
  .middleware(sessionLocationMiddleware)
207
235
  .annotateMerge(OpenApi.annotations({
208
- identifier: "v2.session.switchAgent",
236
+ identifier: "session.switchAgent",
209
237
  summary: "Switch session agent",
210
238
  description: "Switch the agent used by subsequent provider turns.",
211
239
  })))
@@ -217,31 +245,34 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
217
245
  })
218
246
  .middleware(sessionLocationMiddleware)
219
247
  .annotateMerge(OpenApi.annotations({
220
- identifier: "v2.session.switchModel",
248
+ identifier: "session.switchModel",
221
249
  summary: "Switch session model",
222
250
  description: "Switch the model used by subsequent provider turns.",
223
251
  })))
224
- .add(HttpApiEndpoint.post("session.rename", "/api/session/:sessionID/rename", {
252
+ .add(HttpApiEndpoint.patch("session.update", "/api/session/:sessionID", {
225
253
  params: { sessionID: Session.ID },
226
- payload: Schema.Struct({ title: Schema.String }),
254
+ payload: Schema.Struct({
255
+ title: Schema.String.pipe(Schema.optional),
256
+ permissions: Permission.Ruleset.pipe(Schema.optional),
257
+ }),
227
258
  success: HttpApiSchema.NoContent,
228
259
  error: SessionNotFoundError,
229
260
  })
230
261
  .middleware(sessionLocationMiddleware)
231
262
  .annotateMerge(OpenApi.annotations({
232
- identifier: "v2.session.rename",
233
- summary: "Rename session",
234
- description: "Update the session title.",
263
+ identifier: "session.update",
264
+ summary: "Update session",
265
+ description: "Update mutable session properties.",
235
266
  })))
236
267
  .add(HttpApiEndpoint.post("session.move", "/api/session/:sessionID/move", {
237
268
  params: { sessionID: Session.ID },
238
- payload: Schema.Struct({ ...Location.Ref.fields, delivery: SessionInbox.Delivery.pipe(Schema.optional) }),
269
+ payload: Schema.Struct({ ...Location.PublicRef.fields, delivery: SessionInbox.Delivery.pipe(Schema.optional) }),
239
270
  success: HttpApiSchema.NoContent,
240
271
  error: [SessionNotFoundError, InvalidRequestError],
241
272
  }).annotateMerge(OpenApi.annotations({
242
- identifier: "v2.session.move",
273
+ identifier: "session.move",
243
274
  summary: "Move session",
244
- description: "Move a session to another project directory, optionally transferring local changes.",
275
+ description: "Move a session to another project directory at the requested delivery boundary.",
245
276
  })))
246
277
  .add(HttpApiEndpoint.post("session.prompt", "/api/session/:sessionID/prompt", {
247
278
  params: { sessionID: Session.ID },
@@ -257,14 +288,14 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
257
288
  })
258
289
  .middleware(sessionLocationMiddleware)
259
290
  .annotateMerge(OpenApi.annotations({
260
- identifier: "v2.session.prompt",
291
+ identifier: "session.prompt",
261
292
  summary: "Send message",
262
293
  description: "Durably admit one session input and schedule agent-loop execution unless resume is false.",
263
294
  })))
264
295
  .add(HttpApiEndpoint.post("session.command", "/api/session/:sessionID/command", {
265
296
  params: { sessionID: Session.ID },
266
297
  payload: Schema.Struct({
267
- command: Schema.String,
298
+ name: Schema.String,
268
299
  ...PromptInput.Prompt.fields,
269
300
  delivery: SessionInbox.Delivery.pipe(Schema.optional),
270
301
  }),
@@ -273,15 +304,14 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
273
304
  })
274
305
  .middleware(sessionLocationMiddleware)
275
306
  .annotateMerge(OpenApi.annotations({
276
- identifier: "v2.session.command",
307
+ identifier: "session.command",
277
308
  summary: "Run command",
278
309
  description: "Execute a slash command callback immediately.",
279
310
  })))
280
- .add(HttpApiEndpoint.post("session.skill", "/api/session/:sessionID/skill", {
311
+ .add(HttpApiEndpoint.post("session.skill", "/api/experimental/session/:sessionID/skill", {
281
312
  params: { sessionID: Session.ID },
282
313
  payload: Schema.Struct({
283
- id: SessionMessage.ID.pipe(Schema.optional),
284
- skill: Skill.ID,
314
+ id: Skill.ID,
285
315
  resume: Schema.Boolean.pipe(Schema.optional),
286
316
  }),
287
317
  success: HttpApiSchema.NoContent,
@@ -289,7 +319,7 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
289
319
  })
290
320
  .middleware(sessionLocationMiddleware)
291
321
  .annotateMerge(OpenApi.annotations({
292
- identifier: "v2.session.skill",
322
+ identifier: "experimental.session.skill",
293
323
  summary: "Activate skill",
294
324
  description: "Activate a skill for a session by appending a skill message and resuming execution.",
295
325
  })))
@@ -308,14 +338,14 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
308
338
  })
309
339
  .middleware(sessionLocationMiddleware)
310
340
  .annotateMerge(OpenApi.annotations({
311
- identifier: "v2.session.synthetic",
341
+ identifier: "session.synthetic",
312
342
  summary: "Add synthetic message",
313
343
  description: "Durably admit synthetic session input and schedule execution unless resume is false.",
314
344
  })))
315
345
  .add(HttpApiEndpoint.post("session.shell", "/api/session/:sessionID/shell", {
316
346
  params: { sessionID: Session.ID },
317
347
  payload: Schema.Struct({
318
- id: Event.ID.pipe(Schema.optional),
348
+ id: SessionMessage.ID.pipe(Schema.optional),
319
349
  command: Schema.String,
320
350
  }),
321
351
  success: HttpApiSchema.NoContent,
@@ -323,7 +353,7 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
323
353
  })
324
354
  .middleware(sessionLocationMiddleware)
325
355
  .annotateMerge(OpenApi.annotations({
326
- identifier: "v2.session.shell",
356
+ identifier: "session.shell",
327
357
  summary: "Run shell command",
328
358
  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.",
329
359
  })))
@@ -338,18 +368,18 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
338
368
  })
339
369
  .middleware(sessionLocationMiddleware)
340
370
  .annotateMerge(OpenApi.annotations({
341
- identifier: "v2.session.compact",
371
+ identifier: "session.compact",
342
372
  summary: "Compact session",
343
373
  description: "Durably admit a session compaction request. Steers by default: it runs at the next step boundary instead of waiting behind queued prompts.",
344
374
  })))
345
- .add(HttpApiEndpoint.post("session.wait", "/api/session/:sessionID/wait", {
375
+ .add(HttpApiEndpoint.post("session.wait", "/api/experimental/session/:sessionID/wait", {
346
376
  params: { sessionID: Session.ID },
347
377
  success: HttpApiSchema.NoContent,
348
378
  error: [SessionNotFoundError, ServiceUnavailableError],
349
379
  })
350
380
  .middleware(sessionLocationMiddleware)
351
381
  .annotateMerge(OpenApi.annotations({
352
- identifier: "v2.session.wait",
382
+ identifier: "experimental.session.wait",
353
383
  summary: "Wait for session",
354
384
  description: "Wait for a session agent loop to become idle.",
355
385
  })))
@@ -361,81 +391,93 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
361
391
  })
362
392
  .middleware(sessionLocationMiddleware)
363
393
  .annotateMerge(OpenApi.annotations({
364
- identifier: "v2.session.revert.stage",
394
+ identifier: "session.revert.stage",
365
395
  summary: "Stage session revert",
366
396
  description: "Stage or move a reversible session boundary and optionally apply its file changes.",
367
397
  })))
368
- .add(HttpApiEndpoint.post("session.revert.clear", "/api/session/:sessionID/revert/clear", {
398
+ .add(HttpApiEndpoint.delete("session.revert.clear", "/api/session/:sessionID/revert", {
369
399
  params: { sessionID: Session.ID },
370
400
  success: HttpApiSchema.NoContent,
371
401
  error: [SessionNotFoundError, SessionBusyError, UnknownError],
372
402
  })
373
403
  .middleware(sessionLocationMiddleware)
374
- .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.clear", summary: "Clear staged revert" })))
404
+ .annotateMerge(OpenApi.annotations({ identifier: "session.revert.clear", summary: "Clear staged revert" })))
375
405
  .add(HttpApiEndpoint.post("session.revert.commit", "/api/session/:sessionID/revert/commit", {
376
406
  params: { sessionID: Session.ID },
377
407
  success: HttpApiSchema.NoContent,
378
408
  error: [SessionNotFoundError, SessionBusyError],
379
409
  })
380
410
  .middleware(sessionLocationMiddleware)
381
- .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.commit", summary: "Commit staged revert" })))
411
+ .annotateMerge(OpenApi.annotations({ identifier: "session.revert.commit", summary: "Commit staged revert" })))
382
412
  .add(HttpApiEndpoint.get("session.context", "/api/session/:sessionID/context", {
383
413
  params: { sessionID: Session.ID },
384
- success: Schema.Struct({ data: Schema.Array(SessionMessage.Info) }),
414
+ success: Schema.Struct({ data: Schema.Array(PublicSessionMessage) }),
385
415
  error: [SessionNotFoundError, UnknownError],
386
416
  }).annotateMerge(OpenApi.annotations({
387
- identifier: "v2.session.context",
417
+ identifier: "session.context",
388
418
  summary: "Get session context",
389
419
  description: "Retrieve the active context messages for a session (all messages after the last compaction).",
420
+ })))
421
+ .add(HttpApiEndpoint.get("session.diff", "/api/session/:sessionID/diff", {
422
+ params: { sessionID: Session.ID },
423
+ query: Schema.Struct({
424
+ from: Schema.optional(SessionMessage.ID).annotate({
425
+ description: "User message whose turn to diff. Defaults to the turn of the newest user message.",
426
+ }),
427
+ to: Schema.optional(SessionMessage.ID).annotate({
428
+ description: "Later user message whose turn ends the range. Defaults to the turn of `from` alone.",
429
+ }),
430
+ context: Schema.NumberFromString.pipe(Schema.decodeTo(NonNegativeInt), Schema.optional).annotate({
431
+ description: "Unchanged lines around each hunk. Omit for full-file patches.",
432
+ }),
433
+ }),
434
+ success: Schema.Struct({ data: Schema.Array(FileDiff.Info) }),
435
+ error: [InvalidRequestError, MessageNotFoundError, SessionNotFoundError, UnknownError],
436
+ }).annotateMerge(OpenApi.annotations({
437
+ identifier: "session.diff",
438
+ summary: "Diff session turns",
439
+ description: "Structured per-file diffs of the files a turn changed. A turn runs from the first prompt after the session was last idle until its next idle marker, so prompts steered in while it was busy belong to the same turn; `to` extends the range through a later turn. Compares the range's first recorded snapshot with its last; a step still running in the active session compares against the working copy. Ranges that span a location change are rejected. In sessions without any idle marker, a prompt's turn spans until the next user message.",
390
440
  })))
391
441
  .add(HttpApiEndpoint.get("session.inbox.list", "/api/session/:sessionID/inbox", {
392
442
  params: { sessionID: Session.ID },
393
- success: Schema.Struct({ data: Schema.Array(SessionInbox.Info) }),
443
+ success: Schema.Struct({ data: Schema.Array(PublicInboxInfo) }),
394
444
  error: SessionNotFoundError,
395
445
  }).annotateMerge(OpenApi.annotations({
396
- identifier: "v2.session.inbox.list",
446
+ identifier: "session.inbox.list",
397
447
  summary: "List session inbox",
398
448
  description: "List durable enqueued session work not yet delivered, ordered by enqueue sequence. Includes user, synthetic, compaction, and move items.",
399
449
  })))
400
450
  .add(HttpApiEndpoint.delete("session.inbox.cancel", "/api/session/:sessionID/inbox/:inboxID", {
401
451
  params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
402
452
  success: HttpApiSchema.NoContent,
403
- error: [ConflictError, SessionNotFoundError],
453
+ error: SessionNotFoundError,
404
454
  }).annotateMerge(OpenApi.annotations({
405
- identifier: "v2.session.inbox.cancel",
455
+ identifier: "session.inbox.cancel",
406
456
  summary: "Cancel inbox input",
407
- description: "Cancel an inbox item that has not yet been delivered.",
408
- })))
409
- .add(HttpApiEndpoint.post("session.inbox.steer", "/api/session/:sessionID/inbox/:inboxID/steer", {
410
- params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
411
- success: HttpApiSchema.NoContent,
412
- error: [ConflictError, SessionNotFoundError],
413
- }).annotateMerge(OpenApi.annotations({
414
- identifier: "v2.session.inbox.steer",
415
- summary: "Steer queued item",
416
- description: "Change a queued inbox item to steer delivery and wake session execution.",
457
+ description: "Cancel an inbox item that has not yet been delivered. Unavailable items are a no-op.",
417
458
  })))
418
- .add(HttpApiEndpoint.post("session.inbox.queue", "/api/session/:sessionID/inbox/:inboxID/queue", {
459
+ .add(HttpApiEndpoint.patch("session.inbox.update", "/api/session/:sessionID/inbox/:inboxID", {
419
460
  params: { sessionID: Session.ID, inboxID: SessionMessage.ID },
461
+ payload: Schema.Struct({ delivery: SessionInbox.Delivery }),
420
462
  success: HttpApiSchema.NoContent,
421
463
  error: [ConflictError, SessionNotFoundError],
422
464
  }).annotateMerge(OpenApi.annotations({
423
- identifier: "v2.session.inbox.queue",
424
- summary: "Queue steered item",
425
- description: "Change a steered inbox item to queued delivery.",
465
+ identifier: "session.inbox.update",
466
+ summary: "Update inbox item",
467
+ description: "Change a pending inbox item's delivery mode. Steering wakes session execution.",
426
468
  })))
427
- .add(HttpApiEndpoint.get("session.instructions.entry.list", "/api/session/:sessionID/instructions/entries", {
469
+ .add(HttpApiEndpoint.get("session.instructions.entry.list", "/api/experimental/session/:sessionID/instructions/entries", {
428
470
  params: { sessionID: Session.ID },
429
471
  success: Schema.Struct({ data: Schema.Array(InstructionEntry.Info) }),
430
472
  error: SessionNotFoundError,
431
473
  })
432
474
  .middleware(sessionLocationMiddleware)
433
475
  .annotateMerge(OpenApi.annotations({
434
- identifier: "v2.session.instructions.entry.list",
476
+ identifier: "experimental.session.instructions.entry.list",
435
477
  summary: "List instruction entries",
436
478
  description: "List API-managed instruction entries attached to the session.",
437
479
  })))
438
- .add(HttpApiEndpoint.put("session.instructions.entry.put", "/api/session/:sessionID/instructions/entries/:key", {
480
+ .add(HttpApiEndpoint.put("session.instructions.entry.put", "/api/experimental/session/:sessionID/instructions/entries/:key", {
439
481
  params: { sessionID: Session.ID, key: InstructionEntry.Key },
440
482
  payload: Schema.Struct({ value: Schema.Json }),
441
483
  success: HttpApiSchema.NoContent,
@@ -443,18 +485,18 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
443
485
  })
444
486
  .middleware(sessionLocationMiddleware)
445
487
  .annotateMerge(OpenApi.annotations({
446
- identifier: "v2.session.instructions.entry.put",
488
+ identifier: "experimental.session.instructions.entry.put",
447
489
  summary: "Put instruction entry",
448
490
  description: "Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.",
449
491
  })))
450
- .add(HttpApiEndpoint.delete("session.instructions.entry.remove", "/api/session/:sessionID/instructions/entries/:key", {
492
+ .add(HttpApiEndpoint.delete("session.instructions.entry.remove", "/api/experimental/session/:sessionID/instructions/entries/:key", {
451
493
  params: { sessionID: Session.ID, key: InstructionEntry.Key },
452
494
  success: HttpApiSchema.NoContent,
453
495
  error: SessionNotFoundError,
454
496
  })
455
497
  .middleware(sessionLocationMiddleware)
456
498
  .annotateMerge(OpenApi.annotations({
457
- identifier: "v2.session.instructions.entry.remove",
499
+ identifier: "experimental.session.instructions.entry.remove",
458
500
  summary: "Remove instruction entry",
459
501
  description: "Remove one instruction entry; the removal is announced to the model at the next step boundary.",
460
502
  })))
@@ -468,7 +510,7 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
468
510
  })
469
511
  .middleware(sessionLocationMiddleware)
470
512
  .annotateMerge(OpenApi.annotations({
471
- identifier: "v2.session.generate",
513
+ identifier: "session.generate",
472
514
  summary: "Generate text from session context",
473
515
  description: "Generate transient text from the current session context without mutating session history.",
474
516
  })))
@@ -483,13 +525,13 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
483
525
  }),
484
526
  error: SessionNotFoundError,
485
527
  }).annotateMerge(OpenApi.annotations({
486
- identifier: "v2.session.log",
528
+ identifier: "session.log",
487
529
  summary: "Read the session log",
488
530
  description: "Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.",
489
531
  })))
490
532
  .add(HttpApiEndpoint.post("session.interrupt", "/api/session/:sessionID/interrupt", {
491
533
  params: { sessionID: Session.ID },
492
- query: { continue: BooleanFromString.pipe(Schema.optional) },
534
+ query: { resume: BooleanFromString.pipe(Schema.optional) },
493
535
  success: Schema.Struct({
494
536
  interrupted: Schema.Boolean.annotate({
495
537
  description: "Whether an active execution owned by this OpenCode process was interrupted.",
@@ -499,9 +541,9 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
499
541
  })
500
542
  .middleware(sessionLocationMiddleware)
501
543
  .annotateMerge(OpenApi.annotations({
502
- identifier: "v2.session.interrupt",
544
+ identifier: "session.interrupt",
503
545
  summary: "Interrupt session execution",
504
- description: "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.",
546
+ description: "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.",
505
547
  })))
506
548
  .add(HttpApiEndpoint.post("session.background", "/api/session/:sessionID/background", {
507
549
  params: { sessionID: Session.ID },
@@ -510,18 +552,75 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
510
552
  })
511
553
  .middleware(sessionLocationMiddleware)
512
554
  .annotateMerge(OpenApi.annotations({
513
- identifier: "v2.session.background",
555
+ identifier: "session.background",
514
556
  summary: "Background blocking session tools",
515
557
  description: "Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.",
516
558
  })))
517
559
  .add(HttpApiEndpoint.get("session.message", "/api/session/:sessionID/message/:messageID", {
518
560
  params: { sessionID: Session.ID, messageID: SessionMessage.ID },
519
- success: Schema.Struct({ data: SessionMessage.Info }),
561
+ success: Schema.Struct({ data: PublicSessionMessage }),
520
562
  error: [SessionNotFoundError, MessageNotFoundError],
521
563
  }).annotateMerge(OpenApi.annotations({
522
- identifier: "v2.session.message",
564
+ identifier: "session.message.get",
523
565
  summary: "Get session message",
524
566
  description: "Retrieve one projected message owned by the Session.",
567
+ })))
568
+ .add(HttpApiEndpoint.get("session.form.list", "/api/session/:sessionID/form", {
569
+ params: { sessionID: Schema.String },
570
+ success: Schema.Struct({ data: Schema.Array(Form.Info) }),
571
+ error: SessionNotFoundError,
572
+ })
573
+ .middleware(formLocationMiddleware)
574
+ .annotateMerge(OpenApi.annotations({
575
+ identifier: "session.form.list",
576
+ summary: "List session forms",
577
+ description: "Retrieve pending forms for a session.",
578
+ })))
579
+ .add(HttpApiEndpoint.post("session.form.create", "/api/session/:sessionID/form", {
580
+ params: { sessionID: Schema.String },
581
+ payload: FormCreatePayload,
582
+ success: Schema.Struct({ data: Form.Info }),
583
+ error: [SessionNotFoundError, ConflictError, InvalidRequestError],
584
+ })
585
+ .middleware(formLocationMiddleware)
586
+ .annotateMerge(OpenApi.annotations({
587
+ identifier: "session.form.create",
588
+ summary: "Create session form",
589
+ description: "Create a form for a session.",
590
+ })))
591
+ .add(HttpApiEndpoint.get("session.form.get", "/api/session/:sessionID/form/:formID", {
592
+ params: { sessionID: Schema.String, formID: Form.ID },
593
+ success: Schema.Struct({ data: Form.Detail }),
594
+ error: [SessionNotFoundError, FormNotFoundError],
595
+ })
596
+ .middleware(formLocationMiddleware)
597
+ .annotateMerge(OpenApi.annotations({
598
+ identifier: "session.form.get",
599
+ summary: "Get session form",
600
+ description: "Retrieve a form and its current state for a session.",
601
+ })))
602
+ .add(HttpApiEndpoint.post("session.form.reply", "/api/session/:sessionID/form/:formID/reply", {
603
+ params: { sessionID: Schema.String, formID: Form.ID },
604
+ payload: Form.Reply,
605
+ success: HttpApiSchema.NoContent,
606
+ error: [SessionNotFoundError, FormAlreadySettledError, FormInvalidAnswerError, FormNotFoundError],
607
+ })
608
+ .middleware(formLocationMiddleware)
609
+ .annotateMerge(OpenApi.annotations({
610
+ identifier: "session.form.reply",
611
+ summary: "Reply to form",
612
+ description: "Submit an answer to a pending form.",
613
+ })))
614
+ .add(HttpApiEndpoint.delete("session.form.cancel", "/api/session/:sessionID/form/:formID", {
615
+ params: { sessionID: Schema.String, formID: Form.ID },
616
+ success: HttpApiSchema.NoContent,
617
+ error: [SessionNotFoundError, FormAlreadySettledError, FormNotFoundError],
618
+ })
619
+ .middleware(formLocationMiddleware)
620
+ .annotateMerge(OpenApi.annotations({
621
+ identifier: "session.form.cancel",
622
+ summary: "Cancel form",
623
+ description: "Cancel a pending form.",
525
624
  })))
526
625
  .add(HttpApiEndpoint.put("session.environment", "/api/session/:sessionID/environment", {
527
626
  params: { sessionID: Session.ID },
@@ -529,17 +628,17 @@ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make
529
628
  success: HttpApiSchema.NoContent,
530
629
  error: SessionNotFoundError,
531
630
  }).annotateMerge(OpenApi.annotations({
532
- identifier: "v2.session.environment",
631
+ identifier: "session.environment",
533
632
  summary: "Set session environment",
534
633
  description: "Replace the process environment used by local shell commands for this session.",
535
634
  })))
536
635
  .add(HttpApiEndpoint.post("session.view", "/api/session/:sessionID/view", {
537
636
  params: { sessionID: Session.ID },
538
- payload: Schema.Struct({ idle: NonNegativeInt }),
637
+ payload: Schema.Struct({ idle: DateTimeUtcFromMillis }),
539
638
  success: HttpApiSchema.NoContent,
540
639
  error: SessionNotFoundError,
541
640
  }).annotateMerge(OpenApi.annotations({
542
- identifier: "v2.session.view",
641
+ identifier: "session.view",
543
642
  summary: "View session",
544
643
  description: "Mark the idle transition observed by the viewer as viewed.",
545
644
  })))