@dexto/server 1.6.20 → 1.6.22

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 (133) hide show
  1. package/dist/a2a/jsonrpc/methods.cjs +1 -1
  2. package/dist/a2a/jsonrpc/methods.d.ts +14 -4
  3. package/dist/a2a/jsonrpc/methods.d.ts.map +1 -1
  4. package/dist/a2a/jsonrpc/methods.js +1 -1
  5. package/dist/approval/wire-approval-events.cjs +44 -0
  6. package/dist/approval/wire-approval-events.d.ts +4 -0
  7. package/dist/approval/wire-approval-events.d.ts.map +1 -0
  8. package/dist/approval/wire-approval-events.js +20 -0
  9. package/dist/events/session-sse-subscriber.cjs +167 -0
  10. package/dist/events/session-sse-subscriber.d.ts +13 -0
  11. package/dist/events/session-sse-subscriber.d.ts.map +1 -0
  12. package/dist/events/session-sse-subscriber.js +143 -0
  13. package/dist/hono/__tests__/test-fixtures.cjs +8 -0
  14. package/dist/hono/__tests__/test-fixtures.d.ts +1 -0
  15. package/dist/hono/__tests__/test-fixtures.d.ts.map +1 -1
  16. package/dist/hono/__tests__/test-fixtures.js +8 -0
  17. package/dist/hono/index.cjs +40 -8
  18. package/dist/hono/index.d.ts +45 -4531
  19. package/dist/hono/index.d.ts.map +1 -1
  20. package/dist/hono/index.js +43 -9
  21. package/dist/hono/node/index.cjs +51 -6
  22. package/dist/hono/node/index.d.ts.map +1 -1
  23. package/dist/hono/node/index.js +51 -6
  24. package/dist/hono/routes/a2a-jsonrpc.d.ts.map +1 -1
  25. package/dist/hono/routes/a2a-tasks.cjs +158 -32
  26. package/dist/hono/routes/a2a-tasks.d.ts +1 -502
  27. package/dist/hono/routes/a2a-tasks.d.ts.map +1 -1
  28. package/dist/hono/routes/a2a-tasks.js +162 -32
  29. package/dist/hono/routes/a2a.d.ts.map +1 -1
  30. package/dist/hono/routes/agents.cjs +410 -329
  31. package/dist/hono/routes/agents.d.ts +16043 -68
  32. package/dist/hono/routes/agents.d.ts.map +1 -1
  33. package/dist/hono/routes/agents.js +418 -330
  34. package/dist/hono/routes/approvals.cjs +102 -88
  35. package/dist/hono/routes/approvals.d.ts +2089 -142
  36. package/dist/hono/routes/approvals.d.ts.map +1 -1
  37. package/dist/hono/routes/approvals.js +108 -89
  38. package/dist/hono/routes/dexto-auth.cjs +40 -33
  39. package/dist/hono/routes/dexto-auth.d.ts +401 -2
  40. package/dist/hono/routes/dexto-auth.d.ts.map +1 -1
  41. package/dist/hono/routes/dexto-auth.js +40 -33
  42. package/dist/hono/routes/discovery.cjs +16 -14
  43. package/dist/hono/routes/discovery.d.ts +586 -1
  44. package/dist/hono/routes/discovery.d.ts.map +1 -1
  45. package/dist/hono/routes/discovery.js +16 -14
  46. package/dist/hono/routes/greeting.cjs +26 -22
  47. package/dist/hono/routes/greeting.d.ts +787 -3
  48. package/dist/hono/routes/greeting.d.ts.map +1 -1
  49. package/dist/hono/routes/greeting.js +26 -22
  50. package/dist/hono/routes/health.d.ts +1 -1
  51. package/dist/hono/routes/key.cjs +60 -52
  52. package/dist/hono/routes/key.d.ts +1597 -1
  53. package/dist/hono/routes/key.d.ts.map +1 -1
  54. package/dist/hono/routes/key.js +60 -52
  55. package/dist/hono/routes/llm.cjs +382 -349
  56. package/dist/hono/routes/llm.d.ts +12148 -98
  57. package/dist/hono/routes/llm.d.ts.map +1 -1
  58. package/dist/hono/routes/llm.js +386 -349
  59. package/dist/hono/routes/mcp.cjs +257 -226
  60. package/dist/hono/routes/mcp.d.ts +6605 -309
  61. package/dist/hono/routes/mcp.d.ts.map +1 -1
  62. package/dist/hono/routes/mcp.js +263 -225
  63. package/dist/hono/routes/memory.cjs +102 -89
  64. package/dist/hono/routes/memory.d.ts +5368 -4
  65. package/dist/hono/routes/memory.d.ts.map +1 -1
  66. package/dist/hono/routes/memory.js +108 -90
  67. package/dist/hono/routes/messages.cjs +189 -191
  68. package/dist/hono/routes/messages.d.ts +3900 -12
  69. package/dist/hono/routes/messages.d.ts.map +1 -1
  70. package/dist/hono/routes/messages.js +192 -191
  71. package/dist/hono/routes/models.cjs +106 -64
  72. package/dist/hono/routes/models.d.ts +2875 -2
  73. package/dist/hono/routes/models.d.ts.map +1 -1
  74. package/dist/hono/routes/models.js +108 -64
  75. package/dist/hono/routes/openrouter.cjs +79 -65
  76. package/dist/hono/routes/openrouter.d.ts +854 -1
  77. package/dist/hono/routes/openrouter.d.ts.map +1 -1
  78. package/dist/hono/routes/openrouter.js +79 -65
  79. package/dist/hono/routes/prompts.cjs +136 -109
  80. package/dist/hono/routes/prompts.d.ts +2818 -10
  81. package/dist/hono/routes/prompts.d.ts.map +1 -1
  82. package/dist/hono/routes/prompts.js +138 -109
  83. package/dist/hono/routes/queue.cjs +133 -120
  84. package/dist/hono/routes/queue.d.ts +5240 -11
  85. package/dist/hono/routes/queue.d.ts.map +1 -1
  86. package/dist/hono/routes/queue.js +136 -120
  87. package/dist/hono/routes/resources.cjs +65 -46
  88. package/dist/hono/routes/resources.d.ts +1983 -5
  89. package/dist/hono/routes/resources.d.ts.map +1 -1
  90. package/dist/hono/routes/resources.js +72 -47
  91. package/dist/hono/routes/schedules.cjs +233 -226
  92. package/dist/hono/routes/schedules.d.ts +4198 -22
  93. package/dist/hono/routes/schedules.d.ts.map +1 -1
  94. package/dist/hono/routes/schedules.js +233 -226
  95. package/dist/hono/routes/search.cjs +34 -30
  96. package/dist/hono/routes/search.d.ts +3094 -17
  97. package/dist/hono/routes/search.d.ts.map +1 -1
  98. package/dist/hono/routes/search.js +40 -31
  99. package/dist/hono/routes/sessions.cjs +491 -393
  100. package/dist/hono/routes/sessions.d.ts +18263 -65
  101. package/dist/hono/routes/sessions.d.ts.map +1 -1
  102. package/dist/hono/routes/sessions.js +497 -395
  103. package/dist/hono/routes/static.d.ts.map +1 -1
  104. package/dist/hono/routes/system-prompt.cjs +57 -61
  105. package/dist/hono/routes/system-prompt.d.ts +1228 -2
  106. package/dist/hono/routes/system-prompt.d.ts.map +1 -1
  107. package/dist/hono/routes/system-prompt.js +58 -62
  108. package/dist/hono/routes/tools.cjs +29 -34
  109. package/dist/hono/routes/tools.d.ts +1755 -6
  110. package/dist/hono/routes/tools.d.ts.map +1 -1
  111. package/dist/hono/routes/tools.js +33 -33
  112. package/dist/hono/routes/webhooks.cjs +115 -123
  113. package/dist/hono/routes/webhooks.d.ts +2501 -11
  114. package/dist/hono/routes/webhooks.d.ts.map +1 -1
  115. package/dist/hono/routes/webhooks.js +120 -124
  116. package/dist/hono/routes/workspaces.cjs +84 -79
  117. package/dist/hono/routes/workspaces.d.ts +2093 -2
  118. package/dist/hono/routes/workspaces.d.ts.map +1 -1
  119. package/dist/hono/routes/workspaces.js +89 -80
  120. package/dist/hono/schemas/responses.cjs +463 -260
  121. package/dist/hono/schemas/responses.d.ts +1893 -209
  122. package/dist/hono/schemas/responses.d.ts.map +1 -1
  123. package/dist/hono/schemas/responses.js +203 -14
  124. package/dist/hono/start-server.cjs +9 -0
  125. package/dist/hono/start-server.d.ts.map +1 -1
  126. package/dist/hono/start-server.js +9 -0
  127. package/dist/hono/types.d.ts +11 -0
  128. package/dist/hono/types.d.ts.map +1 -1
  129. package/dist/index.cjs +5 -1
  130. package/dist/index.d.ts +2 -0
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +2 -0
  133. package/package.json +7 -7
@@ -6,11 +6,15 @@ import {
6
6
  zodToIssues
7
7
  } from "@dexto/core";
8
8
  import {
9
+ BadRequestErrorResponse,
10
+ ConflictErrorResponse,
11
+ InternalErrorResponse,
9
12
  SessionMetadataSchema,
10
13
  InternalMessageSchema,
14
+ NotFoundErrorResponse,
11
15
  ScopedUsageSummarySchema,
12
- StandardErrorEnvelopeSchema,
13
- UsageSummarySchema
16
+ UsageSummarySchema,
17
+ toApiInternalMessage
14
18
  } from "../schemas/responses.js";
15
19
  import { handleHonoError } from "../middleware/error.js";
16
20
  const CreateSessionSchema = z.object({
@@ -56,423 +60,456 @@ function mapSessionMetadata(sessionId, metadata, defaults) {
56
60
  parentSessionId: metadata?.parentSessionId ?? defaults?.parentSessionId ?? null
57
61
  };
58
62
  }
59
- function createSessionsRouter(getAgent) {
60
- const app = new OpenAPIHono({
61
- defaultHook: (result, ctx) => {
62
- if (!result.success) {
63
- const issues = zodToIssues(result.error);
64
- return handleHonoError(
65
- ctx,
66
- new DextoRuntimeError(
67
- "validation_failed",
68
- "validation",
69
- ErrorType.USER,
70
- issues[0]?.message ?? "Validation failed",
71
- { issues }
72
- )
73
- );
74
- }
75
- }
76
- });
77
- app.onError((err, ctx) => handleHonoError(ctx, err));
78
- const listRoute = createRoute({
79
- method: "get",
80
- path: "/sessions",
81
- summary: "List Sessions",
82
- description: "Retrieves a list of all active sessions",
83
- tags: ["sessions"],
84
- responses: {
85
- 200: {
86
- description: "List of all active sessions",
87
- content: {
88
- "application/json": {
89
- schema: z.object({
90
- sessions: z.array(SessionMetadataSchema).describe("Array of session metadata objects")
91
- }).strict()
92
- }
63
+ const listRoute = createRoute({
64
+ method: "get",
65
+ path: "/sessions",
66
+ summary: "List Sessions",
67
+ description: "Retrieves a list of all active sessions",
68
+ tags: ["sessions"],
69
+ responses: {
70
+ 200: {
71
+ description: "List of all active sessions",
72
+ content: {
73
+ "application/json": {
74
+ schema: z.object({
75
+ sessions: z.array(SessionMetadataSchema).describe("Array of session metadata objects")
76
+ }).strict()
93
77
  }
94
78
  }
95
- }
96
- });
97
- const createRouteDef = createRoute({
98
- method: "post",
99
- path: "/sessions",
100
- summary: "Create Session",
101
- description: "Creates a new session",
102
- tags: ["sessions"],
103
- request: { body: { content: { "application/json": { schema: CreateSessionSchema } } } },
104
- responses: {
105
- 201: {
106
- description: "Session created successfully",
107
- content: {
108
- "application/json": {
109
- schema: z.object({
110
- session: SessionMetadataSchema.describe(
111
- "Newly created session metadata"
112
- )
113
- }).strict()
114
- }
79
+ },
80
+ 400: BadRequestErrorResponse,
81
+ 500: InternalErrorResponse
82
+ }
83
+ });
84
+ const createRouteDef = createRoute({
85
+ method: "post",
86
+ path: "/sessions",
87
+ summary: "Create Session",
88
+ description: "Creates a new session",
89
+ tags: ["sessions"],
90
+ request: { body: { content: { "application/json": { schema: CreateSessionSchema } } } },
91
+ responses: {
92
+ 201: {
93
+ description: "Session created successfully",
94
+ content: {
95
+ "application/json": {
96
+ schema: z.object({
97
+ session: SessionMetadataSchema.describe(
98
+ "Newly created session metadata"
99
+ )
100
+ }).strict()
115
101
  }
116
102
  }
117
- }
118
- });
119
- const getRoute = createRoute({
120
- method: "get",
121
- path: "/sessions/{sessionId}",
122
- summary: "Get Session Details",
123
- description: "Fetches details for a specific session",
124
- tags: ["sessions"],
125
- request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
126
- responses: {
127
- 200: {
128
- description: "Session details with metadata",
129
- content: {
130
- "application/json": {
131
- schema: z.object({
132
- session: SessionMetadataSchema.extend({
133
- history: z.number().int().nonnegative().describe("Number of messages in history")
134
- }).strict().describe("Session metadata with history count")
135
- }).strict()
136
- }
103
+ },
104
+ 400: BadRequestErrorResponse,
105
+ 500: InternalErrorResponse
106
+ }
107
+ });
108
+ const getRoute = createRoute({
109
+ method: "get",
110
+ path: "/sessions/{sessionId}",
111
+ summary: "Get Session Details",
112
+ description: "Fetches details for a specific session",
113
+ tags: ["sessions"],
114
+ request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
115
+ responses: {
116
+ 200: {
117
+ description: "Session details with metadata",
118
+ content: {
119
+ "application/json": {
120
+ schema: z.object({
121
+ session: SessionMetadataSchema.extend({
122
+ history: z.number().int().nonnegative().describe("Number of messages in history")
123
+ }).strict().describe("Session metadata with history count")
124
+ }).strict()
137
125
  }
138
126
  }
139
- }
140
- });
141
- const forkRoute = createRoute({
142
- method: "post",
143
- path: "/sessions/{sessionId}/fork",
144
- summary: "Fork Session",
145
- description: "Creates a new child session by cloning the specified parent session history and metadata lineage.",
146
- tags: ["sessions"],
147
- request: {
148
- params: z.object({
149
- sessionId: z.string().describe("Parent session identifier")
150
- })
151
127
  },
152
- responses: {
153
- 201: {
154
- description: "Forked session created successfully",
155
- content: {
156
- "application/json": {
157
- schema: z.object({
158
- session: SessionMetadataSchema.describe(
159
- "Newly created child session metadata"
160
- )
161
- }).strict()
162
- }
163
- }
164
- },
165
- 400: {
166
- description: "Invalid fork request (for example, max session limit reached)",
167
- content: {
168
- "application/json": {
169
- schema: StandardErrorEnvelopeSchema
170
- }
171
- }
172
- },
173
- 404: {
174
- description: "Parent session not found",
175
- content: {
176
- "application/json": {
177
- schema: StandardErrorEnvelopeSchema
178
- }
128
+ 400: BadRequestErrorResponse,
129
+ 404: NotFoundErrorResponse,
130
+ 500: InternalErrorResponse
131
+ }
132
+ });
133
+ const forkRoute = createRoute({
134
+ method: "post",
135
+ path: "/sessions/{sessionId}/fork",
136
+ summary: "Fork Session",
137
+ description: "Creates a new child session by cloning the specified parent session history and metadata lineage.",
138
+ tags: ["sessions"],
139
+ request: {
140
+ params: z.object({
141
+ sessionId: z.string().describe("Parent session identifier")
142
+ })
143
+ },
144
+ responses: {
145
+ 201: {
146
+ description: "Forked session created successfully",
147
+ content: {
148
+ "application/json": {
149
+ schema: z.object({
150
+ session: SessionMetadataSchema.describe(
151
+ "Newly created child session metadata"
152
+ )
153
+ }).strict()
179
154
  }
180
155
  }
181
- }
182
- });
183
- const historyRoute = createRoute({
184
- method: "get",
185
- path: "/sessions/{sessionId}/history",
186
- summary: "Get Session History",
187
- description: "Retrieves the conversation history for a session along with processing status",
188
- tags: ["sessions"],
189
- request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
190
- responses: {
191
- 200: {
192
- description: "Session conversation history",
193
- content: {
194
- "application/json": {
195
- schema: z.object({
196
- history: z.array(InternalMessageSchema).describe("Array of messages in conversation history"),
197
- isBusy: z.boolean().describe(
198
- "Whether the session is currently processing a message"
199
- )
200
- }).strict()
201
- }
156
+ },
157
+ 400: {
158
+ ...BadRequestErrorResponse
159
+ },
160
+ 404: {
161
+ ...NotFoundErrorResponse
162
+ },
163
+ 500: InternalErrorResponse
164
+ }
165
+ });
166
+ const historyRoute = createRoute({
167
+ method: "get",
168
+ path: "/sessions/{sessionId}/history",
169
+ summary: "Get Session History",
170
+ description: "Retrieves the conversation history for a session along with processing status",
171
+ tags: ["sessions"],
172
+ request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
173
+ responses: {
174
+ 200: {
175
+ description: "Session conversation history",
176
+ content: {
177
+ "application/json": {
178
+ schema: z.object({
179
+ history: z.array(InternalMessageSchema).describe("Array of messages in conversation history"),
180
+ isBusy: z.boolean().describe("Whether the session is currently processing a message")
181
+ }).strict()
202
182
  }
203
183
  }
204
- }
205
- });
206
- const listSessionPromptContributorsRoute = createRoute({
207
- method: "get",
208
- path: "/sessions/{sessionId}/system-prompt/contributors",
209
- summary: "List Session System Prompt Contributors",
210
- description: "Lists static system prompt contributors that apply only to the specified session.",
211
- tags: ["sessions", "config"],
212
- request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
213
- responses: {
214
- 200: {
215
- description: "Current session contributor list",
216
- content: {
217
- "application/json": {
218
- schema: z.object({
219
- contributors: z.array(SessionPromptContributorInfoSchema).describe("Registered session prompt contributors.")
220
- }).strict()
221
- }
184
+ },
185
+ 400: BadRequestErrorResponse,
186
+ 404: NotFoundErrorResponse,
187
+ 500: InternalErrorResponse
188
+ }
189
+ });
190
+ const listSessionPromptContributorsRoute = createRoute({
191
+ method: "get",
192
+ path: "/sessions/{sessionId}/system-prompt/contributors",
193
+ summary: "List Session System Prompt Contributors",
194
+ description: "Lists static system prompt contributors that apply only to the specified session.",
195
+ tags: ["sessions", "config"],
196
+ request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
197
+ responses: {
198
+ 200: {
199
+ description: "Current session contributor list",
200
+ content: {
201
+ "application/json": {
202
+ schema: z.object({
203
+ contributors: z.array(SessionPromptContributorInfoSchema).describe("Registered session prompt contributors.")
204
+ }).strict()
222
205
  }
223
- },
224
- 404: {
225
- description: "Session not found",
226
- content: {
227
- "application/json": {
228
- schema: StandardErrorEnvelopeSchema
229
- }
206
+ }
207
+ },
208
+ 404: {
209
+ ...NotFoundErrorResponse
210
+ },
211
+ 500: InternalErrorResponse
212
+ }
213
+ });
214
+ const upsertSessionPromptContributorRoute = createRoute({
215
+ method: "post",
216
+ path: "/sessions/{sessionId}/system-prompt/contributors",
217
+ summary: "Upsert Session System Prompt Contributor",
218
+ description: "Adds or updates a static system prompt contributor that applies only to the specified session. Set enabled=false to remove it.",
219
+ tags: ["sessions", "config"],
220
+ request: {
221
+ params: z.object({ sessionId: z.string().describe("Session identifier") }),
222
+ body: {
223
+ required: true,
224
+ content: {
225
+ "application/json": {
226
+ schema: UpsertSessionPromptContributorSchema
230
227
  }
231
228
  }
232
229
  }
233
- });
234
- const upsertSessionPromptContributorRoute = createRoute({
235
- method: "post",
236
- path: "/sessions/{sessionId}/system-prompt/contributors",
237
- summary: "Upsert Session System Prompt Contributor",
238
- description: "Adds or updates a static system prompt contributor that applies only to the specified session. Set enabled=false to remove it.",
239
- tags: ["sessions", "config"],
240
- request: {
241
- params: z.object({ sessionId: z.string().describe("Session identifier") }),
242
- body: {
243
- required: true,
244
- content: {
245
- "application/json": {
246
- schema: UpsertSessionPromptContributorSchema
247
- }
230
+ },
231
+ responses: {
232
+ 200: {
233
+ description: "Session contributor upsert result",
234
+ content: {
235
+ "application/json": {
236
+ schema: z.object({
237
+ id: z.string().describe("Contributor identifier"),
238
+ enabled: z.boolean().describe("Whether the contributor remains enabled"),
239
+ priority: z.number().optional().describe("Contributor priority"),
240
+ replaced: z.boolean().optional().describe("Whether an existing contributor was replaced"),
241
+ removed: z.boolean().optional().describe("Whether the contributor was removed"),
242
+ contentLength: z.number().optional().describe("Stored content length in characters"),
243
+ truncated: z.boolean().optional().describe("Whether the submitted content was truncated")
244
+ }).strict()
248
245
  }
249
246
  }
250
247
  },
251
- responses: {
252
- 200: {
253
- description: "Session contributor upsert result",
254
- content: {
255
- "application/json": {
256
- schema: z.object({
257
- id: z.string().describe("Contributor identifier"),
258
- enabled: z.boolean().describe("Whether the contributor remains enabled"),
259
- priority: z.number().optional().describe("Contributor priority"),
260
- replaced: z.boolean().optional().describe("Whether an existing contributor was replaced"),
261
- removed: z.boolean().optional().describe("Whether the contributor was removed"),
262
- contentLength: z.number().optional().describe("Stored content length in characters"),
263
- truncated: z.boolean().optional().describe("Whether the submitted content was truncated")
264
- }).strict()
265
- }
248
+ 400: {
249
+ ...BadRequestErrorResponse
250
+ },
251
+ 404: {
252
+ ...NotFoundErrorResponse
253
+ },
254
+ 500: InternalErrorResponse
255
+ }
256
+ });
257
+ const deleteRoute = createRoute({
258
+ method: "delete",
259
+ path: "/sessions/{sessionId}",
260
+ summary: "Delete Session",
261
+ description: "Permanently deletes a session and all its conversation history. This action cannot be undone",
262
+ tags: ["sessions"],
263
+ request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
264
+ responses: {
265
+ 200: {
266
+ description: "Session deleted successfully",
267
+ content: {
268
+ "application/json": {
269
+ schema: z.object({
270
+ status: z.literal("deleted").describe("Deletion status"),
271
+ sessionId: z.string().describe("ID of the deleted session")
272
+ }).strict()
266
273
  }
267
- },
268
- 400: {
269
- description: "Invalid session contributor request",
270
- content: {
271
- "application/json": {
272
- schema: StandardErrorEnvelopeSchema
273
- }
274
+ }
275
+ },
276
+ 400: BadRequestErrorResponse,
277
+ 404: NotFoundErrorResponse,
278
+ 500: InternalErrorResponse
279
+ }
280
+ });
281
+ const cancelRoute = createRoute({
282
+ method: "post",
283
+ path: "/sessions/{sessionId}/cancel",
284
+ summary: "Cancel Session Run",
285
+ description: "Cancels an in-flight agent run for the specified session. By default (soft cancel), only the current LLM call is cancelled and queued messages continue processing. Set clearQueue=true for hard cancel to also clear any queued messages.",
286
+ tags: ["sessions"],
287
+ request: {
288
+ params: z.object({ sessionId: z.string().describe("Session identifier") }),
289
+ body: {
290
+ content: {
291
+ "application/json": {
292
+ schema: z.object({
293
+ clearQueue: z.boolean().optional().default(false).describe(
294
+ "If true (hard cancel), clears queued messages. If false (soft cancel, default), queued messages continue processing."
295
+ )
296
+ }).strict()
274
297
  }
275
298
  },
276
- 404: {
277
- description: "Session not found",
278
- content: {
279
- "application/json": {
280
- schema: StandardErrorEnvelopeSchema
281
- }
282
- }
283
- }
299
+ required: false
284
300
  }
285
- });
286
- const deleteRoute = createRoute({
287
- method: "delete",
288
- path: "/sessions/{sessionId}",
289
- summary: "Delete Session",
290
- description: "Permanently deletes a session and all its conversation history. This action cannot be undone",
291
- tags: ["sessions"],
292
- request: { params: z.object({ sessionId: z.string().describe("Session identifier") }) },
293
- responses: {
294
- 200: {
295
- description: "Session deleted successfully",
296
- content: {
297
- "application/json": {
298
- schema: z.object({
299
- status: z.literal("deleted").describe("Deletion status"),
300
- sessionId: z.string().describe("ID of the deleted session")
301
- }).strict()
302
- }
301
+ },
302
+ responses: {
303
+ 200: {
304
+ description: "Cancel operation result",
305
+ content: {
306
+ "application/json": {
307
+ schema: z.object({
308
+ cancelled: z.boolean().describe("Whether a run was cancelled"),
309
+ sessionId: z.string().describe("Session ID"),
310
+ queueCleared: z.boolean().describe("Whether queued messages were cleared"),
311
+ clearedCount: z.number().describe("Number of queued messages cleared (0 if soft cancel)")
312
+ }).strict()
303
313
  }
304
314
  }
305
- }
306
- });
307
- const cancelRoute = createRoute({
308
- method: "post",
309
- path: "/sessions/{sessionId}/cancel",
310
- summary: "Cancel Session Run",
311
- description: "Cancels an in-flight agent run for the specified session. By default (soft cancel), only the current LLM call is cancelled and queued messages continue processing. Set clearQueue=true for hard cancel to also clear any queued messages.",
312
- tags: ["sessions"],
313
- request: {
314
- params: z.object({ sessionId: z.string().describe("Session identifier") }),
315
- body: {
316
- content: {
317
- "application/json": {
318
- schema: z.object({
319
- clearQueue: z.boolean().optional().default(false).describe(
320
- "If true (hard cancel), clears queued messages. If false (soft cancel, default), queued messages continue processing."
321
- )
322
- }).strict()
323
- }
315
+ },
316
+ 400: BadRequestErrorResponse,
317
+ 404: NotFoundErrorResponse,
318
+ 409: ConflictErrorResponse,
319
+ 500: InternalErrorResponse
320
+ }
321
+ });
322
+ const eventsRoute = createRoute({
323
+ method: "get",
324
+ path: "/sessions/{sessionId}/events",
325
+ summary: "Attach Session Event Stream",
326
+ description: "Attaches to the live SSE event stream for an already-running session without starting a new run. Use this to reattach after a page reload or connection interruption.",
327
+ tags: ["sessions"],
328
+ request: {
329
+ params: z.object({ sessionId: z.string().describe("Session identifier") })
330
+ },
331
+ responses: {
332
+ 200: {
333
+ description: "SSE stream of live session events",
334
+ headers: {
335
+ "Content-Type": {
336
+ description: "SSE content type",
337
+ schema: { type: "string", example: "text/event-stream" }
324
338
  },
325
- required: false
339
+ "Cache-Control": {
340
+ description: "Disable caching for stream",
341
+ schema: { type: "string", example: "no-cache" }
342
+ },
343
+ Connection: {
344
+ description: "Keep connection alive for streaming",
345
+ schema: { type: "string", example: "keep-alive" }
346
+ },
347
+ "X-Accel-Buffering": {
348
+ description: "Disable nginx buffering",
349
+ schema: { type: "string", example: "no" }
350
+ }
351
+ },
352
+ content: {
353
+ "text/event-stream": {
354
+ schema: z.string().describe(
355
+ "Server-Sent Events stream for an active session. Events use the same payloads as /message-stream."
356
+ )
357
+ }
326
358
  }
327
359
  },
328
- responses: {
329
- 200: {
330
- description: "Cancel operation result",
331
- content: {
332
- "application/json": {
333
- schema: z.object({
334
- cancelled: z.boolean().describe("Whether a run was cancelled"),
335
- sessionId: z.string().describe("Session ID"),
336
- queueCleared: z.boolean().describe("Whether queued messages were cleared"),
337
- clearedCount: z.number().describe(
338
- "Number of queued messages cleared (0 if soft cancel)"
360
+ 400: BadRequestErrorResponse,
361
+ 404: NotFoundErrorResponse,
362
+ 409: ConflictErrorResponse,
363
+ 500: InternalErrorResponse
364
+ }
365
+ });
366
+ const loadRoute = createRoute({
367
+ method: "get",
368
+ path: "/sessions/{sessionId}/load",
369
+ summary: "Load Session",
370
+ description: "Validates and retrieves session information including processing status. The client should track the active session.",
371
+ tags: ["sessions"],
372
+ request: {
373
+ params: z.object({ sessionId: z.string().describe("Session identifier") })
374
+ },
375
+ responses: {
376
+ 200: {
377
+ description: "Session information retrieved successfully",
378
+ content: {
379
+ "application/json": {
380
+ schema: z.object({
381
+ session: SessionMetadataSchema.extend({
382
+ isBusy: z.boolean().describe(
383
+ "Whether the session is currently processing a message"
384
+ ),
385
+ usageSummary: UsageSummarySchema.describe(
386
+ "Exact usage summary derived from assistant message history"
387
+ ),
388
+ activeUsageScopeId: z.string().nullable().describe(
389
+ "Current runtime usage scope identifier, if configured"
390
+ ),
391
+ activeUsageScope: ScopedUsageSummarySchema.nullable().describe(
392
+ "Usage summary for the current runtime scope, if configured"
339
393
  )
340
- }).strict()
341
- }
394
+ }).describe("Session metadata with processing status")
395
+ }).strict()
342
396
  }
343
397
  }
344
- }
345
- });
346
- const loadRoute = createRoute({
347
- method: "get",
348
- path: "/sessions/{sessionId}/load",
349
- summary: "Load Session",
350
- description: "Validates and retrieves session information including processing status. The client should track the active session.",
351
- tags: ["sessions"],
352
- request: {
353
- params: z.object({ sessionId: z.string().describe("Session identifier") })
354
398
  },
355
- responses: {
356
- 200: {
357
- description: "Session information retrieved successfully",
358
- content: {
359
- "application/json": {
360
- schema: z.object({
361
- session: SessionMetadataSchema.extend({
362
- isBusy: z.boolean().describe(
363
- "Whether the session is currently processing a message"
364
- ),
365
- usageSummary: UsageSummarySchema.describe(
366
- "Exact usage summary derived from assistant message history"
367
- ),
368
- activeUsageScopeId: z.string().nullable().describe(
369
- "Current runtime usage scope identifier, if configured"
370
- ),
371
- activeUsageScope: ScopedUsageSummarySchema.nullable().describe(
372
- "Usage summary for the current runtime scope, if configured"
373
- )
374
- }).describe("Session metadata with processing status")
375
- }).strict()
376
- }
377
- }
378
- },
379
- 404: {
380
- description: "Session not found",
381
- content: {
382
- "application/json": {
383
- schema: z.object({
384
- error: z.string().describe("Error message")
385
- }).strict()
386
- }
399
+ 404: {
400
+ ...NotFoundErrorResponse
401
+ },
402
+ 400: BadRequestErrorResponse,
403
+ 500: InternalErrorResponse
404
+ }
405
+ });
406
+ const clearContextRoute = createRoute({
407
+ method: "post",
408
+ path: "/sessions/{sessionId}/clear-context",
409
+ summary: "Clear Session Context",
410
+ description: "Clears the model context window for a session while preserving conversation history for review.",
411
+ tags: ["sessions"],
412
+ request: {
413
+ params: z.object({ sessionId: z.string().describe("Session identifier") })
414
+ },
415
+ responses: {
416
+ 200: {
417
+ description: "Session context cleared successfully",
418
+ content: {
419
+ "application/json": {
420
+ schema: z.object({
421
+ status: z.literal("context cleared").describe("Context clear status"),
422
+ sessionId: z.string().describe("Session ID")
423
+ }).strict()
387
424
  }
388
425
  }
389
- }
390
- });
391
- const clearContextRoute = createRoute({
392
- method: "post",
393
- path: "/sessions/{sessionId}/clear-context",
394
- summary: "Clear Session Context",
395
- description: "Clears the model context window for a session while preserving conversation history for review.",
396
- tags: ["sessions"],
397
- request: {
398
- params: z.object({ sessionId: z.string().describe("Session identifier") })
399
426
  },
400
- responses: {
401
- 200: {
402
- description: "Session context cleared successfully",
403
- content: {
404
- "application/json": {
405
- schema: z.object({
406
- status: z.literal("context cleared").describe("Context clear status"),
407
- sessionId: z.string().describe("Session ID")
408
- }).strict()
409
- }
427
+ 400: BadRequestErrorResponse,
428
+ 404: NotFoundErrorResponse,
429
+ 500: InternalErrorResponse
430
+ }
431
+ });
432
+ const patchRoute = createRoute({
433
+ method: "patch",
434
+ path: "/sessions/{sessionId}",
435
+ summary: "Update Session Title",
436
+ description: "Updates the title of an existing session",
437
+ tags: ["sessions"],
438
+ request: {
439
+ params: z.object({ sessionId: z.string().describe("Session identifier") }),
440
+ body: {
441
+ content: {
442
+ "application/json": {
443
+ schema: z.object({
444
+ title: z.string().min(1, "Title is required").max(120, "Title too long").describe("New title for the session (maximum 120 characters)")
445
+ })
410
446
  }
411
447
  }
412
448
  }
413
- });
414
- const patchRoute = createRoute({
415
- method: "patch",
416
- path: "/sessions/{sessionId}",
417
- summary: "Update Session Title",
418
- description: "Updates the title of an existing session",
419
- tags: ["sessions"],
420
- request: {
421
- params: z.object({ sessionId: z.string().describe("Session identifier") }),
422
- body: {
423
- content: {
424
- "application/json": {
425
- schema: z.object({
426
- title: z.string().min(1, "Title is required").max(120, "Title too long").describe("New title for the session (maximum 120 characters)")
427
- })
428
- }
449
+ },
450
+ responses: {
451
+ 200: {
452
+ description: "Session updated successfully",
453
+ content: {
454
+ "application/json": {
455
+ schema: z.object({
456
+ session: SessionMetadataSchema.describe("Updated session metadata")
457
+ }).strict()
429
458
  }
430
459
  }
431
460
  },
432
- responses: {
433
- 200: {
434
- description: "Session updated successfully",
435
- content: {
436
- "application/json": {
437
- schema: z.object({
438
- session: SessionMetadataSchema.describe("Updated session metadata")
439
- }).strict()
440
- }
461
+ 400: BadRequestErrorResponse,
462
+ 404: NotFoundErrorResponse,
463
+ 500: InternalErrorResponse
464
+ }
465
+ });
466
+ const generateTitleRoute = createRoute({
467
+ method: "post",
468
+ path: "/sessions/{sessionId}/generate-title",
469
+ summary: "Generate Session Title",
470
+ description: "Generates a descriptive title for the session using the first user message. Returns existing title if already set.",
471
+ tags: ["sessions"],
472
+ request: {
473
+ params: z.object({ sessionId: z.string().describe("Session identifier") })
474
+ },
475
+ responses: {
476
+ 200: {
477
+ description: "Title generated successfully",
478
+ content: {
479
+ "application/json": {
480
+ schema: z.object({
481
+ title: z.string().nullable().describe("Generated title, or null if generation failed"),
482
+ sessionId: z.string().describe("Session ID")
483
+ }).strict()
441
484
  }
442
485
  }
443
- }
444
- });
445
- const generateTitleRoute = createRoute({
446
- method: "post",
447
- path: "/sessions/{sessionId}/generate-title",
448
- summary: "Generate Session Title",
449
- description: "Generates a descriptive title for the session using the first user message. Returns existing title if already set.",
450
- tags: ["sessions"],
451
- request: {
452
- params: z.object({ sessionId: z.string().describe("Session identifier") })
453
486
  },
454
- responses: {
455
- 200: {
456
- description: "Title generated successfully",
457
- content: {
458
- "application/json": {
459
- schema: z.object({
460
- title: z.string().nullable().describe("Generated title, or null if generation failed"),
461
- sessionId: z.string().describe("Session ID")
462
- }).strict()
463
- }
464
- }
465
- },
466
- 404: {
467
- description: "Session not found",
468
- content: {
469
- "application/json": {
470
- schema: StandardErrorEnvelopeSchema
471
- }
472
- }
487
+ 404: {
488
+ ...NotFoundErrorResponse
489
+ },
490
+ 400: BadRequestErrorResponse,
491
+ 500: InternalErrorResponse
492
+ }
493
+ });
494
+ function createSessionsRouter(getAgent, sessionSseSubscriber) {
495
+ const app = new OpenAPIHono({
496
+ defaultHook: (result, ctx) => {
497
+ if (!result.success) {
498
+ const issues = zodToIssues(result.error);
499
+ return handleHonoError(
500
+ ctx,
501
+ new DextoRuntimeError(
502
+ "validation_failed",
503
+ "validation",
504
+ ErrorType.USER,
505
+ issues[0]?.message ?? "Validation failed",
506
+ { issues }
507
+ )
508
+ );
473
509
  }
474
510
  }
475
511
  });
512
+ app.onError((err, ctx) => handleHonoError(ctx, err));
476
513
  return app.openapi(listRoute, async (ctx) => {
477
514
  const agent = await getAgent(ctx);
478
515
  const sessionIds = await agent.listSessions();
@@ -486,7 +523,7 @@ function createSessionsRouter(getAgent) {
486
523
  }
487
524
  })
488
525
  );
489
- return ctx.json({ sessions });
526
+ return ctx.json({ sessions }, 200);
490
527
  }).openapi(createRouteDef, async (ctx) => {
491
528
  const agent = await getAgent(ctx);
492
529
  const { sessionId } = ctx.req.valid("json");
@@ -517,12 +554,15 @@ function createSessionsRouter(getAgent) {
517
554
  const { sessionId } = ctx.req.param();
518
555
  const metadata = await agent.getSessionMetadata(sessionId);
519
556
  const history = await agent.getSessionHistory(sessionId);
520
- return ctx.json({
521
- session: {
522
- ...mapSessionMetadata(sessionId, metadata),
523
- history: history.length
524
- }
525
- });
557
+ return ctx.json(
558
+ {
559
+ session: {
560
+ ...mapSessionMetadata(sessionId, metadata),
561
+ history: history.length
562
+ }
563
+ },
564
+ 200
565
+ );
526
566
  }).openapi(historyRoute, async (ctx) => {
527
567
  const agent = await getAgent(ctx);
528
568
  const { sessionId } = ctx.req.param();
@@ -530,10 +570,26 @@ function createSessionsRouter(getAgent) {
530
570
  agent.getSessionHistory(sessionId),
531
571
  agent.isSessionBusy(sessionId)
532
572
  ]);
533
- return ctx.json({
534
- history,
535
- isBusy
536
- });
573
+ const apiHistory = history.map((message) => toApiInternalMessage(message));
574
+ for (const [index, message] of apiHistory.entries()) {
575
+ const parsed = InternalMessageSchema.safeParse(message);
576
+ if (!parsed.success) {
577
+ throw new DextoRuntimeError(
578
+ "session_history_serialization_failed",
579
+ ErrorScope.SESSION,
580
+ ErrorType.SYSTEM,
581
+ "Failed to serialize session history",
582
+ { sessionId, index, issues: zodToIssues(parsed.error) }
583
+ );
584
+ }
585
+ }
586
+ return ctx.json(
587
+ {
588
+ history: apiHistory,
589
+ isBusy
590
+ },
591
+ 200
592
+ );
537
593
  }).openapi(listSessionPromptContributorsRoute, async (ctx) => {
538
594
  const agent = await getAgent(ctx);
539
595
  const { sessionId } = ctx.req.valid("param");
@@ -613,7 +669,7 @@ function createSessionsRouter(getAgent) {
613
669
  const agent = await getAgent(ctx);
614
670
  const { sessionId } = ctx.req.param();
615
671
  await agent.deleteSession(sessionId);
616
- return ctx.json({ status: "deleted", sessionId });
672
+ return ctx.json({ status: "deleted", sessionId }, 200);
617
673
  }).openapi(cancelRoute, async (ctx) => {
618
674
  const agent = await getAgent(ctx);
619
675
  const { sessionId } = ctx.req.valid("param");
@@ -637,18 +693,61 @@ function createSessionsRouter(getAgent) {
637
693
  if (!cancelled) {
638
694
  agent.logger.debug(`No in-flight run to cancel for session: ${sessionId}`);
639
695
  }
640
- return ctx.json({
641
- cancelled,
642
- sessionId,
643
- queueCleared: clearQueue,
644
- clearedCount
696
+ return ctx.json(
697
+ {
698
+ cancelled,
699
+ sessionId,
700
+ queueCleared: clearQueue,
701
+ clearedCount
702
+ },
703
+ 200
704
+ );
705
+ }).openapi(eventsRoute, async (ctx) => {
706
+ const agent = await getAgent(ctx);
707
+ const { sessionId } = ctx.req.valid("param");
708
+ const sessionIds = await agent.listSessions();
709
+ if (!sessionIds.includes(sessionId)) {
710
+ throw new DextoRuntimeError(
711
+ "session_not_found",
712
+ ErrorScope.SESSION,
713
+ ErrorType.NOT_FOUND,
714
+ `Session not found: ${sessionId}`,
715
+ { sessionId }
716
+ );
717
+ }
718
+ const isBusy = await agent.isSessionBusy(sessionId);
719
+ if (!isBusy) {
720
+ throw new DextoRuntimeError(
721
+ "session_not_busy",
722
+ ErrorScope.SESSION,
723
+ ErrorType.CONFLICT,
724
+ `Session is not running: ${sessionId}`,
725
+ { sessionId }
726
+ );
727
+ }
728
+ const eventStream = sessionSseSubscriber.createStream(
729
+ sessionId
730
+ );
731
+ return new Response(eventStream, {
732
+ headers: {
733
+ "Content-Type": "text/event-stream",
734
+ "Cache-Control": "no-cache",
735
+ Connection: "keep-alive",
736
+ "X-Accel-Buffering": "no"
737
+ }
645
738
  });
646
739
  }).openapi(loadRoute, async (ctx) => {
647
740
  const agent = await getAgent(ctx);
648
741
  const { sessionId } = ctx.req.valid("param");
649
742
  const sessionIds = await agent.listSessions();
650
743
  if (!sessionIds.includes(sessionId)) {
651
- return ctx.json({ error: `Session not found: ${sessionId}` }, 404);
744
+ throw new DextoRuntimeError(
745
+ "session_not_found",
746
+ ErrorScope.SESSION,
747
+ ErrorType.NOT_FOUND,
748
+ `Session not found: ${sessionId}`,
749
+ { sessionId }
750
+ );
652
751
  }
653
752
  const metadata = await agent.getSessionMetadata(sessionId);
654
753
  const isBusy = await agent.isSessionBusy(sessionId);
@@ -674,16 +773,19 @@ function createSessionsRouter(getAgent) {
674
773
  const agent = await getAgent(ctx);
675
774
  const { sessionId } = ctx.req.valid("param");
676
775
  await agent.clearContext(sessionId);
677
- return ctx.json({ status: "context cleared", sessionId });
776
+ return ctx.json({ status: "context cleared", sessionId }, 200);
678
777
  }).openapi(patchRoute, async (ctx) => {
679
778
  const agent = await getAgent(ctx);
680
779
  const { sessionId } = ctx.req.valid("param");
681
780
  const { title } = ctx.req.valid("json");
682
781
  await agent.setSessionTitle(sessionId, title);
683
782
  const metadata = await agent.getSessionMetadata(sessionId);
684
- return ctx.json({
685
- session: mapSessionMetadata(sessionId, metadata, { title })
686
- });
783
+ return ctx.json(
784
+ {
785
+ session: mapSessionMetadata(sessionId, metadata, { title })
786
+ },
787
+ 200
788
+ );
687
789
  }).openapi(generateTitleRoute, async (ctx) => {
688
790
  const agent = await getAgent(ctx);
689
791
  const { sessionId } = ctx.req.valid("param");