@ottocode/server 0.1.173

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 (111) hide show
  1. package/package.json +42 -0
  2. package/src/events/bus.ts +43 -0
  3. package/src/events/types.ts +32 -0
  4. package/src/index.ts +281 -0
  5. package/src/openapi/helpers.ts +64 -0
  6. package/src/openapi/paths/ask.ts +70 -0
  7. package/src/openapi/paths/config.ts +218 -0
  8. package/src/openapi/paths/files.ts +72 -0
  9. package/src/openapi/paths/git.ts +457 -0
  10. package/src/openapi/paths/messages.ts +92 -0
  11. package/src/openapi/paths/sessions.ts +90 -0
  12. package/src/openapi/paths/setu.ts +154 -0
  13. package/src/openapi/paths/stream.ts +26 -0
  14. package/src/openapi/paths/terminals.ts +226 -0
  15. package/src/openapi/schemas.ts +345 -0
  16. package/src/openapi/spec.ts +49 -0
  17. package/src/presets.ts +85 -0
  18. package/src/routes/ask.ts +113 -0
  19. package/src/routes/auth.ts +592 -0
  20. package/src/routes/branch.ts +106 -0
  21. package/src/routes/config/agents.ts +44 -0
  22. package/src/routes/config/cwd.ts +21 -0
  23. package/src/routes/config/defaults.ts +45 -0
  24. package/src/routes/config/index.ts +16 -0
  25. package/src/routes/config/main.ts +73 -0
  26. package/src/routes/config/models.ts +139 -0
  27. package/src/routes/config/providers.ts +46 -0
  28. package/src/routes/config/utils.ts +120 -0
  29. package/src/routes/files.ts +218 -0
  30. package/src/routes/git/branch.ts +75 -0
  31. package/src/routes/git/commit.ts +209 -0
  32. package/src/routes/git/diff.ts +137 -0
  33. package/src/routes/git/index.ts +18 -0
  34. package/src/routes/git/push.ts +160 -0
  35. package/src/routes/git/schemas.ts +48 -0
  36. package/src/routes/git/staging.ts +208 -0
  37. package/src/routes/git/status.ts +83 -0
  38. package/src/routes/git/types.ts +31 -0
  39. package/src/routes/git/utils.ts +249 -0
  40. package/src/routes/openapi.ts +6 -0
  41. package/src/routes/research.ts +392 -0
  42. package/src/routes/root.ts +5 -0
  43. package/src/routes/session-approval.ts +63 -0
  44. package/src/routes/session-files.ts +387 -0
  45. package/src/routes/session-messages.ts +170 -0
  46. package/src/routes/session-stream.ts +61 -0
  47. package/src/routes/sessions.ts +814 -0
  48. package/src/routes/setu.ts +346 -0
  49. package/src/routes/terminals.ts +227 -0
  50. package/src/runtime/agent/registry.ts +351 -0
  51. package/src/runtime/agent/runner-reasoning.ts +108 -0
  52. package/src/runtime/agent/runner-setup.ts +257 -0
  53. package/src/runtime/agent/runner.ts +375 -0
  54. package/src/runtime/agent-registry.ts +6 -0
  55. package/src/runtime/ask/service.ts +369 -0
  56. package/src/runtime/context/environment.ts +202 -0
  57. package/src/runtime/debug/index.ts +117 -0
  58. package/src/runtime/debug/state.ts +140 -0
  59. package/src/runtime/errors/api-error.ts +192 -0
  60. package/src/runtime/errors/handling.ts +199 -0
  61. package/src/runtime/message/compaction-auto.ts +154 -0
  62. package/src/runtime/message/compaction-context.ts +101 -0
  63. package/src/runtime/message/compaction-detect.ts +26 -0
  64. package/src/runtime/message/compaction-limits.ts +37 -0
  65. package/src/runtime/message/compaction-mark.ts +111 -0
  66. package/src/runtime/message/compaction-prune.ts +75 -0
  67. package/src/runtime/message/compaction.ts +21 -0
  68. package/src/runtime/message/history-builder.ts +266 -0
  69. package/src/runtime/message/service.ts +468 -0
  70. package/src/runtime/message/tool-history-tracker.ts +204 -0
  71. package/src/runtime/prompt/builder.ts +167 -0
  72. package/src/runtime/provider/anthropic.ts +50 -0
  73. package/src/runtime/provider/copilot.ts +12 -0
  74. package/src/runtime/provider/google.ts +8 -0
  75. package/src/runtime/provider/index.ts +60 -0
  76. package/src/runtime/provider/moonshot.ts +8 -0
  77. package/src/runtime/provider/oauth-adapter.ts +237 -0
  78. package/src/runtime/provider/openai.ts +18 -0
  79. package/src/runtime/provider/opencode.ts +7 -0
  80. package/src/runtime/provider/openrouter.ts +7 -0
  81. package/src/runtime/provider/selection.ts +118 -0
  82. package/src/runtime/provider/setu.ts +126 -0
  83. package/src/runtime/provider/zai.ts +16 -0
  84. package/src/runtime/session/branch.ts +280 -0
  85. package/src/runtime/session/db-operations.ts +285 -0
  86. package/src/runtime/session/manager.ts +99 -0
  87. package/src/runtime/session/queue.ts +243 -0
  88. package/src/runtime/stream/abort-handler.ts +65 -0
  89. package/src/runtime/stream/error-handler.ts +371 -0
  90. package/src/runtime/stream/finish-handler.ts +101 -0
  91. package/src/runtime/stream/handlers.ts +5 -0
  92. package/src/runtime/stream/step-finish.ts +93 -0
  93. package/src/runtime/stream/types.ts +25 -0
  94. package/src/runtime/tools/approval.ts +180 -0
  95. package/src/runtime/tools/context.ts +83 -0
  96. package/src/runtime/tools/mapping.ts +154 -0
  97. package/src/runtime/tools/setup.ts +44 -0
  98. package/src/runtime/topup/manager.ts +110 -0
  99. package/src/runtime/utils/cwd.ts +69 -0
  100. package/src/runtime/utils/token.ts +35 -0
  101. package/src/tools/adapter.ts +634 -0
  102. package/src/tools/database/get-parent-session.ts +183 -0
  103. package/src/tools/database/get-session-context.ts +161 -0
  104. package/src/tools/database/index.ts +42 -0
  105. package/src/tools/database/present-session-links.ts +47 -0
  106. package/src/tools/database/query-messages.ts +160 -0
  107. package/src/tools/database/query-sessions.ts +126 -0
  108. package/src/tools/database/search-history.ts +135 -0
  109. package/src/types/sql-imports.d.ts +5 -0
  110. package/sst-env.d.ts +8 -0
  111. package/tsconfig.json +7 -0
@@ -0,0 +1,160 @@
1
+ import type { Hono } from 'hono';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { gitPushSchema } from './schemas.ts';
5
+ import { validateAndGetGitRoot, getCurrentBranch } from './utils.ts';
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ export function registerPushRoute(app: Hono) {
10
+ app.post('/v1/git/push', async (c) => {
11
+ try {
12
+ let body = {};
13
+ try {
14
+ body = await c.req.json();
15
+ } catch (jsonError) {
16
+ console.warn(
17
+ 'Failed to parse JSON body for git push, using empty object:',
18
+ jsonError,
19
+ );
20
+ }
21
+
22
+ const { project } = gitPushSchema.parse(body);
23
+
24
+ const requestedPath = project || process.cwd();
25
+
26
+ const validation = await validateAndGetGitRoot(requestedPath);
27
+ if ('error' in validation) {
28
+ return c.json(
29
+ { status: 'error', error: validation.error, code: validation.code },
30
+ 400,
31
+ );
32
+ }
33
+
34
+ const { gitRoot } = validation;
35
+
36
+ try {
37
+ const { stdout: remotes } = await execFileAsync('git', ['remote'], {
38
+ cwd: gitRoot,
39
+ });
40
+ if (!remotes.trim()) {
41
+ return c.json(
42
+ { status: 'error', error: 'No remote repository configured' },
43
+ 400,
44
+ );
45
+ }
46
+ } catch {
47
+ return c.json(
48
+ { status: 'error', error: 'No remote repository configured' },
49
+ 400,
50
+ );
51
+ }
52
+
53
+ const branch = await getCurrentBranch(gitRoot);
54
+ let hasUpstream = false;
55
+ try {
56
+ await execFileAsync(
57
+ 'git',
58
+ ['rev-parse', '--abbrev-ref', '@{upstream}'],
59
+ {
60
+ cwd: gitRoot,
61
+ },
62
+ );
63
+ hasUpstream = true;
64
+ } catch {}
65
+
66
+ try {
67
+ let pushOutput: string;
68
+ let pushError: string;
69
+
70
+ if (hasUpstream) {
71
+ const result = await execFileAsync('git', ['push'], { cwd: gitRoot });
72
+ pushOutput = result.stdout;
73
+ pushError = result.stderr;
74
+ } else {
75
+ const result = await execFileAsync(
76
+ 'git',
77
+ ['push', '--set-upstream', 'origin', branch],
78
+ { cwd: gitRoot },
79
+ );
80
+ pushOutput = result.stdout;
81
+ pushError = result.stderr;
82
+ }
83
+
84
+ return c.json({
85
+ status: 'ok',
86
+ data: {
87
+ output: pushOutput.trim() || pushError.trim(),
88
+ },
89
+ });
90
+ } catch (pushErr: unknown) {
91
+ const error = pushErr as {
92
+ message?: string;
93
+ stderr?: string;
94
+ code?: number;
95
+ };
96
+ const errorMessage = error.stderr || error.message || 'Failed to push';
97
+
98
+ if (
99
+ errorMessage.includes('failed to push') ||
100
+ errorMessage.includes('rejected')
101
+ ) {
102
+ return c.json(
103
+ {
104
+ status: 'error',
105
+ error: 'Push rejected. Try pulling changes first with: git pull',
106
+ details: errorMessage,
107
+ },
108
+ 400,
109
+ );
110
+ }
111
+
112
+ if (
113
+ errorMessage.includes('Permission denied') ||
114
+ errorMessage.includes('authentication') ||
115
+ errorMessage.includes('could not read')
116
+ ) {
117
+ return c.json(
118
+ {
119
+ status: 'error',
120
+ error: 'Authentication failed. Check your git credentials',
121
+ details: errorMessage,
122
+ },
123
+ 401,
124
+ );
125
+ }
126
+
127
+ if (
128
+ errorMessage.includes('Could not resolve host') ||
129
+ errorMessage.includes('network')
130
+ ) {
131
+ return c.json(
132
+ {
133
+ status: 'error',
134
+ error: 'Network error. Check your internet connection',
135
+ details: errorMessage,
136
+ },
137
+ 503,
138
+ );
139
+ }
140
+
141
+ return c.json(
142
+ {
143
+ status: 'error',
144
+ error: 'Failed to push commits',
145
+ details: errorMessage,
146
+ },
147
+ 500,
148
+ );
149
+ }
150
+ } catch (error) {
151
+ return c.json(
152
+ {
153
+ status: 'error',
154
+ error: error instanceof Error ? error.message : 'Failed to push',
155
+ },
156
+ 500,
157
+ );
158
+ }
159
+ });
160
+ }
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod/v3';
2
+
3
+ export const gitStatusSchema = z.object({
4
+ project: z.string().optional(),
5
+ });
6
+
7
+ export const gitDiffSchema = z.object({
8
+ project: z.string().optional(),
9
+ file: z.string(),
10
+ staged: z
11
+ .string()
12
+ .optional()
13
+ .transform((val) => val === 'true'),
14
+ });
15
+
16
+ export const gitStageSchema = z.object({
17
+ project: z.string().optional(),
18
+ files: z.array(z.string()),
19
+ });
20
+
21
+ export const gitUnstageSchema = z.object({
22
+ project: z.string().optional(),
23
+ files: z.array(z.string()),
24
+ });
25
+
26
+ export const gitRestoreSchema = z.object({
27
+ project: z.string().optional(),
28
+ files: z.array(z.string()),
29
+ });
30
+
31
+ export const gitDeleteSchema = z.object({
32
+ project: z.string().optional(),
33
+ files: z.array(z.string()),
34
+ });
35
+
36
+ export const gitCommitSchema = z.object({
37
+ project: z.string().optional(),
38
+ message: z.string().min(1),
39
+ });
40
+
41
+ export const gitGenerateCommitMessageSchema = z.object({
42
+ project: z.string().optional(),
43
+ sessionId: z.string().optional(),
44
+ });
45
+
46
+ export const gitPushSchema = z.object({
47
+ project: z.string().optional(),
48
+ });
@@ -0,0 +1,208 @@
1
+ import type { Hono } from 'hono';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import {
5
+ gitStageSchema,
6
+ gitUnstageSchema,
7
+ gitRestoreSchema,
8
+ gitDeleteSchema,
9
+ } from './schemas.ts';
10
+ import { validateAndGetGitRoot } from './utils.ts';
11
+
12
+ const execFileAsync = promisify(execFile);
13
+
14
+ export function registerStagingRoutes(app: Hono) {
15
+ app.post('/v1/git/stage', async (c) => {
16
+ try {
17
+ const body = await c.req.json();
18
+ const { files, project } = gitStageSchema.parse(body);
19
+
20
+ const requestedPath = project || process.cwd();
21
+
22
+ const validation = await validateAndGetGitRoot(requestedPath);
23
+ if ('error' in validation) {
24
+ return c.json(
25
+ { status: 'error', error: validation.error, code: validation.code },
26
+ 400,
27
+ );
28
+ }
29
+
30
+ const { gitRoot } = validation;
31
+
32
+ if (files.length === 0) {
33
+ return c.json(
34
+ {
35
+ status: 'error',
36
+ error: 'No files specified',
37
+ },
38
+ 400,
39
+ );
40
+ }
41
+
42
+ await execFileAsync('git', ['add', ...files], { cwd: gitRoot });
43
+
44
+ return c.json({
45
+ status: 'ok',
46
+ data: {
47
+ staged: files,
48
+ },
49
+ });
50
+ } catch (error) {
51
+ return c.json(
52
+ {
53
+ status: 'error',
54
+ error:
55
+ error instanceof Error ? error.message : 'Failed to stage files',
56
+ },
57
+ 500,
58
+ );
59
+ }
60
+ });
61
+
62
+ app.post('/v1/git/unstage', async (c) => {
63
+ try {
64
+ const body = await c.req.json();
65
+ const { files, project } = gitUnstageSchema.parse(body);
66
+
67
+ const requestedPath = project || process.cwd();
68
+
69
+ const validation = await validateAndGetGitRoot(requestedPath);
70
+ if ('error' in validation) {
71
+ return c.json(
72
+ { status: 'error', error: validation.error, code: validation.code },
73
+ 400,
74
+ );
75
+ }
76
+
77
+ const { gitRoot } = validation;
78
+
79
+ if (files.length === 0) {
80
+ return c.json(
81
+ {
82
+ status: 'error',
83
+ error: 'No files specified',
84
+ },
85
+ 400,
86
+ );
87
+ }
88
+
89
+ await execFileAsync('git', ['reset', 'HEAD', '--', ...files], {
90
+ cwd: gitRoot,
91
+ });
92
+
93
+ return c.json({
94
+ status: 'ok',
95
+ data: {
96
+ unstaged: files,
97
+ },
98
+ });
99
+ } catch (error) {
100
+ return c.json(
101
+ {
102
+ status: 'error',
103
+ error:
104
+ error instanceof Error ? error.message : 'Failed to unstage files',
105
+ },
106
+ 500,
107
+ );
108
+ }
109
+ });
110
+
111
+ app.post('/v1/git/restore', async (c) => {
112
+ try {
113
+ const body = await c.req.json();
114
+ const { files, project } = gitRestoreSchema.parse(body);
115
+
116
+ const requestedPath = project || process.cwd();
117
+
118
+ const validation = await validateAndGetGitRoot(requestedPath);
119
+ if ('error' in validation) {
120
+ return c.json(
121
+ { status: 'error', error: validation.error, code: validation.code },
122
+ 400,
123
+ );
124
+ }
125
+
126
+ const { gitRoot } = validation;
127
+
128
+ if (files.length === 0) {
129
+ return c.json(
130
+ {
131
+ status: 'error',
132
+ error: 'No files specified',
133
+ },
134
+ 400,
135
+ );
136
+ }
137
+
138
+ await execFileAsync('git', ['restore', '--', ...files], {
139
+ cwd: gitRoot,
140
+ });
141
+
142
+ return c.json({
143
+ status: 'ok',
144
+ data: {
145
+ restored: files,
146
+ },
147
+ });
148
+ } catch (error) {
149
+ return c.json(
150
+ {
151
+ status: 'error',
152
+ error:
153
+ error instanceof Error ? error.message : 'Failed to restore files',
154
+ },
155
+ 500,
156
+ );
157
+ }
158
+ });
159
+
160
+ app.post('/v1/git/delete', async (c) => {
161
+ try {
162
+ const body = await c.req.json();
163
+ const { files, project } = gitDeleteSchema.parse(body);
164
+
165
+ const requestedPath = project || process.cwd();
166
+
167
+ const validation = await validateAndGetGitRoot(requestedPath);
168
+ if ('error' in validation) {
169
+ return c.json(
170
+ { status: 'error', error: validation.error, code: validation.code },
171
+ 400,
172
+ );
173
+ }
174
+
175
+ const { gitRoot } = validation;
176
+
177
+ if (files.length === 0) {
178
+ return c.json(
179
+ {
180
+ status: 'error',
181
+ error: 'No files specified',
182
+ },
183
+ 400,
184
+ );
185
+ }
186
+
187
+ await execFileAsync('git', ['clean', '-f', '--', ...files], {
188
+ cwd: gitRoot,
189
+ });
190
+
191
+ return c.json({
192
+ status: 'ok',
193
+ data: {
194
+ deleted: files,
195
+ },
196
+ });
197
+ } catch (error) {
198
+ return c.json(
199
+ {
200
+ status: 'error',
201
+ error:
202
+ error instanceof Error ? error.message : 'Failed to delete files',
203
+ },
204
+ 500,
205
+ );
206
+ }
207
+ });
208
+ }
@@ -0,0 +1,83 @@
1
+ import type { Hono } from 'hono';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import { gitStatusSchema } from './schemas.ts';
5
+ import {
6
+ validateAndGetGitRoot,
7
+ parseGitStatus,
8
+ getAheadBehind,
9
+ getCurrentBranch,
10
+ } from './utils.ts';
11
+
12
+ const execFileAsync = promisify(execFile);
13
+
14
+ export function registerStatusRoute(app: Hono) {
15
+ app.get('/v1/git/status', async (c) => {
16
+ try {
17
+ const query = gitStatusSchema.parse({
18
+ project: c.req.query('project'),
19
+ });
20
+
21
+ const requestedPath = query.project || process.cwd();
22
+
23
+ const validation = await validateAndGetGitRoot(requestedPath);
24
+ if ('error' in validation) {
25
+ return c.json(
26
+ { status: 'error', error: validation.error, code: validation.code },
27
+ 400,
28
+ );
29
+ }
30
+
31
+ const { gitRoot } = validation;
32
+
33
+ const { stdout: statusOutput } = await execFileAsync(
34
+ 'git',
35
+ ['status', '--porcelain=v2'],
36
+ { cwd: gitRoot },
37
+ );
38
+
39
+ const { staged, unstaged, untracked, conflicted } = parseGitStatus(
40
+ statusOutput,
41
+ gitRoot,
42
+ );
43
+
44
+ const { ahead, behind } = await getAheadBehind(gitRoot);
45
+
46
+ const branch = await getCurrentBranch(gitRoot);
47
+
48
+ const hasChanges =
49
+ staged.length > 0 ||
50
+ unstaged.length > 0 ||
51
+ untracked.length > 0 ||
52
+ conflicted.length > 0;
53
+
54
+ const hasConflicts = conflicted.length > 0;
55
+
56
+ return c.json({
57
+ status: 'ok',
58
+ data: {
59
+ branch,
60
+ ahead,
61
+ behind,
62
+ gitRoot,
63
+ workingDir: requestedPath,
64
+ staged,
65
+ unstaged,
66
+ untracked,
67
+ conflicted,
68
+ hasChanges,
69
+ hasConflicts,
70
+ },
71
+ });
72
+ } catch (error) {
73
+ return c.json(
74
+ {
75
+ status: 'error',
76
+ error:
77
+ error instanceof Error ? error.message : 'Failed to get status',
78
+ },
79
+ 500,
80
+ );
81
+ }
82
+ });
83
+ }
@@ -0,0 +1,31 @@
1
+ export interface GitFile {
2
+ path: string;
3
+ absPath: string;
4
+ status:
5
+ | 'modified'
6
+ | 'added'
7
+ | 'deleted'
8
+ | 'renamed'
9
+ | 'untracked'
10
+ | 'conflicted';
11
+ staged: boolean;
12
+ insertions?: number;
13
+ deletions?: number;
14
+ oldPath?: string;
15
+ isNew: boolean;
16
+ conflictType?:
17
+ | 'both-modified'
18
+ | 'deleted-by-us'
19
+ | 'deleted-by-them'
20
+ | 'both-added'
21
+ | 'both-deleted';
22
+ }
23
+
24
+ export interface GitRoot {
25
+ gitRoot: string;
26
+ }
27
+
28
+ export interface GitError {
29
+ error: string;
30
+ code?: string;
31
+ }