@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.
Files changed (99) hide show
  1. package/dist/hono/__tests__/test-fixtures.cjs +1 -1
  2. package/dist/hono/__tests__/test-fixtures.d.ts.map +1 -1
  3. package/dist/hono/__tests__/test-fixtures.js +1 -1
  4. package/dist/hono/index.cjs +41 -5
  5. package/dist/hono/index.d.ts +451 -452
  6. package/dist/hono/index.d.ts.map +1 -1
  7. package/dist/hono/index.js +41 -5
  8. package/dist/hono/routes/a2a-jsonrpc.cjs +3 -3
  9. package/dist/hono/routes/a2a-jsonrpc.d.ts +4 -1
  10. package/dist/hono/routes/a2a-jsonrpc.d.ts.map +1 -1
  11. package/dist/hono/routes/a2a-jsonrpc.js +3 -3
  12. package/dist/hono/routes/a2a-tasks.cjs +5 -5
  13. package/dist/hono/routes/a2a-tasks.d.ts +13 -10
  14. package/dist/hono/routes/a2a-tasks.d.ts.map +1 -1
  15. package/dist/hono/routes/a2a-tasks.js +5 -5
  16. package/dist/hono/routes/agents.cjs +25 -35
  17. package/dist/hono/routes/agents.d.ts +6 -407
  18. package/dist/hono/routes/agents.d.ts.map +1 -1
  19. package/dist/hono/routes/agents.js +25 -35
  20. package/dist/hono/routes/approvals.cjs +2 -2
  21. package/dist/hono/routes/approvals.d.ts +4 -1
  22. package/dist/hono/routes/approvals.d.ts.map +1 -1
  23. package/dist/hono/routes/approvals.js +2 -2
  24. package/dist/hono/routes/discovery.cjs +67 -0
  25. package/dist/hono/routes/discovery.d.ts +44 -0
  26. package/dist/hono/routes/discovery.d.ts.map +1 -0
  27. package/dist/hono/routes/discovery.js +43 -0
  28. package/dist/hono/routes/greeting.cjs +2 -2
  29. package/dist/hono/routes/greeting.d.ts +2 -2
  30. package/dist/hono/routes/greeting.d.ts.map +1 -1
  31. package/dist/hono/routes/greeting.js +2 -2
  32. package/dist/hono/routes/health.d.ts +2 -2
  33. package/dist/hono/routes/health.d.ts.map +1 -1
  34. package/dist/hono/routes/key.cjs +110 -0
  35. package/dist/hono/routes/key.d.ts +48 -0
  36. package/dist/hono/routes/key.d.ts.map +1 -0
  37. package/dist/hono/routes/key.js +90 -0
  38. package/dist/hono/routes/llm.cjs +12 -34
  39. package/dist/hono/routes/llm.d.ts +173 -25
  40. package/dist/hono/routes/llm.d.ts.map +1 -1
  41. package/dist/hono/routes/llm.js +12 -35
  42. package/dist/hono/routes/mcp.cjs +8 -8
  43. package/dist/hono/routes/mcp.d.ts +2 -2
  44. package/dist/hono/routes/mcp.d.ts.map +1 -1
  45. package/dist/hono/routes/mcp.js +8 -8
  46. package/dist/hono/routes/memory.cjs +5 -5
  47. package/dist/hono/routes/memory.d.ts +4 -1
  48. package/dist/hono/routes/memory.d.ts.map +1 -1
  49. package/dist/hono/routes/memory.js +5 -5
  50. package/dist/hono/routes/messages.cjs +4 -4
  51. package/dist/hono/routes/messages.d.ts +12 -12
  52. package/dist/hono/routes/messages.d.ts.map +1 -1
  53. package/dist/hono/routes/messages.js +4 -4
  54. package/dist/hono/routes/models.cjs +319 -0
  55. package/dist/hono/routes/models.d.ts +107 -0
  56. package/dist/hono/routes/models.d.ts.map +1 -0
  57. package/dist/hono/routes/models.js +305 -0
  58. package/dist/hono/routes/openrouter.cjs +153 -0
  59. package/dist/hono/routes/openrouter.d.ts +54 -0
  60. package/dist/hono/routes/openrouter.d.ts.map +1 -0
  61. package/dist/hono/routes/openrouter.js +134 -0
  62. package/dist/hono/routes/prompts.cjs +5 -5
  63. package/dist/hono/routes/prompts.d.ts +4 -1
  64. package/dist/hono/routes/prompts.d.ts.map +1 -1
  65. package/dist/hono/routes/prompts.js +5 -5
  66. package/dist/hono/routes/queue.cjs +4 -4
  67. package/dist/hono/routes/queue.d.ts +4 -1
  68. package/dist/hono/routes/queue.d.ts.map +1 -1
  69. package/dist/hono/routes/queue.js +4 -4
  70. package/dist/hono/routes/resources.cjs +3 -3
  71. package/dist/hono/routes/resources.d.ts +2 -2
  72. package/dist/hono/routes/resources.d.ts.map +1 -1
  73. package/dist/hono/routes/resources.js +3 -3
  74. package/dist/hono/routes/search.cjs +2 -2
  75. package/dist/hono/routes/search.d.ts +6 -3
  76. package/dist/hono/routes/search.d.ts.map +1 -1
  77. package/dist/hono/routes/search.js +2 -2
  78. package/dist/hono/routes/sessions.cjs +9 -9
  79. package/dist/hono/routes/sessions.d.ts +3 -3
  80. package/dist/hono/routes/sessions.d.ts.map +1 -1
  81. package/dist/hono/routes/sessions.js +9 -9
  82. package/dist/hono/routes/tools.cjs +126 -0
  83. package/dist/hono/routes/tools.d.ts +42 -0
  84. package/dist/hono/routes/tools.d.ts.map +1 -0
  85. package/dist/hono/routes/tools.js +102 -0
  86. package/dist/hono/routes/webhooks.cjs +4 -4
  87. package/dist/hono/routes/webhooks.d.ts +4 -1
  88. package/dist/hono/routes/webhooks.d.ts.map +1 -1
  89. package/dist/hono/routes/webhooks.js +4 -4
  90. package/dist/hono/schemas/responses.d.ts +41 -41
  91. package/dist/hono/start-server.cjs +102 -0
  92. package/dist/hono/start-server.d.ts +61 -0
  93. package/dist/hono/start-server.d.ts.map +1 -0
  94. package/dist/hono/start-server.js +78 -0
  95. package/dist/index.cjs +2 -0
  96. package/dist/index.d.ts +1 -0
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +1 -0
  99. 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;