@ottocode/server 0.1.260 → 0.1.262
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
package/src/routes/tunnel.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
logger,
|
|
10
10
|
} from '@ottocode/sdk';
|
|
11
11
|
import { getServerPort } from '../state.ts';
|
|
12
|
+
import { openApiRoute } from '../openapi/route.ts';
|
|
12
13
|
|
|
13
14
|
let activeTunnel: OttoTunnel | null = null;
|
|
14
15
|
let tunnelUrl: string | null = null;
|
|
@@ -17,143 +18,385 @@ let tunnelError: string | null = null;
|
|
|
17
18
|
let progressMessage: string | null = null;
|
|
18
19
|
|
|
19
20
|
export function registerTunnelRoutes(app: Hono) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
openApiRoute(
|
|
22
|
+
app,
|
|
23
|
+
{
|
|
24
|
+
method: 'get',
|
|
25
|
+
path: '/v1/tunnel/status',
|
|
26
|
+
tags: ['tunnel'],
|
|
27
|
+
operationId: 'getTunnelStatus',
|
|
28
|
+
summary: 'Get tunnel status',
|
|
29
|
+
responses: {
|
|
30
|
+
'200': {
|
|
31
|
+
description: 'OK',
|
|
32
|
+
content: {
|
|
33
|
+
'application/json': {
|
|
34
|
+
schema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
status: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
enum: ['idle', 'starting', 'connected', 'error'],
|
|
40
|
+
},
|
|
41
|
+
url: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
nullable: true,
|
|
44
|
+
},
|
|
45
|
+
error: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
nullable: true,
|
|
48
|
+
},
|
|
49
|
+
binaryInstalled: {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
},
|
|
52
|
+
isRunning: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['status', 'binaryInstalled', 'isRunning'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
async (c) => {
|
|
64
|
+
const binaryInstalled = await isTunnelBinaryInstalled();
|
|
31
65
|
|
|
32
|
-
app.post('/v1/tunnel/start', async (c) => {
|
|
33
|
-
if (activeTunnel?.isRunning) {
|
|
34
66
|
return c.json({
|
|
35
|
-
|
|
67
|
+
status: tunnelStatus,
|
|
36
68
|
url: tunnelUrl,
|
|
37
|
-
|
|
69
|
+
error: tunnelError,
|
|
70
|
+
binaryInstalled,
|
|
71
|
+
isRunning: activeTunnel?.isRunning ?? false,
|
|
38
72
|
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
openApiRoute(
|
|
77
|
+
app,
|
|
78
|
+
{
|
|
79
|
+
method: 'post',
|
|
80
|
+
path: '/v1/tunnel/start',
|
|
81
|
+
tags: ['tunnel'],
|
|
82
|
+
operationId: 'startTunnel',
|
|
83
|
+
summary: 'Start a tunnel',
|
|
84
|
+
requestBody: {
|
|
85
|
+
required: false,
|
|
86
|
+
content: {
|
|
87
|
+
'application/json': {
|
|
88
|
+
schema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
port: {
|
|
92
|
+
type: 'integer',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
responses: {
|
|
100
|
+
'200': {
|
|
101
|
+
description: 'OK',
|
|
102
|
+
content: {
|
|
103
|
+
'application/json': {
|
|
104
|
+
schema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
ok: {
|
|
108
|
+
type: 'boolean',
|
|
109
|
+
},
|
|
110
|
+
url: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
},
|
|
113
|
+
message: {
|
|
114
|
+
type: 'string',
|
|
115
|
+
},
|
|
116
|
+
error: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['ok'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
async (c) => {
|
|
128
|
+
if (activeTunnel?.isRunning) {
|
|
129
|
+
return c.json({
|
|
130
|
+
ok: true,
|
|
131
|
+
url: tunnelUrl,
|
|
132
|
+
message: 'Tunnel already running',
|
|
133
|
+
});
|
|
48
134
|
}
|
|
49
135
|
|
|
50
|
-
|
|
51
|
-
|
|
136
|
+
try {
|
|
137
|
+
const body = await c.req.json().catch(() => ({}));
|
|
138
|
+
let port = body.port;
|
|
52
139
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
140
|
+
// Use server's known port if not explicitly provided
|
|
141
|
+
if (!port) {
|
|
142
|
+
port = getServerPort() || 9100;
|
|
143
|
+
}
|
|
56
144
|
|
|
57
|
-
|
|
145
|
+
// Kill any stale tunnel processes first
|
|
146
|
+
await killStaleTunnels();
|
|
58
147
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
148
|
+
tunnelStatus = 'starting';
|
|
149
|
+
tunnelError = null;
|
|
150
|
+
progressMessage = 'Initializing...';
|
|
62
151
|
|
|
63
|
-
|
|
64
|
-
tunnelStatus = 'connected';
|
|
65
|
-
progressMessage = null;
|
|
152
|
+
activeTunnel = new OttoTunnel();
|
|
66
153
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
tunnelStatus = 'error';
|
|
71
|
-
});
|
|
154
|
+
const url = await activeTunnel.start(port, (msg) => {
|
|
155
|
+
progressMessage = msg;
|
|
156
|
+
});
|
|
72
157
|
|
|
73
|
-
|
|
74
|
-
tunnelStatus = '
|
|
75
|
-
|
|
76
|
-
activeTunnel = null;
|
|
77
|
-
});
|
|
158
|
+
tunnelUrl = url;
|
|
159
|
+
tunnelStatus = 'connected';
|
|
160
|
+
progressMessage = null;
|
|
78
161
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} catch (error) {
|
|
85
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
-
tunnelStatus = 'error';
|
|
87
|
-
tunnelError = message;
|
|
88
|
-
progressMessage = null;
|
|
89
|
-
|
|
90
|
-
logger.error('Failed to start tunnel:', error);
|
|
91
|
-
return c.json({ ok: false, error: message }, 500);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
162
|
+
activeTunnel.on('error', (err) => {
|
|
163
|
+
logger.error('Tunnel error:', err);
|
|
164
|
+
tunnelError = err.message;
|
|
165
|
+
tunnelStatus = 'error';
|
|
166
|
+
});
|
|
94
167
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
168
|
+
activeTunnel.on('exit', () => {
|
|
169
|
+
tunnelStatus = 'idle';
|
|
170
|
+
tunnelUrl = null;
|
|
171
|
+
activeTunnel = null;
|
|
172
|
+
});
|
|
99
173
|
|
|
100
|
-
|
|
101
|
-
|
|
174
|
+
return c.json({
|
|
175
|
+
ok: true,
|
|
176
|
+
url: tunnelUrl,
|
|
177
|
+
message: 'Tunnel started',
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
tunnelStatus = 'error';
|
|
182
|
+
tunnelError = message;
|
|
183
|
+
progressMessage = null;
|
|
184
|
+
|
|
185
|
+
logger.error('Failed to start tunnel:', error);
|
|
186
|
+
return c.json({ ok: false, error: message }, 500);
|
|
102
187
|
}
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
openApiRoute(
|
|
192
|
+
app,
|
|
193
|
+
{
|
|
194
|
+
method: 'post',
|
|
195
|
+
path: '/v1/tunnel/register',
|
|
196
|
+
tags: ['tunnel'],
|
|
197
|
+
operationId: 'registerTunnel',
|
|
198
|
+
summary: 'Register an external tunnel URL',
|
|
199
|
+
requestBody: {
|
|
200
|
+
required: true,
|
|
201
|
+
content: {
|
|
202
|
+
'application/json': {
|
|
203
|
+
schema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
url: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
required: ['url'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
responses: {
|
|
216
|
+
'200': {
|
|
217
|
+
description: 'OK',
|
|
218
|
+
content: {
|
|
219
|
+
'application/json': {
|
|
220
|
+
schema: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
ok: {
|
|
224
|
+
type: 'boolean',
|
|
225
|
+
},
|
|
226
|
+
url: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
},
|
|
229
|
+
message: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
required: ['ok'],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
'400': {
|
|
239
|
+
description: 'Bad Request',
|
|
240
|
+
content: {
|
|
241
|
+
'application/json': {
|
|
242
|
+
schema: {
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: {
|
|
245
|
+
error: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
required: ['error'],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
async (c) => {
|
|
257
|
+
try {
|
|
258
|
+
const body = await c.req.json().catch(() => ({}));
|
|
259
|
+
const { url } = body;
|
|
260
|
+
|
|
261
|
+
if (!url) {
|
|
262
|
+
return c.json({ ok: false, error: 'URL is required' }, 400);
|
|
263
|
+
}
|
|
103
264
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
265
|
+
tunnelUrl = url;
|
|
266
|
+
tunnelStatus = 'connected';
|
|
267
|
+
tunnelError = null;
|
|
268
|
+
progressMessage = null;
|
|
108
269
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
270
|
+
return c.json({
|
|
271
|
+
ok: true,
|
|
272
|
+
url: tunnelUrl,
|
|
273
|
+
message: 'External tunnel registered',
|
|
274
|
+
});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
277
|
+
logger.error('Failed to register external tunnel:', error);
|
|
278
|
+
return c.json({ ok: false, error: message }, 500);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
openApiRoute(
|
|
284
|
+
app,
|
|
285
|
+
{
|
|
286
|
+
method: 'post',
|
|
287
|
+
path: '/v1/tunnel/stop',
|
|
288
|
+
tags: ['tunnel'],
|
|
289
|
+
operationId: 'stopTunnel',
|
|
290
|
+
summary: 'Stop the tunnel',
|
|
291
|
+
responses: {
|
|
292
|
+
'200': {
|
|
293
|
+
description: 'OK',
|
|
294
|
+
content: {
|
|
295
|
+
'application/json': {
|
|
296
|
+
schema: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
ok: {
|
|
300
|
+
type: 'boolean',
|
|
301
|
+
},
|
|
302
|
+
message: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
required: ['ok'],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
async (c) => {
|
|
314
|
+
if (!activeTunnel) {
|
|
315
|
+
return c.json({ ok: true, message: 'No tunnel running' });
|
|
316
|
+
}
|
|
120
317
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
activeTunnel.stop();
|
|
128
|
-
activeTunnel = null;
|
|
129
|
-
tunnelUrl = null;
|
|
130
|
-
tunnelStatus = 'idle';
|
|
131
|
-
tunnelError = null;
|
|
132
|
-
|
|
133
|
-
return c.json({ ok: true, message: 'Tunnel stopped' });
|
|
134
|
-
} catch (error) {
|
|
135
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
-
return c.json({ ok: false, error: message }, 500);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
318
|
+
try {
|
|
319
|
+
activeTunnel.stop();
|
|
320
|
+
activeTunnel = null;
|
|
321
|
+
tunnelUrl = null;
|
|
322
|
+
tunnelStatus = 'idle';
|
|
323
|
+
tunnelError = null;
|
|
139
324
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
325
|
+
return c.json({ ok: true, message: 'Tunnel stopped' });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
328
|
+
return c.json({ ok: false, error: message }, 500);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
openApiRoute(
|
|
334
|
+
app,
|
|
335
|
+
{
|
|
336
|
+
method: 'get',
|
|
337
|
+
path: '/v1/tunnel/qr',
|
|
338
|
+
tags: ['tunnel'],
|
|
339
|
+
operationId: 'getTunnelQR',
|
|
340
|
+
summary: 'Get QR code for tunnel URL',
|
|
341
|
+
responses: {
|
|
342
|
+
'200': {
|
|
343
|
+
description: 'OK',
|
|
344
|
+
content: {
|
|
345
|
+
'application/json': {
|
|
346
|
+
schema: {
|
|
347
|
+
type: 'object',
|
|
348
|
+
properties: {
|
|
349
|
+
ok: {
|
|
350
|
+
type: 'boolean',
|
|
351
|
+
},
|
|
352
|
+
url: {
|
|
353
|
+
type: 'string',
|
|
354
|
+
},
|
|
355
|
+
qrCode: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
required: ['ok'],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
'400': {
|
|
365
|
+
description: 'Bad Request',
|
|
366
|
+
content: {
|
|
367
|
+
'application/json': {
|
|
368
|
+
schema: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: {
|
|
371
|
+
error: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
required: ['error'],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
async (c) => {
|
|
383
|
+
if (!tunnelUrl) {
|
|
384
|
+
return c.json({ ok: false, error: 'No tunnel URL available' }, 400);
|
|
385
|
+
}
|
|
144
386
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
387
|
+
try {
|
|
388
|
+
const qrCode = await generateQRCode(tunnelUrl);
|
|
389
|
+
return c.json({
|
|
390
|
+
ok: true,
|
|
391
|
+
url: tunnelUrl,
|
|
392
|
+
qrCode,
|
|
393
|
+
});
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
396
|
+
return c.json({ ok: false, error: message }, 500);
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
);
|
|
157
400
|
|
|
158
401
|
const handleTunnelStream = async (c: Context) => {
|
|
159
402
|
return streamSSE(c as Context, async (stream) => {
|
|
@@ -200,8 +443,41 @@ export function registerTunnelRoutes(app: Hono) {
|
|
|
200
443
|
});
|
|
201
444
|
};
|
|
202
445
|
|
|
203
|
-
|
|
204
|
-
|
|
446
|
+
const tunnelStreamRoute = {
|
|
447
|
+
tags: ['tunnel'],
|
|
448
|
+
summary: 'Subscribe to tunnel status stream',
|
|
449
|
+
responses: {
|
|
450
|
+
'200': {
|
|
451
|
+
description: 'SSE stream of tunnel status updates',
|
|
452
|
+
content: {
|
|
453
|
+
'text/event-stream': {
|
|
454
|
+
schema: { type: 'string' },
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
openApiRoute(
|
|
462
|
+
app,
|
|
463
|
+
{
|
|
464
|
+
method: 'get',
|
|
465
|
+
path: '/v1/tunnel/stream',
|
|
466
|
+
operationId: 'subscribeTunnelStream',
|
|
467
|
+
...tunnelStreamRoute,
|
|
468
|
+
},
|
|
469
|
+
handleTunnelStream,
|
|
470
|
+
);
|
|
471
|
+
openApiRoute(
|
|
472
|
+
app,
|
|
473
|
+
{
|
|
474
|
+
method: 'post',
|
|
475
|
+
path: '/v1/tunnel/stream',
|
|
476
|
+
operationId: 'subscribeTunnelStreamPost',
|
|
477
|
+
...tunnelStreamRoute,
|
|
478
|
+
},
|
|
479
|
+
handleTunnelStream,
|
|
480
|
+
);
|
|
205
481
|
}
|
|
206
482
|
|
|
207
483
|
export function stopActiveTunnel() {
|
|
@@ -155,6 +155,7 @@ async function processAskRequest(
|
|
|
155
155
|
|
|
156
156
|
if (request.credentials) {
|
|
157
157
|
for (const [provider, creds] of Object.entries(request.credentials)) {
|
|
158
|
+
if (!creds) continue;
|
|
158
159
|
const envKey =
|
|
159
160
|
providerEnvVar(provider as ProviderId) ??
|
|
160
161
|
`${provider.toUpperCase()}_API_KEY`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { catalog, getModelInfo } from '@ottocode/sdk';
|
|
2
|
-
import type { ProviderId } from '@ottocode/sdk';
|
|
2
|
+
import type { BuiltInProviderId, ProviderId } from '@ottocode/sdk';
|
|
3
3
|
|
|
4
4
|
export const PRUNE_PROTECT = 40_000;
|
|
5
5
|
|
|
@@ -60,9 +60,9 @@ export function getModelLimits(
|
|
|
60
60
|
if (info?.limit?.context && info?.limit?.output) {
|
|
61
61
|
return { context: info.limit.context, output: info.limit.output };
|
|
62
62
|
}
|
|
63
|
-
for (const key of Object.keys(catalog) as
|
|
63
|
+
for (const key of Object.keys(catalog) as BuiltInProviderId[]) {
|
|
64
64
|
const entry = catalog[key];
|
|
65
|
-
const m = entry?.models?.find((x) => x.id === model);
|
|
65
|
+
const m = entry?.models?.find((x: { id: string }) => x.id === model);
|
|
66
66
|
if (m?.limit?.context && m?.limit?.output) {
|
|
67
67
|
return { context: m.limit.context, output: m.limit.output };
|
|
68
68
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getProviderDefinition,
|
|
5
5
|
getModelNpmBinding,
|
|
6
6
|
getUnderlyingProviderKey,
|
|
7
|
+
isBuiltInProviderId,
|
|
7
8
|
modelSupportsReasoning,
|
|
8
9
|
type OttoConfig,
|
|
9
10
|
type ProviderId,
|
|
@@ -118,7 +119,7 @@ function getOpenAICompatibleProviderOptionKeys(
|
|
|
118
119
|
cfg?: OttoConfig,
|
|
119
120
|
): string[] {
|
|
120
121
|
const definition = cfg ? getProviderDefinition(cfg, provider) : undefined;
|
|
121
|
-
const entry = catalog[provider];
|
|
122
|
+
const entry = isBuiltInProviderId(provider) ? catalog[provider] : undefined;
|
|
122
123
|
const keys = new Set<string>(['openaiCompatible', toCamelCaseKey(provider)]);
|
|
123
124
|
const label = definition?.label ?? entry?.label;
|
|
124
125
|
if (label) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { getDb } from '@ottocode/database';
|
|
2
2
|
import { messages, messageParts, sessions } from '@ottocode/database/schema';
|
|
3
3
|
import { eq } from 'drizzle-orm';
|
|
4
|
-
import { catalog, type ProviderId } from '@ottocode/sdk';
|
|
4
|
+
import { catalog, isBuiltInProviderId, type ProviderId } from '@ottocode/sdk';
|
|
5
5
|
import type { RunOpts } from './queue.ts';
|
|
6
6
|
|
|
7
7
|
export type UsageData = {
|
|
@@ -77,10 +77,11 @@ export function resolveUsageProvider(
|
|
|
77
77
|
) {
|
|
78
78
|
return provider;
|
|
79
79
|
}
|
|
80
|
-
const entry = catalog[provider];
|
|
80
|
+
const entry = isBuiltInProviderId(provider) ? catalog[provider] : undefined;
|
|
81
81
|
const normalizedModel = model.includes('/') ? model.split('/').at(-1) : model;
|
|
82
82
|
const modelEntry = entry?.models.find(
|
|
83
|
-
(m
|
|
83
|
+
(m: { id?: string }) =>
|
|
84
|
+
m.id?.toLowerCase() === normalizedModel?.toLowerCase(),
|
|
84
85
|
);
|
|
85
86
|
const npm = modelEntry?.provider?.npm ?? '';
|
|
86
87
|
if (npm.includes('openai')) return 'openai';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { catalog } from '@ottocode/sdk';
|
|
1
|
+
import { catalog, isBuiltInProviderId } from '@ottocode/sdk';
|
|
2
2
|
import type { ProviderName } from '../provider/index.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -13,11 +13,16 @@ export function getMaxOutputTokens(
|
|
|
13
13
|
return undefined;
|
|
14
14
|
}
|
|
15
15
|
try {
|
|
16
|
+
if (!isBuiltInProviderId(provider)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
16
19
|
const providerCatalog = catalog[provider];
|
|
17
20
|
if (!providerCatalog) {
|
|
18
21
|
return undefined;
|
|
19
22
|
}
|
|
20
|
-
const modelInfo = providerCatalog.models.find(
|
|
23
|
+
const modelInfo = providerCatalog.models.find(
|
|
24
|
+
(m: { id: string }) => m.id === modelId,
|
|
25
|
+
);
|
|
21
26
|
if (!modelInfo) {
|
|
22
27
|
return undefined;
|
|
23
28
|
}
|
package/src/tools/adapter.ts
CHANGED
|
@@ -530,6 +530,27 @@ export function adaptTools(
|
|
|
530
530
|
streamedResult = (chunk as { result: unknown }).result;
|
|
531
531
|
continue;
|
|
532
532
|
}
|
|
533
|
+
if (
|
|
534
|
+
chunk &&
|
|
535
|
+
typeof chunk === 'object' &&
|
|
536
|
+
'terminalId' in chunk &&
|
|
537
|
+
typeof (chunk as { terminalId?: unknown }).terminalId ===
|
|
538
|
+
'string'
|
|
539
|
+
) {
|
|
540
|
+
publish({
|
|
541
|
+
type: 'tool.delta',
|
|
542
|
+
sessionId: ctx.sessionId,
|
|
543
|
+
payload: {
|
|
544
|
+
name,
|
|
545
|
+
channel: 'terminal',
|
|
546
|
+
delta: (chunk as { terminalId: string }).terminalId,
|
|
547
|
+
stepIndex: stepIndexForEvent,
|
|
548
|
+
callId: callIdFromQueue,
|
|
549
|
+
messageId: ctx.messageId,
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
533
554
|
const delta =
|
|
534
555
|
typeof chunk === 'string'
|
|
535
556
|
? chunk
|