dot-studio 0.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/LICENSE +21 -0
- package/README.md +214 -0
- package/client/assets/index-C2eIILoa.css +41 -0
- package/client/assets/index-DUPZ_Lw5.js +616 -0
- package/client/assets/index.es-Btlrnc3g.js +1 -0
- package/client/index.html +14 -0
- package/dist/cli.js +196 -0
- package/dist/server/index.js +79 -0
- package/dist/server/lib/act-runtime.js +1282 -0
- package/dist/server/lib/cache.js +31 -0
- package/dist/server/lib/config.js +53 -0
- package/dist/server/lib/dot-authoring.js +245 -0
- package/dist/server/lib/dot-loader.js +61 -0
- package/dist/server/lib/dot-login.js +190 -0
- package/dist/server/lib/model-catalog.js +111 -0
- package/dist/server/lib/opencode-auth.js +69 -0
- package/dist/server/lib/opencode-errors.js +220 -0
- package/dist/server/lib/opencode-sidecar.js +144 -0
- package/dist/server/lib/opencode.js +12 -0
- package/dist/server/lib/package-bin.js +63 -0
- package/dist/server/lib/project-config.js +39 -0
- package/dist/server/lib/prompt.js +222 -0
- package/dist/server/lib/request-context.js +27 -0
- package/dist/server/lib/runtime-tools.js +208 -0
- package/dist/server/routes/assets.js +161 -0
- package/dist/server/routes/chat.js +356 -0
- package/dist/server/routes/compile.js +105 -0
- package/dist/server/routes/dot.js +270 -0
- package/dist/server/routes/health.js +56 -0
- package/dist/server/routes/opencode.js +421 -0
- package/dist/server/routes/stages.js +137 -0
- package/dist/server/start.js +23 -0
- package/dist/server/terminal.js +282 -0
- package/dist/shared/mcp-config.js +19 -0
- package/dist/shared/model-variants.js +50 -0
- package/dist/shared/project-mcp.js +22 -0
- package/dist/shared/session-metadata.js +26 -0
- package/package.json +103 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Chat Session Routes (OpenCode proxy)
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { getOpencode } from '../lib/opencode.js';
|
|
4
|
+
import { compilePrompt } from './compile.js';
|
|
5
|
+
import { buildEnabledToolMap, describeUnavailableRuntimeTools, resolveRuntimeTools } from '../lib/runtime-tools.js';
|
|
6
|
+
import { requestDirectoryQuery, resolveRequestWorkingDir } from '../lib/request-context.js';
|
|
7
|
+
import { buildStudioSessionTitle } from '../../shared/session-metadata.js';
|
|
8
|
+
import { StudioValidationError, jsonOpencodeError, unwrapOpencodeResult, } from '../lib/opencode-errors.js';
|
|
9
|
+
const chat = new Hono();
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
function normalizeIncompleteToolParts(messages, settledAt) {
|
|
14
|
+
return messages.map((message) => {
|
|
15
|
+
if (!Array.isArray(message?.parts) || message.parts.length === 0) {
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
18
|
+
const nextParts = message.parts.map((part) => {
|
|
19
|
+
if (part?.type !== 'tool' || !part.state) {
|
|
20
|
+
return part;
|
|
21
|
+
}
|
|
22
|
+
const status = part.state.status;
|
|
23
|
+
if (status !== 'pending' && status !== 'running') {
|
|
24
|
+
return part;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...part,
|
|
28
|
+
state: {
|
|
29
|
+
...part.state,
|
|
30
|
+
status: 'error',
|
|
31
|
+
error: part.state.error || 'Stopped before the tool finished.',
|
|
32
|
+
time: {
|
|
33
|
+
...(part.state.time || {}),
|
|
34
|
+
...(typeof part.state.time?.start === 'number' ? {} : { start: settledAt }),
|
|
35
|
+
end: settledAt,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
...message,
|
|
42
|
+
parts: nextParts,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function waitForSessionToSettle(oc, sessionId, directoryQuery) {
|
|
47
|
+
const deadline = Date.now() + 3_000;
|
|
48
|
+
while (Date.now() < deadline) {
|
|
49
|
+
const statuses = unwrapOpencodeResult(await oc.session.status({
|
|
50
|
+
...directoryQuery,
|
|
51
|
+
}));
|
|
52
|
+
const status = statuses?.[sessionId];
|
|
53
|
+
if (!status || status.type === 'idle') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await sleep(150);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function uniqueAssetRefs(refs) {
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
return refs.filter((ref) => {
|
|
62
|
+
const key = ref.kind === 'registry' ? `registry:${ref.urn}` : `draft:${ref.draftId}`;
|
|
63
|
+
if (seen.has(key)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
seen.add(key);
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// ── Create Session ──────────────────────────────────────
|
|
71
|
+
chat.post('/api/chat/sessions', async (c) => {
|
|
72
|
+
const { performerId, performerName, configHash } = await c.req.json();
|
|
73
|
+
try {
|
|
74
|
+
const oc = await getOpencode();
|
|
75
|
+
const session = unwrapOpencodeResult(await oc.session.create({
|
|
76
|
+
...requestDirectoryQuery(c),
|
|
77
|
+
title: buildStudioSessionTitle(performerId, performerName, configHash),
|
|
78
|
+
}));
|
|
79
|
+
return c.json({
|
|
80
|
+
sessionId: session.id,
|
|
81
|
+
title: session.title,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return jsonOpencodeError(c, err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// ── Delete Session ──────────────────────────────────────
|
|
89
|
+
chat.delete('/api/chat/sessions/:id', async (c) => {
|
|
90
|
+
try {
|
|
91
|
+
const oc = await getOpencode();
|
|
92
|
+
unwrapOpencodeResult(await oc.session.delete({
|
|
93
|
+
sessionID: c.req.param('id'),
|
|
94
|
+
...requestDirectoryQuery(c),
|
|
95
|
+
}));
|
|
96
|
+
return c.json({ ok: true });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return jsonOpencodeError(c, err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
chat.put('/api/chat/sessions/:id', async (c) => {
|
|
103
|
+
const body = await c.req.json().catch(() => ({}));
|
|
104
|
+
const title = body.title;
|
|
105
|
+
if (!title || !title.trim()) {
|
|
106
|
+
return jsonOpencodeError(c, new StudioValidationError('Thread title is required.', 'fix_input'));
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const oc = await getOpencode();
|
|
110
|
+
const updated = unwrapOpencodeResult(await oc.session.update({
|
|
111
|
+
sessionID: c.req.param('id'),
|
|
112
|
+
...requestDirectoryQuery(c),
|
|
113
|
+
title: title.trim(),
|
|
114
|
+
}));
|
|
115
|
+
return c.json(updated);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return jsonOpencodeError(c, err);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// ── Send message ────────────────────────────────────────
|
|
122
|
+
chat.post('/api/chat/sessions/:id/send', async (c) => {
|
|
123
|
+
const { message, performer, attachments } = await c.req.json();
|
|
124
|
+
if (!performer?.model) {
|
|
125
|
+
return jsonOpencodeError(c, new StudioValidationError('Select a model for this performer before sending prompts.', 'select_model'));
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const oc = await getOpencode();
|
|
129
|
+
const cwd = resolveRequestWorkingDir(c);
|
|
130
|
+
const envelope = await compilePrompt(cwd, performer?.talRef || null, uniqueAssetRefs([...(performer?.danceRefs || []), ...(performer?.extraDanceRefs || [])]), performer?.model || null, performer?.drafts || {}, performer?.modelVariant || null, performer?.danceDeliveryMode || 'auto');
|
|
131
|
+
const toolResolution = await resolveRuntimeTools(cwd, performer.model, performer?.mcpServerNames || []);
|
|
132
|
+
const unavailableSummary = describeUnavailableRuntimeTools(toolResolution);
|
|
133
|
+
if (toolResolution.selectedMcpServers.length > 0 && toolResolution.resolvedTools.length === 0 && unavailableSummary) {
|
|
134
|
+
throw new StudioValidationError(`Selected MCP servers are unavailable: ${unavailableSummary}.`, 'fix_input');
|
|
135
|
+
}
|
|
136
|
+
const parts = [{ type: 'text', text: message }];
|
|
137
|
+
if (attachments && attachments.length > 0) {
|
|
138
|
+
if (envelope.capabilitySnapshot && !envelope.capabilitySnapshot.attachment) {
|
|
139
|
+
return jsonOpencodeError(c, new StudioValidationError('Selected model does not support attachments. Remove the files or choose a model that supports them.', 'choose_model'), { model: performer.model });
|
|
140
|
+
}
|
|
141
|
+
for (const att of attachments) {
|
|
142
|
+
parts.push({
|
|
143
|
+
type: 'file',
|
|
144
|
+
mime: att.mime,
|
|
145
|
+
url: att.url,
|
|
146
|
+
filename: att.filename,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const tools = buildEnabledToolMap([
|
|
151
|
+
...toolResolution.resolvedTools,
|
|
152
|
+
...(envelope.toolName ? [envelope.toolName] : []),
|
|
153
|
+
]);
|
|
154
|
+
unwrapOpencodeResult(await oc.session.promptAsync({
|
|
155
|
+
sessionID: c.req.param('id'),
|
|
156
|
+
directory: cwd,
|
|
157
|
+
model: { providerID: performer.model.provider, modelID: performer.model.modelId },
|
|
158
|
+
agent: performer.agentId || (performer.planMode ? 'plan' : 'build'),
|
|
159
|
+
system: envelope.system,
|
|
160
|
+
...(performer.modelVariant ? { variant: performer.modelVariant } : {}),
|
|
161
|
+
...(tools ? { tools } : {}),
|
|
162
|
+
parts,
|
|
163
|
+
}));
|
|
164
|
+
return c.json({ accepted: true }, 202);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
return jsonOpencodeError(c, err, { model: performer.model });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// ── Abort ───────────────────────────────────────────────
|
|
171
|
+
chat.post('/api/chat/sessions/:id/abort', async (c) => {
|
|
172
|
+
try {
|
|
173
|
+
const oc = await getOpencode();
|
|
174
|
+
const directoryQuery = requestDirectoryQuery(c);
|
|
175
|
+
unwrapOpencodeResult(await oc.session.abort({
|
|
176
|
+
sessionID: c.req.param('id'),
|
|
177
|
+
...directoryQuery,
|
|
178
|
+
}));
|
|
179
|
+
await waitForSessionToSettle(oc, c.req.param('id'), directoryQuery).catch(() => { });
|
|
180
|
+
return c.json({ ok: true });
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
return jsonOpencodeError(c, err);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// ── SSE event stream ────────────────────────────────────
|
|
187
|
+
chat.get('/api/chat/events', async (c) => {
|
|
188
|
+
try {
|
|
189
|
+
const oc = await getOpencode();
|
|
190
|
+
const events = await oc.event.subscribe(requestDirectoryQuery(c));
|
|
191
|
+
const stream = new ReadableStream({
|
|
192
|
+
async start(controller) {
|
|
193
|
+
try {
|
|
194
|
+
for await (const event of events.stream) {
|
|
195
|
+
const data = JSON.stringify(event);
|
|
196
|
+
controller.enqueue(new TextEncoder().encode(`data: ${data}\n\n`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
controller.close();
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
return new Response(stream, {
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'text/event-stream',
|
|
207
|
+
'Cache-Control': 'no-cache',
|
|
208
|
+
Connection: 'keep-alive',
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
return jsonOpencodeError(c, err, { defaultStatus: 503 });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
// ── Messages ────────────────────────────────────────────
|
|
217
|
+
chat.get('/api/chat/sessions/:id/messages', async (c) => {
|
|
218
|
+
try {
|
|
219
|
+
const oc = await getOpencode();
|
|
220
|
+
const directoryQuery = requestDirectoryQuery(c);
|
|
221
|
+
const sessionId = c.req.param('id');
|
|
222
|
+
const data = unwrapOpencodeResult(await oc.session.messages({
|
|
223
|
+
sessionID: sessionId,
|
|
224
|
+
...directoryQuery,
|
|
225
|
+
}));
|
|
226
|
+
const statuses = unwrapOpencodeResult(await oc.session.status({
|
|
227
|
+
...directoryQuery,
|
|
228
|
+
}));
|
|
229
|
+
const status = statuses?.[sessionId];
|
|
230
|
+
const normalized = !status || status.type === 'idle'
|
|
231
|
+
? normalizeIncompleteToolParts(data || [], Date.now())
|
|
232
|
+
: (data || []);
|
|
233
|
+
return c.json(normalized);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
return jsonOpencodeError(c, err);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
// ── Diff ────────────────────────────────────────────────
|
|
240
|
+
chat.get('/api/chat/sessions/:id/diff', async (c) => {
|
|
241
|
+
try {
|
|
242
|
+
const oc = await getOpencode();
|
|
243
|
+
const data = unwrapOpencodeResult(await oc.session.diff({
|
|
244
|
+
sessionID: c.req.param('id'),
|
|
245
|
+
...requestDirectoryQuery(c),
|
|
246
|
+
}));
|
|
247
|
+
return c.json(data || []);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
return jsonOpencodeError(c, err);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// ── TODO ────────────────────────────────────────────────
|
|
254
|
+
chat.get('/api/chat/sessions/:id/todo', async (c) => {
|
|
255
|
+
try {
|
|
256
|
+
const oc = await getOpencode();
|
|
257
|
+
const data = unwrapOpencodeResult(await oc.session.todo({
|
|
258
|
+
sessionID: c.req.param('id'),
|
|
259
|
+
...requestDirectoryQuery(c),
|
|
260
|
+
}));
|
|
261
|
+
return c.json(data || []);
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
return jsonOpencodeError(c, err);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
// ── Fork ────────────────────────────────────────────────
|
|
268
|
+
chat.post('/api/chat/sessions/:id/fork', async (c) => {
|
|
269
|
+
const { messageId } = await c.req.json();
|
|
270
|
+
try {
|
|
271
|
+
const oc = await getOpencode();
|
|
272
|
+
const data = unwrapOpencodeResult(await oc.session.fork({
|
|
273
|
+
sessionID: c.req.param('id'),
|
|
274
|
+
...requestDirectoryQuery(c),
|
|
275
|
+
messageID: messageId,
|
|
276
|
+
}));
|
|
277
|
+
return c.json(data);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
return jsonOpencodeError(c, err);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
// ── Share ───────────────────────────────────────────────
|
|
284
|
+
chat.post('/api/chat/sessions/:id/share', async (c) => {
|
|
285
|
+
try {
|
|
286
|
+
const oc = await getOpencode();
|
|
287
|
+
const data = unwrapOpencodeResult(await oc.session.share({
|
|
288
|
+
sessionID: c.req.param('id'),
|
|
289
|
+
...requestDirectoryQuery(c),
|
|
290
|
+
}));
|
|
291
|
+
return c.json(data);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
return jsonOpencodeError(c, err);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// ── Summarize ───────────────────────────────────────────
|
|
298
|
+
chat.post('/api/chat/sessions/:id/summarize', async (c) => {
|
|
299
|
+
const { providerID, modelID, auto } = await c.req.json();
|
|
300
|
+
try {
|
|
301
|
+
const oc = await getOpencode();
|
|
302
|
+
const data = unwrapOpencodeResult(await oc.session.summarize({
|
|
303
|
+
sessionID: c.req.param('id'),
|
|
304
|
+
...requestDirectoryQuery(c),
|
|
305
|
+
...(providerID && modelID ? { providerID, modelID } : {}),
|
|
306
|
+
...(typeof auto === 'boolean' ? { auto } : {}),
|
|
307
|
+
}));
|
|
308
|
+
return c.json(data);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
return jsonOpencodeError(c, err);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
// ── Revert ──────────────────────────────────────────────
|
|
315
|
+
chat.post('/api/chat/sessions/:id/revert', async (c) => {
|
|
316
|
+
const { messageId, partId } = await c.req.json();
|
|
317
|
+
try {
|
|
318
|
+
const oc = await getOpencode();
|
|
319
|
+
const data = unwrapOpencodeResult(await oc.session.revert({
|
|
320
|
+
sessionID: c.req.param('id'),
|
|
321
|
+
...requestDirectoryQuery(c),
|
|
322
|
+
messageID: messageId,
|
|
323
|
+
...(partId ? { partID: partId } : {}),
|
|
324
|
+
}));
|
|
325
|
+
return c.json(data);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
return jsonOpencodeError(c, err);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
// ── Unrevert ────────────────────────────────────────────
|
|
332
|
+
chat.post('/api/chat/sessions/:id/unrevert', async (c) => {
|
|
333
|
+
try {
|
|
334
|
+
const oc = await getOpencode();
|
|
335
|
+
const data = unwrapOpencodeResult(await oc.session.unrevert({
|
|
336
|
+
sessionID: c.req.param('id'),
|
|
337
|
+
...requestDirectoryQuery(c),
|
|
338
|
+
}));
|
|
339
|
+
return c.json(data);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
return jsonOpencodeError(c, err);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
// ── List all sessions ───────────────────────────────────
|
|
346
|
+
chat.get('/api/chat/sessions', async (c) => {
|
|
347
|
+
try {
|
|
348
|
+
const oc = await getOpencode();
|
|
349
|
+
const data = unwrapOpencodeResult(await oc.session.list(requestDirectoryQuery(c)));
|
|
350
|
+
return c.json(data || []);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
return jsonOpencodeError(c, err);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
export default chat;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { buildPromptEnvelope } from '../lib/prompt.js';
|
|
3
|
+
import { resolveRuntimeTools } from '../lib/runtime-tools.js';
|
|
4
|
+
import { resolveRequestWorkingDir } from '../lib/request-context.js';
|
|
5
|
+
import { abortActRuntime, runActRuntime, subscribeActRuntimeEvents } from '../lib/act-runtime.js';
|
|
6
|
+
import { StudioValidationError, jsonOpencodeError, } from '../lib/opencode-errors.js';
|
|
7
|
+
const compile = new Hono();
|
|
8
|
+
export async function compilePrompt(cwd, talRef, danceRefs, model, drafts = {}, modelVariant = null, danceDeliveryMode = 'auto') {
|
|
9
|
+
return buildPromptEnvelope({
|
|
10
|
+
cwd,
|
|
11
|
+
talRef,
|
|
12
|
+
danceRefs,
|
|
13
|
+
drafts,
|
|
14
|
+
model,
|
|
15
|
+
modelVariant,
|
|
16
|
+
danceDeliveryMode,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
compile.post('/api/compile', async (c) => {
|
|
20
|
+
const { talRef, danceRefs, drafts = {}, model, modelVariant = null, agentId = null, mcpServerNames = [], planMode = false, danceDeliveryMode = 'auto' } = await c.req.json();
|
|
21
|
+
if (!model) {
|
|
22
|
+
return jsonOpencodeError(c, new StudioValidationError('Select a model for this performer before compiling prompts.', 'select_model'));
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const cwd = resolveRequestWorkingDir(c);
|
|
26
|
+
const preview = await compilePrompt(cwd, talRef || null, danceRefs || [], model, drafts, modelVariant, danceDeliveryMode);
|
|
27
|
+
const toolResolution = await resolveRuntimeTools(cwd, model, mcpServerNames);
|
|
28
|
+
return c.json({
|
|
29
|
+
agent: agentId || (planMode ? 'plan' : 'build'),
|
|
30
|
+
...preview,
|
|
31
|
+
toolResolution,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return jsonOpencodeError(c, err, { model });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
compile.post('/api/act/run', async (c) => {
|
|
39
|
+
try {
|
|
40
|
+
const body = await c.req.json();
|
|
41
|
+
const cwd = resolveRequestWorkingDir(c);
|
|
42
|
+
const result = await runActRuntime({
|
|
43
|
+
cwd,
|
|
44
|
+
actSessionId: body.actSessionId,
|
|
45
|
+
actUrn: body.actUrn,
|
|
46
|
+
stageAct: body.stageAct,
|
|
47
|
+
performers: body.performers,
|
|
48
|
+
drafts: body.drafts,
|
|
49
|
+
input: body.input,
|
|
50
|
+
maxIterations: body.maxIterations,
|
|
51
|
+
resumeSummary: body.resumeSummary,
|
|
52
|
+
});
|
|
53
|
+
return c.json(result);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
return jsonOpencodeError(c, err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
compile.post('/api/act/sessions/:id/abort', async (c) => {
|
|
60
|
+
try {
|
|
61
|
+
const cwd = resolveRequestWorkingDir(c);
|
|
62
|
+
await abortActRuntime(c.req.param('id'), cwd);
|
|
63
|
+
return c.json({ ok: true });
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return jsonOpencodeError(c, err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
compile.get('/api/act/events', async (c) => {
|
|
70
|
+
try {
|
|
71
|
+
const actSessionId = c.req.query('actSessionId')?.trim();
|
|
72
|
+
if (!actSessionId) {
|
|
73
|
+
return c.json({ error: 'actSessionId is required.' }, 400);
|
|
74
|
+
}
|
|
75
|
+
const stream = new ReadableStream({
|
|
76
|
+
start(controller) {
|
|
77
|
+
const encoder = new TextEncoder();
|
|
78
|
+
const unsubscribe = subscribeActRuntimeEvents(actSessionId, (event) => {
|
|
79
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
|
|
80
|
+
});
|
|
81
|
+
const close = () => {
|
|
82
|
+
unsubscribe();
|
|
83
|
+
try {
|
|
84
|
+
controller.close();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Stream may already be closed.
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
c.req.raw.signal?.addEventListener('abort', close, { once: true });
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return new Response(stream, {
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'text/event-stream',
|
|
96
|
+
'Cache-Control': 'no-cache',
|
|
97
|
+
Connection: 'keep-alive',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return jsonOpencodeError(c, err, { defaultStatus: 503 });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
export default compile;
|