@opencode/server 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.
- package/dist/api.d.ts +63 -38
- package/dist/handlers/config.js +4 -9
- package/dist/handlers/credential.d.ts +2 -1
- package/dist/handlers/credential.js +4 -4
- package/dist/handlers/form.d.ts +1 -4
- package/dist/handlers/form.js +6 -58
- package/dist/handlers/integration.js +69 -11
- package/dist/handlers/location.js +0 -1
- package/dist/handlers/model.js +5 -5
- package/dist/handlers/permission.d.ts +1 -1
- package/dist/handlers/permission.js +1 -4
- package/dist/handlers/plugin.js +3 -7
- package/dist/handlers/project.d.ts +1 -1
- package/dist/handlers/project.js +1 -6
- package/dist/handlers/provider.js +4 -4
- package/dist/handlers/server.js +7 -2
- package/dist/handlers/session-error.d.ts +5 -1
- package/dist/handlers/session-error.js +14 -1
- package/dist/handlers/session.d.ts +1 -1
- package/dist/handlers/session.js +68 -45
- package/dist/handlers/shell.js +1 -4
- package/dist/handlers/vcs.js +1 -1
- package/dist/handlers/worktree.d.ts +2 -1
- package/dist/handlers/worktree.js +10 -12
- package/dist/handlers.d.ts +1 -1
- package/dist/handlers.js +1 -3
- package/dist/location.js +2 -5
- package/dist/process.js +14 -14
- package/dist/routes.d.ts +3 -2
- package/dist/routes.js +3 -1
- package/package.json +8 -8
- package/dist/handlers/health.d.ts +0 -2
- package/dist/handlers/health.js +0 -11
- package/dist/handlers/workspace.d.ts +0 -2
- package/dist/handlers/workspace.js +0 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Session } from "@opencode/core/session";
|
|
2
|
-
import { SessionNotFoundError, UnknownError } from "@opencode/protocol/errors";
|
|
2
|
+
import { MessageNotFoundError, SessionNotFoundError, UnknownError } from "@opencode/protocol/errors";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
4
|
export function missingSession(error) {
|
|
5
5
|
return new SessionNotFoundError({
|
|
@@ -7,7 +7,20 @@ export function missingSession(error) {
|
|
|
7
7
|
message: `Session not found: ${error.sessionID}`
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
+
export function missingMessage(error) {
|
|
11
|
+
return new MessageNotFoundError({
|
|
12
|
+
sessionID: error.sessionID,
|
|
13
|
+
messageID: error.messageID,
|
|
14
|
+
message: `Message not found: ${error.messageID}`
|
|
15
|
+
});
|
|
16
|
+
}
|
|
10
17
|
export function failedMessageDecode(error) {
|
|
11
18
|
const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
|
|
12
19
|
return Effect.logError("failed to decode session message").pipe(Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), Effect.andThen(Effect.fail(new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }))));
|
|
13
20
|
}
|
|
21
|
+
export function failedSnapshot(operation, sessionID) {
|
|
22
|
+
return (error) => {
|
|
23
|
+
const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
|
|
24
|
+
return Effect.logError(`failed to ${operation}`, { cause: error }).pipe(Effect.annotateLogs({ ref, sessionID }), Effect.andThen(Effect.fail(new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }))));
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Session } from "@opencode/core/session";
|
|
2
2
|
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
3
|
-
export declare const SessionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session">, never,
|
|
3
|
+
export declare const SessionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session">, never, Session.Service | import("../middleware/session-location").SessionLocationMiddleware | SessionTransfer.Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../middleware/form-location").FormLocationMiddleware)>;
|
package/dist/handlers/session.js
CHANGED
|
@@ -3,6 +3,7 @@ import { SessionStats } from "@opencode/core/session/stats";
|
|
|
3
3
|
import { SessionTitle } from "@opencode/core/session/title";
|
|
4
4
|
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
5
5
|
import { InstructionEntry } from "@opencode/core/session/instruction-entry";
|
|
6
|
+
import { Form } from "@opencode/core/form";
|
|
6
7
|
import { DateTime, Effect, Stream } from "effect";
|
|
7
8
|
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
8
9
|
import { Api } from "../api";
|
|
@@ -11,26 +12,35 @@ import {
|
|
|
11
12
|
ConflictError,
|
|
12
13
|
CommandExecutionError,
|
|
13
14
|
CommandNotFoundError,
|
|
15
|
+
FormAlreadySettledError,
|
|
16
|
+
FormInvalidAnswerError,
|
|
17
|
+
FormNotFoundError,
|
|
14
18
|
InvalidRequestError,
|
|
15
19
|
InvalidCursorError,
|
|
16
20
|
MessageNotFoundError,
|
|
17
21
|
ServiceUnavailableError,
|
|
18
22
|
SessionBusyError,
|
|
19
|
-
SkillNotFoundError
|
|
20
|
-
UnknownError
|
|
23
|
+
SkillNotFoundError
|
|
21
24
|
} from "@opencode/protocol/errors";
|
|
22
25
|
import { AbsolutePath } from "@opencode/core/schema";
|
|
23
|
-
import { failedMessageDecode, missingSession } from "./session-error";
|
|
26
|
+
import { failedMessageDecode, failedSnapshot, missingMessage, missingSession } from "./session-error";
|
|
24
27
|
const DefaultSessionsLimit = 50;
|
|
28
|
+
function missingForm(id) {
|
|
29
|
+
return new FormNotFoundError({ id, message: `Form not found: ${id}` });
|
|
30
|
+
}
|
|
25
31
|
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) => Effect.gen(function* () {
|
|
26
|
-
const session = yield* Session.Service, transfer = yield* SessionTransfer.Service,
|
|
32
|
+
const session = yield* Session.Service, transfer = yield* SessionTransfer.Service, requireOwnedForm = Effect.fnUntraced(function* (sessionID, formID) {
|
|
33
|
+
const form = yield* Form.Service, info = yield* form.get(formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(formID)));
|
|
34
|
+
if (info.sessionID !== sessionID)
|
|
35
|
+
return yield* missingForm(formID);
|
|
36
|
+
return { form, info };
|
|
37
|
+
}), busySession = (error) => new SessionBusyError({
|
|
27
38
|
sessionID: error.sessionID,
|
|
28
39
|
message: `Session is busy: ${error.sessionID}`
|
|
29
40
|
}), 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()));
|
|
30
41
|
return handlers.handle("session.list", Effect.fn(function* (ctx) {
|
|
31
42
|
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({
|
|
32
43
|
...query,
|
|
33
|
-
workspaceID: query.workspace,
|
|
34
44
|
limit: ctx.query.limit ?? DefaultSessionsLimit
|
|
35
45
|
})).data, first = sessions[0], last = sessions.at(-1);
|
|
36
46
|
return {
|
|
@@ -105,7 +115,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
105
115
|
data: yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
|
|
106
116
|
};
|
|
107
117
|
})).handle("session.view", Effect.fn(function* (ctx) {
|
|
108
|
-
yield* session.view({ sessionID: ctx.params.sessionID, idle: ctx.payload.idle }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
118
|
+
yield* session.view({ sessionID: ctx.params.sessionID, idle: DateTime.toEpochMillis(ctx.payload.idle) }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
109
119
|
return HttpApiSchema.NoContent.make();
|
|
110
120
|
})).handle("session.remove", Effect.fn(function* (ctx) {
|
|
111
121
|
yield* session.remove(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
@@ -115,11 +125,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
115
125
|
return HttpApiSchema.NoContent.make();
|
|
116
126
|
})).handle("session.fork", Effect.fn(function* (ctx) {
|
|
117
127
|
return {
|
|
118
|
-
data: yield* session.fork({ sessionID: ctx.params.sessionID,
|
|
119
|
-
sessionID: error.sessionID,
|
|
120
|
-
messageID: error.messageID,
|
|
121
|
-
message: `Message not found: ${error.messageID}`
|
|
122
|
-
})), Effect.catchTag("Session.ForkEmptyError", (error) => new InvalidRequestError({ message: error.message, kind: "empty_session" })))
|
|
128
|
+
data: yield* session.fork({ sessionID: ctx.params.sessionID, before: ctx.payload.before }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", missingMessage), Effect.catchTag("Session.ForkEmptyError", (error) => new InvalidRequestError({ message: error.message, kind: "empty_session" })))
|
|
123
129
|
};
|
|
124
130
|
})).handle("session.switchAgent", Effect.fn(function* (ctx) {
|
|
125
131
|
yield* session.switchAgent({ sessionID: ctx.params.sessionID, agent: ctx.payload.agent }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
@@ -127,18 +133,20 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
127
133
|
})).handle("session.switchModel", Effect.fn(function* (ctx) {
|
|
128
134
|
yield* session.switchModel({ sessionID: ctx.params.sessionID, model: ctx.payload.model }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
129
135
|
return HttpApiSchema.NoContent.make();
|
|
130
|
-
})).handle("session.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
})).handle("session.update", Effect.fn(function* (ctx) {
|
|
137
|
+
yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
138
|
+
if (ctx.payload.title !== void 0)
|
|
139
|
+
if (ctx.payload.title)
|
|
140
|
+
yield* session.rename({ sessionID: ctx.params.sessionID, title: ctx.payload.title }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
141
|
+
else
|
|
142
|
+
yield* (yield* SessionTitle.Service).generate(ctx.params.sessionID);
|
|
143
|
+
if (ctx.payload.permissions !== void 0)
|
|
144
|
+
yield* session.setPermissions({ sessionID: ctx.params.sessionID, permissions: ctx.payload.permissions }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
136
145
|
return HttpApiSchema.NoContent.make();
|
|
137
146
|
})).handle("session.move", Effect.fn(function* (ctx) {
|
|
138
147
|
yield* session.move({
|
|
139
148
|
sessionID: ctx.params.sessionID,
|
|
140
149
|
directory: ctx.payload.directory,
|
|
141
|
-
workspaceID: ctx.payload.workspaceID,
|
|
142
150
|
delivery: ctx.payload.delivery
|
|
143
151
|
}).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}` }))));
|
|
144
152
|
return HttpApiSchema.NoContent.make();
|
|
@@ -162,7 +170,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
162
170
|
})).handle("session.command", Effect.fn(function* (ctx) {
|
|
163
171
|
yield* session.command({
|
|
164
172
|
sessionID: ctx.params.sessionID,
|
|
165
|
-
command: ctx.payload.
|
|
173
|
+
command: ctx.payload.name,
|
|
166
174
|
text: ctx.payload.text,
|
|
167
175
|
files: ctx.payload.files,
|
|
168
176
|
agents: ctx.payload.agents,
|
|
@@ -179,8 +187,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
179
187
|
})).handle("session.skill", Effect.fn(function* (ctx) {
|
|
180
188
|
yield* session.skill({
|
|
181
189
|
sessionID: ctx.params.sessionID,
|
|
182
|
-
|
|
183
|
-
skill: ctx.payload.skill,
|
|
190
|
+
skill: ctx.payload.id,
|
|
184
191
|
resume: ctx.payload.resume
|
|
185
192
|
}).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.SkillNotFoundError", (error) => Effect.fail(new SkillNotFoundError({ skill: error.skill, message: `Skill not found: ${error.skill}` }))));
|
|
186
193
|
return HttpApiSchema.NoContent.make();
|
|
@@ -217,27 +224,11 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
217
224
|
files: ctx.payload.files
|
|
218
225
|
});
|
|
219
226
|
return {
|
|
220
|
-
data: yield* session.revert.stage({ ...ctx.params, ...ctx.payload }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", (
|
|
221
|
-
sessionID: error.sessionID,
|
|
222
|
-
messageID: error.messageID,
|
|
223
|
-
message: `Message not found: ${error.messageID}`
|
|
224
|
-
})), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", (error) => {
|
|
225
|
-
const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
|
|
226
|
-
return Effect.logError("failed to stage session revert", { cause: error }).pipe(Effect.andThen(Effect.fail(new UnknownError({
|
|
227
|
-
message: "Unexpected server error. Check server logs for details.",
|
|
228
|
-
ref
|
|
229
|
-
}))));
|
|
230
|
-
}))
|
|
227
|
+
data: yield* session.revert.stage({ ...ctx.params, ...ctx.payload }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", missingMessage), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", failedSnapshot("stage session revert", ctx.params.sessionID)))
|
|
231
228
|
};
|
|
232
229
|
})).handle("session.revert.clear", Effect.fn(function* (ctx) {
|
|
233
230
|
yield* Effect.log("session.revert.clear", { sessionID: ctx.params.sessionID });
|
|
234
|
-
yield* session.revert.clear(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", (
|
|
235
|
-
const ref = `err_${crypto.randomUUID().slice(0, 8)}`;
|
|
236
|
-
return Effect.logError("failed to clear session revert", { cause: error }).pipe(Effect.andThen(Effect.fail(new UnknownError({
|
|
237
|
-
message: "Unexpected server error. Check server logs for details.",
|
|
238
|
-
ref
|
|
239
|
-
}))));
|
|
240
|
-
}));
|
|
231
|
+
yield* session.revert.clear(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.BusyError", busySession), Effect.catchTag("Snapshot.Error", failedSnapshot("clear session revert", ctx.params.sessionID)));
|
|
241
232
|
return HttpApiSchema.NoContent.make();
|
|
242
233
|
})).handle("session.revert.commit", Effect.fn(function* (ctx) {
|
|
243
234
|
yield* Effect.log("session.revert.commit", { sessionID: ctx.params.sessionID });
|
|
@@ -247,16 +238,19 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
247
238
|
return {
|
|
248
239
|
data: yield* session.context(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageDecodeError", failedMessageDecode))
|
|
249
240
|
};
|
|
241
|
+
})).handle("session.diff", Effect.fn(function* (ctx) {
|
|
242
|
+
return {
|
|
243
|
+
data: yield* session.diff({ sessionID: ctx.params.sessionID, ...ctx.query }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", missingMessage), Effect.catchTag("Session.TurnRangeError", (error) => new InvalidRequestError({ message: error.message, field: error.field })), Effect.catchTag("Snapshot.Error", failedSnapshot("diff session turn", ctx.params.sessionID)))
|
|
244
|
+
};
|
|
250
245
|
})).handle("session.inbox.list", Effect.fn(function* (ctx) {
|
|
251
246
|
return {
|
|
252
247
|
data: yield* session.inbox(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
|
|
253
248
|
};
|
|
254
249
|
})).handle("session.inbox.cancel", Effect.fn(function* (ctx) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return yield* pendingMutation(session.queueInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input is no longer a steer");
|
|
250
|
+
yield* session.cancelInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.InboxConflictError", () => Effect.void));
|
|
251
|
+
return HttpApiSchema.NoContent.make();
|
|
252
|
+
})).handle("session.inbox.update", Effect.fn(function* (ctx) {
|
|
253
|
+
return yield* pendingMutation(ctx.payload.delivery === "steer" ? session.steerInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }) : session.queueInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), `Pending input cannot change to ${ctx.payload.delivery}`);
|
|
260
254
|
})).handle("session.instructions.entry.list", Effect.fn(function* (ctx) {
|
|
261
255
|
return { data: yield* (yield* InstructionEntry.Service).list(ctx.params.sessionID) };
|
|
262
256
|
})).handle("session.instructions.entry.put", Effect.fn(function* (ctx) {
|
|
@@ -271,7 +265,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
271
265
|
yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
272
266
|
return session.log({ sessionID: ctx.params.sessionID, after: ctx.query.after, follow: ctx.query.follow }).pipe(Stream.orDie);
|
|
273
267
|
})).handle("session.interrupt", Effect.fn(function* (ctx) {
|
|
274
|
-
return { interrupted: yield* session.interrupt(ctx.params.sessionID, {
|
|
268
|
+
return { interrupted: yield* session.interrupt(ctx.params.sessionID, { resume: ctx.query.resume }) };
|
|
275
269
|
})).handle("session.background", Effect.fn(function* (ctx) {
|
|
276
270
|
yield* session.background(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
277
271
|
return HttpApiSchema.NoContent.make();
|
|
@@ -285,5 +279,34 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|
|
285
279
|
messageID: ctx.params.messageID,
|
|
286
280
|
message: `Message not found: ${ctx.params.messageID}`
|
|
287
281
|
});
|
|
282
|
+
})).handle("session.form.list", Effect.fn(function* (ctx) {
|
|
283
|
+
return { data: yield* (yield* Form.Service).list({ sessionID: ctx.params.sessionID }) };
|
|
284
|
+
})).handle("session.form.create", Effect.fn(function* (ctx) {
|
|
285
|
+
return { data: yield* (yield* Form.Service).create({
|
|
286
|
+
id: ctx.payload.id,
|
|
287
|
+
sessionID: ctx.params.sessionID,
|
|
288
|
+
title: ctx.payload.title,
|
|
289
|
+
metadata: ctx.payload.metadata,
|
|
290
|
+
fields: ctx.payload.fields
|
|
291
|
+
}).pipe(Effect.catchTags({
|
|
292
|
+
"Form.AlreadyExistsError": (error) => new ConflictError({ resource: error.id, message: error.message }),
|
|
293
|
+
"Form.InvalidFormError": (error) => new InvalidRequestError({ message: error.message, field: "fields" })
|
|
294
|
+
})) };
|
|
295
|
+
})).handle("session.form.get", Effect.fn(function* (ctx) {
|
|
296
|
+
const owned = yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID), state = yield* owned.form.state(ctx.params.formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(ctx.params.formID)));
|
|
297
|
+
return { data: { ...owned.info, state } };
|
|
298
|
+
})).handle("session.form.reply", Effect.fn(function* (ctx) {
|
|
299
|
+
yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.reply({ id: ctx.params.formID, answer: ctx.payload.answer }).pipe(Effect.catchTags({
|
|
300
|
+
"Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
|
|
301
|
+
"Form.InvalidAnswerError": (error) => new FormInvalidAnswerError({ id: error.id, message: error.message }),
|
|
302
|
+
"Form.NotFoundError": () => missingForm(ctx.params.formID)
|
|
303
|
+
}));
|
|
304
|
+
return HttpApiSchema.NoContent.make();
|
|
305
|
+
})).handle("session.form.cancel", Effect.fn(function* (ctx) {
|
|
306
|
+
yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.cancel(ctx.params.formID).pipe(Effect.catchTags({
|
|
307
|
+
"Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
|
|
308
|
+
"Form.NotFoundError": () => missingForm(ctx.params.formID)
|
|
309
|
+
}));
|
|
310
|
+
return HttpApiSchema.NoContent.make();
|
|
288
311
|
}));
|
|
289
312
|
}));
|
package/dist/handlers/shell.js
CHANGED
|
@@ -15,14 +15,11 @@ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers)
|
|
|
15
15
|
})).handle("shell.get", Effect.fn(function* (ctx) {
|
|
16
16
|
const shell = yield* Shell.Service;
|
|
17
17
|
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}` }))));
|
|
18
|
-
})).handle("shell.timeout", Effect.fn(function* (ctx) {
|
|
19
|
-
const shell = yield* Shell.Service;
|
|
20
|
-
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}` }))));
|
|
21
18
|
})).handle("shell.output", Effect.fn(function* (ctx) {
|
|
22
19
|
const shell = yield* Shell.Service;
|
|
23
20
|
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}` }))));
|
|
24
21
|
})).handle("shell.remove", Effect.fn(function* (ctx) {
|
|
25
|
-
yield* (yield* Shell.Service).remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () =>
|
|
22
|
+
yield* (yield* Shell.Service).remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.void));
|
|
26
23
|
return HttpApiSchema.NoContent.make();
|
|
27
24
|
}));
|
|
28
25
|
}));
|
package/dist/handlers/vcs.js
CHANGED
|
@@ -11,7 +11,7 @@ export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) =>
|
|
|
11
11
|
return yield* (yield* Vcs.Service).base().pipe(Effect.mapError((error) => new ServiceUnavailableError({ service: "vcs", message: error.message })));
|
|
12
12
|
}))).handle("vcs.status", () => response(Effect.gen(function* () {
|
|
13
13
|
return yield* (yield* Vcs.Service).status();
|
|
14
|
-
}))).handle("vcs.
|
|
14
|
+
}))).handle("vcs.branch.list", (ctx) => response(Effect.gen(function* () {
|
|
15
15
|
return yield* (yield* Vcs.Service).branches({ search: ctx.query.search, limit: Math.min(ctx.query.limit ?? 50, 100) });
|
|
16
16
|
}))).handle("vcs.diff", (ctx) => response(Effect.gen(function* () {
|
|
17
17
|
return yield* (yield* Vcs.Service).diff(ctx.query.mode, { context: ctx.query.context, base: ctx.query.base }).pipe(Effect.mapError((error) => new ServiceUnavailableError({ service: "vcs", message: error.message })));
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { Worktree } from "@opencode/core/worktree";
|
|
2
|
+
export declare const WorktreeHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | Worktree.Service>;
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { Git } from "@opencode/core/git";
|
|
2
2
|
import { Worktree } from "@opencode/core/worktree";
|
|
3
|
-
import {
|
|
3
|
+
import { Project } from "@opencode/core/project";
|
|
4
|
+
import { ProjectNotFoundError } from "@opencode/protocol/errors";
|
|
4
5
|
import { WorktreeError } from "@opencode/protocol/groups/worktree";
|
|
5
6
|
import { Effect } from "effect";
|
|
6
7
|
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
|
|
7
8
|
import { Api } from "../api";
|
|
8
|
-
export const WorktreeHandler = HttpApiBuilder.group(Api, "server.worktree", (handlers) =>
|
|
9
|
-
|
|
10
|
-
return Effect.
|
|
11
|
-
|
|
12
|
-
yield* plugins.awaitActivation;
|
|
13
|
-
return yield* action(worktrees);
|
|
14
|
-
}).pipe(badRequest);
|
|
15
|
-
}
|
|
9
|
+
export const WorktreeHandler = HttpApiBuilder.group(Api, "server.worktree", (handlers) => Effect.gen(function* () {
|
|
10
|
+
const worktrees = yield* Worktree.Service;
|
|
11
|
+
return handlers.handle("worktree.list", (ctx) => worktrees.list(ctx.query).pipe(Effect.catchTag("Project.NotFoundError", missingProject))).handle("worktree.create", (ctx) => worktrees.create(ctx.payload).pipe(badRequest)).handle("worktree.remove", (ctx) => worktrees.remove(ctx.payload).pipe(badRequest, Effect.as(HttpApiSchema.NoContent.make()))).handle("worktree.refresh", (ctx) => worktrees.refresh(ctx.payload).pipe(badRequest, Effect.as(HttpApiSchema.NoContent.make())));
|
|
12
|
+
}));
|
|
16
13
|
function badRequest(effect) {
|
|
17
|
-
return effect.pipe(Effect.mapError((error) => new WorktreeError({
|
|
14
|
+
return effect.pipe(Effect.catchTag("Project.NotFoundError", missingProject), Effect.mapError((error) => error instanceof ProjectNotFoundError ? error : new WorktreeError({
|
|
18
15
|
name: "WorktreeError",
|
|
19
16
|
data: {
|
|
20
17
|
message: message(error),
|
|
@@ -22,6 +19,9 @@ function badRequest(effect) {
|
|
|
22
19
|
}
|
|
23
20
|
})));
|
|
24
21
|
}
|
|
22
|
+
function missingProject(error) {
|
|
23
|
+
return Effect.fail(new ProjectNotFoundError({ projectID: error.projectID, message: `Project not found: ${error.projectID}` }));
|
|
24
|
+
}
|
|
25
25
|
function message(error) {
|
|
26
26
|
if (error instanceof Worktree.SourceDirectoryNotFoundError)
|
|
27
27
|
return error.directory ? `Worktree source not found: ${error.directory}` : `Worktree source not found for project: ${error.projectID}`;
|
|
@@ -33,7 +33,5 @@ function message(error) {
|
|
|
33
33
|
return `Invalid worktree directory: ${error.directory}`;
|
|
34
34
|
if (error instanceof Worktree.StrategyUnavailableError)
|
|
35
35
|
return `Worktree strategy unavailable: ${error.strategy}`;
|
|
36
|
-
if (error instanceof Worktree.UnsupportedLocationError)
|
|
37
|
-
return "Worktree operations only support local locations";
|
|
38
36
|
return error.message;
|
|
39
37
|
}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
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.rpc"> | 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.
|
|
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.rpc"> | 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.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.experimental"> | 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/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("@opencode/util/global").Service | import("@opencode/core/bus").Service | import("@opencode/core/persistent-pty/index").Service | import("@opencode/core/location-service-map").Service | import("@opencode/core/session").Service | import("@opencode/core/instance").Service | import("@opencode/core/worktree").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode/core/pty/ticket").Service | import("@opencode/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/permission/saved").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/plugin/update").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/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/credential").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/project").Service>>;
|
package/dist/handlers.js
CHANGED
|
@@ -13,7 +13,6 @@ import { RpcHandler } from "./handlers/rpc";
|
|
|
13
13
|
import { EventHandler } from "./handlers/event";
|
|
14
14
|
import { AgentHandler } from "./handlers/agent";
|
|
15
15
|
import { PluginHandler } from "./handlers/plugin";
|
|
16
|
-
import { HealthHandler } from "./handlers/health";
|
|
17
16
|
import { ServerHandler } from "./handlers/server";
|
|
18
17
|
import { DebugHandler } from "./handlers/debug";
|
|
19
18
|
import { PtyHandler } from "./handlers/pty";
|
|
@@ -31,5 +30,4 @@ import { VcsHandler } from "./handlers/vcs";
|
|
|
31
30
|
import { EventFeed } from "./event-feed";
|
|
32
31
|
import { MigrationHandler } from "./handlers/migration";
|
|
33
32
|
import { ConfigHandler } from "./handlers/config";
|
|
34
|
-
|
|
35
|
-
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, RpcHandler, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, PersistentPtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, WorkspaceHandler, VcsHandler, ConfigHandler);
|
|
33
|
+
export const handlers = Layer.mergeAll(ServerHandler, DebugHandler, MigrationHandler, LocationHandler, AgentHandler, PluginHandler, SessionHandler, MessageHandler, ModelHandler, GenerateHandler, ProviderHandler, IntegrationHandler, WebSearchHandler, McpHandler, CredentialHandler, ProjectHandler, FormHandler, PermissionHandler, FileSystemHandler, CommandHandler, SkillHandler, RpcHandler, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, PersistentPtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, VcsHandler, ConfigHandler);
|
package/dist/location.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Location } from "@opencode/core/location";
|
|
|
2
2
|
import { LocationServiceMap } from "@opencode/core/location-services";
|
|
3
3
|
import { AbsolutePath } from "@opencode/core/schema";
|
|
4
4
|
import { Session } from "@opencode/core/session";
|
|
5
|
-
import { Workspace } from "@opencode/core/workspace";
|
|
6
5
|
import { InvalidRequestError } from "@opencode/protocol/errors";
|
|
7
6
|
import { Effect, Layer, Schema } from "effect";
|
|
8
7
|
import { HttpServerRequest } from "effect/unstable/http";
|
|
@@ -17,7 +16,6 @@ export function response(data) {
|
|
|
17
16
|
return {
|
|
18
17
|
location: new Location.Info({
|
|
19
18
|
directory: location.directory,
|
|
20
|
-
workspaceID: location.workspaceID,
|
|
21
19
|
project: location.project
|
|
22
20
|
}),
|
|
23
21
|
data: yield* data
|
|
@@ -30,10 +28,9 @@ export const sessionInfo = Effect.fnUntraced(function* (sessions, sessionID) {
|
|
|
30
28
|
return yield* sessions.get(id).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
|
|
31
29
|
});
|
|
32
30
|
export function requestRef(request) {
|
|
33
|
-
const
|
|
31
|
+
const directory = new URL(request.url, "http://localhost").searchParams.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd());
|
|
34
32
|
return Location.Ref.make({
|
|
35
|
-
directory: AbsolutePath.make(directory)
|
|
36
|
-
workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : void 0
|
|
33
|
+
directory: AbsolutePath.make(directory)
|
|
37
34
|
});
|
|
38
35
|
}
|
|
39
36
|
function decode(input) {
|
package/dist/process.js
CHANGED
|
@@ -27,8 +27,14 @@ export const start = Effect.fn("ServerProcess.start")(function* (options, lifecy
|
|
|
27
27
|
const password = options.password;
|
|
28
28
|
if (!password)
|
|
29
29
|
return yield* Effect.fail(Error("Missing server password"));
|
|
30
|
-
const hostname = options.hostname ?? "127.0.0.1", port = Option.fromNullishOr(options.port), shutdown = yield* Latch.make(), status = yield* Status.make(), bound = yield* listen({ hostname, port }),
|
|
31
|
-
|
|
30
|
+
const hostname = options.hostname ?? "127.0.0.1", port = Option.fromNullishOr(options.port), shutdown = yield* Latch.make(), status = yield* Status.make(), bound = yield* listen({ hostname, port }), urls = () => {
|
|
31
|
+
const address = bound.server.address();
|
|
32
|
+
if (address === null || typeof address === "string")
|
|
33
|
+
return [];
|
|
34
|
+
const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
|
|
35
|
+
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
|
|
36
|
+
}, application = yield* Ref.make(Option.none());
|
|
37
|
+
yield* bound.http.serve(dispatch(password, status, application, options.app?.version ?? "unknown", urls).pipe(HttpMiddleware.cors({ allowedOrigins: (origin) => isAllowedCorsOrigin(origin, options), maxAge: 86400 })), errorResponseLogger).pipe(withoutParentSpan);
|
|
32
38
|
if (lifecycle)
|
|
33
39
|
yield* lifecycle.onListen(bound.http.address, shutdown.open.pipe(Effect.asVoid)).pipe(Effect.flatMap((cleanup) => Effect.addFinalizer(() => Scope.close(bound.scope, Exit.void).pipe(Effect.andThen(cleanup)))), Effect.uninterruptible);
|
|
34
40
|
const parentScope = yield* Scope.Scope, applicationScope = yield* Scope.fork(parentScope);
|
|
@@ -37,13 +43,7 @@ export const start = Effect.fn("ServerProcess.start")(function* (options, lifecy
|
|
|
37
43
|
const context = yield* Layer.buildWithScope(createRoutes({
|
|
38
44
|
...options,
|
|
39
45
|
password
|
|
40
|
-
}, ()
|
|
41
|
-
const address = bound.server.address();
|
|
42
|
-
if (address === null || typeof address === "string")
|
|
43
|
-
return [];
|
|
44
|
-
const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
|
|
45
|
-
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
|
|
46
|
-
}).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)), applicationScope);
|
|
46
|
+
}, urls).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)), applicationScope);
|
|
47
47
|
if (lifecycle)
|
|
48
48
|
yield* installRestartContinuity(Context.get(context, SessionRestart.Service)).pipe(Effect.provideService(Scope.Scope, applicationScope));
|
|
49
49
|
const app = Context.get(context, HttpRouter.HttpRouter).asHttpEffect().pipe(HttpMiddleware.compression(), Effect.provideService(HttpPlatform.HttpPlatform, Context.get(context, HttpPlatform.HttpPlatform)));
|
|
@@ -87,14 +87,14 @@ function addressInUse(error) {
|
|
|
87
87
|
const cause = error.cause;
|
|
88
88
|
return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "EADDRINUSE";
|
|
89
89
|
}
|
|
90
|
-
function dispatch(password, status, application, version) {
|
|
90
|
+
function dispatch(password, status, application, version, urls) {
|
|
91
91
|
const auth = ServerAuth.Config.of({ password: Option.some(password), username: "opencode" });
|
|
92
92
|
return Effect.gen(function* () {
|
|
93
93
|
const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
|
|
94
|
-
if (request.method === "GET" && url.pathname === "/api/
|
|
94
|
+
if (request.method === "GET" && url.pathname === "/api/status") {
|
|
95
95
|
if (!(yield* authorizedRequest(request, auth)))
|
|
96
96
|
return unauthorized();
|
|
97
|
-
return yield*
|
|
97
|
+
return yield* statusResponse(status, version, urls);
|
|
98
98
|
}
|
|
99
99
|
const state = yield* status.current, app = yield* Ref.get(application), ready = state.type === "ready" && Option.isSome(app);
|
|
100
100
|
if ((!ready || !hasPtyConnectTicketURL(url) && !hasPersistentPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
|
|
@@ -110,9 +110,9 @@ function unauthorized() {
|
|
|
110
110
|
headers: { "www-authenticate": 'Basic realm="Secure Area"' }
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
|
-
const
|
|
113
|
+
const statusResponse = Effect.fnUntraced(function* (status, version, urls) {
|
|
114
114
|
const state = yield* status.current;
|
|
115
|
-
return HttpServerResponse.jsonUnsafe({
|
|
115
|
+
return HttpServerResponse.jsonUnsafe({ version, pid: process.pid, urls: urls() }, {
|
|
116
116
|
status: state.type === "ready" ? 200 : state.type === "failed" ? 500 : 503,
|
|
117
117
|
headers: state.type === "starting" || state.type === "stopping" ? { "retry-after": "1" } : void 0
|
|
118
118
|
});
|
package/dist/routes.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
|
7
7
|
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
8
8
|
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
9
9
|
import { Project } from "@opencode/core/project";
|
|
10
|
+
import { Worktree } from "@opencode/core/worktree";
|
|
10
11
|
import { Session } from "@opencode/core/session";
|
|
11
12
|
import { Instance } from "@opencode/core/instance/service";
|
|
12
13
|
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
@@ -23,7 +24,7 @@ import { HttpRouter } from "effect/unstable/http";
|
|
|
23
24
|
import { Layer } from "effect";
|
|
24
25
|
import { PtyEnvironment } from "./pty-environment";
|
|
25
26
|
import type { ServerOptions } from "./options";
|
|
26
|
-
export declare function createRoutes(options?: ServerOptions, serviceURLs?: () => ReadonlyArray<string>, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
27
|
+
export declare function createRoutes(options?: ServerOptions, serviceURLs?: () => ReadonlyArray<string>, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | Worktree.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
27
28
|
type InstanceNode = (replacements: () => LayerNode.Replacements) => LayerNode.Provider<Instance.Service, never, typeof Node.tags.values.global>;
|
|
28
|
-
export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements, instances?: InstanceNode): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
29
|
+
export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements, instances?: InstanceNode): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | Worktree.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
|
|
29
30
|
export {};
|
package/dist/routes.js
CHANGED
|
@@ -14,6 +14,7 @@ import { PermissionSaved } from "@opencode/core/permission/saved";
|
|
|
14
14
|
import { PtyTicket } from "@opencode/core/pty/ticket";
|
|
15
15
|
import { PersistentPty } from "@opencode/core/persistent-pty";
|
|
16
16
|
import { Project } from "@opencode/core/project";
|
|
17
|
+
import { Worktree } from "@opencode/core/worktree";
|
|
17
18
|
import { Session } from "@opencode/core/session";
|
|
18
19
|
import { Instance } from "@opencode/core/instance/service";
|
|
19
20
|
import { SessionTransfer } from "@opencode/core/session/transfer";
|
|
@@ -53,6 +54,7 @@ const applicationServiceNodes = [
|
|
|
53
54
|
httpClient,
|
|
54
55
|
Job.node,
|
|
55
56
|
Project.node,
|
|
57
|
+
Worktree.node,
|
|
56
58
|
Session.node,
|
|
57
59
|
Instance.node,
|
|
58
60
|
SessionTransfer.node,
|
|
@@ -110,7 +112,7 @@ function makeRoutes(auth, options, serviceURLs, overrides, instances) {
|
|
|
110
112
|
const { simulationReplacements } = yield* Effect.promise(() => import("@opencode/simulation/backend")), simulation = yield* simulationReplacements({ version: App.make(options.app).version });
|
|
111
113
|
return build([...overrides, ...simulation]);
|
|
112
114
|
})) : build(overrides)).pipe(Layer.flatMap((context) => {
|
|
113
|
-
const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, PermissionSaved.Service, PluginUpdate.Service, Project.Service, WellKnown.Service)(context)), ServerInfo.layer(serviceURLs, options.app)), api = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(Layer.provide(handlers.pipe(Layer.provide(services), Layer.provide(Layer.succeed(CorsConfig, options)))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), Layer.provide(authorizationLayer), Layer.provide(schemaErrorLayer), Layer.provide(auth), HttpRouter.provideRequest(requestServices), Layer.provideMerge(services), Layer.provideMerge(HttpRouter.layer));
|
|
115
|
+
const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, Credential.Service, PermissionSaved.Service, PluginUpdate.Service, Project.Service, WellKnown.Service)(context)), ServerInfo.layer(serviceURLs, options.app)), api = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(Layer.provide(handlers.pipe(Layer.provide(services), Layer.provide(Layer.succeed(CorsConfig, options)))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), Layer.provide(authorizationLayer), Layer.provide(schemaErrorLayer), Layer.provide(auth), HttpRouter.provideRequest(requestServices), Layer.provideMerge(services), Layer.provideMerge(HttpRouter.layer));
|
|
114
116
|
return Layer.merge(api, V1Migration.layer.pipe(Layer.provide(services)));
|
|
115
117
|
}));
|
|
116
118
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@opencode/server",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -30,17 +30,17 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
32
32
|
"@effect/platform-node-shared": "4.0.0-rc.112",
|
|
33
|
-
"@opencode/core": "2.0.
|
|
34
|
-
"@opencode/protocol": "2.0.
|
|
35
|
-
"@opencode/schema": "2.0.
|
|
36
|
-
"@opencode/simulation": "2.0.
|
|
37
|
-
"@opencode/util": "2.0.
|
|
33
|
+
"@opencode/core": "2.0.4",
|
|
34
|
+
"@opencode/protocol": "2.0.4",
|
|
35
|
+
"@opencode/schema": "2.0.4",
|
|
36
|
+
"@opencode/simulation": "2.0.4",
|
|
37
|
+
"@opencode/util": "2.0.4",
|
|
38
38
|
"drizzle-orm": "1.0.0-rc.5-169397b",
|
|
39
39
|
"effect": "4.0.0-rc.112"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@opencode/client": "2.0.
|
|
43
|
-
"@opencode/plugin": "2.0.
|
|
42
|
+
"@opencode/client": "2.0.4",
|
|
43
|
+
"@opencode/plugin": "2.0.4",
|
|
44
44
|
"@tsconfig/bun": "1.0.9",
|
|
45
45
|
"@types/bun": "1.4.0",
|
|
46
46
|
"@typescript/native-preview": "7.0.0-dev.20251207.1"
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { ServerInfo } from "../server-info";
|
|
2
|
-
export declare const HealthHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.health">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", ServerInfo.Service>>;
|
package/dist/handlers/health.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
3
|
-
import { Api } from "../api";
|
|
4
|
-
import { ServerInfo } from "../server-info";
|
|
5
|
-
export const HealthHandler = HttpApiBuilder.group(Api, "server.health", (handlers) => handlers.handle("health.get", () => Effect.gen(function* () {
|
|
6
|
-
return {
|
|
7
|
-
healthy: !0,
|
|
8
|
-
version: (yield* ServerInfo.Service).app.version ?? "unknown",
|
|
9
|
-
pid: process.pid ?? 0
|
|
10
|
-
};
|
|
11
|
-
})));
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { Workspace } from "@opencode/core/workspace";
|
|
2
|
-
export declare const WorkspaceHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.workspace">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | Workspace.Service>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Workspace } from "@opencode/core/workspace";
|
|
2
|
-
import { ConflictError, ProviderNotFoundError, UnknownError } from "@opencode/protocol/errors";
|
|
3
|
-
import { Effect } from "effect";
|
|
4
|
-
import { HttpApiBuilder } from "effect/unstable/httpapi";
|
|
5
|
-
import { Api } from "../api";
|
|
6
|
-
export const WorkspaceHandler = HttpApiBuilder.group(Api, "server.workspace", (handlers) => Effect.gen(function* () {
|
|
7
|
-
const workspace = yield* Workspace.Service;
|
|
8
|
-
return handlers.handle("workspace.create", (ctx) => workspace.create(ctx.payload).pipe(Effect.map((workspaceID) => ({ data: workspaceID })), Effect.catchTags({
|
|
9
|
-
"Workspace.CreateConflict": (error) => new ConflictError({
|
|
10
|
-
resource: error.workspaceID,
|
|
11
|
-
message: `Workspace ${error.workspaceID} already uses provider ${error.existingProvider}, not ${error.provider}`
|
|
12
|
-
}),
|
|
13
|
-
"WorkspaceDriver.ProviderNotFound": (error) => new ProviderNotFoundError({
|
|
14
|
-
providerID: error.provider,
|
|
15
|
-
message: `Workspace provider not found: ${error.provider}`
|
|
16
|
-
})
|
|
17
|
-
}))).handle("workspace.destroy", (ctx) => workspace.destroy(ctx.params.workspaceID).pipe(Effect.mapError((error) => new UnknownError({
|
|
18
|
-
message: error._tag === "WorkspaceDriver.ProviderNotFound" ? `Workspace provider not found: ${error.provider}` : error.message ?? "Workspace provider failed to destroy the workspace"
|
|
19
|
-
}))));
|
|
20
|
-
}));
|