@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
@@ -20,127 +20,332 @@ import {
20
20
  adaptSimpleCall,
21
21
  } from '../../runtime/provider/oauth-adapter.ts';
22
22
  import { appendCoAuthorTrailer } from '@ottocode/sdk';
23
+ import { openApiRoute } from '../../openapi/route.ts';
23
24
 
24
25
  const execFileAsync = promisify(execFile);
25
26
 
26
27
  export function registerCommitRoutes(app: Hono) {
27
- app.post('/v1/git/commit', async (c) => {
28
- try {
29
- const body = await c.req.json();
30
- const { message, project } = gitCommitSchema.parse(body);
31
-
32
- const requestedPath = project || process.cwd();
33
-
34
- const validation = await validateAndGetGitRoot(requestedPath);
35
- if ('error' in validation) {
36
- return c.json(
37
- { status: 'error', error: validation.error, code: validation.code },
38
- 400,
39
- );
40
- }
41
-
42
- const { gitRoot } = validation;
43
-
44
- const fullMessage = appendCoAuthorTrailer(message);
45
- const { stdout } = await execFileAsync(
46
- 'git',
47
- ['commit', '-m', fullMessage],
48
- {
49
- cwd: gitRoot,
28
+ openApiRoute(
29
+ app,
30
+ {
31
+ method: 'post',
32
+ path: '/v1/git/commit',
33
+ tags: ['git'],
34
+ operationId: 'commitChanges',
35
+ summary: 'Commit staged changes',
36
+ requestBody: {
37
+ required: true,
38
+ content: {
39
+ 'application/json': {
40
+ schema: {
41
+ type: 'object',
42
+ properties: {
43
+ project: {
44
+ type: 'string',
45
+ },
46
+ message: {
47
+ type: 'string',
48
+ minLength: 1,
49
+ },
50
+ },
51
+ required: ['message'],
52
+ },
53
+ },
50
54
  },
51
- );
52
-
53
- return c.json({
54
- status: 'ok',
55
- data: {
56
- message: stdout.trim(),
55
+ },
56
+ responses: {
57
+ '200': {
58
+ description: 'OK',
59
+ content: {
60
+ 'application/json': {
61
+ schema: {
62
+ type: 'object',
63
+ properties: {
64
+ status: {
65
+ type: 'string',
66
+ enum: ['ok'],
67
+ },
68
+ data: {
69
+ $ref: '#/components/schemas/GitCommit',
70
+ },
71
+ },
72
+ required: ['status', 'data'],
73
+ },
74
+ },
75
+ },
57
76
  },
58
- });
59
- } catch (error) {
60
- return c.json(
61
- {
62
- status: 'error',
63
- error: error instanceof Error ? error.message : 'Failed to commit',
77
+ '400': {
78
+ description: 'Error',
79
+ content: {
80
+ 'application/json': {
81
+ schema: {
82
+ type: 'object',
83
+ properties: {
84
+ status: {
85
+ type: 'string',
86
+ enum: ['error'],
87
+ },
88
+ error: {
89
+ type: 'string',
90
+ },
91
+ code: {
92
+ type: 'string',
93
+ },
94
+ },
95
+ required: ['status', 'error'],
96
+ },
97
+ },
98
+ },
64
99
  },
65
- 500,
66
- );
67
- }
68
- });
100
+ '500': {
101
+ description: 'Error',
102
+ content: {
103
+ 'application/json': {
104
+ schema: {
105
+ type: 'object',
106
+ properties: {
107
+ status: {
108
+ type: 'string',
109
+ enum: ['error'],
110
+ },
111
+ error: {
112
+ type: 'string',
113
+ },
114
+ code: {
115
+ type: 'string',
116
+ },
117
+ },
118
+ required: ['status', 'error'],
119
+ },
120
+ },
121
+ },
122
+ },
123
+ },
124
+ },
125
+ async (c) => {
126
+ try {
127
+ const body = await c.req.json();
128
+ const { message, project } = gitCommitSchema.parse(body);
129
+
130
+ const requestedPath = project || process.cwd();
131
+
132
+ const validation = await validateAndGetGitRoot(requestedPath);
133
+ if ('error' in validation) {
134
+ return c.json(
135
+ { status: 'error', error: validation.error, code: validation.code },
136
+ 400,
137
+ );
138
+ }
69
139
 
70
- app.post('/v1/git/generate-commit-message', async (c) => {
71
- try {
72
- const body = await c.req.json();
73
- const { project, sessionId } = gitGenerateCommitMessageSchema.parse(body);
140
+ const { gitRoot } = validation;
74
141
 
75
- const requestedPath = project || process.cwd();
142
+ const fullMessage = appendCoAuthorTrailer(message);
143
+ const { stdout } = await execFileAsync(
144
+ 'git',
145
+ ['commit', '-m', fullMessage],
146
+ {
147
+ cwd: gitRoot,
148
+ },
149
+ );
76
150
 
77
- const validation = await validateAndGetGitRoot(requestedPath);
78
- if ('error' in validation) {
151
+ return c.json({
152
+ status: 'ok',
153
+ data: {
154
+ message: stdout.trim(),
155
+ },
156
+ });
157
+ } catch (error) {
79
158
  return c.json(
80
- { status: 'error', error: validation.error, code: validation.code },
81
- 400,
159
+ {
160
+ status: 'error',
161
+ error: error instanceof Error ? error.message : 'Failed to commit',
162
+ },
163
+ 500,
82
164
  );
83
165
  }
84
-
85
- const { gitRoot } = validation;
86
-
87
- const { stdout: diff } = await execFileAsync(
88
- 'git',
89
- ['diff', '--cached'],
90
- {
91
- cwd: gitRoot,
166
+ },
167
+ );
168
+
169
+ openApiRoute(
170
+ app,
171
+ {
172
+ method: 'post',
173
+ path: '/v1/git/generate-commit-message',
174
+ tags: ['git'],
175
+ operationId: 'generateCommitMessage',
176
+ summary: 'Generate AI-powered commit message',
177
+ description:
178
+ 'Uses AI to generate a commit message based on staged changes',
179
+ requestBody: {
180
+ required: false,
181
+ content: {
182
+ 'application/json': {
183
+ schema: {
184
+ type: 'object',
185
+ properties: {
186
+ project: {
187
+ type: 'string',
188
+ },
189
+ sessionId: {
190
+ type: 'string',
191
+ description: 'Session ID to use session provider',
192
+ },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ responses: {
199
+ '200': {
200
+ description: 'OK',
201
+ content: {
202
+ 'application/json': {
203
+ schema: {
204
+ type: 'object',
205
+ properties: {
206
+ status: {
207
+ type: 'string',
208
+ enum: ['ok'],
209
+ },
210
+ data: {
211
+ type: 'object',
212
+ properties: {
213
+ message: {
214
+ type: 'string',
215
+ },
216
+ },
217
+ required: ['message'],
218
+ },
219
+ },
220
+ required: ['status', 'data'],
221
+ },
222
+ },
223
+ },
224
+ },
225
+ '400': {
226
+ description: 'Error',
227
+ content: {
228
+ 'application/json': {
229
+ schema: {
230
+ type: 'object',
231
+ properties: {
232
+ status: {
233
+ type: 'string',
234
+ enum: ['error'],
235
+ },
236
+ error: {
237
+ type: 'string',
238
+ },
239
+ code: {
240
+ type: 'string',
241
+ },
242
+ },
243
+ required: ['status', 'error'],
244
+ },
245
+ },
246
+ },
92
247
  },
93
- );
248
+ '500': {
249
+ description: 'Error',
250
+ content: {
251
+ 'application/json': {
252
+ schema: {
253
+ type: 'object',
254
+ properties: {
255
+ status: {
256
+ type: 'string',
257
+ enum: ['error'],
258
+ },
259
+ error: {
260
+ type: 'string',
261
+ },
262
+ code: {
263
+ type: 'string',
264
+ },
265
+ },
266
+ required: ['status', 'error'],
267
+ },
268
+ },
269
+ },
270
+ },
271
+ },
272
+ },
273
+ async (c) => {
274
+ try {
275
+ const body = await c.req.json();
276
+ const { project, sessionId } =
277
+ gitGenerateCommitMessageSchema.parse(body);
278
+
279
+ const requestedPath = project || process.cwd();
280
+
281
+ const validation = await validateAndGetGitRoot(requestedPath);
282
+ if ('error' in validation) {
283
+ return c.json(
284
+ { status: 'error', error: validation.error, code: validation.code },
285
+ 400,
286
+ );
287
+ }
94
288
 
95
- if (!diff.trim()) {
96
- return c.json(
289
+ const { gitRoot } = validation;
290
+
291
+ const { stdout: diff } = await execFileAsync(
292
+ 'git',
293
+ ['diff', '--cached'],
97
294
  {
98
- status: 'error',
99
- error: 'No staged changes to generate message from',
295
+ cwd: gitRoot,
100
296
  },
101
- 400,
102
297
  );
103
- }
104
298
 
105
- const { stdout: statusOutput } = await execFileAsync(
106
- 'git',
107
- ['status', '--porcelain=v2'],
108
- { cwd: gitRoot },
109
- );
110
- const { staged } = parseGitStatus(statusOutput, gitRoot);
111
- const fileList = staged.map((f) => `${f.status}: ${f.path}`).join('\n');
112
-
113
- const config = await loadConfig();
114
-
115
- let provider = (config.defaults?.provider || 'anthropic') as ProviderId;
116
- let currentModel = config.defaults?.model ?? 'claude-3-5-sonnet-20241022';
117
-
118
- if (sessionId) {
119
- const db = await getDb();
120
- const [session] = await db
121
- .select({ provider: sessions.provider, model: sessions.model })
122
- .from(sessions)
123
- .where(eq(sessions.id, sessionId));
124
- if (session?.provider) {
125
- provider = session.provider as ProviderId;
299
+ if (!diff.trim()) {
300
+ return c.json(
301
+ {
302
+ status: 'error',
303
+ error: 'No staged changes to generate message from',
304
+ },
305
+ 400,
306
+ );
126
307
  }
127
- if (session?.model) {
128
- currentModel = session.model;
308
+
309
+ const { stdout: statusOutput } = await execFileAsync(
310
+ 'git',
311
+ ['status', '--porcelain=v2'],
312
+ { cwd: gitRoot },
313
+ );
314
+ const { staged } = parseGitStatus(statusOutput, gitRoot);
315
+ const fileList = staged.map((f) => `${f.status}: ${f.path}`).join('\n');
316
+
317
+ const config = await loadConfig();
318
+
319
+ let provider = (config.defaults?.provider || 'anthropic') as ProviderId;
320
+ let currentModel =
321
+ config.defaults?.model ?? 'claude-3-5-sonnet-20241022';
322
+
323
+ if (sessionId) {
324
+ const db = await getDb();
325
+ const [session] = await db
326
+ .select({ provider: sessions.provider, model: sessions.model })
327
+ .from(sessions)
328
+ .where(eq(sessions.id, sessionId));
329
+ if (session?.provider) {
330
+ provider = session.provider as ProviderId;
331
+ }
332
+ if (session?.model) {
333
+ currentModel = session.model;
334
+ }
129
335
  }
130
- }
131
336
 
132
- const auth = await getAuth(provider, config.projectRoot);
133
- const oauth = detectOAuth(provider, auth);
134
- const providerDefinition = getProviderDefinition(config, provider);
337
+ const auth = await getAuth(provider, config.projectRoot);
338
+ const oauth = detectOAuth(provider, auth);
339
+ const providerDefinition = getProviderDefinition(config, provider);
135
340
 
136
- const modelId =
137
- providerDefinition?.source === 'custom' ||
138
- providerDefinition?.compatibility === 'ollama'
139
- ? currentModel
140
- : (getFastModelForAuth(provider, auth?.type) ?? currentModel);
141
- const model = await resolveModel(provider, modelId, config);
341
+ const modelId =
342
+ providerDefinition?.source === 'custom' ||
343
+ providerDefinition?.compatibility === 'ollama'
344
+ ? currentModel
345
+ : (getFastModelForAuth(provider, auth?.type) ?? currentModel);
346
+ const model = await resolveModel(provider, modelId, config);
142
347
 
143
- const userPrompt = `Generate a commit message for these git changes.
348
+ const userPrompt = `Generate a commit message for these git changes.
144
349
 
145
350
  Staged files:
146
351
  ${fileList}
@@ -168,56 +373,57 @@ refactor(auth): return success status from login functions
168
373
 
169
374
  Commit message:`;
170
375
 
171
- const commitInstructions =
172
- 'You are a helpful assistant that generates accurate git commit messages based on the actual diff content.';
376
+ const commitInstructions =
377
+ 'You are a helpful assistant that generates accurate git commit messages based on the actual diff content.';
173
378
 
174
- const adapted = adaptSimpleCall(oauth, {
175
- instructions: commitInstructions,
176
- userContent: userPrompt,
177
- maxOutputTokens: 500,
178
- });
379
+ const adapted = adaptSimpleCall(oauth, {
380
+ instructions: commitInstructions,
381
+ userContent: userPrompt,
382
+ maxOutputTokens: 500,
383
+ });
179
384
 
180
- if (adapted.forceStream) {
181
- const result = streamText({
385
+ if (adapted.forceStream) {
386
+ const result = streamText({
387
+ model,
388
+ system: adapted.system,
389
+ messages: adapted.messages,
390
+ providerOptions: adapted.providerOptions,
391
+ });
392
+ let text = '';
393
+ for await (const chunk of result.textStream) {
394
+ text += chunk;
395
+ }
396
+ const message = text.trim();
397
+ return c.json({ status: 'ok', data: { message } });
398
+ }
399
+
400
+ const { text } = await generateText({
182
401
  model,
183
402
  system: adapted.system,
184
403
  messages: adapted.messages,
185
- providerOptions: adapted.providerOptions,
404
+ maxOutputTokens: adapted.maxOutputTokens,
186
405
  });
187
- let text = '';
188
- for await (const chunk of result.textStream) {
189
- text += chunk;
190
- }
191
- const message = text.trim();
192
- return c.json({ status: 'ok', data: { message } });
193
- }
194
406
 
195
- const { text } = await generateText({
196
- model,
197
- system: adapted.system,
198
- messages: adapted.messages,
199
- maxOutputTokens: adapted.maxOutputTokens,
200
- });
201
-
202
- const message = text.trim();
407
+ const message = text.trim();
203
408
 
204
- return c.json({
205
- status: 'ok',
206
- data: {
207
- message,
208
- },
209
- });
210
- } catch (error) {
211
- return c.json(
212
- {
213
- status: 'error',
214
- error:
215
- error instanceof Error
216
- ? error.message
217
- : 'Failed to generate commit message',
218
- },
219
- 500,
220
- );
221
- }
222
- });
409
+ return c.json({
410
+ status: 'ok',
411
+ data: {
412
+ message,
413
+ },
414
+ });
415
+ } catch (error) {
416
+ return c.json(
417
+ {
418
+ status: 'error',
419
+ error:
420
+ error instanceof Error
421
+ ? error.message
422
+ : 'Failed to generate commit message',
423
+ },
424
+ 500,
425
+ );
426
+ }
427
+ },
428
+ );
223
429
  }