@cogitator-ai/tetsu 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -63,24 +63,26 @@ A Tetsu controller named `Cogitator`: `operationId`s in the OpenAPI document are
63
63
 
64
64
  ## Endpoints
65
65
 
66
- | Method | Path | Description |
67
- | -------- | -------------------------- | --------------------------------------- |
68
- | `GET` | `/health`, `/ready` | Liveness and readiness, without `auth` |
69
- | `GET` | `/agents` | Agents with their description and tools |
70
- | `POST` | `/agents/:name/run` | Run an agent and wait for the answer |
71
- | `POST` | `/agents/:name/stream` | Run an agent over SSE |
72
- | `GET` | `/tools` | Tools of every agent, as JSON Schema |
73
- | `GET` | `/threads/:id` | Messages of a memory thread |
74
- | `POST` | `/threads/:id/messages` | Append a message |
75
- | `DELETE` | `/threads/:id` | Delete a thread |
76
- | `GET` | `/workflows` | Workflows with their nodes |
77
- | `POST` | `/workflows/:name/run` | Run a workflow |
78
- | `POST` | `/workflows/:name/stream` | Run a workflow over SSE |
79
- | `GET` | `/swarms` | Swarms with their agents |
80
- | `POST` | `/swarms/:name/run` | Run a swarm |
81
- | `POST` | `/swarms/:name/stream` | Run a swarm over SSE |
82
- | `GET` | `/swarms/:name/blackboard` | Configured blackboard sections |
83
- | `GET` | `/ws` | WebSocket, when `websocket` is set |
66
+ | Method | Path | Description |
67
+ | -------- | ----------------------------- | --------------------------------------- |
68
+ | `GET` | `/health`, `/ready` | Liveness and readiness, without `auth` |
69
+ | `GET` | `/agents` | Agents with their description and tools |
70
+ | `POST` | `/agents/:name/run` | Run an agent and wait for the answer |
71
+ | `POST` | `/agents/:name/stream` | Run an agent over SSE |
72
+ | `POST` | `/agents/:name/resume` | Resume a run paused for approvals |
73
+ | `POST` | `/agents/:name/resume/stream` | Resume a paused run over SSE |
74
+ | `GET` | `/tools` | Tools of every agent, as JSON Schema |
75
+ | `GET` | `/threads/:id` | Messages of a memory thread |
76
+ | `POST` | `/threads/:id/messages` | Append a message |
77
+ | `DELETE` | `/threads/:id` | Delete a thread |
78
+ | `GET` | `/workflows` | Workflows with their nodes |
79
+ | `POST` | `/workflows/:name/run` | Run a workflow |
80
+ | `POST` | `/workflows/:name/stream` | Run a workflow over SSE |
81
+ | `GET` | `/swarms` | Swarms with their agents |
82
+ | `POST` | `/swarms/:name/run` | Run a swarm |
83
+ | `POST` | `/swarms/:name/stream` | Run a swarm over SSE |
84
+ | `GET` | `/swarms/:name/blackboard` | Configured blackboard sections |
85
+ | `GET` | `/ws` | WebSocket, when `websocket` is set |
84
86
 
85
87
  Request bodies:
86
88
 
@@ -88,6 +90,13 @@ Request bodies:
88
90
  // POST /agents/:name/run, /agents/:name/stream
89
91
  { input: string; context?: Record<string, unknown>; threadId?: string }
90
92
 
93
+ // POST /agents/:name/resume, /agents/:name/resume/stream
94
+ {
95
+ threadId: string;
96
+ decisions?: Record<string, { approved: boolean; reason?: string }>; // by tool call id
97
+ defaultDecision?: { approved: boolean; reason?: string };
98
+ }
99
+
91
100
  // POST /swarms/:name/run, /swarms/:name/stream
92
101
  { input: string; context?: Record<string, unknown>; threadId?: string; timeout?: number }
93
102
 
@@ -113,6 +122,7 @@ Every error is answered in Tetsu's envelope:
113
122
  | 403 | `THREAD_FORBIDDEN` | `authorizeThread` refused the thread |
114
123
  | 404 | `AGENT_NOT_FOUND`, `WORKFLOW_NOT_FOUND`, `SWARM_NOT_FOUND` | No such name |
115
124
  | 409 | `BLACKBOARD_DISABLED` | The swarm has no blackboard |
125
+ | 409 | `RUN_NOT_PAUSED` | A resume named a thread without a paused run |
116
126
  | 422 | `VALIDATION_FAILED` | The body or the path failed its schema; `issues` lists every problem |
117
127
  | 501 | `PACKAGE_NOT_INSTALLED` | `@cogitator-ai/workflows` or `@cogitator-ai/swarms` is missing |
118
128
  | 503 | `MEMORY_NOT_CONFIGURED` | A thread endpoint was called on a runtime without memory |
@@ -201,6 +211,39 @@ cogitatorController({
201
211
 
202
212
  Use it for rules ownership does not express, such as thread ids your server hands out.
203
213
 
214
+ ## Approvals
215
+
216
+ A tool with `requiresApproval` pauses the run before it executes. `POST /agents/:name/run` then answers with `status: 'paused'` and the calls waiting for a decision; the run's checkpoint stays on the server, stored by the runtime per thread:
217
+
218
+ ```json
219
+ {
220
+ "output": "Let me refund that.",
221
+ "threadId": "thread_…",
222
+ "status": "paused",
223
+ "pendingApprovals": [
224
+ {
225
+ "toolCallId": "call_1",
226
+ "toolName": "refund",
227
+ "arguments": { "order": "A-1", "amount": 500 },
228
+ "description": "Refund an order",
229
+ "sideEffects": ["payment"]
230
+ }
231
+ ],
232
+ "usage": { "inputTokens": 10, "outputTokens": 5, "totalTokens": 15 },
233
+ "toolCalls": []
234
+ }
235
+ ```
236
+
237
+ A stream that pauses sends `{"type":"approval-required","threadId":"…","approvals":[…]}` right before `finish`. Continue with the decisions:
238
+
239
+ ```bash
240
+ curl -X POST localhost:3000/cogitator/agents/support/resume \
241
+ -H 'content-type: application/json' \
242
+ -d '{ "threadId": "thread_…", "decisions": { "call_1": { "approved": true } } }'
243
+ ```
244
+
245
+ Approved calls run, declined ones answer the model with the `reason`, and calls without a decision (and no `defaultDecision`) pause the run again. `/agents/:name/resume/stream` streams the rest of the run like `/stream`. Only the user the run belongs to can resume it (`403 THREAD_ACCESS_DENIED` otherwise; `authorizeThread` is checked too), and a thread without a paused run answers `409 RUN_NOT_PAUSED`. Over the WebSocket, send `{ type: 'resume', id?, payload: { name, threadId, decisions?, defaultDecision? } }`.
246
+
204
247
  ## Streaming
205
248
 
206
249
  `/stream` endpoints answer with `text/event-stream` through `@tetsujs/sse`: keep-alive comments every 15 seconds, backpressure, and the run is aborted when the client goes away. Events follow the Cogitator stream protocol shared with the other adapters, one JSON object per `data:` line, ending with `data: [DONE]`:
@@ -218,6 +261,8 @@ data: {"type":"finish","messageId":"msg_…","usage":{"inputTokens":12,"outputTo
218
261
  data: [DONE]
219
262
  ```
220
263
 
264
+ An agent with `reasoning: { summary: true }` also streams its reasoning summary as `reasoning-start`, `reasoning-delta` and `reasoning-end` events. A text or reasoning part opens with its first delta and is closed before a part of the other kind, a tool call or `finish`, so parts never overlap. `POST /agents/:name/run` returns the summary as `reasoning`, and `usage` gains `reasoningTokens`, `cachedInputTokens` and `cacheWriteTokens` when the provider reports them.
265
+
221
266
  A run that fails ends with `{"type":"error","message":"…","code":"…"}` instead of `finish`. An unexpected failure is also reported to the application's `reportError` with `source: "stream"`. Workflow streams send `workflow` events (`node_started`, `node_completed`, `node_error`, `node_progress`, `workflow_completed`), swarm streams send `swarm` events (`agent_start`, `agent_complete`, `agent_error`, `message`, `swarm_completed`).
222
267
 
223
268
  Validation, `401`, `403` and `404` are answered as JSON before the stream opens.
@@ -230,11 +275,12 @@ cogitatorController({ cogitator, agents, auth, websocket: true });
230
275
 
231
276
  The handshake runs `auth` like any route. Each socket runs one agent, workflow or swarm at a time.
232
277
 
233
- | Client sends | Server answers |
234
- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
235
- | `{ type: 'run', id?, payload: { type: 'agent' \| 'workflow' \| 'swarm', name, input, context?, threadId? } }` | `event` frames: `token`, `tool-call`, `tool-result`, then `complete` with the result, or `cancelled` |
236
- | `{ type: 'stop' }` | cancels the current run |
237
- | `{ type: 'ping', id? }` | `{ type: 'pong', id }` |
278
+ | Client sends | Server answers |
279
+ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
280
+ | `{ type: 'run', id?, payload: { type: 'agent' \| 'workflow' \| 'swarm', name, input, context?, threadId? } }` | `event` frames: `token`, `reasoning`, `tool-call`, `tool-result`, then `complete` with the result, or `cancelled` |
281
+ | `{ type: 'resume', id?, payload: { name, threadId, decisions?, defaultDecision? } }` | the same frames for an agent run paused for [approvals](#approvals) |
282
+ | `{ type: 'stop' }` | cancels the current run |
283
+ | `{ type: 'ping', id? }` | `{ type: 'pong', id }` |
238
284
 
239
285
  Errors arrive as `{ type: 'error', id, error, code }` and leave the socket open; an invalid frame gets `code: 'INVALID_MESSAGE'`. Closing the socket cancels its run. Bun's server-level options such as `maxPayloadLength` are set where the app is served:
240
286
 
@@ -40,6 +40,21 @@ export declare const cogitatorController: (deps: CogitatorDeps) => {
40
40
  context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
41
  threadId: z.ZodOptional<z.ZodString>;
42
42
  }, z.core.$strip>;
43
+ }, z.core.$strip>, z.ZodObject<{
44
+ type: z.ZodLiteral<"resume">;
45
+ id: z.ZodOptional<z.ZodString>;
46
+ payload: z.ZodObject<{
47
+ threadId: z.ZodString;
48
+ decisions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
49
+ approved: z.ZodBoolean;
50
+ reason: z.ZodOptional<z.ZodString>;
51
+ }, z.core.$strip>>>;
52
+ defaultDecision: z.ZodOptional<z.ZodObject<{
53
+ approved: z.ZodBoolean;
54
+ reason: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strip>>;
56
+ name: z.ZodString;
57
+ }, z.core.$strip>;
43
58
  }, z.core.$strip>], "type">;
44
59
  }, {
45
60
  readonly beforeParse: readonly [import("@tetsujs/core").Hook<"beforeParse", import("@tetsujs/core").BaseCtx & {
@@ -116,12 +131,27 @@ export declare const cogitatorController: (deps: CogitatorDeps) => {
116
131
  inputTokens: z.ZodNumber;
117
132
  outputTokens: z.ZodNumber;
118
133
  totalTokens: z.ZodNumber;
134
+ reasoningTokens: z.ZodOptional<z.ZodNumber>;
135
+ cachedInputTokens: z.ZodOptional<z.ZodNumber>;
136
+ cacheWriteTokens: z.ZodOptional<z.ZodNumber>;
119
137
  }, z.core.$strip>;
120
138
  toolCalls: z.ZodArray<z.ZodObject<{
121
139
  id: z.ZodString;
122
140
  name: z.ZodString;
123
141
  arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
124
142
  }, z.core.$strip>>;
143
+ reasoning: z.ZodOptional<z.ZodString>;
144
+ status: z.ZodOptional<z.ZodEnum<{
145
+ completed: "completed";
146
+ paused: "paused";
147
+ }>>;
148
+ pendingApprovals: z.ZodOptional<z.ZodArray<z.ZodObject<{
149
+ toolCallId: z.ZodString;
150
+ toolName: z.ZodString;
151
+ arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
152
+ description: z.ZodString;
153
+ sideEffects: z.ZodOptional<z.ZodArray<z.ZodString>>;
154
+ }, z.core.$strip>>>;
125
155
  }, z.core.$strip>;
126
156
  readonly 403: z.ZodObject<{
127
157
  status: z.ZodNumber;
@@ -210,6 +240,9 @@ export declare const cogitatorController: (deps: CogitatorDeps) => {
210
240
  inputTokens: number;
211
241
  outputTokens: number;
212
242
  totalTokens: number;
243
+ reasoningTokens?: number | undefined;
244
+ cachedInputTokens?: number | undefined;
245
+ cacheWriteTokens?: number | undefined;
213
246
  };
214
247
  toolCalls: {
215
248
  id: string;
@@ -217,6 +250,15 @@ export declare const cogitatorController: (deps: CogitatorDeps) => {
217
250
  arguments: Record<string, unknown>;
218
251
  }[];
219
252
  structured?: unknown;
253
+ reasoning?: string | undefined;
254
+ status?: "completed" | "paused" | undefined;
255
+ pendingApprovals?: {
256
+ toolCallId: string;
257
+ toolName: string;
258
+ arguments: Record<string, unknown>;
259
+ description: string;
260
+ sideEffects?: string[] | undefined;
261
+ }[] | undefined;
220
262
  }>, false>;
221
263
  streamAgent: import("@tetsujs/core").RouteDef<"/agents/:name/stream", {
222
264
  readonly params: z.ZodObject<{
@@ -251,6 +293,198 @@ export declare const cogitatorController: (deps: CogitatorDeps) => {
251
293
  readonly error: unknown;
252
294
  }, unknown>];
253
295
  }, undefined, "POST", Promise<Response>, false>;
296
+ resumeAgent: import("@tetsujs/core").RouteDef<"/agents/:name/resume", {
297
+ readonly params: z.ZodObject<{
298
+ name: z.ZodString;
299
+ }, z.core.$strip>;
300
+ readonly body: z.ZodObject<{
301
+ threadId: z.ZodString;
302
+ decisions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
303
+ approved: z.ZodBoolean;
304
+ reason: z.ZodOptional<z.ZodString>;
305
+ }, z.core.$strip>>>;
306
+ defaultDecision: z.ZodOptional<z.ZodObject<{
307
+ approved: z.ZodBoolean;
308
+ reason: z.ZodOptional<z.ZodString>;
309
+ }, z.core.$strip>>;
310
+ }, z.core.$strip>;
311
+ readonly response: {
312
+ readonly 200: z.ZodObject<{
313
+ output: z.ZodString;
314
+ structured: z.ZodOptional<z.ZodUnknown>;
315
+ threadId: z.ZodString;
316
+ usage: z.ZodObject<{
317
+ inputTokens: z.ZodNumber;
318
+ outputTokens: z.ZodNumber;
319
+ totalTokens: z.ZodNumber;
320
+ reasoningTokens: z.ZodOptional<z.ZodNumber>;
321
+ cachedInputTokens: z.ZodOptional<z.ZodNumber>;
322
+ cacheWriteTokens: z.ZodOptional<z.ZodNumber>;
323
+ }, z.core.$strip>;
324
+ toolCalls: z.ZodArray<z.ZodObject<{
325
+ id: z.ZodString;
326
+ name: z.ZodString;
327
+ arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
328
+ }, z.core.$strip>>;
329
+ reasoning: z.ZodOptional<z.ZodString>;
330
+ status: z.ZodOptional<z.ZodEnum<{
331
+ completed: "completed";
332
+ paused: "paused";
333
+ }>>;
334
+ pendingApprovals: z.ZodOptional<z.ZodArray<z.ZodObject<{
335
+ toolCallId: z.ZodString;
336
+ toolName: z.ZodString;
337
+ arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
338
+ description: z.ZodString;
339
+ sideEffects: z.ZodOptional<z.ZodArray<z.ZodString>>;
340
+ }, z.core.$strip>>>;
341
+ }, z.core.$strip>;
342
+ readonly 403: z.ZodObject<{
343
+ status: z.ZodNumber;
344
+ message: z.ZodString;
345
+ error: z.ZodEnum<{
346
+ [x: string]: string;
347
+ }>;
348
+ }, z.core.$strip>;
349
+ readonly 404: z.ZodObject<{
350
+ status: z.ZodNumber;
351
+ message: z.ZodString;
352
+ error: z.ZodEnum<{
353
+ [x: string]: string;
354
+ }>;
355
+ }, z.core.$strip>;
356
+ readonly 409: z.ZodObject<{
357
+ status: z.ZodNumber;
358
+ message: z.ZodString;
359
+ error: z.ZodEnum<{
360
+ [x: string]: string;
361
+ }>;
362
+ }, z.core.$strip>;
363
+ readonly 400: z.ZodObject<{
364
+ status: z.ZodNumber;
365
+ message: z.ZodString;
366
+ error: z.ZodEnum<{
367
+ [x: string]: string;
368
+ }>;
369
+ }, z.core.$strip>;
370
+ readonly 429: z.ZodObject<{
371
+ status: z.ZodNumber;
372
+ message: z.ZodString;
373
+ error: z.ZodEnum<{
374
+ [x: string]: string;
375
+ }>;
376
+ }, z.core.$strip>;
377
+ readonly 500: z.ZodObject<{
378
+ status: z.ZodNumber;
379
+ message: z.ZodString;
380
+ error: z.ZodEnum<{
381
+ [x: string]: string;
382
+ }>;
383
+ }, z.core.$strip>;
384
+ readonly 502: z.ZodObject<{
385
+ status: z.ZodNumber;
386
+ message: z.ZodString;
387
+ error: z.ZodEnum<{
388
+ [x: string]: string;
389
+ }>;
390
+ }, z.core.$strip>;
391
+ readonly 503: z.ZodObject<{
392
+ status: z.ZodNumber;
393
+ message: z.ZodString;
394
+ error: z.ZodEnum<{
395
+ [x: string]: string;
396
+ }>;
397
+ }, z.core.$strip>;
398
+ readonly 504: z.ZodObject<{
399
+ status: z.ZodNumber;
400
+ message: z.ZodString;
401
+ error: z.ZodEnum<{
402
+ [x: string]: string;
403
+ }>;
404
+ }, z.core.$strip>;
405
+ readonly 507: z.ZodObject<{
406
+ status: z.ZodNumber;
407
+ message: z.ZodString;
408
+ error: z.ZodEnum<{
409
+ [x: string]: string;
410
+ }>;
411
+ }, z.core.$strip>;
412
+ };
413
+ }, {
414
+ readonly beforeParse: readonly [import("@tetsujs/core").Hook<"beforeParse", import("@tetsujs/core").BaseCtx & {
415
+ readonly params: {
416
+ [x: string]: string;
417
+ };
418
+ }, Pick<import("./auth.js").CallerFields, "cogitatorAuth">>];
419
+ readonly onError: readonly [import("@tetsujs/core").Hook<"onError", import("@tetsujs/core").BaseCtx & {
420
+ readonly error: unknown;
421
+ }, unknown>];
422
+ }, undefined, "POST", Promise<{
423
+ output: string;
424
+ threadId: string;
425
+ usage: {
426
+ inputTokens: number;
427
+ outputTokens: number;
428
+ totalTokens: number;
429
+ reasoningTokens?: number | undefined;
430
+ cachedInputTokens?: number | undefined;
431
+ cacheWriteTokens?: number | undefined;
432
+ };
433
+ toolCalls: {
434
+ id: string;
435
+ name: string;
436
+ arguments: Record<string, unknown>;
437
+ }[];
438
+ structured?: unknown;
439
+ reasoning?: string | undefined;
440
+ status?: "completed" | "paused" | undefined;
441
+ pendingApprovals?: {
442
+ toolCallId: string;
443
+ toolName: string;
444
+ arguments: Record<string, unknown>;
445
+ description: string;
446
+ sideEffects?: string[] | undefined;
447
+ }[] | undefined;
448
+ }>, false>;
449
+ streamResumeAgent: import("@tetsujs/core").RouteDef<"/agents/:name/resume/stream", {
450
+ readonly params: z.ZodObject<{
451
+ name: z.ZodString;
452
+ }, z.core.$strip>;
453
+ readonly body: z.ZodObject<{
454
+ threadId: z.ZodString;
455
+ decisions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
456
+ approved: z.ZodBoolean;
457
+ reason: z.ZodOptional<z.ZodString>;
458
+ }, z.core.$strip>>>;
459
+ defaultDecision: z.ZodOptional<z.ZodObject<{
460
+ approved: z.ZodBoolean;
461
+ reason: z.ZodOptional<z.ZodString>;
462
+ }, z.core.$strip>>;
463
+ }, z.core.$strip>;
464
+ readonly response: {
465
+ readonly 403: z.ZodObject<{
466
+ status: z.ZodNumber;
467
+ message: z.ZodString;
468
+ error: z.ZodEnum<{
469
+ [x: string]: string;
470
+ }>;
471
+ }, z.core.$strip>;
472
+ readonly 404: z.ZodObject<{
473
+ status: z.ZodNumber;
474
+ message: z.ZodString;
475
+ error: z.ZodLiteral<"AGENT_NOT_FOUND">;
476
+ }, z.core.$strip>;
477
+ };
478
+ }, {
479
+ readonly beforeParse: readonly [import("@tetsujs/core").Hook<"beforeParse", import("@tetsujs/core").BaseCtx & {
480
+ readonly params: {
481
+ [x: string]: string;
482
+ };
483
+ }, Pick<import("./auth.js").CallerFields, "cogitatorAuth">>];
484
+ readonly onError: readonly [import("@tetsujs/core").Hook<"onError", import("@tetsujs/core").BaseCtx & {
485
+ readonly error: unknown;
486
+ }, unknown>];
487
+ }, undefined, "POST", Promise<Response>, false>;
254
488
  getThread: import("@tetsujs/core").RouteDef<"/threads/:id", {
255
489
  readonly params: z.ZodObject<{
256
490
  id: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,OAAO,EAMR,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA8C7B,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AA+C7D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6W9B,CAAC"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,OAAO,EAMR,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmD7B,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AA0D7D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8b9B,CAAC"}
@@ -1,21 +1,23 @@
1
1
  import { controller, route } from '@tetsujs/core';
2
2
  import { sse } from '@tetsujs/sse';
3
3
  import { countMessageTokens } from '@cogitator-ai/memory';
4
- import { createErrorEvent, createFinishEvent, createStartEvent, createSwarmEvent, createTextDeltaEvent, createTextEndEvent, createTextStartEvent, createToolCallDeltaEvent, createToolCallEndEvent, createToolCallStartEvent, createToolResultEvent, createWorkflowEvent, generateId, } from '@cogitator-ai/server-shared';
4
+ import { createApprovalRequiredEvent, createErrorEvent, createFinishEvent, createStartEvent, createSwarmEvent, createToolCallDeltaEvent, createToolCallEndEvent, createToolCallStartEvent, createToolResultEvent, createWorkflowEvent, generateId, } from '@cogitator-ai/server-shared';
5
5
  import { assertThreadAccess, ensureThreadAccess } from '@cogitator-ai/core';
6
6
  import { httpError } from '@tetsujs/core';
7
7
  import { resolveCaller } from './auth.js';
8
8
  import { clientClosedRequest, cogitatorErrors, describeError } from './errors.js';
9
- import { checkThreadAccess, executeSwarm, executeWorkflow, findAgent, findSwarm, findWorkflow, listAgents, listSwarms, listTools, listWorkflows, runAgent, serializeSwarmUsage, toAgentRunResponse, toSwarmRunResponse, toWorkflowRunResponse, } from './operations.js';
10
- import { AddMessageBody, AddMessageResponse, AgentListResponse, AgentRunResponse, BlackboardResponse, errorEnvelope, errorsEnvelope, failureEnvelope, HealthResponse, NameParams, ReadyResponse, RUN_FAILURES, RunBody, SwarmListResponse, SwarmRunBody, SwarmRunResponse, ThreadParams, ThreadResponse, ToolListResponse, WorkflowListResponse, WorkflowRunBody, WorkflowRunResponse, } from './schemas.js';
9
+ import { checkThreadAccess, executeSwarm, executeWorkflow, findAgent, findSwarm, findWorkflow, listAgents, listSwarms, listTools, listWorkflows, resumeAgent, runAgent, serializeSwarmUsage, toAgentRunResponse, toPendingApprovals, toResumeDecisions, toSwarmRunResponse, toWorkflowRunResponse, } from './operations.js';
10
+ import { AddMessageBody, AddMessageResponse, AgentListResponse, AgentRunResponse, BlackboardResponse, errorEnvelope, errorsEnvelope, failureEnvelope, HealthResponse, NameParams, ReadyResponse, ResumeBody, RUN_FAILURES, RunBody, SwarmListResponse, SwarmRunBody, SwarmRunResponse, ThreadParams, ThreadResponse, ToolListResponse, WorkflowListResponse, WorkflowRunBody, WorkflowRunResponse, } from './schemas.js';
11
11
  import { cogitatorSocket } from './socket.js';
12
- import { DONE_EVENT, eventsOf, resolveSignal, sseEvent } from './streaming.js';
12
+ import { DONE_EVENT, eventsOf, MessageParts, resolveSignal, sseEvent } from './streaming.js';
13
13
  const AgentNotFound = errorEnvelope('AGENT_NOT_FOUND', 'No agent is registered under this name');
14
14
  const WorkflowNotFound = errorEnvelope('WORKFLOW_NOT_FOUND', 'No workflow is registered under this name');
15
15
  const SwarmNotFound = errorEnvelope('SWARM_NOT_FOUND', 'No swarm is registered under this name');
16
16
  const RunNotFound = failureEnvelope(404, 'No agent, workflow or swarm is registered under this name, or the agent called a tool it does not have', ['SWARM_NOT_FOUND']);
17
17
  const NotImplemented = failureEnvelope(501, 'The optional package this endpoint needs is not installed', ['PACKAGE_NOT_INSTALLED']);
18
18
  const ThreadForbidden = failureEnvelope(403, 'The memory thread belongs to another user, or `authorizeThread` refused it', ['THREAD_FORBIDDEN']);
19
+ const ResumeForbidden = failureEnvelope(403, 'The paused run belongs to another user, or `authorizeThread` refused the thread', ['THREAD_FORBIDDEN']);
20
+ const ResumeConflict = failureEnvelope(409, 'The thread has no paused run (`RUN_NOT_PAUSED`), or the agent is already running');
19
21
  const ThreadUnreadable = errorsEnvelope(['MEMORY_READ_FAILED'], 'The memory adapter could not read the thread, so its owner is unknown');
20
22
  const ThreadUnwritable = errorsEnvelope(['MEMORY_READ_FAILED', 'MEMORY_WRITE_FAILED'], 'The memory adapter could not read or create the thread');
21
23
  const MemoryNotConfigured = errorEnvelope('MEMORY_NOT_CONFIGURED', 'The Cogitator runtime has no memory adapter');
@@ -128,9 +130,66 @@ export const cogitatorController = controller('Cogitator', (deps) => {
128
130
  const auth = ctx.cogitatorAuth;
129
131
  const body = ctx.body;
130
132
  await checkThreadAccess(deps, auth, body.threadId);
131
- return sse(ctx, (signal) => agentEvents(deps, agent, body, auth, signal), {
132
- until: until(),
133
- });
133
+ return sse(ctx, (signal) => agentEvents((callbacks) => runAgent(deps, agent, { ...body, ...callbacks }, auth), signal), { until: until() });
134
+ },
135
+ }),
136
+ resumeAgent: route({
137
+ method: 'POST',
138
+ path: '/agents/:name/resume',
139
+ schema: {
140
+ params: NameParams,
141
+ body: ResumeBody,
142
+ response: {
143
+ ...RUN_FAILURES,
144
+ 200: AgentRunResponse,
145
+ 403: ResumeForbidden,
146
+ 404: RunNotFound,
147
+ 409: ResumeConflict,
148
+ },
149
+ },
150
+ hooks: { beforeParse: [caller], onError: [errors] },
151
+ docs: {
152
+ summary: 'Resume a run paused for tool approvals and wait for its answer',
153
+ description: 'Approved calls run, declined ones answer the model with the reason, and calls without a decision pause the run again.',
154
+ tags: ['agents'],
155
+ },
156
+ handler: async (ctx) => {
157
+ const agent = findAgent(deps, ctx.params.name);
158
+ const { threadId } = ctx.body;
159
+ await checkThreadAccess(deps, ctx.cogitatorAuth, threadId);
160
+ const signal = ctx.req.signal;
161
+ try {
162
+ const result = await resumeAgent(deps, agent, threadId, { ...toResumeDecisions(ctx.body), signal }, ctx.cogitatorAuth);
163
+ return toAgentRunResponse(result);
164
+ }
165
+ catch (error) {
166
+ if (signal.aborted)
167
+ throw clientClosedRequest();
168
+ throw error;
169
+ }
170
+ },
171
+ }),
172
+ streamResumeAgent: route({
173
+ method: 'POST',
174
+ path: '/agents/:name/resume/stream',
175
+ schema: {
176
+ params: NameParams,
177
+ body: ResumeBody,
178
+ response: { 403: ThreadForbidden, 404: AgentNotFound },
179
+ },
180
+ hooks: { beforeParse: [caller], onError: [errors] },
181
+ docs: {
182
+ summary: 'Resume a run paused for tool approvals and stream the rest of it',
183
+ description: STREAM_DESCRIPTION,
184
+ tags: ['agents'],
185
+ },
186
+ handler: async (ctx) => {
187
+ const agent = findAgent(deps, ctx.params.name);
188
+ const auth = ctx.cogitatorAuth;
189
+ const { threadId } = ctx.body;
190
+ const decisions = toResumeDecisions(ctx.body);
191
+ await checkThreadAccess(deps, auth, threadId);
192
+ return sse(ctx, (signal) => agentEvents((callbacks) => resumeAgent(deps, agent, threadId, { ...decisions, ...callbacks }, auth), signal), { until: until() });
134
193
  },
135
194
  }),
136
195
  getThread: route({
@@ -386,23 +445,24 @@ function* failure(error) {
386
445
  if (described.unexpected)
387
446
  throw error;
388
447
  }
389
- async function* agentEvents(deps, agent, body, auth, signal) {
448
+ async function* agentEvents(start, signal) {
390
449
  const messageId = generateId('msg');
391
- const textId = generateId('txt');
450
+ const parts = new MessageParts();
392
451
  let result;
393
452
  yield sseEvent(createStartEvent(messageId));
394
- yield sseEvent(createTextStartEvent(textId));
395
453
  try {
396
454
  yield* eventsOf(signal, async (emit) => {
397
- result = await runAgent(deps, agent, {
398
- ...body,
455
+ const emitAll = (events) => {
456
+ for (const event of events)
457
+ emit(sseEvent(event));
458
+ };
459
+ result = await start({
399
460
  stream: true,
400
461
  signal,
401
- onToken: (token) => {
402
- if (token)
403
- emit(sseEvent(createTextDeltaEvent(textId, token)));
404
- },
462
+ onToken: (token) => emitAll(parts.delta('text', token)),
463
+ onReasoning: (delta) => emitAll(parts.delta('reasoning', delta)),
405
464
  onToolCall: (call) => {
465
+ emitAll(parts.end('reasoning'));
406
466
  emit(sseEvent(createToolCallStartEvent(call.id, call.name)));
407
467
  emit(sseEvent(createToolCallDeltaEvent(call.id, JSON.stringify(call.arguments))));
408
468
  emit(sseEvent(createToolCallEndEvent(call.id)));
@@ -410,19 +470,22 @@ async function* agentEvents(deps, agent, body, auth, signal) {
410
470
  onToolResult: (toolResult) => {
411
471
  emit(sseEvent(createToolResultEvent(generateId('res'), toolResult.callId, toolResult.result)));
412
472
  },
413
- }, auth);
473
+ });
414
474
  });
415
475
  }
416
476
  catch (error) {
417
477
  if (signal.aborted)
418
478
  return;
419
- yield sseEvent(createTextEndEvent(textId));
479
+ yield* parts.end().map(sseEvent);
420
480
  yield* failure(error);
421
481
  return;
422
482
  }
423
483
  if (signal.aborted || !result)
424
484
  return;
425
- yield sseEvent(createTextEndEvent(textId));
485
+ yield* parts.end().map(sseEvent);
486
+ if (result.status === 'paused') {
487
+ yield sseEvent(createApprovalRequiredEvent(result.threadId, toPendingApprovals(result.pendingApprovals ?? [])));
488
+ }
426
489
  yield sseEvent(createFinishEvent(messageId, {
427
490
  inputTokens: result.usage.inputTokens,
428
491
  outputTokens: result.usage.outputTokens,