@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.
- 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/agent/runner-reasoning.ts +38 -3
- package/src/runtime/agent/runner.ts +1 -0
- package/src/runtime/ask/service.ts +1 -0
- package/src/runtime/message/compaction-limits.ts +3 -3
- package/src/runtime/provider/reasoning.ts +18 -7
- 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() {
|
|
@@ -27,7 +27,7 @@ export async function handleReasoningStart(
|
|
|
27
27
|
reasoningId: string,
|
|
28
28
|
providerMetadata: unknown,
|
|
29
29
|
opts: RunOpts,
|
|
30
|
-
|
|
30
|
+
db: Awaited<ReturnType<typeof getDb>>,
|
|
31
31
|
sharedCtx: ToolAdapterContext,
|
|
32
32
|
getStepIndex: () => number,
|
|
33
33
|
reasoningStates: Map<string, ReasoningState>,
|
|
@@ -43,6 +43,22 @@ export async function handleReasoningStart(
|
|
|
43
43
|
getStepIndex,
|
|
44
44
|
};
|
|
45
45
|
reasoningStates.set(reasoningId, state);
|
|
46
|
+
|
|
47
|
+
if (hasAnthropicRedactedReasoning(providerMetadata)) {
|
|
48
|
+
const delta = '[Reasoning redacted by Anthropic]\n';
|
|
49
|
+
state.text = delta;
|
|
50
|
+
await persistReasoningPart(state, db);
|
|
51
|
+
publish({
|
|
52
|
+
type: 'reasoning.delta',
|
|
53
|
+
sessionId: opts.sessionId,
|
|
54
|
+
payload: {
|
|
55
|
+
messageId: opts.assistantMessageId,
|
|
56
|
+
partId: state.partId,
|
|
57
|
+
stepIndex: getStepIndex(),
|
|
58
|
+
delta,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
async function persistReasoningPart(
|
|
@@ -73,11 +89,23 @@ export async function handleReasoningDelta(
|
|
|
73
89
|
providerMetadata: unknown,
|
|
74
90
|
opts: RunOpts,
|
|
75
91
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
92
|
+
sharedCtx: ToolAdapterContext,
|
|
76
93
|
getStepIndex: () => number,
|
|
77
94
|
reasoningStates: Map<string, ReasoningState>,
|
|
78
95
|
): Promise<void> {
|
|
79
|
-
|
|
80
|
-
if (!state)
|
|
96
|
+
let state = reasoningStates.get(reasoningId);
|
|
97
|
+
if (!state) {
|
|
98
|
+
state = {
|
|
99
|
+
partId: crypto.randomUUID(),
|
|
100
|
+
text: '',
|
|
101
|
+
providerMetadata,
|
|
102
|
+
persisted: false,
|
|
103
|
+
opts,
|
|
104
|
+
sharedCtx,
|
|
105
|
+
getStepIndex,
|
|
106
|
+
};
|
|
107
|
+
reasoningStates.set(reasoningId, state);
|
|
108
|
+
}
|
|
81
109
|
state.text += text;
|
|
82
110
|
if (providerMetadata != null) {
|
|
83
111
|
state.providerMetadata = providerMetadata;
|
|
@@ -108,6 +136,13 @@ export async function handleReasoningDelta(
|
|
|
108
136
|
} catch {}
|
|
109
137
|
}
|
|
110
138
|
|
|
139
|
+
function hasAnthropicRedactedReasoning(providerMetadata: unknown): boolean {
|
|
140
|
+
if (!providerMetadata || typeof providerMetadata !== 'object') return false;
|
|
141
|
+
const anthropic = (providerMetadata as Record<string, unknown>).anthropic;
|
|
142
|
+
if (!anthropic || typeof anthropic !== 'object') return false;
|
|
143
|
+
return 'redactedData' in anthropic;
|
|
144
|
+
}
|
|
145
|
+
|
|
111
146
|
export async function handleReasoningEnd(
|
|
112
147
|
reasoningId: string,
|
|
113
148
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
@@ -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
|
}
|