antechamber 0.1.1 → 0.2.1
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 +45 -0
- package/README.md +24 -14
- package/bin/opencode-antechamber +14 -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 +10 -6
- package/dist/pi-event-translation.js +237 -80
- 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 +113 -65
- package/dist/routes-v2.js +407 -95
- 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 +54 -13
- package/dist/session-diff.d.ts +1 -1
- package/dist/session-diff.js +10 -2
- package/dist/session-history.d.ts +1 -1
- package/dist/session-history.js +110 -44
- package/dist/session-hosts.js +2 -2
- package/dist/session-store.d.ts +8 -4
- package/dist/session-store.js +191 -38
- package/dist/session-tree.d.ts +10 -2
- package/dist/session-tree.js +55 -26
- package/dist/sessions.d.ts +32 -5
- package/dist/sessions.js +331 -120
- 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/vcs.d.ts +5 -0
- package/dist/vcs.js +199 -0
- 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,36 @@ 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
|
+
if (!Array.isArray(body.parts))
|
|
35
|
+
throw new RequestBodyError(400, "parts must be an array");
|
|
36
|
+
const parts = parsePromptParts(body.parts);
|
|
37
|
+
if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue")
|
|
38
|
+
throw new RequestBodyError(400, "delivery must be steer or queue");
|
|
39
|
+
const text = parts
|
|
40
|
+
.filter((part) => part.type === "text")
|
|
41
|
+
.map((part) => part.text ?? "")
|
|
42
|
+
.join("\n");
|
|
43
|
+
const model = modelRefFromBody(body.model ?? (body.variant !== undefined ? defaultModel : undefined), body.variant);
|
|
44
|
+
const native = nativePromptInput(text, parts);
|
|
45
|
+
if (native.message.trim() === "" && !native.images?.length)
|
|
46
|
+
throw new RequestBodyError(400, "prompt requires non-whitespace text, file content, or an agent mention");
|
|
47
|
+
return { text, model, parts, delivery: body.delivery ?? "steer" };
|
|
48
|
+
}
|
|
49
|
+
export async function handleV1(services, req, res, url, path, method, directory) {
|
|
50
|
+
if (handleV1GlobalRoute(res, path, method))
|
|
51
|
+
return;
|
|
33
52
|
// --- sessions ---
|
|
34
53
|
const misc = await miscRoute(method, path, url, directory);
|
|
35
54
|
if (misc !== undefined) {
|
|
@@ -38,7 +57,8 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
38
57
|
}
|
|
39
58
|
if (path === "/session" && method === "POST") {
|
|
40
59
|
const body = (await services.body(req));
|
|
41
|
-
|
|
60
|
+
const metadata = sessionMetadata(body.metadata);
|
|
61
|
+
json(res, 200, await services.sessions.create(sessionDirectory(directory), body.title, body.parentID, metadata === undefined ? {} : { metadata }));
|
|
42
62
|
return;
|
|
43
63
|
}
|
|
44
64
|
if (path === "/session" && method === "GET") {
|
|
@@ -64,46 +84,58 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
64
84
|
json(res, 404, errorBody("NotFoundError", `session not found: ${sessionId}`));
|
|
65
85
|
return;
|
|
66
86
|
}
|
|
87
|
+
const messageMatch = sub?.match(/^\/message\/([^/]+)$/);
|
|
88
|
+
if (messageMatch && method === "GET") {
|
|
89
|
+
const messageID = decodeURIComponent(messageMatch[1]);
|
|
90
|
+
const message = (services.sessions.messages(sessionId) ?? []).find((candidate) => candidate.info.id === messageID);
|
|
91
|
+
if (message === undefined)
|
|
92
|
+
json(res, 404, errorBody("NotFoundError", `message not found: ${messageID}`));
|
|
93
|
+
else
|
|
94
|
+
json(res, 200, message);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
67
97
|
switch (`${method} ${sub ?? ""}`) {
|
|
68
98
|
case "GET /diff":
|
|
69
99
|
json(res, 200, await services.sessions.diff(sessionId));
|
|
70
100
|
return;
|
|
71
101
|
case "GET ":
|
|
72
|
-
json(res, 200, managed.info);
|
|
102
|
+
json(res, 200, { ...managed.info, cost: managed.info.cost ?? 0 });
|
|
73
103
|
return;
|
|
74
104
|
case "PATCH ": {
|
|
75
105
|
const body = (await services.body(req));
|
|
76
|
-
json(res, 200, services.sessions.update(sessionId, {
|
|
106
|
+
json(res, 200, await services.sessions.update(sessionId, {
|
|
107
|
+
title: body.title,
|
|
108
|
+
archived: body.time?.archived,
|
|
109
|
+
metadata: sessionMetadata(body.metadata),
|
|
110
|
+
}));
|
|
77
111
|
return;
|
|
78
112
|
}
|
|
79
113
|
case "DELETE ":
|
|
80
114
|
json(res, 200, await services.sessions.remove(sessionId));
|
|
81
115
|
return;
|
|
82
|
-
case "GET /message":
|
|
83
|
-
|
|
116
|
+
case "GET /message": {
|
|
117
|
+
const messages = services.sessions.messages(sessionId) ?? [];
|
|
118
|
+
const limit = numberParam(url.searchParams.get("limit"));
|
|
119
|
+
json(res, 200, limit === undefined ? messages : limit === 0 ? [] : messages.slice(-limit));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
case "GET /children":
|
|
123
|
+
json(res, 200, services.sessions.list(directory).filter((candidate) => candidate.parentID === sessionId));
|
|
84
124
|
return;
|
|
85
125
|
case "GET /todo":
|
|
86
126
|
json(res, 200, services.sessions.todos(sessionId));
|
|
87
127
|
return;
|
|
88
128
|
case "POST /prompt_async": {
|
|
89
129
|
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);
|
|
130
|
+
const { text, model, parts, delivery } = parseV1Prompt(body, managed.model);
|
|
131
|
+
promptAsync(services, managed, text, model, body.noReply, body.messageID, delivery, parts);
|
|
96
132
|
res.writeHead(204).end();
|
|
97
133
|
return;
|
|
98
134
|
}
|
|
99
135
|
case "POST /message": {
|
|
100
136
|
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);
|
|
137
|
+
const { text, model, parts, delivery } = parseV1Prompt(body, managed.model);
|
|
138
|
+
await services.sessions.prompt(sessionId, text, model, body.noReply, body.messageID, delivery, parts);
|
|
107
139
|
const msgs = services.sessions.messages(sessionId) ?? [];
|
|
108
140
|
json(res, 200, msgs[msgs.length - 1] ?? null);
|
|
109
141
|
return;
|
|
@@ -146,40 +178,34 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
146
178
|
return;
|
|
147
179
|
}
|
|
148
180
|
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
181
|
const managed = services.sessions.get(sessionId);
|
|
155
182
|
if (!managed) {
|
|
156
183
|
json(res, 404, errorBody("NotFoundError", `session not found: ${sessionId}`));
|
|
157
184
|
return;
|
|
158
185
|
}
|
|
159
|
-
|
|
186
|
+
const result = await runSessionShell(services, managed, await services.body(req));
|
|
187
|
+
if (result.kind === "input") {
|
|
188
|
+
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (result.kind === "busy") {
|
|
160
192
|
json(res, 409, errorBody("SessionBusyError", "session is busy"));
|
|
161
193
|
return;
|
|
162
194
|
}
|
|
163
|
-
const result = await runShellCommand({
|
|
164
|
-
sessions: services.sessions,
|
|
165
|
-
session: managed,
|
|
166
|
-
command,
|
|
167
|
-
emit: services.broadcast,
|
|
168
|
-
});
|
|
169
195
|
if (result.kind === "error") {
|
|
170
196
|
json(res, 400, errorBody("BadRequestError", result.message));
|
|
171
197
|
return;
|
|
172
198
|
}
|
|
173
|
-
json(res, 200, result.message
|
|
199
|
+
json(res, 200, result.message);
|
|
174
200
|
return;
|
|
175
201
|
}
|
|
176
202
|
case "POST /command": {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
203
|
+
const result = await runSessionSlash(services, sessionId, await services.body(req));
|
|
204
|
+
if (result.kind === "input") {
|
|
179
205
|
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
180
206
|
return;
|
|
181
207
|
}
|
|
182
|
-
const message =
|
|
208
|
+
const { message } = result;
|
|
183
209
|
json(res, 200, message === undefined ? { info: null, parts: [] } : { info: message.info, parts: message.parts });
|
|
184
210
|
return;
|
|
185
211
|
}
|
|
@@ -198,15 +224,39 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
198
224
|
}
|
|
199
225
|
// --- project / config ---
|
|
200
226
|
if (path === "/project/current" && method === "GET") {
|
|
201
|
-
json(res, 200, {
|
|
227
|
+
json(res, 200, {
|
|
228
|
+
id: directory,
|
|
229
|
+
worktree: directory,
|
|
230
|
+
vcs: "git",
|
|
231
|
+
time: { created: Date.now(), updated: Date.now() },
|
|
232
|
+
sandboxes: [],
|
|
233
|
+
});
|
|
202
234
|
return;
|
|
203
235
|
}
|
|
204
236
|
if (path === "/project" && method === "GET") {
|
|
205
|
-
json(res, 200, [
|
|
237
|
+
json(res, 200, [
|
|
238
|
+
{
|
|
239
|
+
id: directory,
|
|
240
|
+
worktree: directory,
|
|
241
|
+
vcs: "git",
|
|
242
|
+
time: { created: Date.now(), updated: Date.now() },
|
|
243
|
+
sandboxes: [],
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
206
246
|
return;
|
|
207
247
|
}
|
|
208
248
|
if ((path === "/config" || path === "/global/config") && method === "GET") {
|
|
209
|
-
|
|
249
|
+
const cat = await services.catalog();
|
|
250
|
+
json(res, 200, {
|
|
251
|
+
$schema: "https://opencode.ai/config.json",
|
|
252
|
+
default_agent: getAgentName(),
|
|
253
|
+
plugin: [],
|
|
254
|
+
command: {},
|
|
255
|
+
mode: {},
|
|
256
|
+
agent: {},
|
|
257
|
+
username: process.env.OPENCODE_SERVER_USERNAME ?? "opencode",
|
|
258
|
+
...(cat.default ? { model: `${cat.default.providerID}/${cat.default.modelID}` } : {}),
|
|
259
|
+
});
|
|
210
260
|
return;
|
|
211
261
|
}
|
|
212
262
|
// --- model catalog (from pi) ---
|
|
@@ -240,7 +290,15 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
240
290
|
if (url.searchParams.get("archived") === "false") {
|
|
241
291
|
sessions = sessions.filter((s) => s.time.archived === undefined);
|
|
242
292
|
}
|
|
243
|
-
|
|
293
|
+
sessions.sort((a, b) => b.time.updated - a.time.updated || a.id.localeCompare(b.id));
|
|
294
|
+
const cursor = numberParam(url.searchParams.get("cursor"));
|
|
295
|
+
if (cursor !== undefined)
|
|
296
|
+
sessions = sessions.filter((session) => session.time.updated < cursor);
|
|
297
|
+
const limit = numberParam(url.searchParams.get("limit"));
|
|
298
|
+
const page = limit === undefined ? sessions : sessions.slice(0, limit);
|
|
299
|
+
if (limit !== undefined && page.length < sessions.length && page.at(-1)?.time.updated !== undefined)
|
|
300
|
+
res.setHeader("x-next-cursor", String(page.at(-1).time.updated));
|
|
301
|
+
json(res, 200, page);
|
|
244
302
|
return;
|
|
245
303
|
}
|
|
246
304
|
if (path === "/question" && method === "GET") {
|
|
@@ -309,7 +367,12 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
309
367
|
return;
|
|
310
368
|
}
|
|
311
369
|
if (path === "/command" && method === "GET") {
|
|
312
|
-
json(res, 200, await getCommands(directory))
|
|
370
|
+
json(res, 200, (await getCommands(directory)).map((command) => ({
|
|
371
|
+
...command,
|
|
372
|
+
source: "command",
|
|
373
|
+
hints: [],
|
|
374
|
+
subtask: false,
|
|
375
|
+
})));
|
|
313
376
|
return;
|
|
314
377
|
}
|
|
315
378
|
if (path === "/path" && method === "GET") {
|
|
@@ -319,27 +382,12 @@ export async function handleV1(services, req, res, url, path, method, directory)
|
|
|
319
382
|
if (path === "/vcs" && method === "GET") {
|
|
320
383
|
execFile("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: directory, timeout: 3000 }, (err, stdout) => {
|
|
321
384
|
const branch = err ? undefined : stdout.trim() || undefined;
|
|
322
|
-
json(res, 200, branch
|
|
385
|
+
json(res, 200, { branch: branch ?? null, default_branch: null });
|
|
323
386
|
});
|
|
324
387
|
return;
|
|
325
388
|
}
|
|
326
389
|
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
|
-
})));
|
|
390
|
+
json(res, 200, await agentDescriptors(services));
|
|
343
391
|
return;
|
|
344
392
|
}
|
|
345
393
|
json(res, 404, errorBody("NotFoundError", `no route: ${method} ${path}`));
|