antechamber 1.0.40 → 2.0.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 +126 -0
- package/README.md +88 -141
- package/bin/opencode-antechamber +14 -4
- package/dist/approvals.d.ts +9 -1
- package/dist/approvals.js +10 -2
- package/dist/catalog.d.ts +13 -2
- package/dist/catalog.js +88 -52
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/context.d.ts +5 -2
- package/dist/commands/context.js +8 -4
- package/dist/commands/doctor.js +4 -3
- package/dist/commands/setup.js +9 -28
- package/dist/config.d.ts +6 -2
- package/dist/config.js +18 -4
- package/dist/file-watch.d.ts +35 -0
- package/dist/file-watch.js +163 -0
- package/dist/latency.d.ts +10 -0
- package/dist/latency.js +115 -0
- package/dist/misc-routes.js +14 -12
- package/dist/pi-event-translation.js +44 -2
- package/dist/route-services.d.ts +8 -3
- package/dist/routes-v1.d.ts +1 -1
- package/dist/routes-v1.js +29 -418
- package/dist/routes-v2.js +252 -34
- package/dist/self-update.d.ts +2 -1
- package/dist/self-update.js +5 -1
- package/dist/server.d.ts +17 -3
- package/dist/server.js +191 -18
- package/dist/session-commands.d.ts +3 -1
- package/dist/session-commands.js +13 -7
- package/dist/session-history.js +3 -0
- package/dist/session-store.d.ts +19 -10
- package/dist/session-store.js +266 -128
- package/dist/session-tree.d.ts +15 -1
- package/dist/session-tree.js +2 -2
- package/dist/sessions.d.ts +85 -1
- package/dist/sessions.js +238 -10
- package/dist/sse.d.ts +0 -2
- package/dist/sse.js +13 -46
- package/dist/text-generation.d.ts +20 -0
- package/dist/text-generation.js +93 -0
- package/dist/tool-map.js +1 -3
- package/dist/types.d.ts +60 -0
- package/dist/v2.d.ts +8 -1
- package/dist/v2.js +24 -4
- package/dist/work-status.js +1 -1
- package/extensions/antechamber/README.md +100 -0
- package/extensions/antechamber/bin/opencode-antechamber +42 -0
- package/extensions/{omo-dag/docs/DESIGN.md → antechamber/docs/GRAPH.md} +5 -5
- package/extensions/{omo-work/docs/DESIGN.md → antechamber/docs/WORK.md} +3 -3
- package/extensions/antechamber/package.json +60 -0
- package/extensions/{omo-dag → antechamber}/panel/index.html +8 -1
- package/extensions/antechamber/panel/main.js +261 -0
- package/extensions/antechamber/runtime/launcher.js +201 -0
- package/extensions/antechamber/service/main.js +884 -0
- package/package.json +75 -26
- package/extensions/omo-compat/README.md +0 -48
- package/extensions/omo-compat/icon.svg +0 -1
- package/extensions/omo-compat/package.json +0 -43
- package/extensions/omo-dag/README.md +0 -167
- package/extensions/omo-dag/package.json +0 -33
- package/extensions/omo-dag/panel/main.js +0 -176
- package/extensions/omo-dag/service/main.js +0 -457
- package/extensions/omo-work/README.md +0 -88
- package/extensions/omo-work/package.json +0 -30
- package/extensions/omo-work/service/main.js +0 -149
- /package/extensions/{omo-dag → antechamber}/icon.svg +0 -0
- /package/extensions/{omo-work → antechamber}/status/index.html +0 -0
- /package/extensions/{omo-work → antechamber}/status/main.js +0 -0
package/dist/routes-v2.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
+
import { resolve } from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
import { errorBody, json, RequestBodyError } from "./http.js";
|
|
7
8
|
import { nativePromptInput, parsePromptParts, v2PromptAttachments, v2PromptParts } from "./prompt-parts.js";
|
|
@@ -12,10 +13,11 @@ import { toV2Permission } from "./approvals.js";
|
|
|
12
13
|
import { formQuestionAnswer, questionFormID, toQuestionForm } from "./question-forms.js";
|
|
13
14
|
import { getAgentName } from "./agent-name.js";
|
|
14
15
|
import { vcsRead, VcsError } from "./vcs.js";
|
|
15
|
-
import { announceSessionError, compactSession,
|
|
16
|
+
import { announceSessionError, compactSession, unsupportedShare } from "./session-commands.js";
|
|
16
17
|
import { defaultV2Model, flattenModels, toV2Message, toV2Session } from "./v2.js";
|
|
17
18
|
import { SHELL_CANCELLATION_KEY } from "./omo-shells.js";
|
|
18
19
|
import { WorkStatusError } from "./work-status.js";
|
|
20
|
+
import { FileWatchError } from "./file-watch.js";
|
|
19
21
|
import { agentDescriptors, modelRefFromBody, numberParam, OPENCODE_COMPAT_VERSION, promptAsync, runSessionShell, runSessionSlash, sessionDirectory, sessionMetadata, } from "./route-services.js";
|
|
20
22
|
async function v2Agents(services) {
|
|
21
23
|
return (await agentDescriptors(services)).map(({ model, variant, ...agent }) => ({
|
|
@@ -71,18 +73,6 @@ function followsCursor(session, cursor, order) {
|
|
|
71
73
|
const comparison = session.time.updated - cursor.updated || session.id.localeCompare(cursor.id);
|
|
72
74
|
return order === "asc" ? comparison > 0 : comparison < 0;
|
|
73
75
|
}
|
|
74
|
-
async function currentGitBranch(directory) {
|
|
75
|
-
try {
|
|
76
|
-
const { stdout } = await execFileAsync("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
77
|
-
cwd: directory,
|
|
78
|
-
timeout: 3000,
|
|
79
|
-
});
|
|
80
|
-
return stdout.trim() || undefined;
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
76
|
async function initializeGit(directory) {
|
|
87
77
|
try {
|
|
88
78
|
await execFileAsync("git", ["init", "-q"], { cwd: directory, timeout: 3000 });
|
|
@@ -107,7 +97,7 @@ function v2Projects(services) {
|
|
|
107
97
|
vcs: "git",
|
|
108
98
|
}));
|
|
109
99
|
}
|
|
110
|
-
async function handleV2Bootstrap(services, res, path, method, directory) {
|
|
100
|
+
async function handleV2Bootstrap(services, res, url, path, method, directory) {
|
|
111
101
|
const withLocation = (data) => ({ location: { directory }, data });
|
|
112
102
|
if (path === "/api/info" && method === "GET") {
|
|
113
103
|
json(res, 200, {
|
|
@@ -128,17 +118,19 @@ async function handleV2Bootstrap(services, res, path, method, directory) {
|
|
|
128
118
|
return true;
|
|
129
119
|
}
|
|
130
120
|
if (path === "/api/provider" && method === "GET") {
|
|
131
|
-
const cat = await services.catalog();
|
|
121
|
+
const cat = await services.catalog(directory, true);
|
|
132
122
|
json(res, 200, withLocation(cat.providers.map((p) => ({ ...p, activation: "enabled" }))));
|
|
133
123
|
return true;
|
|
134
124
|
}
|
|
135
125
|
if (path === "/api/model" && method === "GET") {
|
|
136
|
-
const cat = await services.catalog();
|
|
137
|
-
|
|
126
|
+
const cat = await services.catalog(directory, true);
|
|
127
|
+
const requested = url.searchParams.getAll("types");
|
|
128
|
+
const types = requested.length === 0 ? ["chat"] : requested;
|
|
129
|
+
json(res, 200, withLocation(flattenModels(cat).filter((model) => types.some((type) => model.capabilities.output.includes(type === "chat" ? "text" : type)))));
|
|
138
130
|
return true;
|
|
139
131
|
}
|
|
140
132
|
if (path === "/api/model/default" && method === "GET") {
|
|
141
|
-
const cat = await services.catalog();
|
|
133
|
+
const cat = await services.catalog(directory);
|
|
142
134
|
json(res, 200, withLocation(defaultV2Model(cat) ?? null));
|
|
143
135
|
return true;
|
|
144
136
|
}
|
|
@@ -157,7 +149,7 @@ async function handleV2Bootstrap(services, res, path, method, directory) {
|
|
|
157
149
|
return true;
|
|
158
150
|
}
|
|
159
151
|
if (path === "/api/command" && method === "GET") {
|
|
160
|
-
json(res, 200, withLocation((await
|
|
152
|
+
json(res, 200, withLocation((await services.commands(directory)).map((command) => ({
|
|
161
153
|
...command,
|
|
162
154
|
source: "command",
|
|
163
155
|
hints: [],
|
|
@@ -210,8 +202,12 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
210
202
|
const location = { directory };
|
|
211
203
|
const withLocation = (data) => ({ location, data });
|
|
212
204
|
// --- bootstrap ---
|
|
213
|
-
if (await handleV2Bootstrap(services, res, path, method, directory))
|
|
205
|
+
if (await handleV2Bootstrap(services, res, url, path, method, directory))
|
|
214
206
|
return;
|
|
207
|
+
if (path === "/api/debug/location" && method === "GET") {
|
|
208
|
+
json(res, 200, []);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
215
211
|
if (path === "/api/debug/location" && method === "DELETE") {
|
|
216
212
|
// OpenChamber evicts a location after git worktree changes so OpenCode re-reads it. antechamber
|
|
217
213
|
// keeps no per-location instance (project and VCS reads are fresh per request): nothing to evict.
|
|
@@ -272,6 +268,56 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
272
268
|
});
|
|
273
269
|
return;
|
|
274
270
|
}
|
|
271
|
+
if (path === "/api/experimental/generate/text" && method === "POST") {
|
|
272
|
+
const body = await services.body(req);
|
|
273
|
+
if (typeof body !== "object" ||
|
|
274
|
+
body === null ||
|
|
275
|
+
!("prompt" in body) ||
|
|
276
|
+
typeof body.prompt !== "string" ||
|
|
277
|
+
body.prompt.trim() === "") {
|
|
278
|
+
json(res, 400, errorBody("InvalidRequestError", "prompt must be a nonempty string"));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const model = "model" in body ? body.model : undefined;
|
|
282
|
+
if (model !== undefined &&
|
|
283
|
+
(typeof model !== "object" ||
|
|
284
|
+
model === null ||
|
|
285
|
+
!("providerID" in model) ||
|
|
286
|
+
typeof model.providerID !== "string" ||
|
|
287
|
+
!("id" in model) ||
|
|
288
|
+
typeof model.id !== "string")) {
|
|
289
|
+
json(res, 400, errorBody("InvalidRequestError", "model must include providerID and id"));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const selected = model !== undefined && typeof model === "object" && model !== null && "providerID" in model && "id" in model
|
|
293
|
+
? { providerID: String(model.providerID), id: String(model.id) }
|
|
294
|
+
: undefined;
|
|
295
|
+
const cancellation = new AbortController();
|
|
296
|
+
const disconnected = () => cancellation.abort(new Error("Text generation caller disconnected"));
|
|
297
|
+
res.once("close", disconnected);
|
|
298
|
+
if (res.destroyed)
|
|
299
|
+
disconnected();
|
|
300
|
+
try {
|
|
301
|
+
json(res, 200, {
|
|
302
|
+
data: {
|
|
303
|
+
text: await services.textGeneration.generate(services.defaultDirectory, body.prompt, selected, cancellation.signal),
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (res.destroyed)
|
|
309
|
+
return;
|
|
310
|
+
json(res, 503, {
|
|
311
|
+
_tag: "ServiceUnavailableError",
|
|
312
|
+
message: error instanceof Error ? error.message : "Text generation is unavailable",
|
|
313
|
+
service: "opencode",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
res.off("close", disconnected);
|
|
318
|
+
}
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
275
321
|
// OmO's background work (monitors, background terminal sessions, detached eval cells) as OpenCode shells.
|
|
276
322
|
if (path === "/api/shell" && method === "GET") {
|
|
277
323
|
json(res, 200, withLocation(services.sessions.shells.list(directory)));
|
|
@@ -291,10 +337,35 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
291
337
|
json(res, 400, errorBody("UnsupportedError", "Session statistics are not available with OmO"));
|
|
292
338
|
return;
|
|
293
339
|
}
|
|
340
|
+
if (path === "/api/experimental/usage/limits" && method === "GET") {
|
|
341
|
+
json(res, 200, withLocation(null));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
294
344
|
if (path === "/api/mcp" || path.startsWith("/api/mcp/") || path.startsWith("/api/experimental/mcp/")) {
|
|
295
345
|
json(res, 400, errorBody("UnsupportedError", "MCP is not available with OmO"));
|
|
296
346
|
return;
|
|
297
347
|
}
|
|
348
|
+
if (path === "/api/experimental/fs/watch" && (method === "POST" || method === "DELETE")) {
|
|
349
|
+
const body = await services.body(req);
|
|
350
|
+
if (typeof body !== "object" || body === null || !("path" in body) || typeof body.path !== "string") {
|
|
351
|
+
json(res, 400, errorBody("InvalidRequestError", "path must be a string"));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const file = await services.fileWatches.file(directory, body.path, method === "POST");
|
|
356
|
+
if (method === "POST")
|
|
357
|
+
services.fileWatches.watch(directory, file, resolve(directory, body.path));
|
|
358
|
+
else
|
|
359
|
+
services.fileWatches.unwatch(directory, file);
|
|
360
|
+
res.writeHead(204).end();
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
if (!(error instanceof FileWatchError))
|
|
364
|
+
throw error;
|
|
365
|
+
json(res, error.status, errorBody(error.kind, error.message));
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
298
369
|
if (path === "/api/fs/find" && method === "GET") {
|
|
299
370
|
const result = await miscRoute("GET", "/find/file", url, directory);
|
|
300
371
|
if (result === undefined) {
|
|
@@ -308,7 +379,7 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
308
379
|
const files = Array.isArray(result.body)
|
|
309
380
|
? result.body
|
|
310
381
|
.filter((entry) => typeof entry === "string")
|
|
311
|
-
.map((path) => ({ path, type: "file" }))
|
|
382
|
+
.map((path) => ({ path, type: path.endsWith("/") ? "directory" : "file" }))
|
|
312
383
|
: [];
|
|
313
384
|
json(res, 200, withLocation(files));
|
|
314
385
|
return;
|
|
@@ -327,7 +398,7 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
327
398
|
json(res, 200, withLocation(services.sessions
|
|
328
399
|
.permissions()
|
|
329
400
|
.filter((permission) => services.sessions.get(permission.sessionID)?.info.directory === directory)
|
|
330
|
-
.map(
|
|
401
|
+
.map(toV2Permission)));
|
|
331
402
|
return;
|
|
332
403
|
}
|
|
333
404
|
if (path === "/api/question/request" && method === "GET") {
|
|
@@ -343,7 +414,10 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
343
414
|
return;
|
|
344
415
|
}
|
|
345
416
|
if (path === "/api/form" && method === "GET") {
|
|
346
|
-
json(res, 200, withLocation(services.sessions
|
|
417
|
+
json(res, 200, withLocation(services.sessions
|
|
418
|
+
.questions()
|
|
419
|
+
.filter((question) => services.sessions.get(question.sessionID)?.info.directory === directory)
|
|
420
|
+
.map(toQuestionForm)));
|
|
347
421
|
return;
|
|
348
422
|
}
|
|
349
423
|
if (path === "/api/vcs/init" && method === "POST") {
|
|
@@ -355,7 +429,7 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
355
429
|
return;
|
|
356
430
|
}
|
|
357
431
|
if (path === "/api/vcs" && method === "GET") {
|
|
358
|
-
const current = await
|
|
432
|
+
const current = await services.vcsBranch(directory);
|
|
359
433
|
json(res, 200, withLocation({ provider: "git", branch: current === undefined ? {} : { current } }));
|
|
360
434
|
return;
|
|
361
435
|
}
|
|
@@ -375,15 +449,18 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
375
449
|
const dir = url.searchParams.get("directory") ?? url.searchParams.get("location");
|
|
376
450
|
const search = url.searchParams.get("search");
|
|
377
451
|
const parentID = url.searchParams.get("parentID");
|
|
452
|
+
const archived = url.searchParams.get("archived");
|
|
378
453
|
const order = url.searchParams.get("order") === "asc" ? "asc" : "desc";
|
|
379
454
|
const limit = numberParam(url.searchParams.get("limit"));
|
|
380
455
|
let infos = (dir !== null && dir !== "" ? services.sessions.list(dir) : services.sessions.listAll()).filter(
|
|
381
|
-
//
|
|
456
|
+
// Synthetic monitor sessions stay internal; 2.x shows them in the OmO Work section.
|
|
382
457
|
(session) => !services.sessions.monitors.owns(session.id));
|
|
383
458
|
if (search)
|
|
384
459
|
infos = infos.filter((s) => s.title.toLowerCase().includes(search.toLowerCase()));
|
|
385
460
|
if (parentID !== null)
|
|
386
|
-
infos = infos.filter((session) => session.parentID === parentID);
|
|
461
|
+
infos = infos.filter((session) => session.parentID === (parentID === "null" ? undefined : parentID));
|
|
462
|
+
if (archived === "true" || archived === "false")
|
|
463
|
+
infos = infos.filter((session) => (session.time.archived !== undefined) === (archived === "true"));
|
|
387
464
|
let sessions = infos.map((info) => v2Session(services, info));
|
|
388
465
|
sessions.sort((left, right) => compareSessions(left, right, order));
|
|
389
466
|
const cursor = decodeSessionCursor(url.searchParams.get("cursor"));
|
|
@@ -430,6 +507,36 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
430
507
|
json(res, 200, { data });
|
|
431
508
|
return;
|
|
432
509
|
}
|
|
510
|
+
const instructionEntry = path.match(/^\/api\/experimental\/session\/([^/]+)\/instructions\/entries\/([^/]+)$/);
|
|
511
|
+
if (instructionEntry && (method === "PUT" || method === "DELETE")) {
|
|
512
|
+
const sessionId = decodeURIComponent(instructionEntry[1]);
|
|
513
|
+
const key = decodeURIComponent(instructionEntry[2]);
|
|
514
|
+
if (services.sessions.get(sessionId) === undefined) {
|
|
515
|
+
json(res, 404, errorBody("NotFoundError", `session not found: ${sessionId}`));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(key)) {
|
|
519
|
+
json(res, 400, errorBody("InvalidRequestError", "instruction entry key is invalid"));
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (method === "PUT") {
|
|
523
|
+
const body = await services.body(req);
|
|
524
|
+
if (typeof body !== "object" || body === null || Array.isArray(body) || !("value" in body)) {
|
|
525
|
+
json(res, 400, errorBody("InvalidRequestError", "instruction entry requires a JSON value"));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (Buffer.byteLength(JSON.stringify(body.value), "utf8") > 256 * 1024) {
|
|
529
|
+
json(res, 413, errorBody("InstructionEntryValueTooLargeError", "instruction entry exceeds 256 KiB"));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
services.sessions.putInstruction(sessionId, key, body.value);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
services.sessions.removeInstruction(sessionId, key);
|
|
536
|
+
}
|
|
537
|
+
res.writeHead(204).end();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
433
540
|
const sessionMatch = path.match(/^\/api\/session\/([^/]+)(\/.+)?$/);
|
|
434
541
|
if (sessionMatch) {
|
|
435
542
|
const sessionId = decodeURIComponent(sessionMatch[1]);
|
|
@@ -553,6 +660,16 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
553
660
|
json(res, 200, { data: v2Session(services, managed.info) });
|
|
554
661
|
return;
|
|
555
662
|
}
|
|
663
|
+
case "POST /view": {
|
|
664
|
+
const body = (await services.body(req));
|
|
665
|
+
if (typeof body.idle !== "number" || !Number.isFinite(body.idle)) {
|
|
666
|
+
json(res, 400, errorBody("InvalidRequestError", "idle must be a finite epoch-millisecond number"));
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
services.sessions.view(sessionId, body.idle);
|
|
670
|
+
res.writeHead(204).end();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
556
673
|
case "PATCH ": {
|
|
557
674
|
const body = (await services.body(req));
|
|
558
675
|
// OpenCode 2.1 takes `archived: boolean` (repeating the current state is a no-op);
|
|
@@ -578,7 +695,35 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
578
695
|
case "GET /message": {
|
|
579
696
|
const order = url.searchParams.get("order");
|
|
580
697
|
const limit = numberParam(url.searchParams.get("limit"));
|
|
581
|
-
|
|
698
|
+
const history = services.sessions.messages(sessionId) ?? [];
|
|
699
|
+
const messagesWithTurns = history.flatMap((message) => {
|
|
700
|
+
if (message.parts.some((part) => part.type === "compaction")) {
|
|
701
|
+
const item = services.sessions.compaction(sessionId, message.info.id);
|
|
702
|
+
return item === undefined ? [] : [item];
|
|
703
|
+
}
|
|
704
|
+
if (message.info.role === "assistant" && message.info.summary === true)
|
|
705
|
+
return [];
|
|
706
|
+
const item = toV2Message(message);
|
|
707
|
+
const boundary = managed.tree.idleBoundaries?.[message.info.id];
|
|
708
|
+
if (boundary === undefined)
|
|
709
|
+
return [item];
|
|
710
|
+
return [
|
|
711
|
+
item,
|
|
712
|
+
{
|
|
713
|
+
id: `msg_idle_${message.info.id.replace(/^msg_/, "")}`,
|
|
714
|
+
type: "idle",
|
|
715
|
+
time: { created: boundary.at },
|
|
716
|
+
outcome: boundary.outcome,
|
|
717
|
+
...(boundary.error !== undefined ? { error: boundary.error } : {}),
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
});
|
|
721
|
+
if (managed.compactionID && !messagesWithTurns.some((message) => message.id === managed.compactionID)) {
|
|
722
|
+
const item = services.sessions.compaction(sessionId, managed.compactionID);
|
|
723
|
+
if (item !== undefined)
|
|
724
|
+
messagesWithTurns.push(item);
|
|
725
|
+
}
|
|
726
|
+
let messages = messagesWithTurns;
|
|
582
727
|
if (order !== "asc")
|
|
583
728
|
messages = messages.reverse(); // v2 default is newest-first
|
|
584
729
|
const cursor = url.searchParams.get("cursor");
|
|
@@ -597,14 +742,52 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
597
742
|
}
|
|
598
743
|
case "POST /compact":
|
|
599
744
|
case "POST /summarize": {
|
|
600
|
-
await services.body(req);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
745
|
+
const body = (await services.body(req));
|
|
746
|
+
if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue")
|
|
747
|
+
throw new RequestBodyError(400, 'delivery must be "steer" or "queue"');
|
|
748
|
+
if (body.id !== undefined && (typeof body.id !== "string" || !body.id.startsWith("msg_")))
|
|
749
|
+
throw new RequestBodyError(400, "id must be a message ID");
|
|
750
|
+
const inputID = body.id ?? `msg_${randomUUID().replaceAll("-", "")}`;
|
|
751
|
+
const admission = services.sessions.admitCompaction(sessionId, inputID);
|
|
752
|
+
if (admission.kind === "conflict") {
|
|
753
|
+
json(res, 409, errorBody("ConflictError", "Message ID already in use"));
|
|
605
754
|
return;
|
|
606
755
|
}
|
|
607
|
-
|
|
756
|
+
if (admission.kind === "existing") {
|
|
757
|
+
json(res, 200, { data: admission.item });
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
// The agent can stop before compaction_end: if the compaction is still this request's, no event closed it,
|
|
761
|
+
// so the client's card would stay "running" unless it is failed here.
|
|
762
|
+
const fail = (message) => {
|
|
763
|
+
if (managed.compactionID !== inputID)
|
|
764
|
+
return;
|
|
765
|
+
delete managed.compactionID;
|
|
766
|
+
delete managed.compactionReason;
|
|
767
|
+
services.broadcast(managed.info.directory, {
|
|
768
|
+
type: "session.compaction.failed",
|
|
769
|
+
properties: {
|
|
770
|
+
sessionID: sessionId,
|
|
771
|
+
inputID,
|
|
772
|
+
reason: "manual",
|
|
773
|
+
error: { name: "CompactionError", data: { message } },
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
};
|
|
777
|
+
void compactSession(services.sessions, sessionId)
|
|
778
|
+
.then((result) => {
|
|
779
|
+
if (result.kind === "error") {
|
|
780
|
+
fail(result.message);
|
|
781
|
+
announceSessionError({ sessions: services.sessions, session: managed, emit: services.broadcast }, "BadRequestError", result.message);
|
|
782
|
+
}
|
|
783
|
+
if (managed.compactionID === inputID)
|
|
784
|
+
delete managed.compactionID;
|
|
785
|
+
})
|
|
786
|
+
.catch((error) => {
|
|
787
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
788
|
+
announceSessionError({ sessions: services.sessions, session: managed, emit: services.broadcast }, error instanceof Error ? error.name : "UnknownError", error instanceof Error ? error.message : String(error));
|
|
789
|
+
});
|
|
790
|
+
json(res, 200, { data: admission.item });
|
|
608
791
|
return;
|
|
609
792
|
}
|
|
610
793
|
case "POST /shell": {
|
|
@@ -626,10 +809,14 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
626
809
|
}
|
|
627
810
|
case "POST /command": {
|
|
628
811
|
const body = (await services.body(req));
|
|
812
|
+
if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue")
|
|
813
|
+
throw new RequestBodyError(400, 'delivery must be "steer" or "queue"');
|
|
629
814
|
const result = await runSessionSlash(services, sessionId, {
|
|
630
815
|
command: body.name,
|
|
631
816
|
arguments: body.text ?? "",
|
|
632
817
|
messageID: body.id,
|
|
818
|
+
parts: [...parsePromptParts(body.parts), ...v2PromptParts("", body.files, body.agents)],
|
|
819
|
+
delivery: body.delivery,
|
|
633
820
|
});
|
|
634
821
|
if (result.kind === "input") {
|
|
635
822
|
json(res, 400, errorBody("BadRequestError", "command is required"));
|
|
@@ -720,6 +907,10 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
720
907
|
}
|
|
721
908
|
const messageID = typeof body.id === "string" && body.id !== "" ? body.id : `msg_${randomUUID().replaceAll("-", "")}`;
|
|
722
909
|
const text = body.text;
|
|
910
|
+
if (body.description !== undefined && typeof body.description !== "string")
|
|
911
|
+
throw new RequestBodyError(400, "description must be a string");
|
|
912
|
+
if (body.description !== undefined)
|
|
913
|
+
(managed.tree.promptDescriptions ??= {})[messageID] = body.description;
|
|
723
914
|
promptAsync(services, managed, text, undefined, true, messageID, body.delivery ?? "steer", undefined, metadata);
|
|
724
915
|
// OpenChamber 2.3.2 treats anything but 200 + the admitted record as a failed send (captured OpenCode shape).
|
|
725
916
|
json(res, 200, {
|
|
@@ -729,6 +920,7 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
729
920
|
time: { created: Date.now() },
|
|
730
921
|
type: "synthetic",
|
|
731
922
|
payload: { text },
|
|
923
|
+
...(body.description !== undefined ? { description: body.description } : {}),
|
|
732
924
|
},
|
|
733
925
|
});
|
|
734
926
|
return;
|
|
@@ -835,7 +1027,33 @@ export async function handleV2(services, req, res, url, path, method, directory)
|
|
|
835
1027
|
return;
|
|
836
1028
|
}
|
|
837
1029
|
case "GET /inbox": {
|
|
838
|
-
|
|
1030
|
+
const messages = services.sessions.messages(sessionId) ?? [];
|
|
1031
|
+
json(res, 200, {
|
|
1032
|
+
data: [...(managed.inputs ?? [])].flatMap(([id, input]) => {
|
|
1033
|
+
if (!input.accepted || input.delivered)
|
|
1034
|
+
return [];
|
|
1035
|
+
const message = messages.find((item) => item.info.id === id);
|
|
1036
|
+
if (message?.info.role !== "user")
|
|
1037
|
+
return [];
|
|
1038
|
+
return [
|
|
1039
|
+
{
|
|
1040
|
+
id,
|
|
1041
|
+
sessionID: sessionId,
|
|
1042
|
+
time: { created: message.info.time.created },
|
|
1043
|
+
type: "user",
|
|
1044
|
+
delivery: input.delivery,
|
|
1045
|
+
payload: {
|
|
1046
|
+
text: message.parts
|
|
1047
|
+
.filter((part) => part.type === "text")
|
|
1048
|
+
.map((part) => part.text)
|
|
1049
|
+
.join("\n"),
|
|
1050
|
+
...v2PromptAttachments(message.parts.filter((part) => part.type === "file" || part.type === "agent")),
|
|
1051
|
+
...(message.info.metadata !== undefined ? { metadata: message.info.metadata } : {}),
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
];
|
|
1055
|
+
}),
|
|
1056
|
+
});
|
|
839
1057
|
return;
|
|
840
1058
|
}
|
|
841
1059
|
case "GET /diff":
|
package/dist/self-update.d.ts
CHANGED
|
@@ -22,10 +22,11 @@ export interface SelfUpdateResult {
|
|
|
22
22
|
readonly to: string;
|
|
23
23
|
readonly method: InstallMethod;
|
|
24
24
|
}
|
|
25
|
+
export declare const MANAGED_UPDATE_HINT = "antechamber is managed by its OpenChamber extension: update the extension in Settings -> Extensions";
|
|
25
26
|
export declare function installMethod(location: InstallLocation, npmRoot: () => string): InstallMethod;
|
|
26
27
|
export declare function isNewerVersion(a: string, b: string): boolean;
|
|
27
28
|
export declare function fetchLatestVersion(env?: NodeJS.ProcessEnv): Promise<string>;
|
|
28
|
-
/** Prefers
|
|
29
|
+
/** Prefers an npm beside the running runtime (bun or node): service PATHs often lack the user's tool directories. */
|
|
29
30
|
export declare function npmBinaryFor(execPath?: string): string;
|
|
30
31
|
export declare function defaultSelfUpdateDeps(env?: NodeJS.ProcessEnv, home?: string): SelfUpdateDeps;
|
|
31
32
|
/** Installs the latest release when it is newer; concurrent callers in this process share one attempt. */
|
package/dist/self-update.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { managedByExtension } from "./config.js";
|
|
7
8
|
import { ADAPTER_VERSION } from "./version.js";
|
|
8
9
|
export class SelfUpdateUnsupportedError extends Error {
|
|
9
10
|
name = "SelfUpdateUnsupportedError";
|
|
@@ -21,7 +22,10 @@ function tryRealpath(path, location) {
|
|
|
21
22
|
function bunInstallDir(location) {
|
|
22
23
|
return location.env.BUN_INSTALL ?? join(location.home, ".bun");
|
|
23
24
|
}
|
|
25
|
+
export const MANAGED_UPDATE_HINT = "antechamber is managed by its OpenChamber extension: update the extension in Settings -> Extensions";
|
|
24
26
|
function locate(location) {
|
|
27
|
+
if (managedByExtension(location.env))
|
|
28
|
+
throw new SelfUpdateUnsupportedError(MANAGED_UPDATE_HINT);
|
|
25
29
|
const root = tryRealpath(location.packageRoot, location) ?? location.packageRoot;
|
|
26
30
|
if (basename(root) !== "antechamber" || basename(dirname(root)) !== "node_modules")
|
|
27
31
|
throw notGlobal(root);
|
|
@@ -139,7 +143,7 @@ function run(binary, args, env, timeout) {
|
|
|
139
143
|
child.stdin?.end();
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
|
-
/** Prefers
|
|
146
|
+
/** Prefers an npm beside the running runtime (bun or node): service PATHs often lack the user's tool directories. */
|
|
143
147
|
export function npmBinaryFor(execPath = process.execPath) {
|
|
144
148
|
const sibling = join(dirname(execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
145
149
|
return existsSync(sibling) ? sibling : "npm";
|
package/dist/server.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/** HTTP/SSE composition root for the OpenCode API backed by pi. */
|
|
2
|
-
import { type SelfUpdateResult } from "./self-update.js";
|
|
3
2
|
export { isLoopbackHost, serverPassword } from "./http.js";
|
|
4
3
|
interface AdapterServerOptions {
|
|
5
4
|
port: number;
|
|
6
5
|
host: string;
|
|
7
6
|
/** Fallback directory when requests carry no ?directory= param. */
|
|
8
7
|
defaultDirectory: string;
|
|
9
|
-
/** Replaces the npm self-update behind POST /global/upgrade (tests). */
|
|
10
|
-
selfUpdate?: () => Promise<SelfUpdateResult>;
|
|
11
8
|
}
|
|
12
9
|
export declare class AdapterServer {
|
|
13
10
|
private options;
|
|
@@ -19,12 +16,29 @@ export declare class AdapterServer {
|
|
|
19
16
|
private webSocketEvents;
|
|
20
17
|
private nextWebSocketEventID;
|
|
21
18
|
private catalogPromise;
|
|
19
|
+
private catalogExpiresAt;
|
|
20
|
+
private catalogColdExpiresAt;
|
|
21
|
+
private readonly commandSnapshots;
|
|
22
|
+
private readonly commandColdReads;
|
|
23
|
+
private readonly branchSnapshots;
|
|
24
|
+
private readonly branchWatchers;
|
|
25
|
+
private readonly branchChecks;
|
|
26
|
+
private readonly projectDirectories;
|
|
27
|
+
private readonly fileWatches;
|
|
28
|
+
private readonly pendingRefreshes;
|
|
29
|
+
private readonly textGeneration;
|
|
30
|
+
private readonly latency;
|
|
31
|
+
private stopping;
|
|
22
32
|
private actualPort;
|
|
23
33
|
/** Bearer token for the `/antechamber/work-status` routes, published in the run record for the OmO Work extension. */
|
|
24
34
|
readonly extensionToken: string;
|
|
25
35
|
private readonly http;
|
|
26
36
|
constructor(options: AdapterServerOptions);
|
|
27
37
|
private catalog;
|
|
38
|
+
private commands;
|
|
39
|
+
private watchBranch;
|
|
40
|
+
private vcsBranch;
|
|
41
|
+
private readBranch;
|
|
28
42
|
start(): Promise<void>;
|
|
29
43
|
get url(): string;
|
|
30
44
|
stop(): Promise<void>;
|