@dexto/server 1.3.0 → 1.5.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.
Files changed (109) hide show
  1. package/dist/approval/manual-approval-handler.cjs +23 -15
  2. package/dist/approval/manual-approval-handler.d.ts.map +1 -1
  3. package/dist/approval/manual-approval-handler.js +23 -15
  4. package/dist/events/webhook-subscriber.cjs +1 -1
  5. package/dist/events/webhook-subscriber.d.ts.map +1 -1
  6. package/dist/events/webhook-subscriber.js +1 -1
  7. package/dist/hono/__tests__/test-fixtures.cjs +3 -3
  8. package/dist/hono/__tests__/test-fixtures.d.ts.map +1 -1
  9. package/dist/hono/__tests__/test-fixtures.js +3 -3
  10. package/dist/hono/index.cjs +46 -5
  11. package/dist/hono/index.d.ts +928 -584
  12. package/dist/hono/index.d.ts.map +1 -1
  13. package/dist/hono/index.js +46 -5
  14. package/dist/hono/middleware/error.d.ts.map +1 -1
  15. package/dist/hono/routes/a2a-jsonrpc.cjs +3 -3
  16. package/dist/hono/routes/a2a-jsonrpc.d.ts +4 -1
  17. package/dist/hono/routes/a2a-jsonrpc.d.ts.map +1 -1
  18. package/dist/hono/routes/a2a-jsonrpc.js +3 -3
  19. package/dist/hono/routes/a2a-tasks.cjs +5 -5
  20. package/dist/hono/routes/a2a-tasks.d.ts +13 -10
  21. package/dist/hono/routes/a2a-tasks.d.ts.map +1 -1
  22. package/dist/hono/routes/a2a-tasks.js +5 -5
  23. package/dist/hono/routes/agents.cjs +30 -42
  24. package/dist/hono/routes/agents.d.ts +7 -401
  25. package/dist/hono/routes/agents.d.ts.map +1 -1
  26. package/dist/hono/routes/agents.js +32 -42
  27. package/dist/hono/routes/approvals.cjs +53 -2
  28. package/dist/hono/routes/approvals.d.ts +29 -1
  29. package/dist/hono/routes/approvals.d.ts.map +1 -1
  30. package/dist/hono/routes/approvals.js +53 -2
  31. package/dist/hono/routes/discovery.cjs +67 -0
  32. package/dist/hono/routes/discovery.d.ts +44 -0
  33. package/dist/hono/routes/discovery.d.ts.map +1 -0
  34. package/dist/hono/routes/discovery.js +43 -0
  35. package/dist/hono/routes/greeting.cjs +2 -2
  36. package/dist/hono/routes/greeting.d.ts +2 -2
  37. package/dist/hono/routes/greeting.d.ts.map +1 -1
  38. package/dist/hono/routes/greeting.js +2 -2
  39. package/dist/hono/routes/health.d.ts +2 -2
  40. package/dist/hono/routes/health.d.ts.map +1 -1
  41. package/dist/hono/routes/key.cjs +110 -0
  42. package/dist/hono/routes/key.d.ts +48 -0
  43. package/dist/hono/routes/key.d.ts.map +1 -0
  44. package/dist/hono/routes/key.js +90 -0
  45. package/dist/hono/routes/llm.cjs +119 -62
  46. package/dist/hono/routes/llm.d.ts +242 -42
  47. package/dist/hono/routes/llm.d.ts.map +1 -1
  48. package/dist/hono/routes/llm.js +118 -58
  49. package/dist/hono/routes/mcp.cjs +16 -12
  50. package/dist/hono/routes/mcp.d.ts +6 -3
  51. package/dist/hono/routes/mcp.d.ts.map +1 -1
  52. package/dist/hono/routes/mcp.js +17 -13
  53. package/dist/hono/routes/memory.cjs +5 -5
  54. package/dist/hono/routes/memory.d.ts +5 -2
  55. package/dist/hono/routes/memory.d.ts.map +1 -1
  56. package/dist/hono/routes/memory.js +5 -5
  57. package/dist/hono/routes/messages.cjs +58 -66
  58. package/dist/hono/routes/messages.d.ts +99 -55
  59. package/dist/hono/routes/messages.d.ts.map +1 -1
  60. package/dist/hono/routes/messages.js +59 -67
  61. package/dist/hono/routes/models.cjs +319 -0
  62. package/dist/hono/routes/models.d.ts +107 -0
  63. package/dist/hono/routes/models.d.ts.map +1 -0
  64. package/dist/hono/routes/models.js +305 -0
  65. package/dist/hono/routes/openrouter.cjs +153 -0
  66. package/dist/hono/routes/openrouter.d.ts +54 -0
  67. package/dist/hono/routes/openrouter.d.ts.map +1 -0
  68. package/dist/hono/routes/openrouter.js +134 -0
  69. package/dist/hono/routes/prompts.cjs +5 -5
  70. package/dist/hono/routes/prompts.d.ts +10 -7
  71. package/dist/hono/routes/prompts.d.ts.map +1 -1
  72. package/dist/hono/routes/prompts.js +5 -5
  73. package/dist/hono/routes/queue.cjs +202 -0
  74. package/dist/hono/routes/queue.d.ts +174 -0
  75. package/dist/hono/routes/queue.d.ts.map +1 -0
  76. package/dist/hono/routes/queue.js +178 -0
  77. package/dist/hono/routes/resources.cjs +3 -3
  78. package/dist/hono/routes/resources.d.ts +3 -3
  79. package/dist/hono/routes/resources.d.ts.map +1 -1
  80. package/dist/hono/routes/resources.js +3 -3
  81. package/dist/hono/routes/search.cjs +2 -2
  82. package/dist/hono/routes/search.d.ts +39 -10
  83. package/dist/hono/routes/search.d.ts.map +1 -1
  84. package/dist/hono/routes/search.js +2 -2
  85. package/dist/hono/routes/sessions.cjs +74 -20
  86. package/dist/hono/routes/sessions.d.ts +25 -4
  87. package/dist/hono/routes/sessions.d.ts.map +1 -1
  88. package/dist/hono/routes/sessions.js +74 -20
  89. package/dist/hono/routes/tools.cjs +126 -0
  90. package/dist/hono/routes/tools.d.ts +42 -0
  91. package/dist/hono/routes/tools.d.ts.map +1 -0
  92. package/dist/hono/routes/tools.js +102 -0
  93. package/dist/hono/routes/webhooks.cjs +4 -4
  94. package/dist/hono/routes/webhooks.d.ts +4 -1
  95. package/dist/hono/routes/webhooks.d.ts.map +1 -1
  96. package/dist/hono/routes/webhooks.js +4 -4
  97. package/dist/hono/schemas/responses.cjs +24 -5
  98. package/dist/hono/schemas/responses.d.ts +838 -120
  99. package/dist/hono/schemas/responses.d.ts.map +1 -1
  100. package/dist/hono/schemas/responses.js +24 -10
  101. package/dist/hono/start-server.cjs +102 -0
  102. package/dist/hono/start-server.d.ts +61 -0
  103. package/dist/hono/start-server.d.ts.map +1 -0
  104. package/dist/hono/start-server.js +78 -0
  105. package/dist/index.cjs +2 -0
  106. package/dist/index.d.ts +1 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +1 -0
  109. package/package.json +5 -4
@@ -25,25 +25,26 @@ var import_zod_openapi = require("@hono/zod-openapi");
25
25
  var import_streaming = require("hono/streaming");
26
26
  var import_core = require("@dexto/core");
27
27
  var import_responses = require("../schemas/responses.js");
28
+ const TextPartSchema = import_zod_openapi.z.object({
29
+ type: import_zod_openapi.z.literal("text").describe("Content type identifier"),
30
+ text: import_zod_openapi.z.string().describe("Text content")
31
+ }).describe("Text content part");
32
+ const ImagePartSchema = import_zod_openapi.z.object({
33
+ type: import_zod_openapi.z.literal("image").describe("Content type identifier"),
34
+ image: import_zod_openapi.z.string().describe("Base64-encoded image data or URL"),
35
+ mimeType: import_zod_openapi.z.string().optional().describe("MIME type (e.g., image/png)")
36
+ }).describe("Image content part");
37
+ const FilePartSchema = import_zod_openapi.z.object({
38
+ type: import_zod_openapi.z.literal("file").describe("Content type identifier"),
39
+ data: import_zod_openapi.z.string().describe("Base64-encoded file data or URL"),
40
+ mimeType: import_zod_openapi.z.string().describe("MIME type (e.g., application/pdf)"),
41
+ filename: import_zod_openapi.z.string().optional().describe("Optional filename")
42
+ }).describe("File content part");
43
+ const ContentPartSchema = import_zod_openapi.z.discriminatedUnion("type", [TextPartSchema, ImagePartSchema, FilePartSchema]).describe("Content part - text, image, or file");
28
44
  const MessageBodySchema = import_zod_openapi.z.object({
29
- message: import_zod_openapi.z.string().optional().describe("The user message text"),
30
- sessionId: import_zod_openapi.z.string().min(1, "Session ID is required").describe("The session to use for this message"),
31
- imageData: import_zod_openapi.z.object({
32
- image: import_zod_openapi.z.string().describe("Base64-encoded image data"),
33
- mimeType: import_zod_openapi.z.string().describe("The MIME type of the image (e.g., image/png)")
34
- }).optional().describe("Optional image data to include with the message"),
35
- fileData: import_zod_openapi.z.object({
36
- data: import_zod_openapi.z.string().describe("Base64-encoded file data"),
37
- mimeType: import_zod_openapi.z.string().describe("The MIME type of the file (e.g., application/pdf)"),
38
- filename: import_zod_openapi.z.string().optional().describe("The filename")
39
- }).optional().describe("Optional file data to include with the message")
40
- }).refine(
41
- (data) => {
42
- const msg = (data.message ?? "").trim();
43
- return msg.length > 0 || !!data.imageData || !!data.fileData;
44
- },
45
- { message: "Must provide either message text, image data, or file data" }
46
- ).describe("Request body for sending a message to the agent");
45
+ content: import_zod_openapi.z.union([import_zod_openapi.z.string(), import_zod_openapi.z.array(ContentPartSchema)]).describe("Message content - string for text, or ContentPart[] for multimodal"),
46
+ sessionId: import_zod_openapi.z.string().min(1, "Session ID is required").describe("The session to use for this message")
47
+ }).describe("Request body for sending a message to the agent");
47
48
  const ResetBodySchema = import_zod_openapi.z.object({
48
49
  sessionId: import_zod_openapi.z.string().min(1, "Session ID is required").describe("The ID of the session to reset")
49
50
  }).describe("Request body for resetting a conversation");
@@ -95,8 +96,7 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
95
96
  tokenUsage: import_responses.TokenUsageSchema.optional().describe("Token usage statistics"),
96
97
  reasoning: import_zod_openapi.z.string().optional().describe("Extended thinking content from reasoning models"),
97
98
  model: import_zod_openapi.z.string().optional().describe("Model used for this response"),
98
- provider: import_zod_openapi.z.enum(import_core.LLM_PROVIDERS).optional().describe("LLM provider"),
99
- router: import_zod_openapi.z.enum(import_core.LLM_ROUTERS).optional().describe("Router used (e.g., vercel)")
99
+ provider: import_zod_openapi.z.enum(import_core.LLM_PROVIDERS).optional().describe("LLM provider")
100
100
  }).strict()
101
101
  }
102
102
  }
@@ -131,7 +131,7 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
131
131
  method: "post",
132
132
  path: "/message-stream",
133
133
  summary: "Stream message response",
134
- description: "Sends a message and streams the response via Server-Sent Events (SSE). Returns SSE stream directly in response. Events include llm:thinking, llm:chunk, llm:tool-call, llm:tool-result, llm:response, and llm:error.",
134
+ description: "Sends a message and streams the response via Server-Sent Events (SSE). Returns SSE stream directly in response. Events include llm:thinking, llm:chunk, llm:tool-call, llm:tool-result, llm:response, and llm:error. If the session is busy processing another message, returns 202 with queue information.",
135
135
  tags: ["messages"],
136
136
  request: {
137
137
  body: {
@@ -167,46 +167,39 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
167
167
  }
168
168
  }
169
169
  },
170
+ 202: {
171
+ description: "Session is busy processing another message. Use the queue endpoints to manage pending messages.",
172
+ content: {
173
+ "application/json": {
174
+ schema: import_zod_openapi.z.object({
175
+ busy: import_zod_openapi.z.literal(true).describe("Indicates session is busy"),
176
+ sessionId: import_zod_openapi.z.string().describe("The session ID"),
177
+ queueLength: import_zod_openapi.z.number().describe("Current number of messages in queue"),
178
+ hint: import_zod_openapi.z.string().describe("Instructions for the client")
179
+ }).strict()
180
+ }
181
+ }
182
+ },
170
183
  400: { description: "Validation error" }
171
184
  }
172
185
  });
173
186
  return app.openapi(messageRoute, async (ctx) => {
174
- const agent = getAgent();
187
+ const agent = await getAgent(ctx);
175
188
  agent.logger.info("Received message via POST /api/message");
176
- const { message, sessionId, imageData, fileData } = ctx.req.valid("json");
177
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
178
- const fileDataInput = fileData ? {
179
- data: fileData.data,
180
- mimeType: fileData.mimeType,
181
- ...fileData.filename && { filename: fileData.filename }
182
- } : void 0;
183
- if (imageDataInput) agent.logger.info("Image data included in message.");
184
- if (fileDataInput) agent.logger.info("File data included in message.");
189
+ const { content, sessionId } = ctx.req.valid("json");
185
190
  agent.logger.info(`Message for session: ${sessionId}`);
186
- agent.run(message || "", imageDataInput, fileDataInput, sessionId, false).catch((error) => {
191
+ agent.generate(content, sessionId).catch((error) => {
187
192
  agent.logger.error(
188
193
  `Error in async message processing: ${error instanceof Error ? error.message : String(error)}`
189
194
  );
190
195
  });
191
196
  return ctx.json({ accepted: true, sessionId }, 202);
192
197
  }).openapi(messageSyncRoute, async (ctx) => {
193
- const agent = getAgent();
198
+ const agent = await getAgent(ctx);
194
199
  agent.logger.info("Received message via POST /api/message-sync");
195
- const { message, sessionId, imageData, fileData } = ctx.req.valid("json");
196
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
197
- const fileDataInput = fileData ? {
198
- data: fileData.data,
199
- mimeType: fileData.mimeType,
200
- ...fileData.filename && { filename: fileData.filename }
201
- } : void 0;
202
- if (imageDataInput) agent.logger.info("Image data included in message.");
203
- if (fileDataInput) agent.logger.info("File data included in message.");
200
+ const { content, sessionId } = ctx.req.valid("json");
204
201
  agent.logger.info(`Message for session: ${sessionId}`);
205
- const result = await agent.generate(message || "", {
206
- sessionId,
207
- imageData: imageDataInput,
208
- fileData: fileDataInput
209
- });
202
+ const result = await agent.generate(content, sessionId);
210
203
  const llmConfig = agent.stateManager.getLLMConfig(sessionId);
211
204
  return ctx.json({
212
205
  response: result.content,
@@ -214,34 +207,33 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
214
207
  tokenUsage: result.usage,
215
208
  reasoning: result.reasoning,
216
209
  model: llmConfig.model,
217
- provider: llmConfig.provider,
218
- router: "vercel"
219
- // Hardcoded for now since we only use Vercel AI SDK
210
+ provider: llmConfig.provider
220
211
  });
221
212
  }).openapi(resetRoute, async (ctx) => {
222
- const agent = getAgent();
213
+ const agent = await getAgent(ctx);
223
214
  agent.logger.info("Received request via POST /api/reset");
224
215
  const { sessionId } = ctx.req.valid("json");
225
216
  await agent.resetConversation(sessionId);
226
217
  return ctx.json({ status: "reset initiated", sessionId });
227
218
  }).openapi(messageStreamRoute, async (ctx) => {
228
- const agent = getAgent();
229
- const body = ctx.req.valid("json");
230
- const { message = "", sessionId, imageData, fileData } = body;
231
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
232
- const fileDataInput = fileData ? {
233
- data: fileData.data,
234
- mimeType: fileData.mimeType,
235
- ...fileData.filename && { filename: fileData.filename }
236
- } : void 0;
219
+ const agent = await getAgent(ctx);
220
+ const { content, sessionId } = ctx.req.valid("json");
221
+ const isBusy = await agent.isSessionBusy(sessionId);
222
+ if (isBusy) {
223
+ const queuedMessages = await agent.getQueuedMessages(sessionId);
224
+ return ctx.json(
225
+ {
226
+ busy: true,
227
+ sessionId,
228
+ queueLength: queuedMessages.length,
229
+ hint: "Use POST /api/queue/{sessionId} to queue this message, or wait for the current request to complete."
230
+ },
231
+ 202
232
+ );
233
+ }
237
234
  const abortController = new AbortController();
238
235
  const { signal } = abortController;
239
- const iterator = await agent.stream(message, {
240
- sessionId,
241
- imageData: imageDataInput,
242
- fileData: fileDataInput,
243
- signal
244
- });
236
+ const iterator = await agent.stream(content, sessionId, { signal });
245
237
  return (0, import_streaming.streamSSE)(ctx, async (stream) => {
246
238
  const pendingApprovalEvents = [];
247
239
  if (approvalCoordinator) {
@@ -1,49 +1,55 @@
1
1
  import { OpenAPIHono } from '@hono/zod-openapi';
2
- import type { DextoAgent } from '@dexto/core';
3
2
  import type { ApprovalCoordinator } from '../../approval/approval-coordinator.js';
4
- export declare function createMessagesRouter(getAgent: () => DextoAgent, approvalCoordinator?: ApprovalCoordinator): OpenAPIHono<import("hono").Env, {
3
+ import type { GetAgentFn } from '../index.js';
4
+ export declare function createMessagesRouter(getAgent: GetAgentFn, approvalCoordinator?: ApprovalCoordinator): OpenAPIHono<import("hono").Env, {
5
5
  "/message": {
6
6
  $post: {
7
7
  input: {
8
8
  json: {
9
- sessionId: string;
10
- message?: string | undefined;
11
- imageData?: {
9
+ content: string | ({
10
+ type: "text";
11
+ text: string;
12
+ } | {
13
+ type: "image";
12
14
  image: string;
13
- mimeType: string;
14
- } | undefined;
15
- fileData?: {
15
+ mimeType?: string | undefined;
16
+ } | {
17
+ type: "file";
16
18
  mimeType: string;
17
19
  data: string;
18
20
  filename?: string | undefined;
19
- } | undefined;
21
+ })[];
22
+ sessionId: string;
20
23
  };
21
24
  };
22
- output: {
23
- sessionId: string;
24
- accepted: true;
25
- };
26
- outputFormat: "json";
27
- status: 202;
25
+ output: {};
26
+ outputFormat: string;
27
+ status: 400;
28
28
  } | {
29
29
  input: {
30
30
  json: {
31
- sessionId: string;
32
- message?: string | undefined;
33
- imageData?: {
31
+ content: string | ({
32
+ type: "text";
33
+ text: string;
34
+ } | {
35
+ type: "image";
34
36
  image: string;
35
- mimeType: string;
36
- } | undefined;
37
- fileData?: {
37
+ mimeType?: string | undefined;
38
+ } | {
39
+ type: "file";
38
40
  mimeType: string;
39
41
  data: string;
40
42
  filename?: string | undefined;
41
- } | undefined;
43
+ })[];
44
+ sessionId: string;
42
45
  };
43
46
  };
44
- output: {};
45
- outputFormat: string;
46
- status: 400;
47
+ output: {
48
+ sessionId: string;
49
+ accepted: true;
50
+ };
51
+ outputFormat: "json";
52
+ status: 202;
47
53
  };
48
54
  };
49
55
  } & {
@@ -51,17 +57,20 @@ export declare function createMessagesRouter(getAgent: () => DextoAgent, approva
51
57
  $post: {
52
58
  input: {
53
59
  json: {
54
- sessionId: string;
55
- message?: string | undefined;
56
- imageData?: {
60
+ content: string | ({
61
+ type: "text";
62
+ text: string;
63
+ } | {
64
+ type: "image";
57
65
  image: string;
58
- mimeType: string;
59
- } | undefined;
60
- fileData?: {
66
+ mimeType?: string | undefined;
67
+ } | {
68
+ type: "file";
61
69
  mimeType: string;
62
70
  data: string;
63
71
  filename?: string | undefined;
64
- } | undefined;
72
+ })[];
73
+ sessionId: string;
65
74
  };
66
75
  };
67
76
  output: {};
@@ -70,17 +79,20 @@ export declare function createMessagesRouter(getAgent: () => DextoAgent, approva
70
79
  } | {
71
80
  input: {
72
81
  json: {
73
- sessionId: string;
74
- message?: string | undefined;
75
- imageData?: {
82
+ content: string | ({
83
+ type: "text";
84
+ text: string;
85
+ } | {
86
+ type: "image";
76
87
  image: string;
77
- mimeType: string;
78
- } | undefined;
79
- fileData?: {
88
+ mimeType?: string | undefined;
89
+ } | {
90
+ type: "file";
80
91
  mimeType: string;
81
92
  data: string;
82
93
  filename?: string | undefined;
83
- } | undefined;
94
+ })[];
95
+ sessionId: string;
84
96
  };
85
97
  };
86
98
  output: {
@@ -94,8 +106,7 @@ export declare function createMessagesRouter(getAgent: () => DextoAgent, approva
94
106
  totalTokens?: number | undefined;
95
107
  } | undefined;
96
108
  model?: string | undefined;
97
- provider?: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | undefined;
98
- router?: "vercel" | "in-built" | undefined;
109
+ provider?: "openai" | "openai-compatible" | "anthropic" | "google" | "groq" | "xai" | "cohere" | "openrouter" | "litellm" | "glama" | "vertex" | "bedrock" | "local" | "ollama" | undefined;
99
110
  };
100
111
  outputFormat: "json";
101
112
  status: 200;
@@ -122,17 +133,20 @@ export declare function createMessagesRouter(getAgent: () => DextoAgent, approva
122
133
  $post: {
123
134
  input: {
124
135
  json: {
125
- sessionId: string;
126
- message?: string | undefined;
127
- imageData?: {
136
+ content: string | ({
137
+ type: "text";
138
+ text: string;
139
+ } | {
140
+ type: "image";
128
141
  image: string;
129
- mimeType: string;
130
- } | undefined;
131
- fileData?: {
142
+ mimeType?: string | undefined;
143
+ } | {
144
+ type: "file";
132
145
  mimeType: string;
133
146
  data: string;
134
147
  filename?: string | undefined;
135
- } | undefined;
148
+ })[];
149
+ sessionId: string;
136
150
  };
137
151
  };
138
152
  output: Response;
@@ -141,22 +155,52 @@ export declare function createMessagesRouter(getAgent: () => DextoAgent, approva
141
155
  } | {
142
156
  input: {
143
157
  json: {
144
- sessionId: string;
145
- message?: string | undefined;
146
- imageData?: {
158
+ content: string | ({
159
+ type: "text";
160
+ text: string;
161
+ } | {
162
+ type: "image";
147
163
  image: string;
148
- mimeType: string;
149
- } | undefined;
150
- fileData?: {
164
+ mimeType?: string | undefined;
165
+ } | {
166
+ type: "file";
151
167
  mimeType: string;
152
168
  data: string;
153
169
  filename?: string | undefined;
154
- } | undefined;
170
+ })[];
171
+ sessionId: string;
155
172
  };
156
173
  };
157
174
  output: {};
158
175
  outputFormat: string;
159
176
  status: 400;
177
+ } | {
178
+ input: {
179
+ json: {
180
+ content: string | ({
181
+ type: "text";
182
+ text: string;
183
+ } | {
184
+ type: "image";
185
+ image: string;
186
+ mimeType?: string | undefined;
187
+ } | {
188
+ type: "file";
189
+ mimeType: string;
190
+ data: string;
191
+ filename?: string | undefined;
192
+ })[];
193
+ sessionId: string;
194
+ };
195
+ };
196
+ output: {
197
+ sessionId: string;
198
+ busy: true;
199
+ queueLength: number;
200
+ hint: string;
201
+ };
202
+ outputFormat: "json";
203
+ status: 202;
160
204
  };
161
205
  };
162
206
  }, "/">;
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/hono/routes/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AA4ClF,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,MAAM,UAAU,EAC1B,mBAAmB,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoW5C"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/hono/routes/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAIhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAElF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA0D9C,wBAAgB,oBAAoB,CAChC,QAAQ,EAAE,UAAU,EACpB,mBAAmB,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA0U5C"}
@@ -1,26 +1,27 @@
1
1
  import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
2
2
  import { streamSSE } from "hono/streaming";
3
- import { LLM_PROVIDERS, LLM_ROUTERS } from "@dexto/core";
3
+ import { LLM_PROVIDERS } from "@dexto/core";
4
4
  import { TokenUsageSchema } from "../schemas/responses.js";
5
+ const TextPartSchema = z.object({
6
+ type: z.literal("text").describe("Content type identifier"),
7
+ text: z.string().describe("Text content")
8
+ }).describe("Text content part");
9
+ const ImagePartSchema = z.object({
10
+ type: z.literal("image").describe("Content type identifier"),
11
+ image: z.string().describe("Base64-encoded image data or URL"),
12
+ mimeType: z.string().optional().describe("MIME type (e.g., image/png)")
13
+ }).describe("Image content part");
14
+ const FilePartSchema = z.object({
15
+ type: z.literal("file").describe("Content type identifier"),
16
+ data: z.string().describe("Base64-encoded file data or URL"),
17
+ mimeType: z.string().describe("MIME type (e.g., application/pdf)"),
18
+ filename: z.string().optional().describe("Optional filename")
19
+ }).describe("File content part");
20
+ const ContentPartSchema = z.discriminatedUnion("type", [TextPartSchema, ImagePartSchema, FilePartSchema]).describe("Content part - text, image, or file");
5
21
  const MessageBodySchema = z.object({
6
- message: z.string().optional().describe("The user message text"),
7
- sessionId: z.string().min(1, "Session ID is required").describe("The session to use for this message"),
8
- imageData: z.object({
9
- image: z.string().describe("Base64-encoded image data"),
10
- mimeType: z.string().describe("The MIME type of the image (e.g., image/png)")
11
- }).optional().describe("Optional image data to include with the message"),
12
- fileData: z.object({
13
- data: z.string().describe("Base64-encoded file data"),
14
- mimeType: z.string().describe("The MIME type of the file (e.g., application/pdf)"),
15
- filename: z.string().optional().describe("The filename")
16
- }).optional().describe("Optional file data to include with the message")
17
- }).refine(
18
- (data) => {
19
- const msg = (data.message ?? "").trim();
20
- return msg.length > 0 || !!data.imageData || !!data.fileData;
21
- },
22
- { message: "Must provide either message text, image data, or file data" }
23
- ).describe("Request body for sending a message to the agent");
22
+ content: z.union([z.string(), z.array(ContentPartSchema)]).describe("Message content - string for text, or ContentPart[] for multimodal"),
23
+ sessionId: z.string().min(1, "Session ID is required").describe("The session to use for this message")
24
+ }).describe("Request body for sending a message to the agent");
24
25
  const ResetBodySchema = z.object({
25
26
  sessionId: z.string().min(1, "Session ID is required").describe("The ID of the session to reset")
26
27
  }).describe("Request body for resetting a conversation");
@@ -72,8 +73,7 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
72
73
  tokenUsage: TokenUsageSchema.optional().describe("Token usage statistics"),
73
74
  reasoning: z.string().optional().describe("Extended thinking content from reasoning models"),
74
75
  model: z.string().optional().describe("Model used for this response"),
75
- provider: z.enum(LLM_PROVIDERS).optional().describe("LLM provider"),
76
- router: z.enum(LLM_ROUTERS).optional().describe("Router used (e.g., vercel)")
76
+ provider: z.enum(LLM_PROVIDERS).optional().describe("LLM provider")
77
77
  }).strict()
78
78
  }
79
79
  }
@@ -108,7 +108,7 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
108
108
  method: "post",
109
109
  path: "/message-stream",
110
110
  summary: "Stream message response",
111
- description: "Sends a message and streams the response via Server-Sent Events (SSE). Returns SSE stream directly in response. Events include llm:thinking, llm:chunk, llm:tool-call, llm:tool-result, llm:response, and llm:error.",
111
+ description: "Sends a message and streams the response via Server-Sent Events (SSE). Returns SSE stream directly in response. Events include llm:thinking, llm:chunk, llm:tool-call, llm:tool-result, llm:response, and llm:error. If the session is busy processing another message, returns 202 with queue information.",
112
112
  tags: ["messages"],
113
113
  request: {
114
114
  body: {
@@ -144,46 +144,39 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
144
144
  }
145
145
  }
146
146
  },
147
+ 202: {
148
+ description: "Session is busy processing another message. Use the queue endpoints to manage pending messages.",
149
+ content: {
150
+ "application/json": {
151
+ schema: z.object({
152
+ busy: z.literal(true).describe("Indicates session is busy"),
153
+ sessionId: z.string().describe("The session ID"),
154
+ queueLength: z.number().describe("Current number of messages in queue"),
155
+ hint: z.string().describe("Instructions for the client")
156
+ }).strict()
157
+ }
158
+ }
159
+ },
147
160
  400: { description: "Validation error" }
148
161
  }
149
162
  });
150
163
  return app.openapi(messageRoute, async (ctx) => {
151
- const agent = getAgent();
164
+ const agent = await getAgent(ctx);
152
165
  agent.logger.info("Received message via POST /api/message");
153
- const { message, sessionId, imageData, fileData } = ctx.req.valid("json");
154
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
155
- const fileDataInput = fileData ? {
156
- data: fileData.data,
157
- mimeType: fileData.mimeType,
158
- ...fileData.filename && { filename: fileData.filename }
159
- } : void 0;
160
- if (imageDataInput) agent.logger.info("Image data included in message.");
161
- if (fileDataInput) agent.logger.info("File data included in message.");
166
+ const { content, sessionId } = ctx.req.valid("json");
162
167
  agent.logger.info(`Message for session: ${sessionId}`);
163
- agent.run(message || "", imageDataInput, fileDataInput, sessionId, false).catch((error) => {
168
+ agent.generate(content, sessionId).catch((error) => {
164
169
  agent.logger.error(
165
170
  `Error in async message processing: ${error instanceof Error ? error.message : String(error)}`
166
171
  );
167
172
  });
168
173
  return ctx.json({ accepted: true, sessionId }, 202);
169
174
  }).openapi(messageSyncRoute, async (ctx) => {
170
- const agent = getAgent();
175
+ const agent = await getAgent(ctx);
171
176
  agent.logger.info("Received message via POST /api/message-sync");
172
- const { message, sessionId, imageData, fileData } = ctx.req.valid("json");
173
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
174
- const fileDataInput = fileData ? {
175
- data: fileData.data,
176
- mimeType: fileData.mimeType,
177
- ...fileData.filename && { filename: fileData.filename }
178
- } : void 0;
179
- if (imageDataInput) agent.logger.info("Image data included in message.");
180
- if (fileDataInput) agent.logger.info("File data included in message.");
177
+ const { content, sessionId } = ctx.req.valid("json");
181
178
  agent.logger.info(`Message for session: ${sessionId}`);
182
- const result = await agent.generate(message || "", {
183
- sessionId,
184
- imageData: imageDataInput,
185
- fileData: fileDataInput
186
- });
179
+ const result = await agent.generate(content, sessionId);
187
180
  const llmConfig = agent.stateManager.getLLMConfig(sessionId);
188
181
  return ctx.json({
189
182
  response: result.content,
@@ -191,34 +184,33 @@ function createMessagesRouter(getAgent, approvalCoordinator) {
191
184
  tokenUsage: result.usage,
192
185
  reasoning: result.reasoning,
193
186
  model: llmConfig.model,
194
- provider: llmConfig.provider,
195
- router: "vercel"
196
- // Hardcoded for now since we only use Vercel AI SDK
187
+ provider: llmConfig.provider
197
188
  });
198
189
  }).openapi(resetRoute, async (ctx) => {
199
- const agent = getAgent();
190
+ const agent = await getAgent(ctx);
200
191
  agent.logger.info("Received request via POST /api/reset");
201
192
  const { sessionId } = ctx.req.valid("json");
202
193
  await agent.resetConversation(sessionId);
203
194
  return ctx.json({ status: "reset initiated", sessionId });
204
195
  }).openapi(messageStreamRoute, async (ctx) => {
205
- const agent = getAgent();
206
- const body = ctx.req.valid("json");
207
- const { message = "", sessionId, imageData, fileData } = body;
208
- const imageDataInput = imageData ? { image: imageData.image, mimeType: imageData.mimeType } : void 0;
209
- const fileDataInput = fileData ? {
210
- data: fileData.data,
211
- mimeType: fileData.mimeType,
212
- ...fileData.filename && { filename: fileData.filename }
213
- } : void 0;
196
+ const agent = await getAgent(ctx);
197
+ const { content, sessionId } = ctx.req.valid("json");
198
+ const isBusy = await agent.isSessionBusy(sessionId);
199
+ if (isBusy) {
200
+ const queuedMessages = await agent.getQueuedMessages(sessionId);
201
+ return ctx.json(
202
+ {
203
+ busy: true,
204
+ sessionId,
205
+ queueLength: queuedMessages.length,
206
+ hint: "Use POST /api/queue/{sessionId} to queue this message, or wait for the current request to complete."
207
+ },
208
+ 202
209
+ );
210
+ }
214
211
  const abortController = new AbortController();
215
212
  const { signal } = abortController;
216
- const iterator = await agent.stream(message, {
217
- sessionId,
218
- imageData: imageDataInput,
219
- fileData: fileDataInput,
220
- signal
221
- });
213
+ const iterator = await agent.stream(content, sessionId, { signal });
222
214
  return streamSSE(ctx, async (stream) => {
223
215
  const pendingApprovalEvents = [];
224
216
  if (approvalCoordinator) {