@ottocode/server 0.1.260 → 0.1.261
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/package.json +4 -3
- package/src/index.ts +5 -4
- package/src/openapi/register.ts +92 -0
- package/src/openapi/route.ts +22 -0
- package/src/routes/ask.ts +210 -99
- package/src/routes/auth.ts +1701 -626
- package/src/routes/branch.ts +281 -90
- package/src/routes/config/agents.ts +79 -32
- package/src/routes/config/cwd.ts +46 -14
- package/src/routes/config/debug.ts +159 -30
- package/src/routes/config/defaults.ts +182 -64
- package/src/routes/config/main.ts +109 -73
- package/src/routes/config/models.ts +304 -137
- package/src/routes/config/providers.ts +462 -166
- package/src/routes/config/utils.ts +2 -2
- package/src/routes/doctor.ts +395 -161
- package/src/routes/files.ts +650 -260
- package/src/routes/git/branch.ts +143 -52
- package/src/routes/git/commit.ts +347 -141
- package/src/routes/git/diff.ts +239 -116
- package/src/routes/git/init.ts +103 -23
- package/src/routes/git/pull.ts +167 -65
- package/src/routes/git/push.ts +222 -117
- package/src/routes/git/remote.ts +401 -100
- package/src/routes/git/staging.ts +502 -141
- package/src/routes/git/status.ts +171 -78
- package/src/routes/mcp.ts +1129 -404
- package/src/routes/openapi.ts +27 -4
- package/src/routes/ottorouter.ts +1221 -389
- package/src/routes/provider-usage.ts +153 -36
- package/src/routes/research.ts +817 -370
- package/src/routes/root.ts +50 -6
- package/src/routes/session-approval.ts +228 -54
- package/src/routes/session-files.ts +265 -134
- package/src/routes/session-messages.ts +330 -150
- package/src/routes/session-stream.ts +83 -2
- package/src/routes/sessions.ts +1830 -780
- package/src/routes/skills.ts +849 -161
- package/src/routes/terminals.ts +469 -103
- package/src/routes/tunnel.ts +394 -118
- package/src/runtime/ask/service.ts +1 -0
- package/src/runtime/message/compaction-limits.ts +3 -3
- package/src/runtime/provider/reasoning.ts +2 -1
- package/src/runtime/session/db-operations.ts +4 -3
- package/src/runtime/utils/token.ts +7 -2
- package/src/tools/adapter.ts +21 -0
- package/src/openapi/paths/ask.ts +0 -81
- package/src/openapi/paths/auth.ts +0 -687
- package/src/openapi/paths/branch.ts +0 -102
- package/src/openapi/paths/config.ts +0 -485
- package/src/openapi/paths/doctor.ts +0 -165
- package/src/openapi/paths/files.ts +0 -236
- package/src/openapi/paths/git.ts +0 -690
- package/src/openapi/paths/mcp.ts +0 -339
- package/src/openapi/paths/messages.ts +0 -103
- package/src/openapi/paths/ottorouter.ts +0 -594
- package/src/openapi/paths/provider-usage.ts +0 -59
- package/src/openapi/paths/research.ts +0 -227
- package/src/openapi/paths/session-approval.ts +0 -93
- package/src/openapi/paths/session-extras.ts +0 -336
- package/src/openapi/paths/session-files.ts +0 -91
- package/src/openapi/paths/sessions.ts +0 -210
- package/src/openapi/paths/skills.ts +0 -377
- package/src/openapi/paths/stream.ts +0 -26
- package/src/openapi/paths/terminals.ts +0 -226
- package/src/openapi/paths/tunnel.ts +0 -163
- package/src/openapi/spec.ts +0 -73
|
@@ -13,170 +13,350 @@ import { eq, inArray } from 'drizzle-orm';
|
|
|
13
13
|
import { dispatchAssistantMessage } from '../runtime/message/service.ts';
|
|
14
14
|
import { logger } from '@ottocode/sdk';
|
|
15
15
|
import { serializeError } from '../runtime/errors/api-error.ts';
|
|
16
|
+
import { openApiRoute } from '../openapi/route.ts';
|
|
16
17
|
|
|
17
18
|
type MessagePartRow = typeof messageParts.$inferSelect;
|
|
18
19
|
type SessionRow = typeof sessions.$inferSelect;
|
|
19
20
|
|
|
20
21
|
export function registerSessionMessagesRoutes(app: Hono) {
|
|
21
22
|
// List messages for a session
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
23
|
+
openApiRoute(
|
|
24
|
+
app,
|
|
25
|
+
{
|
|
26
|
+
method: 'get',
|
|
27
|
+
path: '/v1/sessions/{id}/messages',
|
|
28
|
+
tags: ['messages'],
|
|
29
|
+
operationId: 'listMessages',
|
|
30
|
+
summary: 'List messages for a session',
|
|
31
|
+
parameters: [
|
|
32
|
+
{
|
|
33
|
+
in: 'query',
|
|
34
|
+
name: 'project',
|
|
35
|
+
required: false,
|
|
36
|
+
schema: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
},
|
|
39
|
+
description:
|
|
40
|
+
'Project root override (defaults to current working directory).',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
in: 'path',
|
|
44
|
+
name: 'id',
|
|
45
|
+
required: true,
|
|
46
|
+
schema: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
in: 'query',
|
|
52
|
+
name: 'without',
|
|
53
|
+
required: false,
|
|
54
|
+
schema: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
enum: ['parts'],
|
|
57
|
+
},
|
|
58
|
+
description:
|
|
59
|
+
'Exclude parts from the response. By default, parts are included.',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
responses: {
|
|
63
|
+
'200': {
|
|
64
|
+
description: 'OK',
|
|
65
|
+
content: {
|
|
66
|
+
'application/json': {
|
|
67
|
+
schema: {
|
|
68
|
+
type: 'array',
|
|
69
|
+
items: {
|
|
70
|
+
allOf: [
|
|
71
|
+
{
|
|
72
|
+
$ref: '#/components/schemas/Message',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
parts: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
items: {
|
|
80
|
+
$ref: '#/components/schemas/MessagePart',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: [],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
async (c) => {
|
|
95
|
+
try {
|
|
96
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
97
|
+
const cfg = await loadConfig(projectRoot);
|
|
98
|
+
const db = await getDb(cfg.projectRoot);
|
|
99
|
+
const id = c.req.param('id');
|
|
100
|
+
const rows = await db
|
|
101
|
+
.select()
|
|
102
|
+
.from(messages)
|
|
103
|
+
.where(eq(messages.sessionId, id))
|
|
104
|
+
.orderBy(messages.createdAt);
|
|
105
|
+
const without = c.req.query('without');
|
|
106
|
+
if (without !== 'parts') {
|
|
107
|
+
const ids = rows.map((m) => m.id);
|
|
108
|
+
const parts = ids.length
|
|
109
|
+
? await db
|
|
110
|
+
.select()
|
|
111
|
+
.from(messageParts)
|
|
112
|
+
.where(inArray(messageParts.messageId, ids))
|
|
113
|
+
: [];
|
|
114
|
+
const partsByMsg = new Map<string, MessagePartRow[]>();
|
|
115
|
+
for (const p of parts) {
|
|
116
|
+
const existing = partsByMsg.get(p.messageId);
|
|
117
|
+
if (existing) existing.push(p);
|
|
118
|
+
else partsByMsg.set(p.messageId, [p]);
|
|
119
|
+
}
|
|
120
|
+
const wantParsed = (() => {
|
|
121
|
+
const q = (c.req.query('parsed') || '').toLowerCase();
|
|
122
|
+
return q === '1' || q === 'true' || q === 'yes';
|
|
123
|
+
})();
|
|
124
|
+
function parseContent(raw: string): Record<string, unknown> | string {
|
|
125
|
+
try {
|
|
126
|
+
const v = JSON.parse(String(raw ?? ''));
|
|
127
|
+
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
128
|
+
return v as Record<string, unknown>;
|
|
129
|
+
} catch {}
|
|
130
|
+
return raw;
|
|
131
|
+
}
|
|
132
|
+
const enriched = rows.map((m) => {
|
|
133
|
+
const parts = (partsByMsg.get(m.id) ?? []).sort(
|
|
134
|
+
(a, b) => a.index - b.index,
|
|
135
|
+
);
|
|
136
|
+
const mapped = parts.map((p) => {
|
|
137
|
+
const parsed = parseContent(p.content);
|
|
138
|
+
return wantParsed
|
|
139
|
+
? { ...p, content: parsed }
|
|
140
|
+
: { ...p, contentJson: parsed };
|
|
141
|
+
});
|
|
142
|
+
return { ...m, parts: mapped };
|
|
69
143
|
});
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
return c.json(
|
|
144
|
+
return c.json(enriched);
|
|
145
|
+
}
|
|
146
|
+
return c.json(rows);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.error('Failed to list session messages', error);
|
|
149
|
+
const errorResponse = serializeError(error);
|
|
150
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
73
151
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
logger.error('Failed to list session messages', error);
|
|
77
|
-
const errorResponse = serializeError(error);
|
|
78
|
-
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
152
|
+
},
|
|
153
|
+
);
|
|
81
154
|
|
|
82
155
|
// Post a user message and get assistant reply (non-streaming for v0)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
156
|
+
openApiRoute(
|
|
157
|
+
app,
|
|
158
|
+
{
|
|
159
|
+
method: 'post',
|
|
160
|
+
path: '/v1/sessions/{id}/messages',
|
|
161
|
+
tags: ['messages'],
|
|
162
|
+
operationId: 'createMessage',
|
|
163
|
+
summary: 'Send a user message and enqueue assistant run',
|
|
164
|
+
parameters: [
|
|
165
|
+
{
|
|
166
|
+
in: 'query',
|
|
167
|
+
name: 'project',
|
|
168
|
+
required: false,
|
|
169
|
+
schema: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
},
|
|
172
|
+
description:
|
|
173
|
+
'Project root override (defaults to current working directory).',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
in: 'path',
|
|
177
|
+
name: 'id',
|
|
178
|
+
required: true,
|
|
179
|
+
schema: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
requestBody: {
|
|
185
|
+
required: true,
|
|
186
|
+
content: {
|
|
187
|
+
'application/json': {
|
|
188
|
+
schema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
required: ['content'],
|
|
191
|
+
properties: {
|
|
192
|
+
content: {
|
|
193
|
+
type: 'string',
|
|
194
|
+
},
|
|
195
|
+
agent: {
|
|
196
|
+
type: 'string',
|
|
197
|
+
description: 'Agent name. Defaults to config if omitted.',
|
|
198
|
+
},
|
|
199
|
+
provider: {
|
|
200
|
+
$ref: '#/components/schemas/Provider',
|
|
201
|
+
},
|
|
202
|
+
model: {
|
|
203
|
+
type: 'string',
|
|
204
|
+
},
|
|
205
|
+
userContext: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description:
|
|
208
|
+
'Optional user-provided context to include in the system prompt.',
|
|
209
|
+
},
|
|
210
|
+
reasoningText: {
|
|
211
|
+
type: 'boolean',
|
|
212
|
+
description:
|
|
213
|
+
'Enable extended thinking / reasoning for models that support it.',
|
|
214
|
+
},
|
|
215
|
+
reasoningLevel: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max', 'xhigh'],
|
|
218
|
+
description:
|
|
219
|
+
'Reasoning intensity level for providers/models that support it.',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
responses: {
|
|
227
|
+
'202': {
|
|
228
|
+
description: 'Accepted',
|
|
229
|
+
content: {
|
|
230
|
+
'application/json': {
|
|
231
|
+
schema: {
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: {
|
|
234
|
+
messageId: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
required: ['messageId'],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
'400': {
|
|
244
|
+
description: 'Bad Request',
|
|
245
|
+
content: {
|
|
246
|
+
'application/json': {
|
|
247
|
+
schema: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: {
|
|
250
|
+
error: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ['error'],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
async (c) => {
|
|
262
|
+
try {
|
|
263
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
264
|
+
const cfg = await loadConfig(projectRoot);
|
|
265
|
+
const db = await getDb(cfg.projectRoot);
|
|
266
|
+
const sessionId = c.req.param('id');
|
|
267
|
+
const body = await c.req.json().catch(() => ({}));
|
|
90
268
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
269
|
+
// DEBUG: Log received body
|
|
270
|
+
logger.info('[API] Received message request', {
|
|
271
|
+
sessionId,
|
|
272
|
+
hasContent: !!body?.content,
|
|
273
|
+
hasUserContext: !!body?.userContext,
|
|
274
|
+
userContext: body?.userContext
|
|
275
|
+
? `${String(body.userContext).substring(0, 50)}...`
|
|
276
|
+
: 'NONE',
|
|
277
|
+
});
|
|
100
278
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
279
|
+
// Load session to inherit its provider/model/agent by default
|
|
280
|
+
const sessionRows = await db
|
|
281
|
+
.select()
|
|
282
|
+
.from(sessions)
|
|
283
|
+
.where(eq(sessions.id, sessionId));
|
|
284
|
+
if (!sessionRows.length) {
|
|
285
|
+
logger.warn('Session not found', { sessionId });
|
|
286
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
287
|
+
}
|
|
288
|
+
const sess: SessionRow = sessionRows[0];
|
|
289
|
+
const provider =
|
|
290
|
+
body?.provider ?? sess.provider ?? cfg.defaults.provider;
|
|
291
|
+
const modelName = body?.model ?? sess.model ?? cfg.defaults.model;
|
|
292
|
+
const agent = body?.agent ?? sess.agent ?? cfg.defaults.agent;
|
|
293
|
+
const content = body?.content ?? '';
|
|
294
|
+
const userContext = body?.userContext;
|
|
295
|
+
const images = Array.isArray(body?.images) ? body.images : undefined;
|
|
296
|
+
const files = Array.isArray(body?.files) ? body.files : undefined;
|
|
118
297
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
298
|
+
// DEBUG: Log extracted userContext
|
|
299
|
+
logger.info('[API] Extracted userContext', {
|
|
300
|
+
userContext: userContext
|
|
301
|
+
? `${String(userContext).substring(0, 50)}...`
|
|
302
|
+
: 'NONE',
|
|
303
|
+
typeOf: typeof userContext,
|
|
304
|
+
});
|
|
126
305
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
306
|
+
const reasoning =
|
|
307
|
+
body?.reasoningText ?? cfg.defaults.reasoningText ?? false;
|
|
308
|
+
const reasoningLevel =
|
|
309
|
+
(body?.reasoningLevel as ReasoningLevel | undefined) ??
|
|
310
|
+
cfg.defaults.reasoningLevel ??
|
|
311
|
+
'high';
|
|
133
312
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
313
|
+
// Validate model capabilities if tools are allowed for this agent
|
|
314
|
+
const wantsToolCalls = true; // agent toolset may be non-empty
|
|
315
|
+
try {
|
|
316
|
+
validateProviderModel(provider, modelName, cfg, { wantsToolCalls });
|
|
317
|
+
} catch (err) {
|
|
318
|
+
logger.error('Model validation failed', err, { provider, modelName });
|
|
319
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
320
|
+
return c.json({ error: message }, 400);
|
|
321
|
+
}
|
|
322
|
+
// Enforce provider auth: only allow providers/models the user authenticated for
|
|
323
|
+
const authorized = await isProviderAuthorized(cfg, provider);
|
|
324
|
+
if (!authorized) {
|
|
325
|
+
logger.warn('Provider not authorized', { provider });
|
|
326
|
+
return c.json(
|
|
327
|
+
{
|
|
328
|
+
error: `Provider ${provider} is not configured. Run \`otto auth login\` to add credentials.`,
|
|
329
|
+
},
|
|
330
|
+
400,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
await ensureProviderEnv(cfg, provider);
|
|
334
|
+
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
156
335
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
336
|
+
const { assistantMessageId } = await dispatchAssistantMessage({
|
|
337
|
+
cfg,
|
|
338
|
+
db,
|
|
339
|
+
session: sess,
|
|
340
|
+
agent,
|
|
341
|
+
provider,
|
|
342
|
+
model: modelName,
|
|
343
|
+
content,
|
|
344
|
+
oneShot: Boolean(body?.oneShot),
|
|
345
|
+
userContext,
|
|
346
|
+
reasoningText:
|
|
347
|
+
providerDefinition?.compatibility === 'ollama'
|
|
348
|
+
? (body?.reasoningText ?? false)
|
|
349
|
+
: reasoning,
|
|
350
|
+
reasoningLevel,
|
|
351
|
+
images,
|
|
352
|
+
files,
|
|
353
|
+
});
|
|
354
|
+
return c.json({ messageId: assistantMessageId }, 202);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
logger.error('Failed to create session message', error);
|
|
357
|
+
const errorResponse = serializeError(error);
|
|
358
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
);
|
|
182
362
|
}
|
|
@@ -2,6 +2,7 @@ import type { Context } from 'hono';
|
|
|
2
2
|
import type { Hono } from 'hono';
|
|
3
3
|
import { subscribe } from '../events/bus.ts';
|
|
4
4
|
import type { OttoEvent } from '../events/types.ts';
|
|
5
|
+
import { openApiRoute } from '../openapi/route.ts';
|
|
5
6
|
|
|
6
7
|
function safeStringify(obj: unknown): string {
|
|
7
8
|
return JSON.stringify(obj, (_key, value) =>
|
|
@@ -57,6 +58,86 @@ function handleSessionStream(c: Context) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export function registerSessionStreamRoute(app: Hono) {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
openApiRoute(
|
|
62
|
+
app,
|
|
63
|
+
{
|
|
64
|
+
method: 'get',
|
|
65
|
+
path: '/v1/sessions/{id}/stream',
|
|
66
|
+
tags: ['stream'],
|
|
67
|
+
operationId: 'subscribeSessionStream',
|
|
68
|
+
summary: 'Subscribe to session event stream (SSE)',
|
|
69
|
+
parameters: [
|
|
70
|
+
{
|
|
71
|
+
in: 'query',
|
|
72
|
+
name: 'project',
|
|
73
|
+
required: false,
|
|
74
|
+
schema: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
},
|
|
77
|
+
description:
|
|
78
|
+
'Project root override (defaults to current working directory).',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
in: 'path',
|
|
82
|
+
name: 'id',
|
|
83
|
+
required: true,
|
|
84
|
+
schema: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
responses: {
|
|
90
|
+
'200': {
|
|
91
|
+
description: 'text/event-stream',
|
|
92
|
+
content: {
|
|
93
|
+
'text/event-stream': {
|
|
94
|
+
schema: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description:
|
|
97
|
+
'SSE event stream. Events include session.created, message.created, message.part.delta, tool.call, tool.delta, tool.result, message.completed, error.',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
handleSessionStream,
|
|
105
|
+
);
|
|
106
|
+
openApiRoute(
|
|
107
|
+
app,
|
|
108
|
+
{
|
|
109
|
+
method: 'post',
|
|
110
|
+
path: '/v1/sessions/{id}/stream',
|
|
111
|
+
tags: ['stream'],
|
|
112
|
+
operationId: 'subscribeSessionStreamPost',
|
|
113
|
+
summary: 'Subscribe to session event stream (SSE) using POST',
|
|
114
|
+
parameters: [
|
|
115
|
+
{
|
|
116
|
+
in: 'query',
|
|
117
|
+
name: 'project',
|
|
118
|
+
required: false,
|
|
119
|
+
schema: { type: 'string' },
|
|
120
|
+
description:
|
|
121
|
+
'Project root override (defaults to current working directory).',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
in: 'path',
|
|
125
|
+
name: 'id',
|
|
126
|
+
required: true,
|
|
127
|
+
schema: { type: 'string' },
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
responses: {
|
|
131
|
+
'200': {
|
|
132
|
+
description: 'text/event-stream',
|
|
133
|
+
content: {
|
|
134
|
+
'text/event-stream': {
|
|
135
|
+
schema: { type: 'string' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
handleSessionStream,
|
|
142
|
+
);
|
|
62
143
|
}
|