@ottocode/server 0.1.259 → 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.
Files changed (69) hide show
  1. package/package.json +4 -3
  2. package/src/index.ts +5 -4
  3. package/src/openapi/register.ts +92 -0
  4. package/src/openapi/route.ts +22 -0
  5. package/src/routes/ask.ts +210 -99
  6. package/src/routes/auth.ts +1701 -626
  7. package/src/routes/branch.ts +281 -90
  8. package/src/routes/config/agents.ts +79 -32
  9. package/src/routes/config/cwd.ts +46 -14
  10. package/src/routes/config/debug.ts +159 -30
  11. package/src/routes/config/defaults.ts +182 -64
  12. package/src/routes/config/main.ts +109 -73
  13. package/src/routes/config/models.ts +304 -137
  14. package/src/routes/config/providers.ts +462 -166
  15. package/src/routes/config/utils.ts +2 -2
  16. package/src/routes/doctor.ts +395 -161
  17. package/src/routes/files.ts +650 -260
  18. package/src/routes/git/branch.ts +143 -52
  19. package/src/routes/git/commit.ts +347 -141
  20. package/src/routes/git/diff.ts +239 -116
  21. package/src/routes/git/init.ts +103 -23
  22. package/src/routes/git/pull.ts +167 -65
  23. package/src/routes/git/push.ts +222 -117
  24. package/src/routes/git/remote.ts +401 -100
  25. package/src/routes/git/staging.ts +502 -141
  26. package/src/routes/git/status.ts +171 -78
  27. package/src/routes/mcp.ts +1129 -404
  28. package/src/routes/openapi.ts +27 -4
  29. package/src/routes/ottorouter.ts +1221 -389
  30. package/src/routes/provider-usage.ts +153 -36
  31. package/src/routes/research.ts +817 -370
  32. package/src/routes/root.ts +50 -6
  33. package/src/routes/session-approval.ts +228 -54
  34. package/src/routes/session-files.ts +265 -134
  35. package/src/routes/session-messages.ts +330 -150
  36. package/src/routes/session-stream.ts +83 -2
  37. package/src/routes/sessions.ts +1830 -780
  38. package/src/routes/skills.ts +849 -161
  39. package/src/routes/terminals.ts +469 -103
  40. package/src/routes/tunnel.ts +394 -118
  41. package/src/runtime/agent/runner-reasoning.ts +38 -3
  42. package/src/runtime/agent/runner.ts +1 -0
  43. package/src/runtime/ask/service.ts +1 -0
  44. package/src/runtime/message/compaction-limits.ts +3 -3
  45. package/src/runtime/provider/reasoning.ts +18 -7
  46. package/src/runtime/session/db-operations.ts +4 -3
  47. package/src/runtime/utils/token.ts +7 -2
  48. package/src/tools/adapter.ts +21 -0
  49. package/src/openapi/paths/ask.ts +0 -81
  50. package/src/openapi/paths/auth.ts +0 -687
  51. package/src/openapi/paths/branch.ts +0 -102
  52. package/src/openapi/paths/config.ts +0 -485
  53. package/src/openapi/paths/doctor.ts +0 -165
  54. package/src/openapi/paths/files.ts +0 -236
  55. package/src/openapi/paths/git.ts +0 -690
  56. package/src/openapi/paths/mcp.ts +0 -339
  57. package/src/openapi/paths/messages.ts +0 -103
  58. package/src/openapi/paths/ottorouter.ts +0 -594
  59. package/src/openapi/paths/provider-usage.ts +0 -59
  60. package/src/openapi/paths/research.ts +0 -227
  61. package/src/openapi/paths/session-approval.ts +0 -93
  62. package/src/openapi/paths/session-extras.ts +0 -336
  63. package/src/openapi/paths/session-files.ts +0 -91
  64. package/src/openapi/paths/sessions.ts +0 -210
  65. package/src/openapi/paths/skills.ts +0 -377
  66. package/src/openapi/paths/stream.ts +0 -26
  67. package/src/openapi/paths/terminals.ts +0 -226
  68. package/src/openapi/paths/tunnel.ts +0 -163
  69. package/src/openapi/spec.ts +0 -73
@@ -8,100 +8,291 @@ import {
8
8
  getParentSession,
9
9
  } from '../runtime/session/branch.ts';
10
10
  import { serializeError } from '../runtime/errors/api-error.ts';
11
+ import { openApiRoute } from '../openapi/route.ts';
11
12
 
12
13
  export function registerBranchRoutes(app: Hono) {
13
- app.post('/v1/sessions/:sessionId/branch', async (c) => {
14
- try {
15
- const sessionId = c.req.param('sessionId');
16
- const projectRoot = c.req.query('project') || process.cwd();
17
- const cfg = await loadConfig(projectRoot);
18
- const db = await getDb(cfg.projectRoot);
19
-
20
- const body = (await c.req.json().catch(() => ({}))) as Record<
21
- string,
22
- unknown
23
- >;
24
-
25
- const fromMessageId = body.fromMessageId;
26
- if (typeof fromMessageId !== 'string' || !fromMessageId.trim()) {
27
- return c.json({ error: 'fromMessageId is required' }, 400);
14
+ openApiRoute(
15
+ app,
16
+ {
17
+ method: 'post',
18
+ path: '/v1/sessions/{sessionId}/branch',
19
+ tags: ['sessions'],
20
+ operationId: 'createBranch',
21
+ summary: 'Create a branch from a session message',
22
+ parameters: [
23
+ {
24
+ in: 'path',
25
+ name: 'sessionId',
26
+ required: true,
27
+ schema: {
28
+ type: 'string',
29
+ },
30
+ },
31
+ {
32
+ in: 'query',
33
+ name: 'project',
34
+ required: false,
35
+ schema: {
36
+ type: 'string',
37
+ },
38
+ description:
39
+ 'Project root override (defaults to current working directory).',
40
+ },
41
+ ],
42
+ requestBody: {
43
+ required: true,
44
+ content: {
45
+ 'application/json': {
46
+ schema: {
47
+ type: 'object',
48
+ properties: {
49
+ fromMessageId: {
50
+ type: 'string',
51
+ },
52
+ provider: {
53
+ type: 'string',
54
+ },
55
+ model: {
56
+ type: 'string',
57
+ },
58
+ agent: {
59
+ type: 'string',
60
+ },
61
+ title: {
62
+ type: 'string',
63
+ },
64
+ },
65
+ required: ['fromMessageId'],
66
+ },
67
+ },
68
+ },
69
+ },
70
+ responses: {
71
+ '201': {
72
+ description: 'Created',
73
+ content: {
74
+ 'application/json': {
75
+ schema: {
76
+ $ref: '#/components/schemas/Session',
77
+ },
78
+ },
79
+ },
80
+ },
81
+ '400': {
82
+ description: 'Bad Request',
83
+ content: {
84
+ 'application/json': {
85
+ schema: {
86
+ type: 'object',
87
+ properties: {
88
+ error: {
89
+ type: 'string',
90
+ },
91
+ },
92
+ required: ['error'],
93
+ },
94
+ },
95
+ },
96
+ },
97
+ },
98
+ },
99
+ async (c) => {
100
+ try {
101
+ const sessionId = c.req.param('sessionId');
102
+ const projectRoot = c.req.query('project') || process.cwd();
103
+ const cfg = await loadConfig(projectRoot);
104
+ const db = await getDb(cfg.projectRoot);
105
+
106
+ const body = (await c.req.json().catch(() => ({}))) as Record<
107
+ string,
108
+ unknown
109
+ >;
110
+
111
+ const fromMessageId = body.fromMessageId;
112
+ if (typeof fromMessageId !== 'string' || !fromMessageId.trim()) {
113
+ return c.json({ error: 'fromMessageId is required' }, 400);
114
+ }
115
+
116
+ const provider =
117
+ typeof body.provider === 'string' &&
118
+ hasConfiguredProvider(cfg, body.provider)
119
+ ? body.provider
120
+ : undefined;
121
+
122
+ const model =
123
+ typeof body.model === 'string' && body.model.trim()
124
+ ? body.model.trim()
125
+ : undefined;
126
+
127
+ const agent =
128
+ typeof body.agent === 'string' && body.agent.trim()
129
+ ? body.agent.trim()
130
+ : undefined;
131
+
132
+ const title =
133
+ typeof body.title === 'string' && body.title.trim()
134
+ ? body.title.trim()
135
+ : undefined;
136
+
137
+ const result = await createBranch({
138
+ db,
139
+ parentSessionId: sessionId,
140
+ fromMessageId: fromMessageId.trim(),
141
+ provider,
142
+ model,
143
+ agent,
144
+ title,
145
+ projectPath: cfg.projectRoot,
146
+ });
147
+
148
+ return c.json(result, 201);
149
+ } catch (err) {
150
+ logger.error('Failed to create branch', err);
151
+ const errorResponse = serializeError(err);
152
+ return c.json(errorResponse, errorResponse.error.status || 400);
28
153
  }
154
+ },
155
+ );
156
+
157
+ openApiRoute(
158
+ app,
159
+ {
160
+ method: 'get',
161
+ path: '/v1/sessions/{sessionId}/branches',
162
+ tags: ['sessions'],
163
+ operationId: 'listBranches',
164
+ summary: 'List branches of a session',
165
+ parameters: [
166
+ {
167
+ in: 'path',
168
+ name: 'sessionId',
169
+ required: true,
170
+ schema: {
171
+ type: 'string',
172
+ },
173
+ },
174
+ {
175
+ in: 'query',
176
+ name: 'project',
177
+ required: false,
178
+ schema: {
179
+ type: 'string',
180
+ },
181
+ description:
182
+ 'Project root override (defaults to current working directory).',
183
+ },
184
+ ],
185
+ responses: {
186
+ '200': {
187
+ description: 'OK',
188
+ content: {
189
+ 'application/json': {
190
+ schema: {
191
+ type: 'object',
192
+ properties: {
193
+ branches: {
194
+ type: 'array',
195
+ items: {
196
+ $ref: '#/components/schemas/Session',
197
+ },
198
+ },
199
+ },
200
+ required: ['branches'],
201
+ },
202
+ },
203
+ },
204
+ },
205
+ },
206
+ },
207
+ async (c) => {
208
+ try {
209
+ const sessionId = c.req.param('sessionId');
210
+ const projectRoot = c.req.query('project') || process.cwd();
211
+ const cfg = await loadConfig(projectRoot);
212
+ const db = await getDb(cfg.projectRoot);
29
213
 
30
- const provider =
31
- typeof body.provider === 'string' &&
32
- hasConfiguredProvider(cfg, body.provider)
33
- ? body.provider
34
- : undefined;
35
-
36
- const model =
37
- typeof body.model === 'string' && body.model.trim()
38
- ? body.model.trim()
39
- : undefined;
40
-
41
- const agent =
42
- typeof body.agent === 'string' && body.agent.trim()
43
- ? body.agent.trim()
44
- : undefined;
45
-
46
- const title =
47
- typeof body.title === 'string' && body.title.trim()
48
- ? body.title.trim()
49
- : undefined;
50
-
51
- const result = await createBranch({
52
- db,
53
- parentSessionId: sessionId,
54
- fromMessageId: fromMessageId.trim(),
55
- provider,
56
- model,
57
- agent,
58
- title,
59
- projectPath: cfg.projectRoot,
60
- });
61
-
62
- return c.json(result, 201);
63
- } catch (err) {
64
- logger.error('Failed to create branch', err);
65
- const errorResponse = serializeError(err);
66
- return c.json(errorResponse, errorResponse.error.status || 400);
67
- }
68
- });
69
-
70
- app.get('/v1/sessions/:sessionId/branches', async (c) => {
71
- try {
72
- const sessionId = c.req.param('sessionId');
73
- const projectRoot = c.req.query('project') || process.cwd();
74
- const cfg = await loadConfig(projectRoot);
75
- const db = await getDb(cfg.projectRoot);
76
-
77
- const branches = await listBranches(db, sessionId, cfg.projectRoot);
78
-
79
- return c.json({ branches });
80
- } catch (err) {
81
- logger.error('Failed to list branches', err);
82
- const errorResponse = serializeError(err);
83
- return c.json(errorResponse, errorResponse.error.status || 500);
84
- }
85
- });
86
-
87
- app.get('/v1/sessions/:sessionId/parent', async (c) => {
88
- try {
89
- const sessionId = c.req.param('sessionId');
90
- const projectRoot = c.req.query('project') || process.cwd();
91
- const cfg = await loadConfig(projectRoot);
92
- const db = await getDb(cfg.projectRoot);
93
-
94
- const parent = await getParentSession(db, sessionId, cfg.projectRoot);
95
-
96
- if (!parent) {
97
- return c.json({ parent: null });
214
+ const branches = await listBranches(db, sessionId, cfg.projectRoot);
215
+
216
+ return c.json({ branches });
217
+ } catch (err) {
218
+ logger.error('Failed to list branches', err);
219
+ const errorResponse = serializeError(err);
220
+ return c.json(errorResponse, errorResponse.error.status || 500);
98
221
  }
222
+ },
223
+ );
224
+
225
+ openApiRoute(
226
+ app,
227
+ {
228
+ method: 'get',
229
+ path: '/v1/sessions/{sessionId}/parent',
230
+ tags: ['sessions'],
231
+ operationId: 'getParentSession',
232
+ summary: 'Get parent session of a branch',
233
+ parameters: [
234
+ {
235
+ in: 'path',
236
+ name: 'sessionId',
237
+ required: true,
238
+ schema: {
239
+ type: 'string',
240
+ },
241
+ },
242
+ {
243
+ in: 'query',
244
+ name: 'project',
245
+ required: false,
246
+ schema: {
247
+ type: 'string',
248
+ },
249
+ description:
250
+ 'Project root override (defaults to current working directory).',
251
+ },
252
+ ],
253
+ responses: {
254
+ '200': {
255
+ description: 'OK',
256
+ content: {
257
+ 'application/json': {
258
+ schema: {
259
+ type: 'object',
260
+ properties: {
261
+ parent: {
262
+ nullable: true,
263
+ allOf: [
264
+ {
265
+ $ref: '#/components/schemas/Session',
266
+ },
267
+ ],
268
+ },
269
+ },
270
+ required: ['parent'],
271
+ },
272
+ },
273
+ },
274
+ },
275
+ },
276
+ },
277
+ async (c) => {
278
+ try {
279
+ const sessionId = c.req.param('sessionId');
280
+ const projectRoot = c.req.query('project') || process.cwd();
281
+ const cfg = await loadConfig(projectRoot);
282
+ const db = await getDb(cfg.projectRoot);
99
283
 
100
- return c.json({ parent });
101
- } catch (err) {
102
- logger.error('Failed to get parent session', err);
103
- const errorResponse = serializeError(err);
104
- return c.json(errorResponse, errorResponse.error.status || 500);
105
- }
106
- });
284
+ const parent = await getParentSession(db, sessionId, cfg.projectRoot);
285
+
286
+ if (!parent) {
287
+ return c.json({ parent: null });
288
+ }
289
+
290
+ return c.json({ parent });
291
+ } catch (err) {
292
+ logger.error('Failed to get parent session', err);
293
+ const errorResponse = serializeError(err);
294
+ return c.json(errorResponse, errorResponse.error.status || 500);
295
+ }
296
+ },
297
+ );
107
298
  }
@@ -4,43 +4,90 @@ import type { EmbeddedAppConfig } from '../../index.ts';
4
4
  import { logger } from '@ottocode/sdk';
5
5
  import { serializeError } from '../../runtime/errors/api-error.ts';
6
6
  import { discoverAllAgents, getDefault } from './utils.ts';
7
+ import { openApiRoute } from '../../openapi/route.ts';
7
8
 
8
9
  export function registerAgentsRoute(app: Hono) {
9
- app.get('/v1/config/agents', async (c) => {
10
- try {
11
- const embeddedConfig = (
12
- c as unknown as {
13
- get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
10
+ openApiRoute(
11
+ app,
12
+ {
13
+ method: 'get',
14
+ path: '/v1/config/agents',
15
+ tags: ['config'],
16
+ operationId: 'getAgents',
17
+ summary: 'Get available agents',
18
+ parameters: [
19
+ {
20
+ in: 'query',
21
+ name: 'project',
22
+ required: false,
23
+ schema: {
24
+ type: 'string',
25
+ },
26
+ description:
27
+ 'Project root override (defaults to current working directory).',
28
+ },
29
+ ],
30
+ responses: {
31
+ '200': {
32
+ description: 'OK',
33
+ content: {
34
+ 'application/json': {
35
+ schema: {
36
+ type: 'object',
37
+ properties: {
38
+ agents: {
39
+ type: 'array',
40
+ items: {
41
+ type: 'string',
42
+ },
43
+ },
44
+ default: {
45
+ type: 'string',
46
+ },
47
+ },
48
+ required: ['agents', 'default'],
49
+ },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ async (c) => {
56
+ try {
57
+ const embeddedConfig = (
58
+ c as unknown as {
59
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
60
+ }
61
+ ).get('embeddedConfig');
62
+
63
+ if (embeddedConfig) {
64
+ const agents = embeddedConfig.agents
65
+ ? Object.keys(embeddedConfig.agents)
66
+ : ['general', 'build', 'plan', 'init'];
67
+ return c.json({
68
+ agents,
69
+ default: getDefault(
70
+ embeddedConfig.agent,
71
+ embeddedConfig.defaults?.agent,
72
+ 'general',
73
+ ),
74
+ });
14
75
  }
15
- ).get('embeddedConfig');
16
76
 
17
- if (embeddedConfig) {
18
- const agents = embeddedConfig.agents
19
- ? Object.keys(embeddedConfig.agents)
20
- : ['general', 'build', 'plan', 'init'];
77
+ const projectRoot = c.req.query('project') || process.cwd();
78
+ const cfg = await loadConfig(projectRoot);
79
+
80
+ const allAgents = await discoverAllAgents(cfg.projectRoot);
81
+
21
82
  return c.json({
22
- agents,
23
- default: getDefault(
24
- embeddedConfig.agent,
25
- embeddedConfig.defaults?.agent,
26
- 'general',
27
- ),
83
+ agents: allAgents,
84
+ default: cfg.defaults.agent,
28
85
  });
86
+ } catch (error) {
87
+ logger.error('Failed to get agents', error);
88
+ const errorResponse = serializeError(error);
89
+ return c.json(errorResponse, errorResponse.error.status || 500);
29
90
  }
30
-
31
- const projectRoot = c.req.query('project') || process.cwd();
32
- const cfg = await loadConfig(projectRoot);
33
-
34
- const allAgents = await discoverAllAgents(cfg.projectRoot);
35
-
36
- return c.json({
37
- agents: allAgents,
38
- default: cfg.defaults.agent,
39
- });
40
- } catch (error) {
41
- logger.error('Failed to get agents', error);
42
- const errorResponse = serializeError(error);
43
- return c.json(errorResponse, errorResponse.error.status || 500);
44
- }
45
- });
91
+ },
92
+ );
46
93
  }
@@ -2,20 +2,52 @@ import type { Hono } from 'hono';
2
2
  import { basename } from 'node:path';
3
3
  import { logger } from '@ottocode/sdk';
4
4
  import { serializeError } from '../../runtime/errors/api-error.ts';
5
+ import { openApiRoute } from '../../openapi/route.ts';
5
6
 
6
7
  export function registerCwdRoute(app: Hono) {
7
- app.get('/v1/config/cwd', (c) => {
8
- try {
9
- const cwd = process.cwd();
10
- const dirName = basename(cwd);
11
- return c.json({
12
- cwd,
13
- dirName,
14
- });
15
- } catch (error) {
16
- logger.error('Failed to get current working directory', error);
17
- const errorResponse = serializeError(error);
18
- return c.json(errorResponse, errorResponse.error.status || 500);
19
- }
20
- });
8
+ openApiRoute(
9
+ app,
10
+ {
11
+ method: 'get',
12
+ path: '/v1/config/cwd',
13
+ tags: ['config'],
14
+ operationId: 'getCwd',
15
+ summary: 'Get current working directory info',
16
+ responses: {
17
+ '200': {
18
+ description: 'OK',
19
+ content: {
20
+ 'application/json': {
21
+ schema: {
22
+ type: 'object',
23
+ properties: {
24
+ cwd: {
25
+ type: 'string',
26
+ },
27
+ dirName: {
28
+ type: 'string',
29
+ },
30
+ },
31
+ required: ['cwd', 'dirName'],
32
+ },
33
+ },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ (c) => {
39
+ try {
40
+ const cwd = process.cwd();
41
+ const dirName = basename(cwd);
42
+ return c.json({
43
+ cwd,
44
+ dirName,
45
+ });
46
+ } catch (error) {
47
+ logger.error('Failed to get current working directory', error);
48
+ const errorResponse = serializeError(error);
49
+ return c.json(errorResponse, errorResponse.error.status || 500);
50
+ }
51
+ },
52
+ );
21
53
  }