@ottocode/server 0.1.260 → 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 (67) 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/ask/service.ts +1 -0
  42. package/src/runtime/message/compaction-limits.ts +3 -3
  43. package/src/runtime/provider/reasoning.ts +2 -1
  44. package/src/runtime/session/db-operations.ts +4 -3
  45. package/src/runtime/utils/token.ts +7 -2
  46. package/src/tools/adapter.ts +21 -0
  47. package/src/openapi/paths/ask.ts +0 -81
  48. package/src/openapi/paths/auth.ts +0 -687
  49. package/src/openapi/paths/branch.ts +0 -102
  50. package/src/openapi/paths/config.ts +0 -485
  51. package/src/openapi/paths/doctor.ts +0 -165
  52. package/src/openapi/paths/files.ts +0 -236
  53. package/src/openapi/paths/git.ts +0 -690
  54. package/src/openapi/paths/mcp.ts +0 -339
  55. package/src/openapi/paths/messages.ts +0 -103
  56. package/src/openapi/paths/ottorouter.ts +0 -594
  57. package/src/openapi/paths/provider-usage.ts +0 -59
  58. package/src/openapi/paths/research.ts +0 -227
  59. package/src/openapi/paths/session-approval.ts +0 -93
  60. package/src/openapi/paths/session-extras.ts +0 -336
  61. package/src/openapi/paths/session-files.ts +0 -91
  62. package/src/openapi/paths/sessions.ts +0 -210
  63. package/src/openapi/paths/skills.ts +0 -377
  64. package/src/openapi/paths/stream.ts +0 -26
  65. package/src/openapi/paths/terminals.ts +0 -226
  66. package/src/openapi/paths/tunnel.ts +0 -163
  67. package/src/openapi/spec.ts +0 -73
@@ -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
- app.get('/v1/tunnel/status', async (c) => {
21
- const binaryInstalled = await isTunnelBinaryInstalled();
22
-
23
- return c.json({
24
- status: tunnelStatus,
25
- url: tunnelUrl,
26
- error: tunnelError,
27
- binaryInstalled,
28
- isRunning: activeTunnel?.isRunning ?? false,
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
- ok: true,
67
+ status: tunnelStatus,
36
68
  url: tunnelUrl,
37
- message: 'Tunnel already running',
69
+ error: tunnelError,
70
+ binaryInstalled,
71
+ isRunning: activeTunnel?.isRunning ?? false,
38
72
  });
39
- }
40
-
41
- try {
42
- const body = await c.req.json().catch(() => ({}));
43
- let port = body.port;
44
-
45
- // Use server's known port if not explicitly provided
46
- if (!port) {
47
- port = getServerPort() || 9100;
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
- // Kill any stale tunnel processes first
51
- await killStaleTunnels();
136
+ try {
137
+ const body = await c.req.json().catch(() => ({}));
138
+ let port = body.port;
52
139
 
53
- tunnelStatus = 'starting';
54
- tunnelError = null;
55
- progressMessage = 'Initializing...';
140
+ // Use server's known port if not explicitly provided
141
+ if (!port) {
142
+ port = getServerPort() || 9100;
143
+ }
56
144
 
57
- activeTunnel = new OttoTunnel();
145
+ // Kill any stale tunnel processes first
146
+ await killStaleTunnels();
58
147
 
59
- const url = await activeTunnel.start(port, (msg) => {
60
- progressMessage = msg;
61
- });
148
+ tunnelStatus = 'starting';
149
+ tunnelError = null;
150
+ progressMessage = 'Initializing...';
62
151
 
63
- tunnelUrl = url;
64
- tunnelStatus = 'connected';
65
- progressMessage = null;
152
+ activeTunnel = new OttoTunnel();
66
153
 
67
- activeTunnel.on('error', (err) => {
68
- logger.error('Tunnel error:', err);
69
- tunnelError = err.message;
70
- tunnelStatus = 'error';
71
- });
154
+ const url = await activeTunnel.start(port, (msg) => {
155
+ progressMessage = msg;
156
+ });
72
157
 
73
- activeTunnel.on('exit', () => {
74
- tunnelStatus = 'idle';
75
- tunnelUrl = null;
76
- activeTunnel = null;
77
- });
158
+ tunnelUrl = url;
159
+ tunnelStatus = 'connected';
160
+ progressMessage = null;
78
161
 
79
- return c.json({
80
- ok: true,
81
- url: tunnelUrl,
82
- message: 'Tunnel started',
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
- app.post('/v1/tunnel/register', async (c) => {
96
- try {
97
- const body = await c.req.json().catch(() => ({}));
98
- const { url } = body;
168
+ activeTunnel.on('exit', () => {
169
+ tunnelStatus = 'idle';
170
+ tunnelUrl = null;
171
+ activeTunnel = null;
172
+ });
99
173
 
100
- if (!url) {
101
- return c.json({ ok: false, error: 'URL is required' }, 400);
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
- tunnelUrl = url;
105
- tunnelStatus = 'connected';
106
- tunnelError = null;
107
- progressMessage = null;
265
+ tunnelUrl = url;
266
+ tunnelStatus = 'connected';
267
+ tunnelError = null;
268
+ progressMessage = null;
108
269
 
109
- return c.json({
110
- ok: true,
111
- url: tunnelUrl,
112
- message: 'External tunnel registered',
113
- });
114
- } catch (error) {
115
- const message = error instanceof Error ? error.message : String(error);
116
- logger.error('Failed to register external tunnel:', error);
117
- return c.json({ ok: false, error: message }, 500);
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
- app.post('/v1/tunnel/stop', async (c) => {
122
- if (!activeTunnel) {
123
- return c.json({ ok: true, message: 'No tunnel running' });
124
- }
125
-
126
- try {
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
- app.get('/v1/tunnel/qr', async (c) => {
141
- if (!tunnelUrl) {
142
- return c.json({ ok: false, error: 'No tunnel URL available' }, 400);
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
- try {
146
- const qrCode = await generateQRCode(tunnelUrl);
147
- return c.json({
148
- ok: true,
149
- url: tunnelUrl,
150
- qrCode,
151
- });
152
- } catch (error) {
153
- const message = error instanceof Error ? error.message : String(error);
154
- return c.json({ ok: false, error: message }, 500);
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
- app.get('/v1/tunnel/stream', handleTunnelStream);
204
- app.post('/v1/tunnel/stream', handleTunnelStream);
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 ProviderId[]) {
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) => m.id?.toLowerCase() === normalizedModel?.toLowerCase(),
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((m) => m.id === modelId);
23
+ const modelInfo = providerCatalog.models.find(
24
+ (m: { id: string }) => m.id === modelId,
25
+ );
21
26
  if (!modelInfo) {
22
27
  return undefined;
23
28
  }
@@ -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