@opencode-ai/server 0.0.0-dev-17880

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 (101) hide show
  1. package/dist/api.d.ts +7528 -0
  2. package/dist/api.js +9 -0
  3. package/dist/auth.d.ts +17 -0
  4. package/dist/auth.js +17 -0
  5. package/dist/cors.d.ts +7 -0
  6. package/dist/cors.js +36 -0
  7. package/dist/event-feed.d.ts +32 -0
  8. package/dist/event-feed.js +60 -0
  9. package/dist/fetch.d.ts +68 -0
  10. package/dist/fetch.js +11 -0
  11. package/dist/handlers/agent.d.ts +1 -0
  12. package/dist/handlers/agent.js +17 -0
  13. package/dist/handlers/command.d.ts +1 -0
  14. package/dist/handlers/command.js +5 -0
  15. package/dist/handlers/config.d.ts +1 -0
  16. package/dist/handlers/config.js +4 -0
  17. package/dist/handlers/credential.d.ts +1 -0
  18. package/dist/handlers/credential.js +11 -0
  19. package/dist/handlers/debug.d.ts +1 -0
  20. package/dist/handlers/debug.js +11 -0
  21. package/dist/handlers/event.d.ts +2 -0
  22. package/dist/handlers/event.js +27 -0
  23. package/dist/handlers/form.d.ts +1 -0
  24. package/dist/handlers/form.js +57 -0
  25. package/dist/handlers/fs.d.ts +1 -0
  26. package/dist/handlers/fs.js +19 -0
  27. package/dist/handlers/generate.d.ts +1 -0
  28. package/dist/handlers/generate.js +10 -0
  29. package/dist/handlers/health.d.ts +2 -0
  30. package/dist/handlers/health.js +11 -0
  31. package/dist/handlers/integration.d.ts +2 -0
  32. package/dist/handlers/integration.js +83 -0
  33. package/dist/handlers/location.d.ts +1 -0
  34. package/dist/handlers/location.js +12 -0
  35. package/dist/handlers/mcp.d.ts +1 -0
  36. package/dist/handlers/mcp.js +31 -0
  37. package/dist/handlers/message.d.ts +2 -0
  38. package/dist/handlers/message.js +42 -0
  39. package/dist/handlers/migration.d.ts +1 -0
  40. package/dist/handlers/migration.js +7 -0
  41. package/dist/handlers/model.d.ts +1 -0
  42. package/dist/handlers/model.js +22 -0
  43. package/dist/handlers/permission.d.ts +2 -0
  44. package/dist/handlers/permission.js +56 -0
  45. package/dist/handlers/plugin-readiness.d.ts +4 -0
  46. package/dist/handlers/plugin-readiness.js +9 -0
  47. package/dist/handlers/plugin.d.ts +1 -0
  48. package/dist/handlers/plugin.js +8 -0
  49. package/dist/handlers/project.d.ts +2 -0
  50. package/dist/handlers/project.js +10 -0
  51. package/dist/handlers/provider.d.ts +1 -0
  52. package/dist/handlers/provider.js +20 -0
  53. package/dist/handlers/pty.d.ts +3 -0
  54. package/dist/handlers/pty.js +109 -0
  55. package/dist/handlers/reference.d.ts +1 -0
  56. package/dist/handlers/reference.js +5 -0
  57. package/dist/handlers/server.d.ts +2 -0
  58. package/dist/handlers/server.js +7 -0
  59. package/dist/handlers/session-error.d.ts +5 -0
  60. package/dist/handlers/session-error.js +13 -0
  61. package/dist/handlers/session.d.ts +3 -0
  62. package/dist/handlers/session.js +275 -0
  63. package/dist/handlers/shell.d.ts +1 -0
  64. package/dist/handlers/shell.js +30 -0
  65. package/dist/handlers/skill.d.ts +1 -0
  66. package/dist/handlers/skill.js +5 -0
  67. package/dist/handlers/vcs.d.ts +1 -0
  68. package/dist/handlers/vcs.js +14 -0
  69. package/dist/handlers/websearch.d.ts +1 -0
  70. package/dist/handlers/websearch.js +38 -0
  71. package/dist/handlers/worktree.d.ts +2 -0
  72. package/dist/handlers/worktree.js +32 -0
  73. package/dist/handlers.d.ts +2 -0
  74. package/dist/handlers.js +32 -0
  75. package/dist/location.d.ts +23 -0
  76. package/dist/location.js +44 -0
  77. package/dist/middleware/authorization.d.ts +7 -0
  78. package/dist/middleware/authorization.js +48 -0
  79. package/dist/middleware/form-location.d.ts +18 -0
  80. package/dist/middleware/form-location.js +42 -0
  81. package/dist/middleware/schema-error.d.ts +3 -0
  82. package/dist/middleware/schema-error.js +15 -0
  83. package/dist/middleware/session-location.d.ts +18 -0
  84. package/dist/middleware/session-location.js +36 -0
  85. package/dist/options.d.ts +38 -0
  86. package/dist/options.js +32 -0
  87. package/dist/process.d.ts +47 -0
  88. package/dist/process.js +115 -0
  89. package/dist/pty-environment.d.ts +13 -0
  90. package/dist/pty-environment.js +9 -0
  91. package/dist/request-tracing.d.ts +2 -0
  92. package/dist/request-tracing.js +2 -0
  93. package/dist/routes.d.ts +23 -0
  94. package/dist/routes.js +112 -0
  95. package/dist/server-info.d.ts +11 -0
  96. package/dist/server-info.js +22 -0
  97. package/dist/service-status.d.ts +25 -0
  98. package/dist/service-status.js +11 -0
  99. package/dist/workerd.d.ts +45 -0
  100. package/dist/workerd.js +57 -0
  101. package/package.json +45 -0
@@ -0,0 +1,275 @@
1
+ import { Session } from "@opencode-ai/core/session";
2
+ import { SessionTransfer } from "@opencode-ai/core/session/transfer";
3
+ import { InstructionEntry } from "@opencode-ai/core/session/instruction-entry";
4
+ import { DateTime, Effect, Stream } from "effect";
5
+ import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
6
+ import { Api } from "../api";
7
+ import { SessionsCursor } from "@opencode-ai/protocol/groups/session";
8
+ import {
9
+ ConflictError,
10
+ CommandEvaluationError,
11
+ CommandNotFoundError,
12
+ InvalidRequestError,
13
+ InvalidCursorError,
14
+ MessageNotFoundError,
15
+ ServiceUnavailableError,
16
+ SessionBusyError,
17
+ SkillNotFoundError,
18
+ UnknownError
19
+ } from "@opencode-ai/protocol/errors";
20
+ import { AbsolutePath } from "@opencode-ai/core/schema";
21
+ import { failedMessageDecode, missingSession } from "./session-error";
22
+ const DefaultSessionsLimit = 50;
23
+ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) => Effect.gen(function* () {
24
+ const session = yield* Session.Service, transfer = yield* SessionTransfer.Service, busySession = (error) => new SessionBusyError({
25
+ sessionID: error.sessionID,
26
+ message: `Session is busy: ${error.sessionID}`
27
+ }), pendingMutation = (effect, conflict) => effect.pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.InboxConflictError", (error) => new ConflictError({ resource: error.inboxID, message: `${conflict}: ${error.inboxID}` })), Effect.as(HttpApiSchema.NoContent.make()));
28
+ return handlers.handle("session.list", Effect.fn(function* (ctx) {
29
+ const query = ctx.query.cursor !== void 0 ? yield* SessionsCursor.parse(ctx.query.cursor).pipe(Effect.mapError(() => new InvalidCursorError({ message: "Invalid cursor" }))) : ctx.query, sessions = (yield* session.list({
30
+ ...query,
31
+ workspaceID: query.workspace,
32
+ limit: ctx.query.limit ?? DefaultSessionsLimit
33
+ })).data, first = sessions[0], last = sessions.at(-1);
34
+ return {
35
+ data: sessions,
36
+ cursor: {
37
+ previous: first ? SessionsCursor.make({
38
+ ...query,
39
+ anchor: {
40
+ id: first.id,
41
+ time: DateTime.toEpochMillis(first.time.updated),
42
+ direction: "previous"
43
+ }
44
+ }) : void 0,
45
+ next: last ? SessionsCursor.make({
46
+ ...query,
47
+ anchor: {
48
+ id: last.id,
49
+ time: DateTime.toEpochMillis(last.time.updated),
50
+ direction: "next"
51
+ }
52
+ }) : void 0
53
+ }
54
+ };
55
+ })).handle("session.create", Effect.fn(function* (ctx) {
56
+ return {
57
+ data: yield* session.create({
58
+ id: ctx.payload.id,
59
+ title: ctx.payload.title,
60
+ agent: ctx.payload.agent,
61
+ model: ctx.payload.model,
62
+ location: ctx.payload.location ?? { directory: AbsolutePath.make(process.cwd()) }
63
+ }).pipe(Effect.orDie)
64
+ };
65
+ })).handle("session.import", Effect.fn(function* (ctx) {
66
+ return {
67
+ data: yield* transfer.import({
68
+ data: { info: ctx.payload.info, messages: ctx.payload.messages },
69
+ location: ctx.payload.location ?? { directory: AbsolutePath.make(process.cwd()) }
70
+ }).pipe(Effect.catchTag("SessionTransfer.ImportConflictError", (error) => new ConflictError({
71
+ message: `Session already exists: ${error.sessionID}`,
72
+ resource: error.sessionID
73
+ })))
74
+ };
75
+ })).handle("session.export", Effect.fn(function* (ctx) {
76
+ return {
77
+ data: yield* transfer.export({ sessionID: ctx.params.sessionID, sanitize: ctx.query.sanitize }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageDecodeError", failedMessageDecode))
78
+ };
79
+ })).handle("session.active", Effect.fn(function* () {
80
+ const active = yield* session.active;
81
+ return {
82
+ data: Object.fromEntries(Array.from(active, (sessionID) => [sessionID, { type: "running" }]))
83
+ };
84
+ })).handle("session.get", Effect.fn(function* (ctx) {
85
+ return {
86
+ data: yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
87
+ };
88
+ })).handle("session.view", Effect.fn(function* (ctx) {
89
+ yield* session.view({ sessionID: ctx.params.sessionID, idle: ctx.payload.idle }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
90
+ return HttpApiSchema.NoContent.make();
91
+ })).handle("session.remove", Effect.fn(function* (ctx) {
92
+ yield* session.remove(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
93
+ return HttpApiSchema.NoContent.make();
94
+ })).handle("session.environment", Effect.fn(function* (ctx) {
95
+ yield* session.environment({ sessionID: ctx.params.sessionID, variables: ctx.payload.variables }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
96
+ return HttpApiSchema.NoContent.make();
97
+ })).handle("session.fork", Effect.fn(function* (ctx) {
98
+ return {
99
+ data: yield* session.fork({ sessionID: ctx.params.sessionID, boundary: ctx.payload.boundary }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", (error) => new MessageNotFoundError({
100
+ sessionID: error.sessionID,
101
+ messageID: error.messageID,
102
+ message: `Message not found: ${error.messageID}`
103
+ })), Effect.catchTag("Session.ForkEmptyError", (error) => new InvalidRequestError({ message: error.message, kind: "empty_session" })))
104
+ };
105
+ })).handle("session.switchAgent", Effect.fn(function* (ctx) {
106
+ yield* session.switchAgent({ sessionID: ctx.params.sessionID, agent: ctx.payload.agent }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
107
+ return HttpApiSchema.NoContent.make();
108
+ })).handle("session.switchModel", Effect.fn(function* (ctx) {
109
+ yield* session.switchModel({ sessionID: ctx.params.sessionID, model: ctx.payload.model }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
110
+ return HttpApiSchema.NoContent.make();
111
+ })).handle("session.rename", Effect.fn(function* (ctx) {
112
+ yield* session.rename({ sessionID: ctx.params.sessionID, title: ctx.payload.title }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
113
+ return HttpApiSchema.NoContent.make();
114
+ })).handle("session.move", Effect.fn(function* (ctx) {
115
+ yield* session.move({
116
+ sessionID: ctx.params.sessionID,
117
+ directory: ctx.payload.directory,
118
+ workspaceID: ctx.payload.workspaceID,
119
+ delivery: ctx.payload.delivery
120
+ }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.DestinationNotFoundError", (error) => Effect.fail(new InvalidRequestError({ message: `Directory does not exist: ${error.directory}` }))), Effect.catchTag("Session.DestinationNotDirectoryError", (error) => Effect.fail(new InvalidRequestError({ message: `Not a directory: ${error.directory}` }))), Effect.catchTag("Session.DestinationUnavailableError", (error) => Effect.fail(new InvalidRequestError({ message: `Directory is unavailable: ${error.directory}` }))));
121
+ return HttpApiSchema.NoContent.make();
122
+ })).handle("session.prompt", Effect.fn(function* (ctx) {
123
+ return {
124
+ data: yield* session.prompt({
125
+ sessionID: ctx.params.sessionID,
126
+ id: ctx.payload.id,
127
+ text: ctx.payload.text,
128
+ files: ctx.payload.files,
129
+ agents: ctx.payload.agents,
130
+ skills: ctx.payload.skills,
131
+ metadata: ctx.payload.metadata,
132
+ delivery: ctx.payload.delivery,
133
+ resume: ctx.payload.resume
134
+ }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.PromptConflictError", (error) => Effect.fail(new ConflictError({
135
+ message: `Prompt message ID conflicts with an existing durable record: ${error.messageID}`,
136
+ resource: error.messageID
137
+ }))), Effect.catchTag("Session.AttachmentError", (error) => Effect.fail(new InvalidRequestError({ message: error.message, field: "files" }))), Effect.catchTag("Session.SkillNotFoundError", (error) => Effect.fail(new InvalidRequestError({ message: `Skill not found: ${error.skill}`, field: "skills" }))))
138
+ };
139
+ })).handle("session.command", Effect.fn(function* (ctx) {
140
+ return {
141
+ data: yield* session.command({
142
+ sessionID: ctx.params.sessionID,
143
+ id: ctx.payload.id,
144
+ command: ctx.payload.command,
145
+ arguments: ctx.payload.arguments,
146
+ agent: ctx.payload.agent,
147
+ model: ctx.payload.model,
148
+ files: ctx.payload.files,
149
+ agents: ctx.payload.agents,
150
+ skills: ctx.payload.skills,
151
+ delivery: ctx.payload.delivery,
152
+ resume: ctx.payload.resume
153
+ }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Command.NotFoundError", (error) => Effect.fail(new CommandNotFoundError({
154
+ command: error.command,
155
+ message: error.message
156
+ }))), Effect.catchTag("Command.EvaluationError", (error) => Effect.fail(new CommandEvaluationError({
157
+ command: error.command,
158
+ message: error.message
159
+ }))), Effect.catchTag("Session.PromptConflictError", (error) => Effect.fail(new ConflictError({
160
+ message: `Prompt message ID conflicts with an existing durable record: ${error.messageID}`,
161
+ resource: error.messageID
162
+ }))), Effect.catchTag("Session.AttachmentError", (error) => Effect.fail(new InvalidRequestError({ message: error.message, field: "files" }))), Effect.catchTag("Session.SkillNotFoundError", (error) => Effect.fail(new InvalidRequestError({ message: `Skill not found: ${error.skill}`, field: "skills" }))))
163
+ };
164
+ })).handle("session.skill", Effect.fn(function* (ctx) {
165
+ yield* session.skill({
166
+ sessionID: ctx.params.sessionID,
167
+ id: ctx.payload.id,
168
+ skill: ctx.payload.skill,
169
+ resume: ctx.payload.resume
170
+ }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.SkillNotFoundError", (error) => Effect.fail(new SkillNotFoundError({ skill: error.skill, message: `Skill not found: ${error.skill}` }))));
171
+ return HttpApiSchema.NoContent.make();
172
+ })).handle("session.synthetic", Effect.fn(function* (ctx) {
173
+ return { data: yield* session.synthetic({
174
+ id: ctx.payload.id,
175
+ sessionID: ctx.params.sessionID,
176
+ text: ctx.payload.text,
177
+ description: ctx.payload.description,
178
+ metadata: ctx.payload.metadata,
179
+ delivery: ctx.payload.delivery,
180
+ resume: ctx.payload.resume
181
+ }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.SyntheticConflictError", (error) => Effect.fail(new ConflictError({
182
+ message: `Synthetic input ID conflicts with an existing durable record: ${error.inputID}`,
183
+ resource: error.inputID
184
+ })))) };
185
+ })).handle("session.shell", Effect.fn(function* (ctx) {
186
+ yield* session.shell({ sessionID: ctx.params.sessionID, id: ctx.payload.id, command: ctx.payload.command }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
187
+ return HttpApiSchema.NoContent.make();
188
+ })).handle("session.compact", Effect.fn(function* (ctx) {
189
+ return {
190
+ data: yield* session.compact({ sessionID: ctx.params.sessionID, id: ctx.payload.id, delivery: ctx.payload.delivery }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.CompactionConflictError", (error) => Effect.fail(new ConflictError({
191
+ message: `Compaction input ID conflicts with an existing durable record: ${error.inputID}`,
192
+ resource: error.inputID
193
+ }))))
194
+ };
195
+ })).handle("session.wait", Effect.fn(function* (ctx) {
196
+ yield* session.wait(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
197
+ return HttpApiSchema.NoContent.make();
198
+ })).handle("session.revert.stage", Effect.fn(function* (ctx) {
199
+ yield* Effect.log("session.revert.stage", {
200
+ sessionID: ctx.params.sessionID,
201
+ messageID: ctx.payload.messageID,
202
+ files: ctx.payload.files
203
+ });
204
+ return {
205
+ data: yield* session.revert.stage({ ...ctx.params, ...ctx.payload }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", (error) => new MessageNotFoundError({
206
+ sessionID: error.sessionID,
207
+ messageID: error.messageID,
208
+ message: `Message not found: ${error.messageID}`
209
+ })), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", (error) => {
210
+ const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
211
+ return Effect.logError("failed to stage session revert", { cause: error }).pipe(Effect.andThen(Effect.fail(new UnknownError({
212
+ message: "Unexpected server error. Check server logs for details.",
213
+ ref
214
+ }))));
215
+ }))
216
+ };
217
+ })).handle("session.revert.clear", Effect.fn(function* (ctx) {
218
+ yield* Effect.log("session.revert.clear", { sessionID: ctx.params.sessionID });
219
+ yield* session.revert.clear(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", (error) => {
220
+ const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
221
+ return Effect.logError("failed to clear session revert", { cause: error }).pipe(Effect.andThen(Effect.fail(new UnknownError({
222
+ message: "Unexpected server error. Check server logs for details.",
223
+ ref
224
+ }))));
225
+ }));
226
+ return HttpApiSchema.NoContent.make();
227
+ })).handle("session.revert.commit", Effect.fn(function* (ctx) {
228
+ yield* Effect.log("session.revert.commit", { sessionID: ctx.params.sessionID });
229
+ yield* session.revert.commit(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.BusyError", busySession));
230
+ return HttpApiSchema.NoContent.make();
231
+ })).handle("session.context", Effect.fn(function* (ctx) {
232
+ return {
233
+ data: yield* session.context(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageDecodeError", failedMessageDecode))
234
+ };
235
+ })).handle("session.inbox.list", Effect.fn(function* (ctx) {
236
+ return {
237
+ data: yield* session.inbox(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
238
+ };
239
+ })).handle("session.inbox.cancel", Effect.fn(function* (ctx) {
240
+ return yield* pendingMutation(session.cancelInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input can no longer be cancelled");
241
+ })).handle("session.inbox.steer", Effect.fn(function* (ctx) {
242
+ return yield* pendingMutation(session.steerInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input is no longer queued");
243
+ })).handle("session.inbox.queue", Effect.fn(function* (ctx) {
244
+ return yield* pendingMutation(session.queueInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input is no longer a steer");
245
+ })).handle("session.instructions.entry.list", Effect.fn(function* (ctx) {
246
+ return { data: yield* (yield* InstructionEntry.Service).list(ctx.params.sessionID) };
247
+ })).handle("session.instructions.entry.put", Effect.fn(function* (ctx) {
248
+ yield* (yield* InstructionEntry.Service).put({ sessionID: ctx.params.sessionID, key: ctx.params.key, value: ctx.payload.value });
249
+ return HttpApiSchema.NoContent.make();
250
+ })).handle("session.instructions.entry.remove", Effect.fn(function* (ctx) {
251
+ yield* (yield* InstructionEntry.Service).remove({ sessionID: ctx.params.sessionID, key: ctx.params.key });
252
+ return HttpApiSchema.NoContent.make();
253
+ })).handle("session.generate", Effect.fn(function* (ctx) {
254
+ return { data: { text: yield* session.generate({ sessionID: ctx.params.sessionID, prompt: ctx.payload.prompt }).pipe(Effect.mapError((error) => error._tag === "Session.NotFoundError" ? missingSession(error) : new ServiceUnavailableError({ message: error.message, service: "session generation" }))) } };
255
+ })).handle("session.log", Effect.fn(function* (ctx) {
256
+ yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
257
+ return session.log({ sessionID: ctx.params.sessionID, after: ctx.query.after, follow: ctx.query.follow }).pipe(Stream.orDie);
258
+ })).handle("session.interrupt", Effect.fn(function* (ctx) {
259
+ yield* session.interrupt(ctx.params.sessionID, { continue: ctx.query.continue });
260
+ return HttpApiSchema.NoContent.make();
261
+ })).handle("session.background", Effect.fn(function* (ctx) {
262
+ yield* session.background(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
263
+ return HttpApiSchema.NoContent.make();
264
+ })).handle("session.message", Effect.fn(function* (ctx) {
265
+ yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
266
+ const message = yield* session.message(ctx.params);
267
+ if (message)
268
+ return { data: message };
269
+ return yield* new MessageNotFoundError({
270
+ sessionID: ctx.params.sessionID,
271
+ messageID: ctx.params.messageID,
272
+ message: `Message not found: ${ctx.params.messageID}`
273
+ });
274
+ }));
275
+ }));
@@ -0,0 +1 @@
1
+ export declare const ShellHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.shell">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
@@ -0,0 +1,30 @@
1
+ import { Shell } from "@opencode-ai/core/shell";
2
+ import { Location } from "@opencode-ai/core/location";
3
+ import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor-service";
4
+ import { Effect } from "effect";
5
+ import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
6
+ import { ShellNotFoundError } from "@opencode-ai/protocol/errors";
7
+ import { Api } from "../api";
8
+ import { response } from "../location";
9
+ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers) => Effect.gen(function* () {
10
+ return handlers.handle("shell.list", Effect.fn(function* () {
11
+ const shell = yield* Shell.Service;
12
+ return yield* response(shell.list());
13
+ })).handle("shell.create", Effect.fn(function* (ctx) {
14
+ yield* (yield* PluginSupervisor.Service).flush;
15
+ const shell = yield* Shell.Service, location = yield* Location.Service;
16
+ return yield* response(shell.create({ ...ctx.payload, cwd: ctx.payload.cwd || location.directory }).pipe(Effect.orDie));
17
+ })).handle("shell.get", Effect.fn(function* (ctx) {
18
+ const shell = yield* Shell.Service;
19
+ return yield* response(shell.get(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
20
+ })).handle("shell.timeout", Effect.fn(function* (ctx) {
21
+ const shell = yield* Shell.Service;
22
+ return yield* response(shell.timeout(ctx.params.id, ctx.payload.timeout).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
23
+ })).handle("shell.output", Effect.fn(function* (ctx) {
24
+ const shell = yield* Shell.Service;
25
+ return yield* response(shell.output(ctx.params.id, { cursor: ctx.query.cursor, limit: ctx.query.limit }).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
26
+ })).handle("shell.remove", Effect.fn(function* (ctx) {
27
+ yield* (yield* Shell.Service).remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` })));
28
+ return HttpApiSchema.NoContent.make();
29
+ }));
30
+ }));
@@ -0,0 +1 @@
1
+ export declare const SkillHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.skill">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
@@ -0,0 +1,5 @@
1
+ import { Skill } from "@opencode-ai/core/skill";
2
+ import { HttpApiBuilder } from "effect/unstable/httpapi";
3
+ import { Api } from "../api";
4
+ import { response } from "../location";
5
+ export const SkillHandler = HttpApiBuilder.group(Api, "server.skill", (handlers) => handlers.handle("skill.list", () => response(Skill.Service.use((skill) => skill.list()))));
@@ -0,0 +1 @@
1
+ export declare const VcsHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.vcs">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
@@ -0,0 +1,14 @@
1
+ import { Vcs } from "@opencode-ai/core/vcs";
2
+ import { Effect } from "effect";
3
+ import { HttpApiBuilder } from "effect/unstable/httpapi";
4
+ import { Api } from "../api";
5
+ import { response } from "../location";
6
+ export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) => Effect.gen(function* () {
7
+ return handlers.handle("vcs.get", () => response(Effect.gen(function* () {
8
+ return yield* (yield* Vcs.Service).info();
9
+ }))).handle("vcs.status", () => response(Effect.gen(function* () {
10
+ return yield* (yield* Vcs.Service).status();
11
+ }))).handle("vcs.diff", (ctx) => response(Effect.gen(function* () {
12
+ return yield* (yield* Vcs.Service).diff(ctx.query.mode, { context: ctx.query.context });
13
+ })));
14
+ }));
@@ -0,0 +1 @@
1
+ export declare const WebSearchHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.websearch">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
@@ -0,0 +1,38 @@
1
+ import { WebSearch } from "@opencode-ai/core/websearch";
2
+ import { InvalidRequestError, ServiceUnavailableError } from "@opencode-ai/protocol/errors";
3
+ import { Effect } from "effect";
4
+ import { HttpApiBuilder } from "effect/unstable/httpapi";
5
+ import { Api } from "../api";
6
+ import { response } from "../location";
7
+ import { pluginReadiness } from "./plugin-readiness";
8
+ const awaitPlugins = pluginReadiness(() => new ServiceUnavailableError({
9
+ message: "Web search provider initialization timed out",
10
+ service: "websearch"
11
+ })).pipe(Effect.withSpan("server.websearch.awaitPlugins"));
12
+ export const WebSearchHandler = HttpApiBuilder.group(Api, "server.websearch", (handlers) => Effect.gen(function* () {
13
+ return handlers.handle("websearch.providers", Effect.fn("server.websearch.providers")(function* () {
14
+ yield* awaitPlugins;
15
+ const websearch = yield* WebSearch.Service;
16
+ return yield* response(websearch.providers());
17
+ })).handle("websearch.query", Effect.fn("server.websearch.query")(function* (request) {
18
+ yield* awaitPlugins;
19
+ const websearch = yield* WebSearch.Service;
20
+ return yield* response(websearch.query(request.payload).pipe(Effect.catchTags({
21
+ "WebSearch.ProviderRequired": () => new InvalidRequestError({
22
+ message: "Web search provider is required",
23
+ kind: "websearch_provider_required",
24
+ field: "providerID"
25
+ }),
26
+ "WebSearch.ProviderNotFound": (error) => new InvalidRequestError({
27
+ message: `Web search provider not found: ${error.providerID}`,
28
+ kind: "websearch_provider_not_found",
29
+ field: "providerID"
30
+ }),
31
+ "WebSearch.Disabled": () => new InvalidRequestError({ message: "Web search is disabled", kind: "websearch_disabled" }),
32
+ "WebSearch.Request": (error) => new ServiceUnavailableError({
33
+ message: `Web search request failed: ${error.providerID}`,
34
+ service: error.providerID
35
+ })
36
+ })));
37
+ }));
38
+ }));
@@ -0,0 +1,2 @@
1
+ import { Worktree } from "@opencode-ai/core/worktree";
2
+ export declare const WorktreeHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | Worktree.Service>;
@@ -0,0 +1,32 @@
1
+ import { Git } from "@opencode-ai/core/git";
2
+ import { Worktree } from "@opencode-ai/core/worktree";
3
+ import { WorktreeError } from "@opencode-ai/protocol/groups/worktree";
4
+ import { Effect } from "effect";
5
+ import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
6
+ import { Api } from "../api";
7
+ export const WorktreeHandler = HttpApiBuilder.group(Api, "server.worktree", (handlers) => Effect.gen(function* () {
8
+ const worktrees = yield* Worktree.Service;
9
+ return handlers.handle("worktree.list", (ctx) => worktrees.list(ctx.params.projectID)).handle("worktree.create", (ctx) => badRequest(worktrees.create({ ...ctx.payload, projectID: ctx.params.projectID }))).handle("worktree.remove", (ctx) => badRequest(worktrees.remove({ ...ctx.payload, projectID: ctx.params.projectID })).pipe(Effect.as(HttpApiSchema.NoContent.make()))).handle("worktree.refresh", (ctx) => badRequest(worktrees.refresh({ projectID: ctx.params.projectID })).pipe(Effect.as(HttpApiSchema.NoContent.make())));
10
+ }));
11
+ function badRequest(effect) {
12
+ return effect.pipe(Effect.mapError((error) => new WorktreeError({
13
+ name: "WorktreeError",
14
+ data: {
15
+ message: message(error),
16
+ forceRequired: error instanceof Git.WorktreeError ? error.forceRequired : void 0
17
+ }
18
+ })));
19
+ }
20
+ function message(error) {
21
+ if (error instanceof Worktree.SourceDirectoryNotFoundError)
22
+ return error.directory ? `Worktree source not found: ${error.directory}` : `Worktree source not found for project: ${error.projectID}`;
23
+ if (error instanceof Worktree.DestinationExistsError)
24
+ return `Worktree destination already exists: ${error.directory}`;
25
+ if (error instanceof Worktree.DirectoryUnavailableError)
26
+ return `Worktree directory unavailable: ${error.directory}`;
27
+ if (error instanceof Worktree.InvalidDirectoryError)
28
+ return `Invalid worktree directory: ${error.directory}`;
29
+ if (error instanceof Worktree.StrategyUnavailableError)
30
+ return `Worktree strategy unavailable: ${error.strategy}`;
31
+ return error.message;
32
+ }
@@ -0,0 +1,2 @@
1
+ import { Layer } from "effect";
2
+ export declare const handlers: Layer.Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.generate"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.message"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.model"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.provider"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.fs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.form"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.command"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.skill"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.event"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.agent"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.plugin"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.health"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.server"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.debug"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.pty"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.shell"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.reference"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.location"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.integration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.websearch"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.mcp"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.credential"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.vcs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.migration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.config">, never, import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("@opencode-ai/core/bus").Service | import("@opencode-ai/core/worktree").Service | import("@opencode-ai/core/session").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode-ai/core/pty/ticket").Service | import("@opencode-ai/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/permission/saved").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("./server-info").Service> | import("./pty-environment").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/project").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/database/database").Service>>;
@@ -0,0 +1,32 @@
1
+ import { Layer } from "effect";
2
+ import { GenerateHandler } from "./handlers/generate";
3
+ import { MessageHandler } from "./handlers/message";
4
+ import { ModelHandler } from "./handlers/model";
5
+ import { ProviderHandler } from "./handlers/provider";
6
+ import { SessionHandler } from "./handlers/session";
7
+ import { PermissionHandler } from "./handlers/permission";
8
+ import { FileSystemHandler } from "./handlers/fs";
9
+ import { FormHandler } from "./handlers/form";
10
+ import { CommandHandler } from "./handlers/command";
11
+ import { SkillHandler } from "./handlers/skill";
12
+ import { EventHandler } from "./handlers/event";
13
+ import { AgentHandler } from "./handlers/agent";
14
+ import { PluginHandler } from "./handlers/plugin";
15
+ import { HealthHandler } from "./handlers/health";
16
+ import { ServerHandler } from "./handlers/server";
17
+ import { DebugHandler } from "./handlers/debug";
18
+ import { PtyHandler } from "./handlers/pty";
19
+ import { ShellHandler } from "./handlers/shell";
20
+ import { ReferenceHandler } from "./handlers/reference";
21
+ import { LocationHandler } from "./handlers/location";
22
+ import { IntegrationHandler } from "./handlers/integration";
23
+ import { WebSearchHandler } from "./handlers/websearch";
24
+ import { McpHandler } from "./handlers/mcp";
25
+ import { CredentialHandler } from "./handlers/credential";
26
+ import { ProjectHandler } from "./handlers/project";
27
+ import { WorktreeHandler } from "./handlers/worktree";
28
+ import { VcsHandler } from "./handlers/vcs";
29
+ import { EventFeed } from "./event-feed";
30
+ import { MigrationHandler } from "./handlers/migration";
31
+ import { ConfigHandler } from "./handlers/config";
32
+ export const handlers = Layer.mergeAll(HealthHandler, ServerHandler, DebugHandler, MigrationHandler, LocationHandler, AgentHandler, PluginHandler, SessionHandler, MessageHandler, ModelHandler, GenerateHandler, ProviderHandler, IntegrationHandler, WebSearchHandler, McpHandler, CredentialHandler, ProjectHandler, FormHandler, PermissionHandler, FileSystemHandler, CommandHandler, SkillHandler, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, VcsHandler, ConfigHandler);
@@ -0,0 +1,23 @@
1
+ import { Location } from "@opencode-ai/core/location";
2
+ import { LocationServiceMap } from "@opencode-ai/core/location-services";
3
+ import { Effect, Layer } from "effect";
4
+ import { HttpServerRequest } from "effect/unstable/http";
5
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
6
+ export type LocationServices = Layer.Success<ReturnType<(typeof LocationServiceMap.Service)["get"]>>;
7
+ declare const LocationMiddleware_base: HttpApiMiddleware.ServiceClass<LocationMiddleware, "@opencode/HttpApiLocation", {
8
+ requires: never;
9
+ provides: Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service;
10
+ error: never;
11
+ clientError: never;
12
+ requiredForClient: false;
13
+ security: never;
14
+ }, HttpApiMiddleware.HttpApiMiddleware<Location.Service | import("@opencode-ai/core/agent").Service | import("@opencode-ai/core/aisdk").Service | import("@opencode-ai/core/form").Service | import("@opencode-ai/core/integration").Service | import("@opencode-ai/core/catalog").Service | import("@opencode-ai/core/environment/environment").Service | import("@opencode-ai/core/mcp/index").Service | import("@opencode-ai/core/shell/select").Service | import("@opencode-ai/core/command").Service | import("@opencode-ai/core/config").Service | import("@opencode-ai/core/file-mutation").Service | import("@opencode-ai/core/formatter").Service | import("@opencode-ai/core/filesystem/search").Service | import("@opencode-ai/core/filesystem").Service | import("@opencode-ai/core/model-resolver").Service | import("@opencode-ai/core/generate").Service | import("@opencode-ai/core/image").Service | import("@opencode-ai/core/plugin/supervisor-service").Service | import("@opencode-ai/core/reference").Service | import("@opencode-ai/core/websearch").Service | import("@opencode-ai/core/plugin").Service | import("@opencode-ai/core/skill").Service | import("@opencode-ai/core/tool").Service | import("@opencode-ai/core/location-mutation").Service | import("@opencode-ai/core/permission").Service | import("@opencode-ai/core/tool/read-filesystem").Service | import("@opencode-ai/core/session/instructions").Service | import("@opencode-ai/core/shell").Service | import("@opencode-ai/core/instruction-discovery").Service | import("@opencode-ai/core/session/compaction").Service | import("@opencode-ai/core/snapshot").Service | import("@opencode-ai/core/tool-output").Service | import("@opencode-ai/core/session/runner/model").Service | import("@opencode-ai/core/session/model-transport").Service | import("@opencode-ai/core/pty").Service | import("@opencode-ai/core/instructions/builtins").Service | import("@opencode-ai/core/skill/instructions").Service | import("@opencode-ai/core/reference/instructions").Service | import("@opencode-ai/core/session/instruction-entry").Service | import("@opencode-ai/core/session/generate").Service | import("@opencode-ai/core/tool/mcp").Service | import("@opencode-ai/core/session/title").Service | import("@opencode-ai/core/session/runner/index").Service | import("@opencode-ai/core/vcs").Service | import("@opencode-ai/core/filesystem/location-watcher").Service, never, never>>;
15
+ export declare class LocationMiddleware extends LocationMiddleware_base {
16
+ }
17
+ export declare function response<A, E, R>(data: Effect.Effect<A, E, R>): Effect.Effect<{
18
+ location: Location.Info;
19
+ data: A;
20
+ }, E, Location.Service | R>;
21
+ export declare function requestRef(request: HttpServerRequest.HttpServerRequest): Location.Ref;
22
+ export declare const layer: Layer.Layer<LocationMiddleware, never, LocationServiceMap.Service>;
23
+ export {};
@@ -0,0 +1,44 @@
1
+ import { Location } from "@opencode-ai/core/location";
2
+ import { LocationServiceMap } from "@opencode-ai/core/location-services";
3
+ import { AbsolutePath } from "@opencode-ai/core/schema";
4
+ import { Workspace } from "@opencode-ai/core/workspace";
5
+ import { Effect, Layer } from "effect";
6
+ import { HttpServerRequest } from "effect/unstable/http";
7
+ import { HttpApiMiddleware } from "effect/unstable/httpapi";
8
+
9
+ export class LocationMiddleware extends HttpApiMiddleware.Service()("@opencode/HttpApiLocation") {
10
+ }
11
+ export function response(data) {
12
+ return Effect.gen(function* () {
13
+ const location = yield* Location.Service;
14
+ return {
15
+ location: new Location.Info({
16
+ directory: location.directory,
17
+ workspaceID: location.workspaceID,
18
+ project: location.project
19
+ }),
20
+ data: yield* data
21
+ };
22
+ });
23
+ }
24
+ export function requestRef(request) {
25
+ const query = new URL(request.url, "http://localhost").searchParams, workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"], directory = query.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd());
26
+ return Location.Ref.make({
27
+ directory: AbsolutePath.make(directory),
28
+ workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : void 0
29
+ });
30
+ }
31
+ function decode(input) {
32
+ try {
33
+ return decodeURIComponent(input);
34
+ } catch {
35
+ return input;
36
+ }
37
+ }
38
+ export const layer = Layer.effect(LocationMiddleware, Effect.gen(function* () {
39
+ const locations = yield* LocationServiceMap.Service;
40
+ return LocationMiddleware.of((effect) => Effect.gen(function* () {
41
+ const request = yield* HttpServerRequest.HttpServerRequest;
42
+ return yield* effect.pipe(Effect.provide(locations.get(requestRef(request))));
43
+ }));
44
+ }));
@@ -0,0 +1,7 @@
1
+ import { ServerAuth } from "../auth";
2
+ import { Authorization } from "@opencode-ai/protocol/middleware/authorization";
3
+ export { Authorization } from "@opencode-ai/protocol/middleware/authorization";
4
+ import { Effect, Layer } from "effect";
5
+ import { HttpServerRequest } from "effect/unstable/http";
6
+ export declare function authorizedRequest(request: HttpServerRequest.HttpServerRequest, config: ServerAuth.Info): Effect.Effect<boolean, never, never>;
7
+ export declare const authorizationLayer: Layer.Layer<Authorization, never, ServerAuth.Config>;
@@ -0,0 +1,48 @@
1
+ import { ServerAuth } from "../auth";
2
+ import { UnauthorizedError } from "@opencode-ai/protocol/errors";
3
+ import { Authorization } from "@opencode-ai/protocol/middleware/authorization";
4
+ export { Authorization } from "@opencode-ai/protocol/middleware/authorization";
5
+ import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty";
6
+ import { Effect, Encoding, Layer, Redacted } from "effect";
7
+ import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
8
+ const AUTH_TOKEN_QUERY = "auth_token", WWW_AUTHENTICATE = 'Basic realm="Secure Area"';
9
+ function emptyCredential() {
10
+ return { username: "", password: Redacted.make("") };
11
+ }
12
+ function decodeCredential(input) {
13
+ return Effect.fromResult(Encoding.decodeBase64String(input)).pipe(Effect.match({
14
+ onFailure: emptyCredential,
15
+ onSuccess: (header) => {
16
+ const separator = header.indexOf(":");
17
+ if (separator === -1)
18
+ return emptyCredential();
19
+ return { username: header.slice(0, separator), password: Redacted.make(header.slice(separator + 1)) };
20
+ }
21
+ }));
22
+ }
23
+ function credentialFromRequest(request) {
24
+ const token = new URL(request.url, "http://localhost").searchParams.get(AUTH_TOKEN_QUERY);
25
+ if (token)
26
+ return decodeCredential(token);
27
+ const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "");
28
+ if (match)
29
+ return decodeCredential(match[1]);
30
+ return Effect.succeed(emptyCredential());
31
+ }
32
+ export function authorizedRequest(request, config) {
33
+ return credentialFromRequest(request).pipe(Effect.map((credential) => ServerAuth.authorized(credential, config)));
34
+ }
35
+ export const authorizationLayer = Layer.effect(Authorization, Effect.gen(function* () {
36
+ const config = yield* ServerAuth.Config;
37
+ if (!ServerAuth.required(config))
38
+ return Authorization.of((effect) => effect);
39
+ return Authorization.of((effect) => Effect.gen(function* () {
40
+ const request = yield* HttpServerRequest.HttpServerRequest;
41
+ if (hasPtyConnectTicketURL(new URL(request.url, "http://localhost")))
42
+ return yield* effect;
43
+ if (yield* authorizedRequest(request, config))
44
+ return yield* effect;
45
+ yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)));
46
+ return yield* new UnauthorizedError({ message: "Authentication required" });
47
+ }));
48
+ }));