antechamber 0.1.1 → 0.2.0
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/CHANGELOG.md +28 -0
- package/README.md +17 -14
- package/bin/opencode-antechamber +10 -1
- package/bin/opencode-pi +11 -1
- package/dist/approvals.d.ts +4 -1
- package/dist/approvals.js +51 -33
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.js +1 -1
- package/dist/commands/context.d.ts +0 -1
- package/dist/commands/context.js +1 -1
- package/dist/commands/doctor.d.ts +3 -2
- package/dist/commands/serve.d.ts +2 -1
- package/dist/commands/setup.d.ts +2 -3
- package/dist/commands/setup.js +16 -4
- package/dist/http.d.ts +8 -1
- package/dist/http.js +42 -19
- package/dist/pi-event-translation.d.ts +9 -5
- package/dist/pi-event-translation.js +233 -79
- package/dist/pi-rpc.d.ts +20 -2
- package/dist/pi-rpc.js +96 -29
- package/dist/prompt-parts.d.ts +40 -0
- package/dist/prompt-parts.js +157 -0
- package/dist/route-services.d.ts +45 -2
- package/dist/route-services.js +70 -3
- package/dist/routes-v1.js +110 -65
- package/dist/routes-v2.js +376 -73
- package/dist/server.d.ts +9 -1
- package/dist/server.js +109 -9
- package/dist/session-commands.d.ts +12 -15
- package/dist/session-commands.js +52 -11
- package/dist/session-diff.d.ts +1 -1
- package/dist/session-diff.js +10 -2
- package/dist/session-history.js +108 -42
- package/dist/session-hosts.js +2 -2
- package/dist/session-store.d.ts +8 -4
- package/dist/session-store.js +183 -33
- package/dist/session-tree.d.ts +9 -1
- package/dist/session-tree.js +51 -26
- package/dist/sessions.d.ts +32 -5
- package/dist/sessions.js +330 -119
- package/dist/sse.d.ts +16 -6
- package/dist/sse.js +77 -21
- package/dist/subagents.d.ts +5 -0
- package/dist/subagents.js +84 -71
- package/dist/tool-map.d.ts +3 -2
- package/dist/types.d.ts +152 -1
- package/dist/v2.d.ts +18 -4
- package/dist/v2.js +128 -44
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/omo-dag/README.md +2 -0
- package/extensions/omo-dag/docs/DESIGN.md +10 -1
- package/extensions/omo-dag/panel/index.html +1 -1
- package/extensions/omo-dag/panel/main.js +60 -60
- package/extensions/omo-dag/service/main.js +72 -8
- package/package.json +1 -1
package/dist/routes-v1.js
CHANGED
|
@@ -3,10 +3,11 @@ import { execFile } from "node:child_process";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { errorBody, json } from "./http.js";
|
|
5
5
|
import { miscRoute } from "./misc-routes.js";
|
|
6
|
-
import { getAgentName
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { getAgentName } from "./agent-name.js";
|
|
7
|
+
import { nativePromptInput, parsePromptParts } from "./prompt-parts.js";
|
|
8
|
+
import { RequestBodyError } from "./http.js";
|
|
9
|
+
import { announceSessionError, compactSession, getCommands, unsupportedShare } from "./session-commands.js";
|
|
10
|
+
import { agentDescriptors, modelRefFromBody, numberParam, OPENCODE_COMPAT_VERSION, promptAsync, runSessionShell, runSessionSlash, sessionDirectory, sessionMetadata, } from "./route-services.js";
|
|
10
11
|
/**
|
|
11
12
|
* `archived` list filter per SDK v2 (`boolean | "true" | "false"`):
|
|
12
13
|
* "true" keeps only archived sessions, "false" only live ones, absent = all.
|
|
@@ -18,18 +19,33 @@ function applyArchivedFilter(sessions, archived) {
|
|
|
18
19
|
return sessions.filter((s) => s.time.archived === undefined);
|
|
19
20
|
return sessions;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
function handleV1GlobalRoute(res, path, method) {
|
|
22
23
|
if (method === "GET" && path === "/global/health") {
|
|
23
24
|
json(res, 200, OPENCODE_COMPAT_VERSION === null ? { healthy: true } : { healthy: true, version: OPENCODE_COMPAT_VERSION });
|
|
24
|
-
return;
|
|
25
|
+
return true;
|
|
25
26
|
}
|
|
26
|
-
// There is no opencode install behind this endpoint to upgrade: the adapter
|
|
27
|
-
// speaks the OpenCode API itself. Answer instead of 404 so a direct upgrade
|
|
28
|
-
// call gets a clear reason rather than a bare HTTP status.
|
|
29
27
|
if ((method === "POST" || method === "GET") && path === "/global/upgrade") {
|
|
30
28
|
json(res, 409, errorBody("UnsupportedError", "This endpoint is served by antechamber (a pi adapter), not a real opencode install — there is nothing to upgrade."));
|
|
31
|
-
return;
|
|
29
|
+
return true;
|
|
32
30
|
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
function parseV1Prompt(body, defaultModel) {
|
|
34
|
+
const parts = parsePromptParts(body.parts);
|
|
35
|
+
if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue")
|
|
36
|
+
throw new RequestBodyError(400, "delivery must be steer or queue");
|
|
37
|
+
const text = parts
|
|
38
|
+
.filter((part) => part.type === "text")
|
|
39
|
+
.map((part) => part.text ?? "")
|
|
40
|
+
.join("\n");
|
|
41
|
+
const model = modelRefFromBody(body.model ?? (body.variant !== undefined ? defaultModel : undefined), body.variant);
|
|
42
|
+
if (!body.noReply)
|
|
43
|
+
nativePromptInput(text, parts);
|
|
44
|
+
return { text, model, parts, delivery: body.delivery ?? "steer" };
|
|
45
|
+
}
|
|
46
|
+
export async function handleV1(services, req, res, url, path, method, directory) {
|
|
47
|
+
if (handleV1GlobalRoute(res, path, method))
|
|
48
|
+
return;
|
|
33
49
|
// --- sessions ---
|
|
34
50
|
const misc = await miscRoute(method, path, url, directory);
|
|
35
51
|
if (misc !== undefined) {
|
|
@@ -38,7 +54,8 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
38
54
|
}
|
|
39
55
|
if (path === "/session" && method === "POST") {
|
|
40
56
|
const body = (await services.body(req));
|
|
41
|
-
|
|
57
|
+
const metadata = sessionMetadata(body.metadata);
|
|
58
|
+
json(res, 200, await services.sessions.create(sessionDirectory(directory), body.title, body.parentID, metadata === undefined ? {} : { metadata }));
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
61
|
if (path === "/session" && method === "GET") {
|
|
@@ -64,46 +81,58 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
64
81
|
json(res, 404, errorBody("NotFoundError", `session not found: ${sessionId}`));
|
|
65
82
|
return;
|
|
66
83
|
}
|
|
84
|
+
const messageMatch = sub?.match(/^\/message\/([^/]+)$/);
|
|
85
|
+
if (messageMatch && method === "GET") {
|
|
86
|
+
const messageID = decodeURIComponent(messageMatch[1]);
|
|
87
|
+
const message = (services.sessions.messages(sessionId) ?? []).find((candidate) => candidate.info.id === messageID);
|
|
88
|
+
if (message === undefined)
|
|
89
|
+
json(res, 404, errorBody("NotFoundError", `message not found: ${messageID}`));
|
|
90
|
+
else
|
|
91
|
+
json(res, 200, message);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
67
94
|
switch (`${method} ${sub ?? ""}`) {
|
|
68
95
|
case "GET /diff":
|
|
69
96
|
json(res, 200, await services.sessions.diff(sessionId));
|
|
70
97
|
return;
|
|
71
98
|
case "GET ":
|
|
72
|
-
json(res, 200, managed.info);
|
|
99
|
+
json(res, 200, { ...managed.info, cost: managed.info.cost ?? 0 });
|
|
73
100
|
return;
|
|
74
101
|
case "PATCH ": {
|
|
75
102
|
const body = (await services.body(req));
|
|
76
|
-
json(res, 200, services.sessions.update(sessionId, {
|
|
103
|
+
json(res, 200, await services.sessions.update(sessionId, {
|
|
104
|
+
title: body.title,
|
|
105
|
+
archived: body.time?.archived,
|
|
106
|
+
metadata: sessionMetadata(body.metadata),
|
|
107
|
+
}));
|
|
77
108
|
return;
|
|
78
109
|
}
|
|
79
110
|
case "DELETE ":
|
|
80
111
|
json(res, 200, await services.sessions.remove(sessionId));
|
|
81
112
|
return;
|
|
82
|
-
case "GET /message":
|
|
83
|
-
|
|
113
|
+
case "GET /message": {
|
|
114
|
+
const messages = services.sessions.messages(sessionId) ?? [];
|
|
115
|
+
const limit = numberParam(url.searchParams.get("limit"));
|
|
116
|
+
json(res, 200, limit === undefined ? messages : limit === 0 ? [] : messages.slice(-limit));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
case "GET /children":
|
|
120
|
+
json(res, 200, services.sessions.list(directory).filter((candidate) => candidate.parentID === sessionId));
|
|
84
121
|
return;
|
|
85
122
|
case "GET /todo":
|
|
86
123
|
json(res, 200, services.sessions.todos(sessionId));
|
|
87
124
|
return;
|
|
88
125
|
case "POST /prompt_async": {
|
|
89
126
|
const body = (await services.body(req));
|
|
90
|
-
const text = (body.
|
|
91
|
-
|
|
92
|
-
.map((p) => p.text ?? "")
|
|
93
|
-
.join("\n");
|
|
94
|
-
const model = modelRefFromBody(body.model ?? (body.variant !== undefined ? managed.model : undefined), body.variant);
|
|
95
|
-
promptAsync(services, managed, text, model, body.noReply, body.messageID);
|
|
127
|
+
const { text, model, parts, delivery } = parseV1Prompt(body, managed.model);
|
|
128
|
+
promptAsync(services, managed, text, model, body.noReply, body.messageID, delivery, parts);
|
|
96
129
|
res.writeHead(204).end();
|
|
97
130
|
return;
|
|
98
131
|
}
|
|
99
132
|
case "POST /message": {
|
|
100
133
|
const body = (await services.body(req));
|
|
101
|
-
const text = (body.
|
|
102
|
-
|
|
103
|
-
.map((p) => p.text ?? "")
|
|
104
|
-
.join("\n");
|
|
105
|
-
const model = modelRefFromBody(body.model ?? (body.variant !== undefined ? managed.model : undefined), body.variant);
|
|
106
|
-
await services.sessions.prompt(sessionId, text, model, body.noReply, body.messageID);
|
|
134
|
+
const { text, model, parts, delivery } = parseV1Prompt(body, managed.model);
|
|
135
|
+
await services.sessions.prompt(sessionId, text, model, body.noReply, body.messageID, delivery, parts);
|
|
107
136
|
const msgs = services.sessions.messages(sessionId) ?? [];
|
|
108
137
|
json(res, 200, msgs[msgs.length - 1] ?? null);
|
|
109
138
|
return;
|
|
@@ -146,40 +175,34 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
146
175
|
return;
|
|
147
176
|
}
|
|
148
177
|
case "POST /shell": {
|
|
149
|
-
const command = parseShellCommand(await services.body(req));
|
|
150
|
-
if (command === undefined) {
|
|
151
|
-
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
178
|
const managed = services.sessions.get(sessionId);
|
|
155
179
|
if (!managed) {
|
|
156
180
|
json(res, 404, errorBody("NotFoundError", `session not found: ${sessionId}`));
|
|
157
181
|
return;
|
|
158
182
|
}
|
|
159
|
-
|
|
183
|
+
const result = await runSessionShell(services, managed, await services.body(req));
|
|
184
|
+
if (result.kind === "input") {
|
|
185
|
+
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (result.kind === "busy") {
|
|
160
189
|
json(res, 409, errorBody("SessionBusyError", "session is busy"));
|
|
161
190
|
return;
|
|
162
191
|
}
|
|
163
|
-
const result = await runShellCommand({
|
|
164
|
-
sessions: services.sessions,
|
|
165
|
-
session: managed,
|
|
166
|
-
command,
|
|
167
|
-
emit: services.broadcast,
|
|
168
|
-
});
|
|
169
192
|
if (result.kind === "error") {
|
|
170
193
|
json(res, 400, errorBody("BadRequestError", result.message));
|
|
171
194
|
return;
|
|
172
195
|
}
|
|
173
|
-
json(res, 200, result.message
|
|
196
|
+
json(res, 200, result.message);
|
|
174
197
|
return;
|
|
175
198
|
}
|
|
176
199
|
case "POST /command": {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
200
|
+
const result = await runSessionSlash(services, sessionId, await services.body(req));
|
|
201
|
+
if (result.kind === "input") {
|
|
179
202
|
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
180
203
|
return;
|
|
181
204
|
}
|
|
182
|
-
const message =
|
|
205
|
+
const { message } = result;
|
|
183
206
|
json(res, 200, message === undefined ? { info: null, parts: [] } : { info: message.info, parts: message.parts });
|
|
184
207
|
return;
|
|
185
208
|
}
|
|
@@ -198,15 +221,39 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
198
221
|
}
|
|
199
222
|
// --- project / config ---
|
|
200
223
|
if (path === "/project/current" && method === "GET") {
|
|
201
|
-
json(res, 200, {
|
|
224
|
+
json(res, 200, {
|
|
225
|
+
id: directory,
|
|
226
|
+
worktree: directory,
|
|
227
|
+
vcs: "git",
|
|
228
|
+
time: { created: Date.now(), updated: Date.now() },
|
|
229
|
+
sandboxes: [],
|
|
230
|
+
});
|
|
202
231
|
return;
|
|
203
232
|
}
|
|
204
233
|
if (path === "/project" && method === "GET") {
|
|
205
|
-
json(res, 200, [
|
|
234
|
+
json(res, 200, [
|
|
235
|
+
{
|
|
236
|
+
id: directory,
|
|
237
|
+
worktree: directory,
|
|
238
|
+
vcs: "git",
|
|
239
|
+
time: { created: Date.now(), updated: Date.now() },
|
|
240
|
+
sandboxes: [],
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
206
243
|
return;
|
|
207
244
|
}
|
|
208
245
|
if ((path === "/config" || path === "/global/config") && method === "GET") {
|
|
209
|
-
|
|
246
|
+
const cat = await services.catalog();
|
|
247
|
+
json(res, 200, {
|
|
248
|
+
$schema: "https://opencode.ai/config.json",
|
|
249
|
+
default_agent: getAgentName(),
|
|
250
|
+
plugin: [],
|
|
251
|
+
command: {},
|
|
252
|
+
mode: {},
|
|
253
|
+
agent: {},
|
|
254
|
+
username: process.env.OPENCODE_SERVER_USERNAME ?? "opencode",
|
|
255
|
+
...(cat.default ? { model: `${cat.default.providerID}/${cat.default.modelID}` } : {}),
|
|
256
|
+
});
|
|
210
257
|
return;
|
|
211
258
|
}
|
|
212
259
|
// --- model catalog (from pi) ---
|
|
@@ -240,7 +287,15 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
240
287
|
if (url.searchParams.get("archived") === "false") {
|
|
241
288
|
sessions = sessions.filter((s) => s.time.archived === undefined);
|
|
242
289
|
}
|
|
243
|
-
|
|
290
|
+
sessions.sort((a, b) => b.time.updated - a.time.updated || a.id.localeCompare(b.id));
|
|
291
|
+
const cursor = numberParam(url.searchParams.get("cursor"));
|
|
292
|
+
if (cursor !== undefined)
|
|
293
|
+
sessions = sessions.filter((session) => session.time.updated < cursor);
|
|
294
|
+
const limit = numberParam(url.searchParams.get("limit"));
|
|
295
|
+
const page = limit === undefined ? sessions : sessions.slice(0, limit);
|
|
296
|
+
if (limit !== undefined && page.length < sessions.length && page.at(-1)?.time.updated !== undefined)
|
|
297
|
+
res.setHeader("x-next-cursor", String(page.at(-1).time.updated));
|
|
298
|
+
json(res, 200, page);
|
|
244
299
|
return;
|
|
245
300
|
}
|
|
246
301
|
if (path === "/question" && method === "GET") {
|
|
@@ -309,7 +364,12 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
309
364
|
return;
|
|
310
365
|
}
|
|
311
366
|
if (path === "/command" && method === "GET") {
|
|
312
|
-
json(res, 200, await getCommands(directory))
|
|
367
|
+
json(res, 200, (await getCommands(directory)).map((command) => ({
|
|
368
|
+
...command,
|
|
369
|
+
source: "command",
|
|
370
|
+
hints: [],
|
|
371
|
+
subtask: false,
|
|
372
|
+
})));
|
|
313
373
|
return;
|
|
314
374
|
}
|
|
315
375
|
if (path === "/path" && method === "GET") {
|
|
@@ -319,27 +379,12 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
319
379
|
if (path === "/vcs" && method === "GET") {
|
|
320
380
|
execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: directory, timeout: 3000 }, (err, stdout) => {
|
|
321
381
|
const branch = err ? undefined : stdout.trim() || undefined;
|
|
322
|
-
json(res, 200, branch
|
|
382
|
+
json(res, 200, { branch: branch ?? null, default_branch: null });
|
|
323
383
|
});
|
|
324
384
|
return;
|
|
325
385
|
}
|
|
326
386
|
if (path === "/agent" && method === "GET") {
|
|
327
|
-
|
|
328
|
-
// agent next to pi's own name so the picker — and its default resolution,
|
|
329
|
-
// which looks for `build` — has a real option. Both names run the same
|
|
330
|
-
// pi agent (see getExposedAgentNames).
|
|
331
|
-
const piName = getAgentName();
|
|
332
|
-
const defaults = agentDefaults(await services.catalog());
|
|
333
|
-
json(res, 200, getExposedAgentNames().map((name) => ({
|
|
334
|
-
name,
|
|
335
|
-
description: name === piName
|
|
336
|
-
? `${piName} (pi coding agent via antechamber)`
|
|
337
|
-
: `${name} — ${piName} (pi coding agent via antechamber)`,
|
|
338
|
-
mode: "primary",
|
|
339
|
-
hidden: name === "build" && piName !== "build",
|
|
340
|
-
options: {},
|
|
341
|
-
...defaults,
|
|
342
|
-
})));
|
|
387
|
+
json(res, 200, await agentDescriptors(services));
|
|
343
388
|
return;
|
|
344
389
|
}
|
|
345
390
|
json(res, 404, errorBody("NotFoundError", `no route: ${method} ${path}`));
|