@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.
- package/dist/approval/manual-approval-handler.cjs +23 -15
- package/dist/approval/manual-approval-handler.d.ts.map +1 -1
- package/dist/approval/manual-approval-handler.js +23 -15
- package/dist/events/webhook-subscriber.cjs +1 -1
- package/dist/events/webhook-subscriber.d.ts.map +1 -1
- package/dist/events/webhook-subscriber.js +1 -1
- package/dist/hono/__tests__/test-fixtures.cjs +3 -3
- package/dist/hono/__tests__/test-fixtures.d.ts.map +1 -1
- package/dist/hono/__tests__/test-fixtures.js +3 -3
- package/dist/hono/index.cjs +46 -5
- package/dist/hono/index.d.ts +928 -584
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js +46 -5
- package/dist/hono/middleware/error.d.ts.map +1 -1
- package/dist/hono/routes/a2a-jsonrpc.cjs +3 -3
- package/dist/hono/routes/a2a-jsonrpc.d.ts +4 -1
- package/dist/hono/routes/a2a-jsonrpc.d.ts.map +1 -1
- package/dist/hono/routes/a2a-jsonrpc.js +3 -3
- package/dist/hono/routes/a2a-tasks.cjs +5 -5
- package/dist/hono/routes/a2a-tasks.d.ts +13 -10
- package/dist/hono/routes/a2a-tasks.d.ts.map +1 -1
- package/dist/hono/routes/a2a-tasks.js +5 -5
- package/dist/hono/routes/agents.cjs +30 -42
- package/dist/hono/routes/agents.d.ts +7 -401
- package/dist/hono/routes/agents.d.ts.map +1 -1
- package/dist/hono/routes/agents.js +32 -42
- package/dist/hono/routes/approvals.cjs +53 -2
- package/dist/hono/routes/approvals.d.ts +29 -1
- package/dist/hono/routes/approvals.d.ts.map +1 -1
- package/dist/hono/routes/approvals.js +53 -2
- package/dist/hono/routes/discovery.cjs +67 -0
- package/dist/hono/routes/discovery.d.ts +44 -0
- package/dist/hono/routes/discovery.d.ts.map +1 -0
- package/dist/hono/routes/discovery.js +43 -0
- package/dist/hono/routes/greeting.cjs +2 -2
- package/dist/hono/routes/greeting.d.ts +2 -2
- package/dist/hono/routes/greeting.d.ts.map +1 -1
- package/dist/hono/routes/greeting.js +2 -2
- package/dist/hono/routes/health.d.ts +2 -2
- package/dist/hono/routes/health.d.ts.map +1 -1
- package/dist/hono/routes/key.cjs +110 -0
- package/dist/hono/routes/key.d.ts +48 -0
- package/dist/hono/routes/key.d.ts.map +1 -0
- package/dist/hono/routes/key.js +90 -0
- package/dist/hono/routes/llm.cjs +119 -62
- package/dist/hono/routes/llm.d.ts +242 -42
- package/dist/hono/routes/llm.d.ts.map +1 -1
- package/dist/hono/routes/llm.js +118 -58
- package/dist/hono/routes/mcp.cjs +16 -12
- package/dist/hono/routes/mcp.d.ts +6 -3
- package/dist/hono/routes/mcp.d.ts.map +1 -1
- package/dist/hono/routes/mcp.js +17 -13
- package/dist/hono/routes/memory.cjs +5 -5
- package/dist/hono/routes/memory.d.ts +5 -2
- package/dist/hono/routes/memory.d.ts.map +1 -1
- package/dist/hono/routes/memory.js +5 -5
- package/dist/hono/routes/messages.cjs +58 -66
- package/dist/hono/routes/messages.d.ts +99 -55
- package/dist/hono/routes/messages.d.ts.map +1 -1
- package/dist/hono/routes/messages.js +59 -67
- package/dist/hono/routes/models.cjs +319 -0
- package/dist/hono/routes/models.d.ts +107 -0
- package/dist/hono/routes/models.d.ts.map +1 -0
- package/dist/hono/routes/models.js +305 -0
- package/dist/hono/routes/openrouter.cjs +153 -0
- package/dist/hono/routes/openrouter.d.ts +54 -0
- package/dist/hono/routes/openrouter.d.ts.map +1 -0
- package/dist/hono/routes/openrouter.js +134 -0
- package/dist/hono/routes/prompts.cjs +5 -5
- package/dist/hono/routes/prompts.d.ts +10 -7
- package/dist/hono/routes/prompts.d.ts.map +1 -1
- package/dist/hono/routes/prompts.js +5 -5
- package/dist/hono/routes/queue.cjs +202 -0
- package/dist/hono/routes/queue.d.ts +174 -0
- package/dist/hono/routes/queue.d.ts.map +1 -0
- package/dist/hono/routes/queue.js +178 -0
- package/dist/hono/routes/resources.cjs +3 -3
- package/dist/hono/routes/resources.d.ts +3 -3
- package/dist/hono/routes/resources.d.ts.map +1 -1
- package/dist/hono/routes/resources.js +3 -3
- package/dist/hono/routes/search.cjs +2 -2
- package/dist/hono/routes/search.d.ts +39 -10
- package/dist/hono/routes/search.d.ts.map +1 -1
- package/dist/hono/routes/search.js +2 -2
- package/dist/hono/routes/sessions.cjs +74 -20
- package/dist/hono/routes/sessions.d.ts +25 -4
- package/dist/hono/routes/sessions.d.ts.map +1 -1
- package/dist/hono/routes/sessions.js +74 -20
- package/dist/hono/routes/tools.cjs +126 -0
- package/dist/hono/routes/tools.d.ts +42 -0
- package/dist/hono/routes/tools.d.ts.map +1 -0
- package/dist/hono/routes/tools.js +102 -0
- package/dist/hono/routes/webhooks.cjs +4 -4
- package/dist/hono/routes/webhooks.d.ts +4 -1
- package/dist/hono/routes/webhooks.d.ts.map +1 -1
- package/dist/hono/routes/webhooks.js +4 -4
- package/dist/hono/schemas/responses.cjs +24 -5
- package/dist/hono/schemas/responses.d.ts +838 -120
- package/dist/hono/schemas/responses.d.ts.map +1 -1
- package/dist/hono/schemas/responses.js +24 -10
- package/dist/hono/start-server.cjs +102 -0
- package/dist/hono/start-server.d.ts +61 -0
- package/dist/hono/start-server.d.ts.map +1 -0
- package/dist/hono/start-server.js +78 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- 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
|
-
|
|
30
|
-
sessionId: import_zod_openapi.z.string().min(1, "Session ID is required").describe("The session to use for this message")
|
|
31
|
-
|
|
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 {
|
|
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.
|
|
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 {
|
|
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(
|
|
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
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
content: string | ({
|
|
10
|
+
type: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: "image";
|
|
12
14
|
image: string;
|
|
13
|
-
mimeType
|
|
14
|
-
} |
|
|
15
|
-
|
|
15
|
+
mimeType?: string | undefined;
|
|
16
|
+
} | {
|
|
17
|
+
type: "file";
|
|
16
18
|
mimeType: string;
|
|
17
19
|
data: string;
|
|
18
20
|
filename?: string | undefined;
|
|
19
|
-
}
|
|
21
|
+
})[];
|
|
22
|
+
sessionId: string;
|
|
20
23
|
};
|
|
21
24
|
};
|
|
22
|
-
output: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
outputFormat: "json";
|
|
27
|
-
status: 202;
|
|
25
|
+
output: {};
|
|
26
|
+
outputFormat: string;
|
|
27
|
+
status: 400;
|
|
28
28
|
} | {
|
|
29
29
|
input: {
|
|
30
30
|
json: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
content: string | ({
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "image";
|
|
34
36
|
image: string;
|
|
35
|
-
mimeType
|
|
36
|
-
} |
|
|
37
|
-
|
|
37
|
+
mimeType?: string | undefined;
|
|
38
|
+
} | {
|
|
39
|
+
type: "file";
|
|
38
40
|
mimeType: string;
|
|
39
41
|
data: string;
|
|
40
42
|
filename?: string | undefined;
|
|
41
|
-
}
|
|
43
|
+
})[];
|
|
44
|
+
sessionId: string;
|
|
42
45
|
};
|
|
43
46
|
};
|
|
44
|
-
output: {
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
content: string | ({
|
|
61
|
+
type: "text";
|
|
62
|
+
text: string;
|
|
63
|
+
} | {
|
|
64
|
+
type: "image";
|
|
57
65
|
image: string;
|
|
58
|
-
mimeType
|
|
59
|
-
} |
|
|
60
|
-
|
|
66
|
+
mimeType?: string | undefined;
|
|
67
|
+
} | {
|
|
68
|
+
type: "file";
|
|
61
69
|
mimeType: string;
|
|
62
70
|
data: string;
|
|
63
71
|
filename?: string | undefined;
|
|
64
|
-
}
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
content: string | ({
|
|
83
|
+
type: "text";
|
|
84
|
+
text: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: "image";
|
|
76
87
|
image: string;
|
|
77
|
-
mimeType
|
|
78
|
-
} |
|
|
79
|
-
|
|
88
|
+
mimeType?: string | undefined;
|
|
89
|
+
} | {
|
|
90
|
+
type: "file";
|
|
80
91
|
mimeType: string;
|
|
81
92
|
data: string;
|
|
82
93
|
filename?: string | undefined;
|
|
83
|
-
}
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
content: string | ({
|
|
137
|
+
type: "text";
|
|
138
|
+
text: string;
|
|
139
|
+
} | {
|
|
140
|
+
type: "image";
|
|
128
141
|
image: string;
|
|
129
|
-
mimeType
|
|
130
|
-
} |
|
|
131
|
-
|
|
142
|
+
mimeType?: string | undefined;
|
|
143
|
+
} | {
|
|
144
|
+
type: "file";
|
|
132
145
|
mimeType: string;
|
|
133
146
|
data: string;
|
|
134
147
|
filename?: string | undefined;
|
|
135
|
-
}
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
content: string | ({
|
|
159
|
+
type: "text";
|
|
160
|
+
text: string;
|
|
161
|
+
} | {
|
|
162
|
+
type: "image";
|
|
147
163
|
image: string;
|
|
148
|
-
mimeType
|
|
149
|
-
} |
|
|
150
|
-
|
|
164
|
+
mimeType?: string | undefined;
|
|
165
|
+
} | {
|
|
166
|
+
type: "file";
|
|
151
167
|
mimeType: string;
|
|
152
168
|
data: string;
|
|
153
169
|
filename?: string | undefined;
|
|
154
|
-
}
|
|
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;
|
|
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
|
|
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
|
-
|
|
7
|
-
sessionId: z.string().min(1, "Session ID is required").describe("The session to use for this message")
|
|
8
|
-
|
|
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 {
|
|
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.
|
|
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 {
|
|
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(
|
|
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
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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(
|
|
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) {
|