@opencode-ai/protocol 0.0.0-next-14661

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 (57) hide show
  1. package/dist/api.d.ts +44 -0
  2. package/dist/api.js +61 -0
  3. package/dist/client.d.ts +69 -0
  4. package/dist/client.js +52 -0
  5. package/dist/errors.d.ts +97 -0
  6. package/dist/errors.js +74 -0
  7. package/dist/groups/agent.d.ts +91 -0
  8. package/dist/groups/agent.js +15 -0
  9. package/dist/groups/command.d.ts +51 -0
  10. package/dist/groups/command.js +20 -0
  11. package/dist/groups/credential.d.ts +23 -0
  12. package/dist/groups/credential.js +28 -0
  13. package/dist/groups/event.d.ts +16086 -0
  14. package/dist/groups/event.js +42 -0
  15. package/dist/groups/fs.d.ts +35 -0
  16. package/dist/groups/fs.js +51 -0
  17. package/dist/groups/generate.d.ts +32 -0
  18. package/dist/groups/generate.js +27 -0
  19. package/dist/groups/health.d.ts +5 -0
  20. package/dist/groups/health.js +9 -0
  21. package/dist/groups/integration.d.ts +105 -0
  22. package/dist/groups/integration.js +98 -0
  23. package/dist/groups/location.d.ts +16 -0
  24. package/dist/groups/location.js +32 -0
  25. package/dist/groups/mcp.d.ts +30 -0
  26. package/dist/groups/mcp.js +17 -0
  27. package/dist/groups/message.d.ts +409 -0
  28. package/dist/groups/message.js +37 -0
  29. package/dist/groups/model.d.ts +151 -0
  30. package/dist/groups/model.js +22 -0
  31. package/dist/groups/permission.d.ts +153 -0
  32. package/dist/groups/permission.js +99 -0
  33. package/dist/groups/plugin.d.ts +14 -0
  34. package/dist/groups/plugin.js +20 -0
  35. package/dist/groups/project-copy.d.ts +49 -0
  36. package/dist/groups/project-copy.js +44 -0
  37. package/dist/groups/project.d.ts +24 -0
  38. package/dist/groups/project.js +30 -0
  39. package/dist/groups/provider.d.ts +156 -0
  40. package/dist/groups/provider.js +34 -0
  41. package/dist/groups/pty.d.ts +145 -0
  42. package/dist/groups/pty.js +111 -0
  43. package/dist/groups/question.d.ts +92 -0
  44. package/dist/groups/question.js +56 -0
  45. package/dist/groups/reference.d.ts +13 -0
  46. package/dist/groups/reference.js +20 -0
  47. package/dist/groups/session.d.ts +8020 -0
  48. package/dist/groups/session.js +331 -0
  49. package/dist/groups/shell.d.ts +120 -0
  50. package/dist/groups/shell.js +65 -0
  51. package/dist/groups/skill.d.ts +19 -0
  52. package/dist/groups/skill.js +20 -0
  53. package/dist/middleware/authorization.d.ts +13 -0
  54. package/dist/middleware/authorization.js +6 -0
  55. package/dist/middleware/schema-error.d.ts +13 -0
  56. package/dist/middleware/schema-error.js +4 -0
  57. package/package.json +38 -0
@@ -0,0 +1,331 @@
1
+ import { SessionMessage } from "@opencode-ai/schema/session-message";
2
+ import { SessionInput } from "@opencode-ai/schema/session-input";
3
+ import { PromptInput } from "@opencode-ai/schema/prompt-input";
4
+ import { Session } from "@opencode-ai/schema/session";
5
+ import { Project } from "@opencode-ai/schema/project";
6
+ import { AbsolutePath, NonNegativeInt, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema";
7
+ import { Workspace } from "@opencode-ai/schema/workspace";
8
+ import { Context, Effect, Encoding, Result, Schema, Struct } from "effect";
9
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
10
+ import { ConflictError, InvalidCursorError, InvalidRequestError, MessageNotFoundError, ServiceUnavailableError, SessionBusyError, SessionNotFoundError, SkillNotFoundError, UnknownError, } from "../errors.js";
11
+ import { Agent } from "@opencode-ai/schema/agent";
12
+ import { Model } from "@opencode-ai/schema/model";
13
+ import { Location } from "@opencode-ai/schema/location";
14
+ import { Revert } from "@opencode-ai/schema/revert";
15
+ import { SessionEvent } from "@opencode-ai/schema/session-event";
16
+ const SessionsQueryFields = {
17
+ workspace: Workspace.ID.pipe(Schema.optional),
18
+ limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({
19
+ description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.",
20
+ }),
21
+ order: Schema.optional(Schema.Union([Schema.Literal("asc"), Schema.Literal("desc")])).annotate({
22
+ description: "Session order for the first page. Use desc for newest first or asc for oldest first.",
23
+ }),
24
+ search: Schema.optional(Schema.String),
25
+ };
26
+ const SessionsDirectoryQuery = Schema.Struct({
27
+ ...SessionsQueryFields,
28
+ directory: AbsolutePath,
29
+ });
30
+ const SessionsProjectQuery = Schema.Struct({
31
+ ...SessionsQueryFields,
32
+ project: Project.ID,
33
+ subpath: RelativePath.pipe(Schema.optional),
34
+ });
35
+ const SessionsAllQuery = Schema.Struct(SessionsQueryFields);
36
+ const withCursor = (schema) => schema.mapFields((fields) => ({
37
+ ...Struct.omit(fields, ["limit"]),
38
+ anchor: Session.ListAnchor,
39
+ }));
40
+ const SessionsCursorInput = Schema.Union([
41
+ withCursor(SessionsDirectoryQuery),
42
+ withCursor(SessionsProjectQuery),
43
+ withCursor(SessionsAllQuery),
44
+ ]);
45
+ const SessionsCursorJson = Schema.fromJsonString(SessionsCursorInput);
46
+ const encodeSessionsCursor = Schema.encodeSync(SessionsCursorJson);
47
+ const decodeSessionsCursor = Schema.decodeUnknownEffect(SessionsCursorJson);
48
+ const invalidCursor = "Invalid cursor";
49
+ export const SessionsCursor = Schema.String.pipe(Schema.brand("SessionsCursor"), statics((schema) => {
50
+ const make = schema.make.bind(schema);
51
+ return {
52
+ make: (input) => make(Encoding.encodeBase64Url(encodeSessionsCursor(input))),
53
+ parse: (input) => Effect.suspend(() => {
54
+ const result = Encoding.decodeBase64UrlString(input);
55
+ return Result.isFailure(result)
56
+ ? Effect.fail(invalidCursor)
57
+ : decodeSessionsCursor(result.success).pipe(Effect.mapError(() => invalidCursor));
58
+ }),
59
+ };
60
+ }));
61
+ const SessionActive = Schema.Struct({
62
+ type: Schema.Literal("running"),
63
+ }).annotate({ identifier: "SessionActive" });
64
+ const SessionHistoryLimit = PositiveInt.check(Schema.isLessThanOrEqualTo(100));
65
+ export const SessionHistoryQuery = Schema.Struct({
66
+ limit: Schema.NumberFromString.pipe(Schema.decodeTo(SessionHistoryLimit), Schema.optional),
67
+ after: Schema.NumberFromString.pipe(Schema.decodeTo(NonNegativeInt), Schema.optional),
68
+ });
69
+ const SessionsQueryCursor = SessionsCursor.annotate({
70
+ description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
71
+ });
72
+ export const SessionsQuery = Schema.Struct({
73
+ ...SessionsQueryFields,
74
+ directory: AbsolutePath.pipe(Schema.optional),
75
+ project: Project.ID.pipe(Schema.optional),
76
+ subpath: RelativePath.pipe(Schema.optional),
77
+ cursor: SessionsQueryCursor.pipe(Schema.optional),
78
+ }).annotate({ identifier: "SessionsQuery" });
79
+ export const makeSessionGroup = (sessionLocationMiddleware) => HttpApiGroup.make("server.session")
80
+ .add(HttpApiEndpoint.get("session.list", "/api/session", {
81
+ query: SessionsQuery,
82
+ success: Schema.Struct({
83
+ data: Schema.Array(Session.Info),
84
+ cursor: Schema.Struct({
85
+ previous: SessionsCursor.pipe(Schema.optional),
86
+ next: SessionsCursor.pipe(Schema.optional),
87
+ }),
88
+ }).annotate({ identifier: "SessionsResponse" }),
89
+ error: [InvalidCursorError, InvalidRequestError],
90
+ }).annotateMerge(OpenApi.annotations({
91
+ identifier: "v2.session.list",
92
+ summary: "List sessions",
93
+ 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.",
94
+ })))
95
+ .add(HttpApiEndpoint.post("session.create", "/api/session", {
96
+ payload: Schema.Struct({
97
+ id: Session.ID.pipe(Schema.optional),
98
+ agent: Agent.ID.pipe(Schema.optional),
99
+ model: Model.Ref.pipe(Schema.optional),
100
+ location: Location.Ref.pipe(Schema.optional),
101
+ }),
102
+ success: Schema.Struct({ data: Session.Info }),
103
+ }).annotateMerge(OpenApi.annotations({
104
+ identifier: "v2.session.create",
105
+ summary: "Create session",
106
+ description: "Create a session at the requested location.",
107
+ })))
108
+ .add(HttpApiEndpoint.get("session.active", "/api/session/active", {
109
+ success: Schema.Struct({ data: Schema.Record(Session.ID, SessionActive) }),
110
+ }).annotateMerge(OpenApi.annotations({
111
+ identifier: "v2.session.active",
112
+ summary: "List active sessions",
113
+ description: "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.",
114
+ })))
115
+ .add(HttpApiEndpoint.get("session.get", "/api/session/:sessionID", {
116
+ params: { sessionID: Session.ID },
117
+ success: Schema.Struct({ data: Session.Info }),
118
+ error: SessionNotFoundError,
119
+ })
120
+ .middleware(sessionLocationMiddleware)
121
+ .annotateMerge(OpenApi.annotations({
122
+ identifier: "v2.session.get",
123
+ summary: "Get session",
124
+ description: "Retrieve a session by ID.",
125
+ })))
126
+ .add(HttpApiEndpoint.post("session.fork", "/api/session/:sessionID/fork", {
127
+ params: { sessionID: Session.ID },
128
+ payload: Schema.Struct({ messageID: SessionMessage.ID.pipe(Schema.optional) }),
129
+ success: Schema.Struct({ data: Session.Info }),
130
+ error: [SessionNotFoundError, MessageNotFoundError],
131
+ })
132
+ .middleware(sessionLocationMiddleware)
133
+ .annotateMerge(OpenApi.annotations({
134
+ identifier: "v2.session.fork",
135
+ summary: "Fork session",
136
+ description: "Create a child session by copying projected history from the parent. When messageID is supplied, copy messages before that boundary.",
137
+ })))
138
+ .add(HttpApiEndpoint.post("session.switchAgent", "/api/session/:sessionID/agent", {
139
+ params: { sessionID: Session.ID },
140
+ payload: Schema.Struct({ agent: Agent.ID }),
141
+ success: HttpApiSchema.NoContent,
142
+ error: SessionNotFoundError,
143
+ })
144
+ .middleware(sessionLocationMiddleware)
145
+ .annotateMerge(OpenApi.annotations({
146
+ identifier: "v2.session.switchAgent",
147
+ summary: "Switch session agent",
148
+ description: "Switch the agent used by subsequent provider turns.",
149
+ })))
150
+ .add(HttpApiEndpoint.post("session.switchModel", "/api/session/:sessionID/model", {
151
+ params: { sessionID: Session.ID },
152
+ payload: Schema.Struct({ model: Model.Ref }),
153
+ success: HttpApiSchema.NoContent,
154
+ error: SessionNotFoundError,
155
+ })
156
+ .middleware(sessionLocationMiddleware)
157
+ .annotateMerge(OpenApi.annotations({
158
+ identifier: "v2.session.switchModel",
159
+ summary: "Switch session model",
160
+ description: "Switch the model used by subsequent provider turns.",
161
+ })))
162
+ .add(HttpApiEndpoint.post("session.rename", "/api/session/:sessionID/rename", {
163
+ params: { sessionID: Session.ID },
164
+ payload: Schema.Struct({ title: Schema.String }),
165
+ success: HttpApiSchema.NoContent,
166
+ error: SessionNotFoundError,
167
+ })
168
+ .middleware(sessionLocationMiddleware)
169
+ .annotateMerge(OpenApi.annotations({
170
+ identifier: "v2.session.rename",
171
+ summary: "Rename session",
172
+ description: "Update the session title.",
173
+ })))
174
+ .add(HttpApiEndpoint.post("session.prompt", "/api/session/:sessionID/prompt", {
175
+ params: { sessionID: Session.ID },
176
+ payload: Schema.Struct({
177
+ id: SessionMessage.ID.pipe(Schema.optional),
178
+ prompt: PromptInput.Prompt,
179
+ delivery: SessionInput.Delivery.pipe(Schema.optional),
180
+ resume: Schema.Boolean.pipe(Schema.optional),
181
+ }),
182
+ success: Schema.Struct({ data: SessionInput.Admitted }),
183
+ error: [ConflictError, SessionNotFoundError],
184
+ })
185
+ .middleware(sessionLocationMiddleware)
186
+ .annotateMerge(OpenApi.annotations({
187
+ identifier: "v2.session.prompt",
188
+ summary: "Send message",
189
+ description: "Durably admit one session input and schedule agent-loop execution unless resume is false.",
190
+ })))
191
+ .add(HttpApiEndpoint.post("session.skill", "/api/session/:sessionID/skill", {
192
+ params: { sessionID: Session.ID },
193
+ payload: Schema.Struct({
194
+ id: SessionMessage.ID.pipe(Schema.optional),
195
+ skill: Schema.String,
196
+ resume: Schema.Boolean.pipe(Schema.optional),
197
+ }),
198
+ success: HttpApiSchema.NoContent,
199
+ error: [SessionNotFoundError, SkillNotFoundError],
200
+ })
201
+ .middleware(sessionLocationMiddleware)
202
+ .annotateMerge(OpenApi.annotations({
203
+ identifier: "v2.session.skill",
204
+ summary: "Activate skill",
205
+ description: "Activate a skill for a session by appending a skill message and resuming execution.",
206
+ })))
207
+ .add(HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", {
208
+ params: { sessionID: Session.ID },
209
+ success: HttpApiSchema.NoContent,
210
+ error: [SessionNotFoundError, SessionBusyError, ServiceUnavailableError, UnknownError],
211
+ })
212
+ .middleware(sessionLocationMiddleware)
213
+ .annotateMerge(OpenApi.annotations({
214
+ identifier: "v2.session.compact",
215
+ summary: "Compact session",
216
+ description: "Compact a session conversation.",
217
+ })))
218
+ .add(HttpApiEndpoint.post("session.wait", "/api/session/:sessionID/wait", {
219
+ params: { sessionID: Session.ID },
220
+ success: HttpApiSchema.NoContent,
221
+ error: [SessionNotFoundError, ServiceUnavailableError],
222
+ })
223
+ .middleware(sessionLocationMiddleware)
224
+ .annotateMerge(OpenApi.annotations({
225
+ identifier: "v2.session.wait",
226
+ summary: "Wait for session",
227
+ description: "Wait for a session agent loop to become idle.",
228
+ })))
229
+ .add(HttpApiEndpoint.post("session.revert.stage", "/api/session/:sessionID/revert/stage", {
230
+ params: { sessionID: Session.ID },
231
+ payload: Schema.Struct({ messageID: SessionMessage.ID, files: Schema.Boolean.pipe(Schema.optional) }),
232
+ success: Schema.Struct({ data: Revert.State }),
233
+ error: [MessageNotFoundError, SessionNotFoundError, SessionBusyError, UnknownError],
234
+ })
235
+ .middleware(sessionLocationMiddleware)
236
+ .annotateMerge(OpenApi.annotations({
237
+ identifier: "v2.session.revert.stage",
238
+ summary: "Stage session revert",
239
+ description: "Stage or move a reversible session boundary and optionally apply its file changes.",
240
+ })))
241
+ .add(HttpApiEndpoint.post("session.revert.clear", "/api/session/:sessionID/revert/clear", {
242
+ params: { sessionID: Session.ID },
243
+ success: HttpApiSchema.NoContent,
244
+ error: [SessionNotFoundError, SessionBusyError, UnknownError],
245
+ })
246
+ .middleware(sessionLocationMiddleware)
247
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.clear", summary: "Clear staged revert" })))
248
+ .add(HttpApiEndpoint.post("session.revert.commit", "/api/session/:sessionID/revert/commit", {
249
+ params: { sessionID: Session.ID },
250
+ success: HttpApiSchema.NoContent,
251
+ error: [SessionNotFoundError, SessionBusyError],
252
+ })
253
+ .middleware(sessionLocationMiddleware)
254
+ .annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.commit", summary: "Commit staged revert" })))
255
+ .add(HttpApiEndpoint.get("session.context", "/api/session/:sessionID/context", {
256
+ params: { sessionID: Session.ID },
257
+ success: Schema.Struct({ data: Schema.Array(SessionMessage.Message) }),
258
+ error: [SessionNotFoundError, UnknownError],
259
+ })
260
+ .middleware(sessionLocationMiddleware)
261
+ .annotateMerge(OpenApi.annotations({
262
+ identifier: "v2.session.context",
263
+ summary: "Get session context",
264
+ description: "Retrieve the active context messages for a session (all messages after the last compaction).",
265
+ })))
266
+ .add(HttpApiEndpoint.get("session.history", "/api/session/:sessionID/history", {
267
+ params: { sessionID: Session.ID },
268
+ query: SessionHistoryQuery,
269
+ success: Schema.Struct({
270
+ data: Schema.Array(SessionEvent.Durable),
271
+ hasMore: Schema.Boolean,
272
+ }).annotate({ identifier: "SessionHistory" }),
273
+ error: SessionNotFoundError,
274
+ })
275
+ .middleware(sessionLocationMiddleware)
276
+ .annotateMerge(OpenApi.annotations({
277
+ identifier: "v2.session.history",
278
+ summary: "Get session history",
279
+ description: "Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages.",
280
+ })))
281
+ .add(HttpApiEndpoint.get("session.events", "/api/session/:sessionID/event", {
282
+ params: { sessionID: Session.ID },
283
+ query: {
284
+ after: Schema.NumberFromString.pipe(Schema.decodeTo(NonNegativeInt), Schema.optional),
285
+ },
286
+ success: HttpApiSchema.StreamSse({ data: SessionEvent.Durable }),
287
+ error: SessionNotFoundError,
288
+ })
289
+ .middleware(sessionLocationMiddleware)
290
+ .annotateMerge(OpenApi.annotations({
291
+ identifier: "v2.session.events",
292
+ summary: "Subscribe to session events",
293
+ description: "Replay durable events after an aggregate sequence, then continue with new durable events.",
294
+ })))
295
+ .add(HttpApiEndpoint.post("session.interrupt", "/api/session/:sessionID/interrupt", {
296
+ params: { sessionID: Session.ID },
297
+ success: HttpApiSchema.NoContent,
298
+ error: SessionNotFoundError,
299
+ })
300
+ .middleware(sessionLocationMiddleware)
301
+ .annotateMerge(OpenApi.annotations({
302
+ identifier: "v2.session.interrupt",
303
+ summary: "Interrupt session execution",
304
+ description: "Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op.",
305
+ })))
306
+ .add(HttpApiEndpoint.post("session.background", "/api/session/:sessionID/background", {
307
+ params: { sessionID: Session.ID },
308
+ success: HttpApiSchema.NoContent,
309
+ error: SessionNotFoundError,
310
+ })
311
+ .middleware(sessionLocationMiddleware)
312
+ .annotateMerge(OpenApi.annotations({
313
+ identifier: "v2.session.background",
314
+ summary: "Background blocking session tools",
315
+ description: "Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.",
316
+ })))
317
+ .add(HttpApiEndpoint.get("session.message", "/api/session/:sessionID/message/:messageID", {
318
+ params: { sessionID: Session.ID, messageID: SessionMessage.ID },
319
+ success: Schema.Struct({ data: SessionMessage.Message }),
320
+ error: [SessionNotFoundError, MessageNotFoundError],
321
+ })
322
+ .middleware(sessionLocationMiddleware)
323
+ .annotateMerge(OpenApi.annotations({
324
+ identifier: "v2.session.message",
325
+ summary: "Get session message",
326
+ description: "Retrieve one projected message owned by the Session.",
327
+ })))
328
+ .annotateMerge(OpenApi.annotations({
329
+ title: "sessions",
330
+ description: "Experimental session routes.",
331
+ }));
@@ -0,0 +1,120 @@
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", HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<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
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
11
+ readonly location: typeof Location.Info;
12
+ readonly data: Schema.$Array<Schema.Struct<{
13
+ readonly id: Schema.brand<Schema.String, "ShellID"> & {
14
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
15
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
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.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
24
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
25
+ readonly time: Schema.Struct<{
26
+ readonly started: Schema.Number;
27
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
28
+ }>;
29
+ }>>;
30
+ }>>, HttpApiEndpoint.Json<never>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.create", "POST", "/api/shell", HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<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
+ }>>, HttpApiEndpoint.Json<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.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
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
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
41
+ readonly location: typeof Location.Info;
42
+ readonly data: Schema.Struct<{
43
+ readonly id: Schema.brand<Schema.String, "ShellID"> & {
44
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
45
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
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.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
54
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
55
+ readonly time: Schema.Struct<{
56
+ readonly started: Schema.Number;
57
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
58
+ }>;
59
+ }>;
60
+ }>>, HttpApiEndpoint.Json<never>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.get", "GET", "/api/shell/:id", HttpApiEndpoint.StringTree<Schema.Struct<{
61
+ id: Schema.brand<Schema.String, "ShellID"> & {
62
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
63
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
64
+ };
65
+ }>>, HttpApiEndpoint.StringTree<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
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
71
+ readonly location: typeof Location.Info;
72
+ readonly data: Schema.Struct<{
73
+ readonly id: Schema.brand<Schema.String, "ShellID"> & {
74
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
75
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
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.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
84
+ readonly metadata: Schema.$Record<Schema.String, Schema.Unknown>;
85
+ readonly time: Schema.Struct<{
86
+ readonly started: Schema.Number;
87
+ readonly completed: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Number>>, Schema.optionalKey<Schema.Number>, never, never>;
88
+ }>;
89
+ }>;
90
+ }>>, HttpApiEndpoint.Json<typeof ShellNotFoundError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.output", "GET", "/api/shell/:id/output", HttpApiEndpoint.StringTree<Schema.Struct<{
91
+ id: Schema.brand<Schema.String, "ShellID"> & {
92
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
93
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
94
+ };
95
+ }>>, HttpApiEndpoint.StringTree<Schema.Struct<{
96
+ readonly cursor: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
97
+ readonly limit: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Int>>, Schema.optionalKey<Schema.Int>, never, never>;
98
+ readonly location: Schema.optional<Schema.Struct<{
99
+ readonly directory: Schema.optional<Schema.String>;
100
+ readonly workspace: Schema.optional<Schema.String>;
101
+ }>>;
102
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
103
+ readonly location: typeof Location.Info;
104
+ readonly data: Schema.Struct<{
105
+ readonly output: Schema.String;
106
+ readonly cursor: Schema.Int;
107
+ readonly size: Schema.Int;
108
+ readonly truncated: Schema.Boolean;
109
+ }>;
110
+ }>>, HttpApiEndpoint.Json<typeof ShellNotFoundError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"shell.remove", "DELETE", "/api/shell/:id", HttpApiEndpoint.StringTree<Schema.Struct<{
111
+ id: Schema.brand<Schema.String, "ShellID"> & {
112
+ create: () => string & import("effect/Brand").Brand<"ShellID">;
113
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"ShellID">;
114
+ };
115
+ }>>, HttpApiEndpoint.StringTree<Schema.Struct<{
116
+ readonly location: Schema.optional<Schema.Struct<{
117
+ readonly directory: Schema.optional<Schema.String>;
118
+ readonly workspace: Schema.optional<Schema.String>;
119
+ }>>;
120
+ }>>, HttpApiEndpoint.Json<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<HttpApiSchema.NoContent>, HttpApiEndpoint.Json<typeof ShellNotFoundError>, never, never>, false>;
@@ -0,0 +1,65 @@
1
+ import { Shell } from "@opencode-ai/schema/shell";
2
+ import { Location } from "@opencode-ai/schema/location";
3
+ import { Schema } from "effect";
4
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
5
+ import { ShellNotFoundError } from "../errors.js";
6
+ import { LocationQuery, locationQueryOpenApi } from "./location.js";
7
+ export const ShellGroup = HttpApiGroup.make("server.shell")
8
+ .add(HttpApiEndpoint.get("shell.list", "/api/shell", {
9
+ query: LocationQuery,
10
+ success: Location.response(Schema.Array(Shell.Info)),
11
+ })
12
+ .annotateMerge(locationQueryOpenApi)
13
+ .annotateMerge(OpenApi.annotations({
14
+ identifier: "v2.shell.list",
15
+ summary: "List running shell commands",
16
+ description: "List currently running shell commands for a location. Exited commands are not included.",
17
+ })))
18
+ .add(HttpApiEndpoint.post("shell.create", "/api/shell", {
19
+ query: LocationQuery,
20
+ payload: Shell.CreateInput,
21
+ success: Location.response(Shell.Info),
22
+ })
23
+ .annotateMerge(locationQueryOpenApi)
24
+ .annotateMerge(OpenApi.annotations({
25
+ identifier: "v2.shell.create",
26
+ summary: "Run shell command",
27
+ description: "Spawn one non-interactive shell command for a location. Combined stdout/stderr is captured to a file pageable via output.",
28
+ })))
29
+ .add(HttpApiEndpoint.get("shell.get", "/api/shell/:id", {
30
+ params: { id: Shell.ID },
31
+ query: LocationQuery,
32
+ success: Location.response(Shell.Info),
33
+ error: ShellNotFoundError,
34
+ })
35
+ .annotateMerge(locationQueryOpenApi)
36
+ .annotateMerge(OpenApi.annotations({
37
+ identifier: "v2.shell.get",
38
+ summary: "Get shell command",
39
+ description: "Get one shell command, including its status and exit code once exited.",
40
+ })))
41
+ .add(HttpApiEndpoint.get("shell.output", "/api/shell/:id/output", {
42
+ params: { id: Shell.ID },
43
+ query: Schema.Struct({ ...LocationQuery.fields, ...Shell.OutputInput.fields }),
44
+ success: Location.response(Shell.Output),
45
+ error: ShellNotFoundError,
46
+ })
47
+ .annotateMerge(locationQueryOpenApi)
48
+ .annotateMerge(OpenApi.annotations({
49
+ identifier: "v2.shell.output",
50
+ summary: "Read shell output",
51
+ description: "Page through captured combined output by absolute byte cursor.",
52
+ })))
53
+ .add(HttpApiEndpoint.delete("shell.remove", "/api/shell/:id", {
54
+ params: { id: Shell.ID },
55
+ query: LocationQuery,
56
+ success: HttpApiSchema.NoContent,
57
+ error: ShellNotFoundError,
58
+ })
59
+ .annotateMerge(locationQueryOpenApi)
60
+ .annotateMerge(OpenApi.annotations({
61
+ identifier: "v2.shell.remove",
62
+ summary: "Remove shell command",
63
+ description: "Terminate and remove one shell command and its retained output.",
64
+ })))
65
+ .annotateMerge(OpenApi.annotations({ title: "shell", description: "Experimental location-scoped shell command routes." }));
@@ -0,0 +1,19 @@
1
+ import { Location } from "@opencode-ai/schema/location";
2
+ import { Schema } from "effect";
3
+ import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
4
+ export declare const SkillGroup: HttpApiGroup.HttpApiGroup<"server.skill", HttpApiEndpoint.HttpApiEndpoint<"skill.list", "GET", "/api/skill", HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<Schema.Struct<{
5
+ readonly location: Schema.optional<Schema.Struct<{
6
+ readonly directory: Schema.optional<Schema.String>;
7
+ readonly workspace: Schema.optional<Schema.String>;
8
+ }>>;
9
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
10
+ readonly location: typeof Location.Info;
11
+ readonly data: Schema.$Array<Schema.Struct<{
12
+ readonly name: Schema.String;
13
+ readonly description: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
14
+ readonly slash: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Boolean>>, Schema.optionalKey<Schema.Boolean>, never, never>;
15
+ readonly autoinvoke: Schema.decodeTo<Schema.optional<Schema.toType<Schema.Boolean>>, Schema.optionalKey<Schema.Boolean>, never, never>;
16
+ readonly location: Schema.brand<Schema.String, "AbsolutePath">;
17
+ readonly content: Schema.String;
18
+ }>>;
19
+ }>>, HttpApiEndpoint.Json<never>, never, never>, false>;
@@ -0,0 +1,20 @@
1
+ import { Skill } from "@opencode-ai/schema/skill";
2
+ import { Location } from "@opencode-ai/schema/location";
3
+ import { Schema } from "effect";
4
+ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi";
5
+ import { LocationQuery, locationQueryOpenApi } from "./location.js";
6
+ export const SkillGroup = HttpApiGroup.make("server.skill")
7
+ .add(HttpApiEndpoint.get("skill.list", "/api/skill", {
8
+ query: LocationQuery,
9
+ success: Location.response(Schema.Array(Skill.Info)),
10
+ })
11
+ .annotateMerge(locationQueryOpenApi)
12
+ .annotateMerge(OpenApi.annotations({
13
+ identifier: "v2.skill.list",
14
+ summary: "List skills",
15
+ description: "Retrieve currently registered skills.",
16
+ })))
17
+ .annotateMerge(OpenApi.annotations({
18
+ title: "skills",
19
+ description: "Experimental skill routes.",
20
+ }));
@@ -0,0 +1,13 @@
1
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
2
+ import { UnauthorizedError } from "../errors.js";
3
+ declare const Authorization_base: HttpApiMiddleware.ServiceClass<Authorization, "@opencode/HttpApiAuthorization", {
4
+ requires: never;
5
+ provides: never;
6
+ error: typeof UnauthorizedError;
7
+ clientError: never;
8
+ requiredForClient: false;
9
+ security: never;
10
+ }, HttpApiMiddleware.HttpApiMiddleware<never, typeof UnauthorizedError, never>>;
11
+ export declare class Authorization extends Authorization_base {
12
+ }
13
+ export {};
@@ -0,0 +1,6 @@
1
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
2
+ import { UnauthorizedError } from "../errors.js";
3
+ export class Authorization extends HttpApiMiddleware.Service()("@opencode/HttpApiAuthorization", {
4
+ error: UnauthorizedError,
5
+ }) {
6
+ }
@@ -0,0 +1,13 @@
1
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
2
+ import { InvalidRequestError } from "../errors.js";
3
+ declare const SchemaErrorMiddleware_base: HttpApiMiddleware.ServiceClass<SchemaErrorMiddleware, "@opencode/HttpApiSchemaError", {
4
+ requires: never;
5
+ provides: never;
6
+ error: typeof InvalidRequestError;
7
+ clientError: never;
8
+ requiredForClient: false;
9
+ security: never;
10
+ }, HttpApiMiddleware.HttpApiMiddleware<never, typeof InvalidRequestError, never>>;
11
+ export declare class SchemaErrorMiddleware extends SchemaErrorMiddleware_base {
12
+ }
13
+ export {};
@@ -0,0 +1,4 @@
1
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
2
+ import { InvalidRequestError } from "../errors.js";
3
+ export class SchemaErrorMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiSchemaError", { error: InvalidRequestError }) {
4
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@opencode-ai/protocol",
4
+ "version": "0.0.0-next-14661",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/anomalyco/opencode.git",
10
+ "directory": "packages/protocol"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "exports": {
19
+ "./*": {
20
+ "import": "./dist/*.js",
21
+ "types": "./dist/*.d.ts"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "bun run script/build.ts",
26
+ "typecheck": "tsgo --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@opencode-ai/schema": "0.0.0-next-14661",
30
+ "effect": "4.0.0-beta.83"
31
+ },
32
+ "devDependencies": {
33
+ "@tsconfig/bun": "1.0.9",
34
+ "@types/bun": "1.3.13",
35
+ "@typescript/native-preview": "7.0.0-dev.20251207.1",
36
+ "typescript": "5.8.2"
37
+ }
38
+ }