@cogitator-ai/tetsu 0.4.11 → 0.5.1
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 +58 -44
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -3
- package/dist/auth.js.map +1 -1
- package/dist/connection.d.ts +13 -0
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +15 -1
- package/dist/connection.js.map +1 -1
- package/dist/controller.d.ts +120 -71
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +62 -65
- package/dist/controller.js.map +1 -1
- package/dist/operations.d.ts +9 -12
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +22 -50
- package/dist/operations.js.map +1 -1
- package/dist/schemas.d.ts +82 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +94 -19
- package/dist/schemas.js.map +1 -1
- package/dist/socket.js +5 -5
- package/dist/socket.js.map +1 -1
- package/dist/streaming.d.ts +0 -14
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +0 -44
- package/dist/streaming.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -52,17 +52,19 @@ A run that waits on a slow tool or model can stay silent for longer than the `id
|
|
|
52
52
|
|
|
53
53
|
A Tetsu controller named `Cogitator`: `operationId`s in the OpenAPI document are `cogitatorRunAgent`, `cogitatorGetThread` and so on.
|
|
54
54
|
|
|
55
|
-
| Option
|
|
56
|
-
|
|
|
57
|
-
| `cogitator`
|
|
58
|
-
| `agents`
|
|
59
|
-
| `workflows`
|
|
60
|
-
| `swarms`
|
|
61
|
-
| `auth`
|
|
62
|
-
| `authorizeThread`
|
|
63
|
-
| `websocket`
|
|
64
|
-
| `until`
|
|
65
|
-
| `sseHeartbeatMs`
|
|
55
|
+
| Option | Type | Description |
|
|
56
|
+
| -------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `cogitator` | `Cogitator` | **Required.** The runtime |
|
|
58
|
+
| `agents` | `Record<string, Agent>` | Agents by name |
|
|
59
|
+
| `workflows` | `Record<string, Workflow>` | Workflows by name |
|
|
60
|
+
| `swarms` | `Record<string, SwarmConfig>` | Swarms by name |
|
|
61
|
+
| `auth` | `(ctx) => AuthContext \| undefined` or hook | Establishes the caller, see [Authentication](#authentication) |
|
|
62
|
+
| `authorizeThread` | `(auth, threadId) => boolean` | Decides who may use a memory thread, see [Users and threads](#users-and-threads) |
|
|
63
|
+
| `websocket` | `boolean \| { path?: string }` | Serve the WebSocket endpoint (default path `/ws`) |
|
|
64
|
+
| `until` | `AbortSignal \| (() => AbortSignal)` | Ends open streams and sockets, such as `draining` from `@tetsujs/lifecycle` |
|
|
65
|
+
| `sseHeartbeatMs` | `number` | Heartbeat comment interval of SSE streams (default `5000`, `0` turns it off) |
|
|
66
|
+
| `acceptContext` | `boolean \| string[]` | Keys of a run `context` clients may send: none by default, a list, or `true` for any (it goes into the system prompt) |
|
|
67
|
+
| `threadMessageRoles` | `ThreadMessageRole[]` | Roles `POST /threads/:id/messages` accepts (default `user` and `assistant`) |
|
|
66
68
|
|
|
67
69
|
## Endpoints
|
|
68
70
|
|
|
@@ -91,7 +93,7 @@ Request bodies:
|
|
|
91
93
|
|
|
92
94
|
```typescript
|
|
93
95
|
// POST /agents/:name/run, /agents/:name/stream
|
|
94
|
-
{ input: string; context?: Record<string, unknown>; threadId?: string } // input must contain more than whitespace
|
|
96
|
+
{ input: string; context?: Record<string, unknown>; threadId?: string } // input and threadId must contain more than whitespace
|
|
95
97
|
|
|
96
98
|
// POST /agents/:name/resume, /agents/:name/resume/stream
|
|
97
99
|
{
|
|
@@ -101,15 +103,19 @@ Request bodies:
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
// POST /swarms/:name/run, /swarms/:name/stream
|
|
104
|
-
{ input: string; context?: Record<string, unknown>; threadId?: string; timeout?: number }
|
|
106
|
+
{ input: string; context?: Record<string, unknown>; threadId?: string; timeout?: number } // timeout at most 2147483647 ms
|
|
105
107
|
|
|
106
108
|
// POST /workflows/:name/run, /workflows/:name/stream
|
|
107
|
-
{ input?: Record<string, unknown>; options?: { maxConcurrency?: number; maxIterations?: number; checkpoint?:
|
|
109
|
+
{ input?: Record<string, unknown>; options?: { maxConcurrency?: number; maxIterations?: number; checkpoint?: false } }
|
|
108
110
|
|
|
109
111
|
// POST /threads/:id/messages
|
|
110
|
-
{ role: 'user' | 'assistant' | 'system'; content: string; metadata?: Record<string, unknown> }
|
|
112
|
+
{ role: 'user' | 'assistant' | 'system'; content: string; metadata?: Record<string, unknown> } // system only with threadMessageRoles
|
|
111
113
|
```
|
|
112
114
|
|
|
115
|
+
The schemas are refined with the validators every Cogitator adapter shares (`@cogitator-ai/server-shared`), so Tetsu refuses the same bodies as Express, Fastify, Hono, Koa and Next, under its own `VALIDATION_FAILED` envelope. A run puts `context` into the system prompt and the model reads a `system` thread message as operator instructions, so both are refused unless the controller accepts them: `acceptContext` lists the `context` keys clients may send (or `true` for any), and `threadMessageRoles` the roles they may add (`user` and `assistant` by default). A body that is not JSON (`text/plain`, a form) is refused with `415 UNSUPPORTED_MEDIA_TYPE` before it is read, since browsers send those across origins without a CORS preflight. `options.checkpoint: true` is refused: the controller runs each workflow on a fresh executor and keeps no checkpoint store to resume from.
|
|
116
|
+
|
|
117
|
+
`POST /agents/:name/run` and `/resume` answer `{ output, threadId, usage, toolCalls, status, traceId }` plus, when they apply, `reasoning`, `pendingApprovals`, `structured`, `structuredError`, `truncated`, `blocked` and `iterationLimitReached`: the same object as every other adapter, built by `toAgentRunResponse()` of `@cogitator-ai/server-shared`. It never carries the system prompt, the history, trace spans or the checkpoint of a paused run. Swarm routes close the swarm they build once the run ends, so a distributed swarm leaves no Redis connections behind, and `GET /swarms` lists every agent, a router and pipeline stages included.
|
|
118
|
+
|
|
113
119
|
## Errors
|
|
114
120
|
|
|
115
121
|
Every error is answered in Tetsu's envelope:
|
|
@@ -118,20 +124,23 @@ Every error is answered in Tetsu's envelope:
|
|
|
118
124
|
{ "status": 404, "message": "Agent 'ghost' not found", "error": "AGENT_NOT_FOUND" }
|
|
119
125
|
```
|
|
120
126
|
|
|
121
|
-
| Status | `error` | When
|
|
122
|
-
| ------- | ---------------------------------------------------------- |
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
| 403 | `
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
| 409 | `
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
127
|
+
| Status | `error` | When |
|
|
128
|
+
| ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
129
|
+
| 400 | `MALFORMED_JSON` | The body is not valid JSON |
|
|
130
|
+
| 401 | `UNAUTHORIZED` | `auth` returned `undefined`, or threw something other than an `HttpError` (an expired token) |
|
|
131
|
+
| 403 | `THREAD_ACCESS_DENIED` | The thread belongs to another user |
|
|
132
|
+
| 403 | `THREAD_FORBIDDEN` | `authorizeThread` refused the thread |
|
|
133
|
+
| 404 | `AGENT_NOT_FOUND`, `WORKFLOW_NOT_FOUND`, `SWARM_NOT_FOUND` | No such name |
|
|
134
|
+
| 409 | `BLACKBOARD_DISABLED` | The swarm has no blackboard |
|
|
135
|
+
| 409 | `RUN_NOT_PAUSED` | A resume named a thread without a paused run |
|
|
136
|
+
| 413 | `BODY_TOO_LARGE` | The body is over the application's body limit |
|
|
137
|
+
| 415 | `UNSUPPORTED_MEDIA_TYPE` | The body is not JSON (`text/plain`, a form) |
|
|
138
|
+
| 499 | `CLIENT_CLOSED_REQUEST` | The client left before a `/run` or `/resume` answer was ready |
|
|
139
|
+
| 422 | `VALIDATION_FAILED` | The body or the path failed its schema, a `context` key or a thread message role the controller does not accept, `issues` lists every problem |
|
|
140
|
+
| 501 | `PACKAGE_NOT_INSTALLED` | `@cogitator-ai/workflows` or `@cogitator-ai/swarms` is missing |
|
|
141
|
+
| 503 | `MEMORY_NOT_CONFIGURED` | A thread endpoint was called on a runtime with no `memory` config |
|
|
142
|
+
| 4xx/5xx | the `CogitatorError` code | The run failed, e.g. `429 LLM_RATE_LIMITED` with `Retry-After` |
|
|
143
|
+
| 500 | `INTERNAL_SERVER_ERROR` | Anything else, the detail goes to `reportError`, never to the client |
|
|
135
144
|
|
|
136
145
|
Only a `CogitatorError` keeps its message and code. Any other error reaches the client only as a generic internal error: `500 INTERNAL_SERVER_ERROR` in JSON, and `Internal server error` in stream `error` events, WebSocket errors and the `error` field of workflow `node_error` and swarm `agent_error` events. Its text, which can carry connection strings or file paths, never leaves the server.
|
|
137
146
|
|
|
@@ -145,7 +154,7 @@ createApp({ hooks: { onError: [cogitatorErrors()] }, routes });
|
|
|
145
154
|
|
|
146
155
|
## Authentication
|
|
147
156
|
|
|
148
|
-
`auth` runs as a `beforeParse` hook, so a refused request costs no body parsing. It receives the Tetsu context and returns the caller, or `undefined` to answer `401`. Throw an `HttpError` to answer with another status. `/health` and `/ready` stay open for probes.
|
|
157
|
+
`auth` runs as a `beforeParse` hook, so a refused request costs no body parsing. It receives the Tetsu context and returns the caller, or `undefined` to answer `401`. Any other error it throws or rejects with, such as a token that fails to verify, answers `401` too, never a `500`. Throw an `HttpError` to answer with another status. `/health` and `/ready` stay open for probes.
|
|
149
158
|
|
|
150
159
|
```typescript
|
|
151
160
|
cogitatorController({
|
|
@@ -257,23 +266,28 @@ Approved calls run, declined ones answer the model with the `reason`, and calls
|
|
|
257
266
|
`/stream` endpoints answer with `text/event-stream` through `@tetsujs/sse`: keep-alive comments every `sseHeartbeatMs` (5 seconds by default, under the 10 second `idleTimeout` of `Bun.serve`), 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]`:
|
|
258
267
|
|
|
259
268
|
```
|
|
260
|
-
data: {"type":"start","messageId":"msg_…"}
|
|
261
|
-
data: {"type":"text-start","id":"
|
|
262
|
-
data: {"type":"text-delta","id":"
|
|
269
|
+
data: {"type":"start","messageId":"msg_…","threadId":"thread_…"}
|
|
270
|
+
data: {"type":"text-start","id":"txt_1"}
|
|
271
|
+
data: {"type":"text-delta","id":"txt_1","delta":"Let me check."}
|
|
272
|
+
data: {"type":"text-end","id":"txt_1"}
|
|
263
273
|
data: {"type":"tool-call-start","id":"call_1","toolName":"get_weather"}
|
|
264
274
|
data: {"type":"tool-call-delta","id":"call_1","argsTextDelta":"{\"city\":\"Paris\"}"}
|
|
265
275
|
data: {"type":"tool-call-end","id":"call_1"}
|
|
266
276
|
data: {"type":"tool-result","id":"res_…","toolCallId":"call_1","result":"Sunny"}
|
|
267
|
-
data: {"type":"text-
|
|
268
|
-
data: {"type":"
|
|
277
|
+
data: {"type":"text-start","id":"txt_2"}
|
|
278
|
+
data: {"type":"text-delta","id":"txt_2","delta":"Sunny in Paris."}
|
|
279
|
+
data: {"type":"text-end","id":"txt_2"}
|
|
280
|
+
data: {"type":"finish","messageId":"msg_…","usage":{"inputTokens":12,"outputTokens":30,"totalTokens":42},"threadId":"thread_…","status":"completed"}
|
|
269
281
|
data: [DONE]
|
|
270
282
|
```
|
|
271
283
|
|
|
272
|
-
|
|
284
|
+
`start` names the run's thread: the `threadId` of the request, or the thread the controller opened for it, so a client that sent none continues the conversation with this one even if the stream breaks. `finish` repeats it with `status` and the other outcome fields of the JSON answer.
|
|
285
|
+
|
|
286
|
+
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 and text after a tool call starts a new part. An answer the model gave in one piece arrives as one text part before `finish`, and a resumed run that pauses again sends `approval-required` before `finish`. `POST /agents/:name/run` returns the summary as `reasoning`, and `usage` gains `reasoningTokens`, `cachedInputTokens` and `cacheWriteTokens` when the provider reports them. The `finish` event of an agent stream carries the same `usage` as the JSON answer, these counts included.
|
|
273
287
|
|
|
274
|
-
A run that fails ends with `{"type":"error","message":"…","code":"…"}` instead of `finish`. A `CogitatorError` keeps its message and code
|
|
288
|
+
A run that fails ends with `{"type":"error","message":"…","code":"…"}` instead of `finish`. A `CogitatorError` keeps its message and code, anything else is sent as `"message":"Internal server error","code":"INTERNAL_SERVER_ERROR"` and 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`, every event the swarm itself emits under its own name, such as `swarm:start` or `agent:message`, and `swarm_completed`).
|
|
275
289
|
|
|
276
|
-
Validation, `401`, `
|
|
290
|
+
Validation (`415`, `422`), `401`, `404` and the `403 THREAD_FORBIDDEN` of `authorizeThread` are answered as JSON before the stream opens. Whether the thread belongs to the caller is checked by the run itself, so another user's thread ends an open stream with an `error` event, `"code":"THREAD_ACCESS_DENIED"`, instead of `finish`.
|
|
277
291
|
|
|
278
292
|
## WebSocket
|
|
279
293
|
|
|
@@ -283,12 +297,12 @@ cogitatorController({ cogitator, agents, auth, websocket: true });
|
|
|
283
297
|
|
|
284
298
|
The handshake runs `auth` like any route. Each socket runs one agent, workflow or swarm at a time; a `run` or `resume` sent while one is in progress gets an error with `code: 'RUN_IN_PROGRESS'`.
|
|
285
299
|
|
|
286
|
-
| Client sends | Server answers
|
|
287
|
-
| ------------------------------------------------------------------------------------------------------------- |
|
|
288
|
-
| `{ 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` |
|
|
289
|
-
| `{ type: 'resume', id?, payload: { name, threadId, decisions?, defaultDecision? } }` | the same frames for an agent run paused for [approvals](#approvals)
|
|
290
|
-
| `{ type: 'stop' }` | cancels the current run
|
|
291
|
-
| `{ type: 'ping', id? }` | `{ type: 'pong', id }`
|
|
300
|
+
| Client sends | Server answers |
|
|
301
|
+
| ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
302
|
+
| `{ 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 (for an agent, the JSON answer of `/run`), or `cancelled` |
|
|
303
|
+
| `{ type: 'resume', id?, payload: { name, threadId, decisions?, defaultDecision? } }` | the same frames for an agent run paused for [approvals](#approvals) |
|
|
304
|
+
| `{ type: 'stop' }` | cancels the current run |
|
|
305
|
+
| `{ type: 'ping', id? }` | `{ type: 'pong', id }` |
|
|
292
306
|
|
|
293
307
|
Errors arrive as `{ type: 'error', id, error, code }` and leave the socket open; an invalid frame gets `code: 'INVALID_MESSAGE'`, and an error that is not a `CogitatorError` is sent as `Internal server error` with `code: 'INTERNAL_SERVER_ERROR'`. Closing the socket cancels its run. Bun's server-level options such as `maxPayloadLength` are set where the app is served:
|
|
294
308
|
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3E,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,WAAW,GAAG,SAAS,CAAC;CACxC;AASD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS;;;;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3E,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,WAAW,GAAG,SAAS,CAAC;CACxC;AASD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS;;;;wCAehE;AAWD,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,UAAU,CAErE"}
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hook, httpError } from '@tetsujs/core';
|
|
1
|
+
import { hook, HttpError, httpError } from '@tetsujs/core';
|
|
2
2
|
const ANONYMOUS = { cogitatorAuth: undefined };
|
|
3
3
|
function admit(auth) {
|
|
4
4
|
if (!auth)
|
|
@@ -15,10 +15,28 @@ export function callerHook(authenticate) {
|
|
|
15
15
|
return hook.beforeParse((ctx) => {
|
|
16
16
|
if (!authenticate)
|
|
17
17
|
return { ...ANONYMOUS };
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let auth;
|
|
19
|
+
try {
|
|
20
|
+
auth = authenticate(ctx);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw refusal(error);
|
|
24
|
+
}
|
|
25
|
+
return auth instanceof Promise
|
|
26
|
+
? auth.then(admit, (error) => {
|
|
27
|
+
throw refusal(error);
|
|
28
|
+
})
|
|
29
|
+
: admit(auth);
|
|
20
30
|
});
|
|
21
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* What an `authenticate` function that failed answers: an `HttpError` it threw on purpose as
|
|
34
|
+
* it is, anything else (a token that does not verify, an expired session) as `401`, since the
|
|
35
|
+
* caller could not be established. It is not a fault of the server.
|
|
36
|
+
*/
|
|
37
|
+
function refusal(error) {
|
|
38
|
+
return error instanceof HttpError ? error : httpError(401, 'UNAUTHORIZED', 'Unauthorized');
|
|
39
|
+
}
|
|
22
40
|
/**
|
|
23
41
|
* The caller hook for `auth`: built from a function, or taken as it is when it is
|
|
24
42
|
* already a hook — a `callerHook()` wrapped in `secured()` from `@tetsujs/openapi`,
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAQ3D,MAAM,SAAS,GAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AAE7D,SAAS,KAAK,CAAC,IAA6B;IAC1C,IAAI,CAAC,IAAI;QAAE,MAAM,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;IAChE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,YAAsC;IAC/D,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAwC,EAAE;QACpE,IAAI,CAAC,YAAY;YAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC;QAC3C,IAAI,IAA8B,CAAC;QACnC,IAAI,CAAC;YACH,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,IAAI,YAAY,OAAO;YAC5B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAc,EAAE,EAAE;gBAClC,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;YACvB,CAAC,CAAC;YACJ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;AAC7F,CAAC;AAKD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA2B;IACvD,OAAO,OAAO,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpF,CAAC"}
|
package/dist/connection.d.ts
CHANGED
|
@@ -9,4 +9,17 @@
|
|
|
9
9
|
* hook: their heartbeats keep the connection busy.
|
|
10
10
|
*/
|
|
11
11
|
export declare const holdConnection: import("@tetsujs/core").Hook<"beforeHandle", import("@tetsujs/core").BaseCtx, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* The `beforeParse` hook of every route with a JSON body: a body that is not JSON is refused
|
|
14
|
+
* with `415 UNSUPPORTED_MEDIA_TYPE` before it is read.
|
|
15
|
+
*
|
|
16
|
+
* Tetsu parses a body as the route declares it, whatever the `content-type` says. Browsers
|
|
17
|
+
* send `text/plain` and form bodies across origins without a CORS preflight, so without this
|
|
18
|
+
* hook any web page could start a run on a server without auth, or with cookies.
|
|
19
|
+
*/
|
|
20
|
+
export declare const jsonBody: import("@tetsujs/core").Hook<"beforeParse", import("@tetsujs/core").BaseCtx & {
|
|
21
|
+
readonly params: {
|
|
22
|
+
[x: string]: string;
|
|
23
|
+
};
|
|
24
|
+
}, unknown>;
|
|
12
25
|
//# sourceMappingURL=connection.d.ts.map
|
package/dist/connection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,wFAEzB,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;WAGnB,CAAC"}
|
package/dist/connection.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { hook } from '@tetsujs/core';
|
|
1
|
+
import { hook, httpError } from '@tetsujs/core';
|
|
2
|
+
import { refuseNonJsonBody } from '@cogitator-ai/server-shared';
|
|
2
3
|
/**
|
|
3
4
|
* The `beforeHandle` hook of the routes that answer a run with JSON: lifts the idle
|
|
4
5
|
* timeout of `Bun.serve` for this one request.
|
|
@@ -12,4 +13,17 @@ import { hook } from '@tetsujs/core';
|
|
|
12
13
|
export const holdConnection = hook.beforeHandle((ctx) => {
|
|
13
14
|
ctx.server.timeout(ctx.req, 0);
|
|
14
15
|
});
|
|
16
|
+
/**
|
|
17
|
+
* The `beforeParse` hook of every route with a JSON body: a body that is not JSON is refused
|
|
18
|
+
* with `415 UNSUPPORTED_MEDIA_TYPE` before it is read.
|
|
19
|
+
*
|
|
20
|
+
* Tetsu parses a body as the route declares it, whatever the `content-type` says. Browsers
|
|
21
|
+
* send `text/plain` and form bodies across origins without a CORS preflight, so without this
|
|
22
|
+
* hook any web page could start a run on a server without auth, or with cookies.
|
|
23
|
+
*/
|
|
24
|
+
export const jsonBody = hook.beforeParse((ctx) => {
|
|
25
|
+
const refusal = refuseNonJsonBody(ctx.req.method, ctx.req.headers);
|
|
26
|
+
if (refusal)
|
|
27
|
+
throw httpError(refusal.status, refusal.code, refusal.message);
|
|
28
|
+
});
|
|
15
29
|
//# sourceMappingURL=connection.js.map
|
package/dist/connection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE;IACtD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,EAAE;IAC/C,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnE,IAAI,OAAO;QAAE,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
|