@dexto/server 1.4.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/hono/__tests__/test-fixtures.cjs +1 -1
- package/dist/hono/__tests__/test-fixtures.d.ts.map +1 -1
- package/dist/hono/__tests__/test-fixtures.js +1 -1
- package/dist/hono/index.cjs +41 -5
- package/dist/hono/index.d.ts +451 -452
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js +41 -5
- 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 +25 -35
- package/dist/hono/routes/agents.d.ts +6 -407
- package/dist/hono/routes/agents.d.ts.map +1 -1
- package/dist/hono/routes/agents.js +25 -35
- package/dist/hono/routes/approvals.cjs +2 -2
- package/dist/hono/routes/approvals.d.ts +4 -1
- package/dist/hono/routes/approvals.d.ts.map +1 -1
- package/dist/hono/routes/approvals.js +2 -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 +12 -34
- package/dist/hono/routes/llm.d.ts +173 -25
- package/dist/hono/routes/llm.d.ts.map +1 -1
- package/dist/hono/routes/llm.js +12 -35
- package/dist/hono/routes/mcp.cjs +8 -8
- package/dist/hono/routes/mcp.d.ts +2 -2
- package/dist/hono/routes/mcp.d.ts.map +1 -1
- package/dist/hono/routes/mcp.js +8 -8
- package/dist/hono/routes/memory.cjs +5 -5
- package/dist/hono/routes/memory.d.ts +4 -1
- package/dist/hono/routes/memory.d.ts.map +1 -1
- package/dist/hono/routes/memory.js +5 -5
- package/dist/hono/routes/messages.cjs +4 -4
- package/dist/hono/routes/messages.d.ts +12 -12
- package/dist/hono/routes/messages.d.ts.map +1 -1
- package/dist/hono/routes/messages.js +4 -4
- 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 +4 -1
- package/dist/hono/routes/prompts.d.ts.map +1 -1
- package/dist/hono/routes/prompts.js +5 -5
- package/dist/hono/routes/queue.cjs +4 -4
- package/dist/hono/routes/queue.d.ts +4 -1
- package/dist/hono/routes/queue.d.ts.map +1 -1
- package/dist/hono/routes/queue.js +4 -4
- package/dist/hono/routes/resources.cjs +3 -3
- package/dist/hono/routes/resources.d.ts +2 -2
- 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 +6 -3
- package/dist/hono/routes/search.d.ts.map +1 -1
- package/dist/hono/routes/search.js +2 -2
- package/dist/hono/routes/sessions.cjs +9 -9
- package/dist/hono/routes/sessions.d.ts +3 -3
- package/dist/hono/routes/sessions.d.ts.map +1 -1
- package/dist/hono/routes/sessions.js +9 -9
- 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.d.ts +41 -41
- 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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import {
|
|
4
|
+
getLocalModelById,
|
|
5
|
+
listOllamaModels,
|
|
6
|
+
DEFAULT_OLLAMA_URL,
|
|
7
|
+
checkOllamaStatus,
|
|
8
|
+
logger
|
|
9
|
+
} from "@dexto/core";
|
|
10
|
+
import {
|
|
11
|
+
getAllInstalledModels,
|
|
12
|
+
getInstalledModel,
|
|
13
|
+
removeInstalledModel
|
|
14
|
+
} from "@dexto/agent-management";
|
|
15
|
+
const LocalModelSchema = z.object({
|
|
16
|
+
id: z.string().describe("Model identifier"),
|
|
17
|
+
displayName: z.string().describe("Human-readable model name"),
|
|
18
|
+
filePath: z.string().describe("Absolute path to the GGUF file"),
|
|
19
|
+
sizeBytes: z.number().describe("File size in bytes"),
|
|
20
|
+
contextLength: z.number().optional().describe("Maximum context length in tokens"),
|
|
21
|
+
source: z.enum(["huggingface", "manual"]).optional().describe("Where the model was downloaded from")
|
|
22
|
+
}).describe("An installed local GGUF model");
|
|
23
|
+
const OllamaModelSchema = z.object({
|
|
24
|
+
name: z.string().describe("Ollama model name (e.g., llama3.2:latest)"),
|
|
25
|
+
size: z.number().optional().describe("Model size in bytes"),
|
|
26
|
+
digest: z.string().optional().describe("Model digest/hash"),
|
|
27
|
+
modifiedAt: z.string().optional().describe("Last modified timestamp")
|
|
28
|
+
}).describe("An Ollama model");
|
|
29
|
+
const ValidateFileRequestSchema = z.object({
|
|
30
|
+
filePath: z.string().min(1).describe("Absolute path to the GGUF file to validate")
|
|
31
|
+
}).describe("File validation request");
|
|
32
|
+
const ValidateFileResponseSchema = z.object({
|
|
33
|
+
valid: z.boolean().describe("Whether the file exists and is readable"),
|
|
34
|
+
sizeBytes: z.number().optional().describe("File size in bytes if valid"),
|
|
35
|
+
error: z.string().optional().describe("Error message if invalid")
|
|
36
|
+
}).describe("File validation response");
|
|
37
|
+
const listLocalModelsRoute = createRoute({
|
|
38
|
+
method: "get",
|
|
39
|
+
path: "/models/local",
|
|
40
|
+
summary: "List Local Models",
|
|
41
|
+
description: "Returns all installed local GGUF models from ~/.dexto/models/state.json. These are models downloaded from HuggingFace or manually registered.",
|
|
42
|
+
tags: ["models"],
|
|
43
|
+
responses: {
|
|
44
|
+
200: {
|
|
45
|
+
description: "List of installed local models",
|
|
46
|
+
content: {
|
|
47
|
+
"application/json": {
|
|
48
|
+
schema: z.object({
|
|
49
|
+
models: z.array(LocalModelSchema).describe("List of installed local models")
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const listOllamaModelsRoute = createRoute({
|
|
57
|
+
method: "get",
|
|
58
|
+
path: "/models/ollama",
|
|
59
|
+
summary: "List Ollama Models",
|
|
60
|
+
description: "Returns available models from the local Ollama server. Returns empty list with available=false if Ollama is not running.",
|
|
61
|
+
tags: ["models"],
|
|
62
|
+
request: {
|
|
63
|
+
query: z.object({
|
|
64
|
+
baseURL: z.string().url().optional().describe(`Ollama server URL (default: ${DEFAULT_OLLAMA_URL})`)
|
|
65
|
+
})
|
|
66
|
+
},
|
|
67
|
+
responses: {
|
|
68
|
+
200: {
|
|
69
|
+
description: "List of Ollama models",
|
|
70
|
+
content: {
|
|
71
|
+
"application/json": {
|
|
72
|
+
schema: z.object({
|
|
73
|
+
available: z.boolean().describe("Whether Ollama server is running"),
|
|
74
|
+
version: z.string().optional().describe("Ollama server version"),
|
|
75
|
+
models: z.array(OllamaModelSchema).describe("List of available Ollama models"),
|
|
76
|
+
error: z.string().optional().describe("Error message if Ollama not available")
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const validateLocalFileRoute = createRoute({
|
|
84
|
+
method: "post",
|
|
85
|
+
path: "/models/local/validate",
|
|
86
|
+
summary: "Validate GGUF File",
|
|
87
|
+
description: "Validates that a GGUF file exists and is readable. Used by Web UI to validate custom file paths before saving.",
|
|
88
|
+
tags: ["models"],
|
|
89
|
+
request: {
|
|
90
|
+
body: {
|
|
91
|
+
content: {
|
|
92
|
+
"application/json": {
|
|
93
|
+
schema: ValidateFileRequestSchema
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
responses: {
|
|
99
|
+
200: {
|
|
100
|
+
description: "Validation result",
|
|
101
|
+
content: {
|
|
102
|
+
"application/json": {
|
|
103
|
+
schema: ValidateFileResponseSchema
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const DeleteModelRequestSchema = z.object({
|
|
110
|
+
deleteFile: z.boolean().default(true).describe("Whether to also delete the GGUF file from disk")
|
|
111
|
+
}).describe("Delete model request options");
|
|
112
|
+
const DeleteModelResponseSchema = z.object({
|
|
113
|
+
success: z.boolean().describe("Whether the deletion was successful"),
|
|
114
|
+
modelId: z.string().describe("The deleted model ID"),
|
|
115
|
+
fileDeleted: z.boolean().describe("Whether the GGUF file was deleted"),
|
|
116
|
+
error: z.string().optional().describe("Error message if deletion failed")
|
|
117
|
+
}).describe("Delete model response");
|
|
118
|
+
const deleteLocalModelRoute = createRoute({
|
|
119
|
+
method: "delete",
|
|
120
|
+
path: "/models/local/{modelId}",
|
|
121
|
+
summary: "Delete Installed Model",
|
|
122
|
+
description: "Removes an installed local model from state.json. Optionally deletes the GGUF file from disk (default: true).",
|
|
123
|
+
tags: ["models"],
|
|
124
|
+
request: {
|
|
125
|
+
params: z.object({
|
|
126
|
+
modelId: z.string().describe("The model ID to delete")
|
|
127
|
+
}),
|
|
128
|
+
body: {
|
|
129
|
+
content: {
|
|
130
|
+
"application/json": {
|
|
131
|
+
schema: DeleteModelRequestSchema
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
required: false
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
responses: {
|
|
138
|
+
200: {
|
|
139
|
+
description: "Model deleted successfully",
|
|
140
|
+
content: {
|
|
141
|
+
"application/json": {
|
|
142
|
+
schema: DeleteModelResponseSchema
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
404: {
|
|
147
|
+
description: "Model not found",
|
|
148
|
+
content: {
|
|
149
|
+
"application/json": {
|
|
150
|
+
schema: DeleteModelResponseSchema
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
function createModelsRouter() {
|
|
157
|
+
const app = new OpenAPIHono();
|
|
158
|
+
return app.openapi(listLocalModelsRoute, async (ctx) => {
|
|
159
|
+
const installedModels = await getAllInstalledModels();
|
|
160
|
+
const models = installedModels.map((model) => {
|
|
161
|
+
const registryInfo = getLocalModelById(model.id);
|
|
162
|
+
return {
|
|
163
|
+
id: model.id,
|
|
164
|
+
displayName: registryInfo?.name || model.id,
|
|
165
|
+
filePath: model.filePath,
|
|
166
|
+
sizeBytes: model.sizeBytes,
|
|
167
|
+
contextLength: registryInfo?.contextLength,
|
|
168
|
+
source: model.source
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
return ctx.json({ models });
|
|
172
|
+
}).openapi(listOllamaModelsRoute, async (ctx) => {
|
|
173
|
+
const { baseURL } = ctx.req.valid("query");
|
|
174
|
+
const ollamaURL = baseURL || DEFAULT_OLLAMA_URL;
|
|
175
|
+
try {
|
|
176
|
+
const status = await checkOllamaStatus(ollamaURL);
|
|
177
|
+
if (!status.running) {
|
|
178
|
+
return ctx.json({
|
|
179
|
+
available: false,
|
|
180
|
+
models: [],
|
|
181
|
+
error: "Ollama server is not running"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const ollamaModels = await listOllamaModels(ollamaURL);
|
|
185
|
+
return ctx.json({
|
|
186
|
+
available: true,
|
|
187
|
+
version: status.version,
|
|
188
|
+
models: ollamaModels.map((m) => ({
|
|
189
|
+
name: m.name,
|
|
190
|
+
size: m.size,
|
|
191
|
+
digest: m.digest,
|
|
192
|
+
modifiedAt: m.modifiedAt
|
|
193
|
+
}))
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return ctx.json({
|
|
197
|
+
available: false,
|
|
198
|
+
models: [],
|
|
199
|
+
error: error instanceof Error ? error.message : "Failed to connect to Ollama server"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}).openapi(validateLocalFileRoute, async (ctx) => {
|
|
203
|
+
const { filePath } = ctx.req.valid("json");
|
|
204
|
+
if (!filePath.startsWith("/")) {
|
|
205
|
+
return ctx.json({
|
|
206
|
+
valid: false,
|
|
207
|
+
error: "File path must be absolute (start with /)"
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (!filePath.endsWith(".gguf")) {
|
|
211
|
+
return ctx.json({
|
|
212
|
+
valid: false,
|
|
213
|
+
error: "File must have .gguf extension"
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const stats = await fs.stat(filePath);
|
|
218
|
+
if (!stats.isFile()) {
|
|
219
|
+
return ctx.json({
|
|
220
|
+
valid: false,
|
|
221
|
+
error: "Path is not a file"
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
await fs.access(filePath, fs.constants.R_OK);
|
|
225
|
+
return ctx.json({
|
|
226
|
+
valid: true,
|
|
227
|
+
sizeBytes: stats.size
|
|
228
|
+
});
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const nodeError = error;
|
|
231
|
+
if (nodeError.code === "ENOENT") {
|
|
232
|
+
return ctx.json({
|
|
233
|
+
valid: false,
|
|
234
|
+
error: "File not found"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (nodeError.code === "EACCES") {
|
|
238
|
+
return ctx.json({
|
|
239
|
+
valid: false,
|
|
240
|
+
error: "File is not readable (permission denied)"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return ctx.json({
|
|
244
|
+
valid: false,
|
|
245
|
+
error: error instanceof Error ? error.message : "Failed to access file"
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}).openapi(deleteLocalModelRoute, async (ctx) => {
|
|
249
|
+
const { modelId } = ctx.req.valid("param");
|
|
250
|
+
let deleteFile = true;
|
|
251
|
+
try {
|
|
252
|
+
const body = await ctx.req.json();
|
|
253
|
+
if (body && typeof body.deleteFile === "boolean") {
|
|
254
|
+
deleteFile = body.deleteFile;
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
const model = await getInstalledModel(modelId);
|
|
259
|
+
if (!model) {
|
|
260
|
+
return ctx.json(
|
|
261
|
+
{
|
|
262
|
+
success: false,
|
|
263
|
+
modelId,
|
|
264
|
+
fileDeleted: false,
|
|
265
|
+
error: `Model '${modelId}' not found`
|
|
266
|
+
},
|
|
267
|
+
404
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const filePath = model.filePath;
|
|
271
|
+
let fileDeleted = false;
|
|
272
|
+
if (deleteFile && filePath) {
|
|
273
|
+
try {
|
|
274
|
+
await fs.unlink(filePath);
|
|
275
|
+
fileDeleted = true;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const nodeError = error;
|
|
278
|
+
if (nodeError.code === "ENOENT") {
|
|
279
|
+
fileDeleted = true;
|
|
280
|
+
} else {
|
|
281
|
+
logger.warn(
|
|
282
|
+
`Failed to delete GGUF file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const removed = await removeInstalledModel(modelId);
|
|
288
|
+
if (!removed) {
|
|
289
|
+
return ctx.json({
|
|
290
|
+
success: false,
|
|
291
|
+
modelId,
|
|
292
|
+
fileDeleted,
|
|
293
|
+
error: "Failed to remove model from state"
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return ctx.json({
|
|
297
|
+
success: true,
|
|
298
|
+
modelId,
|
|
299
|
+
fileDeleted
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
export {
|
|
304
|
+
createModelsRouter
|
|
305
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var openrouter_exports = {};
|
|
20
|
+
__export(openrouter_exports, {
|
|
21
|
+
createOpenRouterRouter: () => createOpenRouterRouter
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(openrouter_exports);
|
|
24
|
+
var import_zod_openapi = require("@hono/zod-openapi");
|
|
25
|
+
var import_core = require("@dexto/core");
|
|
26
|
+
const ValidateModelParamsSchema = import_zod_openapi.z.object({
|
|
27
|
+
modelId: import_zod_openapi.z.string().min(1).describe("OpenRouter model ID to validate (e.g., anthropic/claude-3.5-sonnet)")
|
|
28
|
+
}).describe("Path parameters for model validation");
|
|
29
|
+
const ValidateModelResponseSchema = import_zod_openapi.z.object({
|
|
30
|
+
valid: import_zod_openapi.z.boolean().describe("Whether the model ID is valid"),
|
|
31
|
+
modelId: import_zod_openapi.z.string().describe("The model ID that was validated"),
|
|
32
|
+
status: import_zod_openapi.z.enum(["valid", "invalid", "unknown"]).describe("Validation status: valid, invalid, or unknown (cache empty)"),
|
|
33
|
+
error: import_zod_openapi.z.string().optional().describe("Error message if invalid"),
|
|
34
|
+
info: import_zod_openapi.z.object({
|
|
35
|
+
contextLength: import_zod_openapi.z.number().describe("Model context length in tokens")
|
|
36
|
+
}).optional().describe("Model information if valid")
|
|
37
|
+
}).describe("Model validation response");
|
|
38
|
+
function createOpenRouterRouter() {
|
|
39
|
+
const app = new import_zod_openapi.OpenAPIHono();
|
|
40
|
+
const validateRoute = (0, import_zod_openapi.createRoute)({
|
|
41
|
+
method: "get",
|
|
42
|
+
path: "/openrouter/validate/{modelId}",
|
|
43
|
+
summary: "Validate OpenRouter Model",
|
|
44
|
+
description: "Validates an OpenRouter model ID against the cached model registry. Refreshes cache if stale.",
|
|
45
|
+
tags: ["openrouter"],
|
|
46
|
+
request: {
|
|
47
|
+
params: ValidateModelParamsSchema
|
|
48
|
+
},
|
|
49
|
+
responses: {
|
|
50
|
+
200: {
|
|
51
|
+
description: "Validation result",
|
|
52
|
+
content: {
|
|
53
|
+
"application/json": {
|
|
54
|
+
schema: ValidateModelResponseSchema
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const refreshRoute = (0, import_zod_openapi.createRoute)({
|
|
61
|
+
method: "post",
|
|
62
|
+
path: "/openrouter/refresh-cache",
|
|
63
|
+
summary: "Refresh OpenRouter Model Cache",
|
|
64
|
+
description: "Forces a refresh of the OpenRouter model registry cache from the API.",
|
|
65
|
+
tags: ["openrouter"],
|
|
66
|
+
responses: {
|
|
67
|
+
200: {
|
|
68
|
+
description: "Cache refreshed successfully",
|
|
69
|
+
content: {
|
|
70
|
+
"application/json": {
|
|
71
|
+
schema: import_zod_openapi.z.object({
|
|
72
|
+
ok: import_zod_openapi.z.literal(true).describe("Success indicator"),
|
|
73
|
+
message: import_zod_openapi.z.string().describe("Status message")
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
500: {
|
|
79
|
+
description: "Cache refresh failed",
|
|
80
|
+
content: {
|
|
81
|
+
"application/json": {
|
|
82
|
+
schema: import_zod_openapi.z.object({
|
|
83
|
+
ok: import_zod_openapi.z.literal(false).describe("Failure indicator"),
|
|
84
|
+
message: import_zod_openapi.z.string().describe("Error message")
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return app.openapi(validateRoute, async (ctx) => {
|
|
92
|
+
const { modelId: encodedModelId } = ctx.req.valid("param");
|
|
93
|
+
const modelId = decodeURIComponent(encodedModelId);
|
|
94
|
+
let status = (0, import_core.lookupOpenRouterModel)(modelId);
|
|
95
|
+
if (status === "unknown") {
|
|
96
|
+
try {
|
|
97
|
+
await (0, import_core.refreshOpenRouterModelCache)();
|
|
98
|
+
status = (0, import_core.lookupOpenRouterModel)(modelId);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
import_core.logger.warn(
|
|
101
|
+
`OpenRouter cache refresh failed during validation: ${error instanceof Error ? error.message : String(error)}`
|
|
102
|
+
);
|
|
103
|
+
return ctx.json({
|
|
104
|
+
valid: false,
|
|
105
|
+
modelId,
|
|
106
|
+
status: "unknown",
|
|
107
|
+
error: "Could not validate model - cache refresh failed"
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (status === "invalid") {
|
|
112
|
+
return ctx.json({
|
|
113
|
+
valid: false,
|
|
114
|
+
modelId,
|
|
115
|
+
status: "invalid",
|
|
116
|
+
error: `Model '${modelId}' not found in OpenRouter. Check the model ID at https://openrouter.ai/models`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const info = (0, import_core.getOpenRouterModelInfo)(modelId);
|
|
120
|
+
return ctx.json({
|
|
121
|
+
valid: true,
|
|
122
|
+
modelId,
|
|
123
|
+
status: "valid",
|
|
124
|
+
...info && { info: { contextLength: info.contextLength } }
|
|
125
|
+
});
|
|
126
|
+
}).openapi(refreshRoute, async (ctx) => {
|
|
127
|
+
try {
|
|
128
|
+
await (0, import_core.refreshOpenRouterModelCache)();
|
|
129
|
+
return ctx.json(
|
|
130
|
+
{
|
|
131
|
+
ok: true,
|
|
132
|
+
message: "OpenRouter model cache refreshed successfully"
|
|
133
|
+
},
|
|
134
|
+
200
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
import_core.logger.error(
|
|
138
|
+
`Failed to refresh OpenRouter cache: ${error instanceof Error ? error.message : String(error)}`
|
|
139
|
+
);
|
|
140
|
+
return ctx.json(
|
|
141
|
+
{
|
|
142
|
+
ok: false,
|
|
143
|
+
message: "Failed to refresh OpenRouter model cache"
|
|
144
|
+
},
|
|
145
|
+
500
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
151
|
+
0 && (module.exports = {
|
|
152
|
+
createOpenRouterRouter
|
|
153
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Validation Routes
|
|
3
|
+
*
|
|
4
|
+
* Standalone routes for validating OpenRouter model IDs against the registry.
|
|
5
|
+
* Decoupled from agent runtime - can be used independently.
|
|
6
|
+
*/
|
|
7
|
+
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
8
|
+
/**
|
|
9
|
+
* Create OpenRouter validation router.
|
|
10
|
+
* No agent dependency - purely utility routes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createOpenRouterRouter(): OpenAPIHono<import("hono").Env, {
|
|
13
|
+
"/openrouter/validate/:modelId": {
|
|
14
|
+
$get: {
|
|
15
|
+
input: {
|
|
16
|
+
param: {
|
|
17
|
+
modelId: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
output: {
|
|
21
|
+
valid: boolean;
|
|
22
|
+
status: "valid" | "unknown" | "invalid";
|
|
23
|
+
modelId: string;
|
|
24
|
+
error?: string | undefined;
|
|
25
|
+
info?: {
|
|
26
|
+
contextLength: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
};
|
|
29
|
+
outputFormat: "json";
|
|
30
|
+
status: 200;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
} & {
|
|
34
|
+
"/openrouter/refresh-cache": {
|
|
35
|
+
$post: {
|
|
36
|
+
input: {};
|
|
37
|
+
output: {
|
|
38
|
+
message: string;
|
|
39
|
+
ok: true;
|
|
40
|
+
};
|
|
41
|
+
outputFormat: "json";
|
|
42
|
+
status: 200;
|
|
43
|
+
} | {
|
|
44
|
+
input: {};
|
|
45
|
+
output: {
|
|
46
|
+
message: string;
|
|
47
|
+
ok: false;
|
|
48
|
+
};
|
|
49
|
+
outputFormat: "json";
|
|
50
|
+
status: 500;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}, "/">;
|
|
54
|
+
//# sourceMappingURL=openrouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../../src/hono/routes/openrouter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAkChE;;;GAGG;AACH,wBAAgB,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8HrC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import {
|
|
3
|
+
logger,
|
|
4
|
+
lookupOpenRouterModel,
|
|
5
|
+
refreshOpenRouterModelCache,
|
|
6
|
+
getOpenRouterModelInfo
|
|
7
|
+
} from "@dexto/core";
|
|
8
|
+
const ValidateModelParamsSchema = z.object({
|
|
9
|
+
modelId: z.string().min(1).describe("OpenRouter model ID to validate (e.g., anthropic/claude-3.5-sonnet)")
|
|
10
|
+
}).describe("Path parameters for model validation");
|
|
11
|
+
const ValidateModelResponseSchema = z.object({
|
|
12
|
+
valid: z.boolean().describe("Whether the model ID is valid"),
|
|
13
|
+
modelId: z.string().describe("The model ID that was validated"),
|
|
14
|
+
status: z.enum(["valid", "invalid", "unknown"]).describe("Validation status: valid, invalid, or unknown (cache empty)"),
|
|
15
|
+
error: z.string().optional().describe("Error message if invalid"),
|
|
16
|
+
info: z.object({
|
|
17
|
+
contextLength: z.number().describe("Model context length in tokens")
|
|
18
|
+
}).optional().describe("Model information if valid")
|
|
19
|
+
}).describe("Model validation response");
|
|
20
|
+
function createOpenRouterRouter() {
|
|
21
|
+
const app = new OpenAPIHono();
|
|
22
|
+
const validateRoute = createRoute({
|
|
23
|
+
method: "get",
|
|
24
|
+
path: "/openrouter/validate/{modelId}",
|
|
25
|
+
summary: "Validate OpenRouter Model",
|
|
26
|
+
description: "Validates an OpenRouter model ID against the cached model registry. Refreshes cache if stale.",
|
|
27
|
+
tags: ["openrouter"],
|
|
28
|
+
request: {
|
|
29
|
+
params: ValidateModelParamsSchema
|
|
30
|
+
},
|
|
31
|
+
responses: {
|
|
32
|
+
200: {
|
|
33
|
+
description: "Validation result",
|
|
34
|
+
content: {
|
|
35
|
+
"application/json": {
|
|
36
|
+
schema: ValidateModelResponseSchema
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const refreshRoute = createRoute({
|
|
43
|
+
method: "post",
|
|
44
|
+
path: "/openrouter/refresh-cache",
|
|
45
|
+
summary: "Refresh OpenRouter Model Cache",
|
|
46
|
+
description: "Forces a refresh of the OpenRouter model registry cache from the API.",
|
|
47
|
+
tags: ["openrouter"],
|
|
48
|
+
responses: {
|
|
49
|
+
200: {
|
|
50
|
+
description: "Cache refreshed successfully",
|
|
51
|
+
content: {
|
|
52
|
+
"application/json": {
|
|
53
|
+
schema: z.object({
|
|
54
|
+
ok: z.literal(true).describe("Success indicator"),
|
|
55
|
+
message: z.string().describe("Status message")
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
500: {
|
|
61
|
+
description: "Cache refresh failed",
|
|
62
|
+
content: {
|
|
63
|
+
"application/json": {
|
|
64
|
+
schema: z.object({
|
|
65
|
+
ok: z.literal(false).describe("Failure indicator"),
|
|
66
|
+
message: z.string().describe("Error message")
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return app.openapi(validateRoute, async (ctx) => {
|
|
74
|
+
const { modelId: encodedModelId } = ctx.req.valid("param");
|
|
75
|
+
const modelId = decodeURIComponent(encodedModelId);
|
|
76
|
+
let status = lookupOpenRouterModel(modelId);
|
|
77
|
+
if (status === "unknown") {
|
|
78
|
+
try {
|
|
79
|
+
await refreshOpenRouterModelCache();
|
|
80
|
+
status = lookupOpenRouterModel(modelId);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.warn(
|
|
83
|
+
`OpenRouter cache refresh failed during validation: ${error instanceof Error ? error.message : String(error)}`
|
|
84
|
+
);
|
|
85
|
+
return ctx.json({
|
|
86
|
+
valid: false,
|
|
87
|
+
modelId,
|
|
88
|
+
status: "unknown",
|
|
89
|
+
error: "Could not validate model - cache refresh failed"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (status === "invalid") {
|
|
94
|
+
return ctx.json({
|
|
95
|
+
valid: false,
|
|
96
|
+
modelId,
|
|
97
|
+
status: "invalid",
|
|
98
|
+
error: `Model '${modelId}' not found in OpenRouter. Check the model ID at https://openrouter.ai/models`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const info = getOpenRouterModelInfo(modelId);
|
|
102
|
+
return ctx.json({
|
|
103
|
+
valid: true,
|
|
104
|
+
modelId,
|
|
105
|
+
status: "valid",
|
|
106
|
+
...info && { info: { contextLength: info.contextLength } }
|
|
107
|
+
});
|
|
108
|
+
}).openapi(refreshRoute, async (ctx) => {
|
|
109
|
+
try {
|
|
110
|
+
await refreshOpenRouterModelCache();
|
|
111
|
+
return ctx.json(
|
|
112
|
+
{
|
|
113
|
+
ok: true,
|
|
114
|
+
message: "OpenRouter model cache refreshed successfully"
|
|
115
|
+
},
|
|
116
|
+
200
|
|
117
|
+
);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.error(
|
|
120
|
+
`Failed to refresh OpenRouter cache: ${error instanceof Error ? error.message : String(error)}`
|
|
121
|
+
);
|
|
122
|
+
return ctx.json(
|
|
123
|
+
{
|
|
124
|
+
ok: false,
|
|
125
|
+
message: "Failed to refresh OpenRouter model cache"
|
|
126
|
+
},
|
|
127
|
+
500
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createOpenRouterRouter
|
|
134
|
+
};
|
|
@@ -162,12 +162,12 @@ function createPromptsRouter(getAgent) {
|
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
164
|
return app.openapi(listRoute, async (ctx) => {
|
|
165
|
-
const agent = getAgent();
|
|
165
|
+
const agent = await getAgent(ctx);
|
|
166
166
|
const prompts = await agent.listPrompts();
|
|
167
167
|
const list = Object.values(prompts);
|
|
168
168
|
return ctx.json({ prompts: list });
|
|
169
169
|
}).openapi(createCustomRoute, async (ctx) => {
|
|
170
|
-
const agent = getAgent();
|
|
170
|
+
const agent = await getAgent(ctx);
|
|
171
171
|
const payload = ctx.req.valid("json");
|
|
172
172
|
const promptArguments = payload.arguments?.map((arg) => ({
|
|
173
173
|
name: arg.name,
|
|
@@ -191,18 +191,18 @@ function createPromptsRouter(getAgent) {
|
|
|
191
191
|
const prompt = await agent.createCustomPrompt(createPayload);
|
|
192
192
|
return ctx.json({ prompt }, 201);
|
|
193
193
|
}).openapi(deleteCustomRoute, async (ctx) => {
|
|
194
|
-
const agent = getAgent();
|
|
194
|
+
const agent = await getAgent(ctx);
|
|
195
195
|
const { name } = ctx.req.valid("param");
|
|
196
196
|
await agent.deleteCustomPrompt(name);
|
|
197
197
|
return ctx.body(null, 204);
|
|
198
198
|
}).openapi(getPromptRoute, async (ctx) => {
|
|
199
|
-
const agent = getAgent();
|
|
199
|
+
const agent = await getAgent(ctx);
|
|
200
200
|
const { name } = ctx.req.valid("param");
|
|
201
201
|
const definition = await agent.getPromptDefinition(name);
|
|
202
202
|
if (!definition) throw import_core.PromptError.notFound(name);
|
|
203
203
|
return ctx.json({ definition });
|
|
204
204
|
}).openapi(resolvePromptRoute, async (ctx) => {
|
|
205
|
-
const agent = getAgent();
|
|
205
|
+
const agent = await getAgent(ctx);
|
|
206
206
|
const { name } = ctx.req.valid("param");
|
|
207
207
|
const { context, args: argsString } = ctx.req.valid("query");
|
|
208
208
|
let parsedArgs;
|