waypoi 0.0.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 (260) hide show
  1. package/.github/instructions/ui.instructions.md +42 -0
  2. package/.github/workflows/ci.yml +35 -0
  3. package/.github/workflows/publish.yml +71 -0
  4. package/.github/workflows/release.yml +48 -0
  5. package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
  6. package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
  7. package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
  8. package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
  9. package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
  10. package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
  11. package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
  12. package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
  13. package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
  14. package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
  15. package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
  16. package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
  17. package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
  18. package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
  19. package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
  20. package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
  21. package/AGENTS.md +48 -0
  22. package/CHANGELOG.md +131 -0
  23. package/README.md +552 -0
  24. package/assets/agent-mode.png +0 -0
  25. package/assets/categorize.png +0 -0
  26. package/assets/dashboard.png +0 -0
  27. package/assets/endpoint-proxy.png +0 -0
  28. package/assets/icon.png +0 -0
  29. package/assets/mcp-generate-image.png +0 -0
  30. package/assets/mcp-understand-image.png +0 -0
  31. package/assets/peek-token-flow.png +0 -0
  32. package/assets/playground.png +0 -0
  33. package/assets/sankey.png +0 -0
  34. package/cli/index.ts +2805 -0
  35. package/cli/legacyRewrite.ts +108 -0
  36. package/cli/modelRef.ts +24 -0
  37. package/dist/cli/index.js +2536 -0
  38. package/dist/cli/legacyRewrite.js +92 -0
  39. package/dist/cli/modelRef.js +20 -0
  40. package/dist/src/benchmark/artifacts.js +131 -0
  41. package/dist/src/benchmark/capabilityClassifier.js +81 -0
  42. package/dist/src/benchmark/capabilityStore.js +144 -0
  43. package/dist/src/benchmark/config.js +238 -0
  44. package/dist/src/benchmark/gates.js +118 -0
  45. package/dist/src/benchmark/jobs.js +252 -0
  46. package/dist/src/benchmark/runner.js +1847 -0
  47. package/dist/src/benchmark/schema.js +353 -0
  48. package/dist/src/benchmark/suites.js +314 -0
  49. package/dist/src/benchmark/tinyQaDataset.js +422 -0
  50. package/dist/src/benchmark/types.js +25 -0
  51. package/dist/src/config.js +47 -0
  52. package/dist/src/index.js +178 -0
  53. package/dist/src/mcp/client.js +215 -0
  54. package/dist/src/mcp/discovery.js +226 -0
  55. package/dist/src/mcp/policy.js +65 -0
  56. package/dist/src/mcp/registry.js +129 -0
  57. package/dist/src/mcp/service.js +460 -0
  58. package/dist/src/middleware/auth.js +179 -0
  59. package/dist/src/middleware/requestCapture.js +192 -0
  60. package/dist/src/middleware/requestStats.js +118 -0
  61. package/dist/src/pools/builder.js +132 -0
  62. package/dist/src/pools/repository.js +69 -0
  63. package/dist/src/pools/scheduler.js +360 -0
  64. package/dist/src/pools/types.js +2 -0
  65. package/dist/src/protocols/adapters/dashscope.js +267 -0
  66. package/dist/src/protocols/adapters/inferenceV2.js +346 -0
  67. package/dist/src/protocols/adapters/openai.js +27 -0
  68. package/dist/src/protocols/registry.js +99 -0
  69. package/dist/src/protocols/types.js +2 -0
  70. package/dist/src/providers/health.js +153 -0
  71. package/dist/src/providers/importer.js +289 -0
  72. package/dist/src/providers/modelRegistry.js +313 -0
  73. package/dist/src/providers/repository.js +361 -0
  74. package/dist/src/providers/types.js +2 -0
  75. package/dist/src/routes/admin.js +531 -0
  76. package/dist/src/routes/audio.js +295 -0
  77. package/dist/src/routes/chat.js +240 -0
  78. package/dist/src/routes/embeddings.js +157 -0
  79. package/dist/src/routes/images.js +288 -0
  80. package/dist/src/routes/mcp.js +256 -0
  81. package/dist/src/routes/mcpService.js +100 -0
  82. package/dist/src/routes/models.js +48 -0
  83. package/dist/src/routes/responses.js +711 -0
  84. package/dist/src/routes/sessions.js +450 -0
  85. package/dist/src/routes/stats.js +270 -0
  86. package/dist/src/routes/ui.js +97 -0
  87. package/dist/src/routes/videos.js +107 -0
  88. package/dist/src/routing/router.js +338 -0
  89. package/dist/src/services/imageGeneration.js +280 -0
  90. package/dist/src/services/imageUnderstanding.js +352 -0
  91. package/dist/src/services/videoGeneration.js +79 -0
  92. package/dist/src/storage/captureRepository.js +1591 -0
  93. package/dist/src/storage/files.js +157 -0
  94. package/dist/src/storage/imageCache.js +346 -0
  95. package/dist/src/storage/repositories.js +388 -0
  96. package/dist/src/storage/sessionRepository.js +370 -0
  97. package/dist/src/storage/statsRepository.js +204 -0
  98. package/dist/src/transport/httpClient.js +126 -0
  99. package/dist/src/types.js +2 -0
  100. package/dist/src/utils/messageMedia.js +285 -0
  101. package/dist/src/utils/modelCapabilities.js +108 -0
  102. package/dist/src/utils/modelDiscovery.js +170 -0
  103. package/dist/src/version.js +5 -0
  104. package/dist/src/workers/captureRetention.js +25 -0
  105. package/dist/src/workers/configWatcher.js +91 -0
  106. package/dist/src/workers/healthChecker.js +21 -0
  107. package/dist/src/workers/statsRotation.js +41 -0
  108. package/docs/LLM/output_schema.md +312 -0
  109. package/docs/benchmark.md +208 -0
  110. package/docs/mcp-guidelines.md +125 -0
  111. package/docs/mcp-service.md +178 -0
  112. package/docs/opencode.md +86 -0
  113. package/docs/providers.md +79 -0
  114. package/examples/benchmark.config.yaml +28 -0
  115. package/examples/providers/alibaba-dashscope.yaml +88 -0
  116. package/examples/providers/alibaba-llm.yaml +64 -0
  117. package/examples/providers/alibaba-registry.yaml +7 -0
  118. package/examples/providers/inference-v2-ray.yaml +29 -0
  119. package/examples/scenarios/assets/omni-call-sample.wav +0 -0
  120. package/examples/scenarios/custom.jsonl +5 -0
  121. package/examples/scenarios/custom.yaml +40 -0
  122. package/model-form-v2.png +0 -0
  123. package/package.json +66 -0
  124. package/provider-form-v2.png +0 -0
  125. package/provider-form.png +0 -0
  126. package/scripts/manual-test.sh +11 -0
  127. package/scripts/version-from-git.js +23 -0
  128. package/src/benchmark/artifacts.ts +149 -0
  129. package/src/benchmark/capabilityClassifier.ts +99 -0
  130. package/src/benchmark/capabilityStore.ts +174 -0
  131. package/src/benchmark/config.ts +337 -0
  132. package/src/benchmark/gates.ts +164 -0
  133. package/src/benchmark/jobs.ts +312 -0
  134. package/src/benchmark/runner.ts +2519 -0
  135. package/src/benchmark/schema.ts +443 -0
  136. package/src/benchmark/suites.ts +323 -0
  137. package/src/benchmark/tinyQaDataset.ts +428 -0
  138. package/src/benchmark/types.ts +442 -0
  139. package/src/config.ts +44 -0
  140. package/src/index.ts +195 -0
  141. package/src/mcp/client.ts +305 -0
  142. package/src/mcp/discovery.ts +266 -0
  143. package/src/mcp/policy.ts +105 -0
  144. package/src/mcp/registry.ts +164 -0
  145. package/src/mcp/service.ts +611 -0
  146. package/src/middleware/auth.ts +251 -0
  147. package/src/middleware/requestCapture.ts +245 -0
  148. package/src/middleware/requestStats.ts +163 -0
  149. package/src/pools/builder.ts +159 -0
  150. package/src/pools/repository.ts +71 -0
  151. package/src/pools/scheduler.ts +425 -0
  152. package/src/pools/types.ts +117 -0
  153. package/src/protocols/adapters/dashscope.ts +335 -0
  154. package/src/protocols/adapters/inferenceV2.ts +428 -0
  155. package/src/protocols/adapters/openai.ts +32 -0
  156. package/src/protocols/registry.ts +117 -0
  157. package/src/protocols/types.ts +81 -0
  158. package/src/providers/health.ts +207 -0
  159. package/src/providers/importer.ts +402 -0
  160. package/src/providers/modelRegistry.ts +415 -0
  161. package/src/providers/repository.ts +439 -0
  162. package/src/providers/types.ts +113 -0
  163. package/src/routes/admin.ts +666 -0
  164. package/src/routes/audio.ts +372 -0
  165. package/src/routes/chat.ts +301 -0
  166. package/src/routes/embeddings.ts +197 -0
  167. package/src/routes/images.ts +356 -0
  168. package/src/routes/mcp.ts +320 -0
  169. package/src/routes/mcpService.ts +114 -0
  170. package/src/routes/models.ts +50 -0
  171. package/src/routes/responses.ts +872 -0
  172. package/src/routes/sessions.ts +558 -0
  173. package/src/routes/stats.ts +312 -0
  174. package/src/routes/ui.ts +96 -0
  175. package/src/routes/videos.ts +132 -0
  176. package/src/routing/router.ts +501 -0
  177. package/src/services/imageGeneration.ts +396 -0
  178. package/src/services/imageUnderstanding.ts +449 -0
  179. package/src/services/videoGeneration.ts +127 -0
  180. package/src/storage/captureRepository.ts +1835 -0
  181. package/src/storage/files.ts +178 -0
  182. package/src/storage/imageCache.ts +405 -0
  183. package/src/storage/repositories.ts +494 -0
  184. package/src/storage/sessionRepository.ts +419 -0
  185. package/src/storage/statsRepository.ts +238 -0
  186. package/src/transport/httpClient.ts +145 -0
  187. package/src/types.ts +322 -0
  188. package/src/utils/messageMedia.ts +293 -0
  189. package/src/utils/modelCapabilities.ts +161 -0
  190. package/src/utils/modelDiscovery.ts +203 -0
  191. package/src/workers/captureRetention.ts +25 -0
  192. package/src/workers/configWatcher.ts +115 -0
  193. package/src/workers/healthChecker.ts +22 -0
  194. package/src/workers/statsRotation.ts +49 -0
  195. package/tests/benchmarkAdminRoutes.test.ts +82 -0
  196. package/tests/benchmarkBasics.test.ts +116 -0
  197. package/tests/captureAdminRoutes.test.ts +420 -0
  198. package/tests/captureRepository.test.ts +797 -0
  199. package/tests/cliLegacyRewrite.test.ts +45 -0
  200. package/tests/imageGeneration.service.test.ts +107 -0
  201. package/tests/imageUnderstanding.service.test.ts +123 -0
  202. package/tests/mcpPolicy.test.ts +105 -0
  203. package/tests/mcpService.test.ts +1245 -0
  204. package/tests/modelRef.test.ts +23 -0
  205. package/tests/modelsRoutes.test.ts +154 -0
  206. package/tests/sessionMediaCache.test.ts +167 -0
  207. package/tests/statsRoutes.test.ts +323 -0
  208. package/tsconfig.json +15 -0
  209. package/ui/index.html +16 -0
  210. package/ui/package-lock.json +8521 -0
  211. package/ui/package.json +52 -0
  212. package/ui/postcss.config.js +6 -0
  213. package/ui/public/assets/apple-touch-icon.png +0 -0
  214. package/ui/public/assets/favicon-16.png +0 -0
  215. package/ui/public/assets/favicon-32.png +0 -0
  216. package/ui/public/assets/icon-192.png +0 -0
  217. package/ui/public/assets/icon-512.png +0 -0
  218. package/ui/src/App.tsx +27 -0
  219. package/ui/src/api/client.ts +1503 -0
  220. package/ui/src/components/EndpointUsageGuide.tsx +361 -0
  221. package/ui/src/components/Layout.tsx +124 -0
  222. package/ui/src/components/MessageContent.tsx +365 -0
  223. package/ui/src/components/ToolCallMessage.tsx +179 -0
  224. package/ui/src/components/ToolPicker.tsx +442 -0
  225. package/ui/src/components/messageContentParser.test.ts +41 -0
  226. package/ui/src/components/messageContentParser.ts +73 -0
  227. package/ui/src/components/thinkingPreview.test.ts +27 -0
  228. package/ui/src/components/thinkingPreview.ts +15 -0
  229. package/ui/src/components/toMermaidSankey.test.ts +78 -0
  230. package/ui/src/components/toMermaidSankey.ts +56 -0
  231. package/ui/src/components/ui/button.tsx +58 -0
  232. package/ui/src/components/ui/input.tsx +21 -0
  233. package/ui/src/components/ui/textarea.tsx +21 -0
  234. package/ui/src/lib/utils.ts +6 -0
  235. package/ui/src/main.tsx +9 -0
  236. package/ui/src/pages/AgentPlayground.tsx +2010 -0
  237. package/ui/src/pages/Benchmark.tsx +988 -0
  238. package/ui/src/pages/Dashboard.tsx +581 -0
  239. package/ui/src/pages/Peek.tsx +962 -0
  240. package/ui/src/pages/Settings.tsx +2013 -0
  241. package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
  242. package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
  243. package/ui/src/pages/agentThinkingContent.test.ts +50 -0
  244. package/ui/src/pages/agentThinkingContent.ts +57 -0
  245. package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
  246. package/ui/src/pages/dashboardTokenUsage.ts +36 -0
  247. package/ui/src/pages/imageUpload.test.ts +39 -0
  248. package/ui/src/pages/imageUpload.ts +71 -0
  249. package/ui/src/pages/peekFilters.test.ts +29 -0
  250. package/ui/src/pages/peekFilters.ts +13 -0
  251. package/ui/src/pages/peekMedia.test.ts +58 -0
  252. package/ui/src/pages/peekMedia.ts +148 -0
  253. package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
  254. package/ui/src/pages/sessionAutoTitle.ts +106 -0
  255. package/ui/src/stores/settings.ts +58 -0
  256. package/ui/src/styles/globals.css +223 -0
  257. package/ui/src/vite-env.d.ts +8 -0
  258. package/ui/tailwind.config.js +106 -0
  259. package/ui/tsconfig.json +32 -0
  260. package/ui/vite.config.ts +37 -0
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerImageRoutes = registerImageRoutes;
4
+ const crypto_1 = require("crypto");
5
+ const router_1 = require("../routing/router");
6
+ const repositories_1 = require("../storage/repositories");
7
+ const scheduler_1 = require("../pools/scheduler");
8
+ const modelRegistry_1 = require("../providers/modelRegistry");
9
+ const imageGeneration_1 = require("../services/imageGeneration");
10
+ const requestCapture_1 = require("../middleware/requestCapture");
11
+ async function registerImageRoutes(app, paths) {
12
+ // POST /v1/images/generations
13
+ app.post("/v1/images/generations", async (req, reply) => {
14
+ const body = req.body;
15
+ if (!body?.prompt) {
16
+ reply.code(400).send({ error: { message: "prompt is required" } });
17
+ return;
18
+ }
19
+ const model = await (0, imageGeneration_1.resolveGenerationModel)(paths, body.model);
20
+ if (!model) {
21
+ reply.code(400).send({ error: { message: "No diffusion model available. Add or enable a provider model." } });
22
+ return;
23
+ }
24
+ const requestId = (0, crypto_1.randomUUID)();
25
+ const start = Date.now();
26
+ const controller = new AbortController();
27
+ req.raw.on("close", () => controller.abort());
28
+ try {
29
+ const generated = await (0, imageGeneration_1.runImageGeneration)(paths, { ...body, model }, req.headers, controller.signal);
30
+ setHeaders(reply, generated.headers);
31
+ reply.code(generated.statusCode).send(generated.payload);
32
+ (0, requestCapture_1.setCaptureRouting)(reply, {
33
+ publicModel: model,
34
+ endpointId: generated.route.endpointId,
35
+ endpointName: generated.route.endpointName,
36
+ upstreamModel: generated.route.upstreamModel,
37
+ });
38
+ await (0, repositories_1.logRequest)(paths, buildLog(requestId, model, {
39
+ attempt: {
40
+ endpoint: {
41
+ id: generated.route.endpointId,
42
+ name: generated.route.endpointName,
43
+ },
44
+ upstreamModel: generated.route.upstreamModel,
45
+ response: {
46
+ statusCode: generated.statusCode,
47
+ },
48
+ },
49
+ }, Date.now() - start, false));
50
+ }
51
+ catch (error) {
52
+ const errorType = error.type ?? error.name;
53
+ (0, requestCapture_1.setCaptureError)(reply, { type: errorType, message: error.message });
54
+ await (0, repositories_1.logRequest)(paths, {
55
+ requestId,
56
+ ts: new Date(),
57
+ route: { publicModel: model },
58
+ request: { stream: false },
59
+ result: {
60
+ errorType,
61
+ errorMessage: error.message
62
+ }
63
+ });
64
+ if (errorType === "invalid_request") {
65
+ reply.code(400).send({ error: { message: error.message } });
66
+ return;
67
+ }
68
+ if (errorType === "tls_verify_failed") {
69
+ reply.code(502).send({ error: { message: error.message, type: errorType } });
70
+ return;
71
+ }
72
+ const status = errorType === "no_endpoints" ||
73
+ errorType === "protocol_stream_unsupported" ||
74
+ errorType === "unsupported_protocol" ||
75
+ errorType === "invalid_protocol_config"
76
+ ? 400
77
+ : errorType === "rate_limited"
78
+ ? 429
79
+ : 502;
80
+ reply.code(status).send({ error: { message: "Image generation unavailable", type: errorType } });
81
+ }
82
+ });
83
+ // POST /v1/images/edits (passthrough)
84
+ app.post("/v1/images/edits", async (req, reply) => {
85
+ const body = req.body;
86
+ if (!body?.prompt) {
87
+ reply.code(400).send({ error: { message: "prompt is required" } });
88
+ return;
89
+ }
90
+ const model = body.model ?? await pickDefaultDiffusionModel(paths);
91
+ if (!model) {
92
+ reply.code(400).send({ error: { message: "No diffusion model available" } });
93
+ return;
94
+ }
95
+ const requestId = (0, crypto_1.randomUUID)();
96
+ const start = Date.now();
97
+ const controller = new AbortController();
98
+ req.raw.on("close", () => controller.abort());
99
+ try {
100
+ const outcome = await (0, router_1.routeRequest)(paths, model, "/v1/images/edits", body, req.headers, controller.signal, {
101
+ endpointType: "diffusion",
102
+ requiredInput: ["image"],
103
+ requiredOutput: ["image"],
104
+ });
105
+ const upstreamBody = await readBody(outcome.attempt.response);
106
+ setHeaders(reply, outcome.attempt.response.headers);
107
+ reply.code(outcome.attempt.response.statusCode).send(upstreamBody.payload);
108
+ (0, requestCapture_1.setCaptureRouting)(reply, {
109
+ publicModel: model,
110
+ endpointId: outcome.attempt.endpoint.id,
111
+ endpointName: outcome.attempt.endpoint.name,
112
+ upstreamModel: outcome.attempt.upstreamModel,
113
+ });
114
+ await (0, repositories_1.logRequest)(paths, buildLog(requestId, model, outcome, Date.now() - start, false));
115
+ }
116
+ catch (error) {
117
+ const errorType = error.type ?? error.name;
118
+ (0, requestCapture_1.setCaptureError)(reply, { type: errorType, message: error.message });
119
+ await (0, repositories_1.logRequest)(paths, {
120
+ requestId,
121
+ ts: new Date(),
122
+ route: { publicModel: model },
123
+ request: { stream: false },
124
+ result: { errorType, errorMessage: error.message }
125
+ });
126
+ if (errorType === "invalid_request") {
127
+ reply.code(400).send({ error: { message: error.message } });
128
+ return;
129
+ }
130
+ if (errorType === "tls_verify_failed") {
131
+ reply.code(502).send({ error: { message: error.message, type: errorType } });
132
+ return;
133
+ }
134
+ const status = errorType === "no_endpoints" ||
135
+ errorType === "protocol_stream_unsupported" ||
136
+ errorType === "unsupported_protocol" ||
137
+ errorType === "invalid_protocol_config"
138
+ ? 400
139
+ : errorType === "rate_limited"
140
+ ? 429
141
+ : 502;
142
+ reply.code(status).send({ error: { message: "Image edit unavailable", type: errorType } });
143
+ }
144
+ });
145
+ // POST /v1/images/variations (passthrough)
146
+ app.post("/v1/images/variations", async (req, reply) => {
147
+ const body = req.body;
148
+ const model = body?.model ?? await pickDefaultImageEditModel(paths);
149
+ if (!model) {
150
+ reply.code(400).send({ error: { message: "No diffusion model available" } });
151
+ return;
152
+ }
153
+ const requestId = (0, crypto_1.randomUUID)();
154
+ const start = Date.now();
155
+ const controller = new AbortController();
156
+ req.raw.on("close", () => controller.abort());
157
+ try {
158
+ const outcome = await (0, router_1.routeRequest)(paths, model, "/v1/images/variations", (body ?? {}), req.headers, controller.signal, {
159
+ endpointType: "diffusion",
160
+ requiredInput: ["image"],
161
+ requiredOutput: ["image"],
162
+ });
163
+ const upstreamBody = await readBody(outcome.attempt.response);
164
+ setHeaders(reply, outcome.attempt.response.headers);
165
+ reply.code(outcome.attempt.response.statusCode).send(upstreamBody.payload);
166
+ (0, requestCapture_1.setCaptureRouting)(reply, {
167
+ publicModel: model,
168
+ endpointId: outcome.attempt.endpoint.id,
169
+ endpointName: outcome.attempt.endpoint.name,
170
+ upstreamModel: outcome.attempt.upstreamModel,
171
+ });
172
+ await (0, repositories_1.logRequest)(paths, buildLog(requestId, model, outcome, Date.now() - start, false));
173
+ }
174
+ catch (error) {
175
+ const errorType = error.type ?? error.name;
176
+ (0, requestCapture_1.setCaptureError)(reply, { type: errorType, message: error.message });
177
+ await (0, repositories_1.logRequest)(paths, {
178
+ requestId,
179
+ ts: new Date(),
180
+ route: { publicModel: model },
181
+ request: { stream: false },
182
+ result: { errorType, errorMessage: error.message }
183
+ });
184
+ if (errorType === "invalid_request") {
185
+ reply.code(400).send({ error: { message: error.message } });
186
+ return;
187
+ }
188
+ if (errorType === "tls_verify_failed") {
189
+ reply.code(502).send({ error: { message: error.message, type: errorType } });
190
+ return;
191
+ }
192
+ const status = errorType === "no_endpoints" ||
193
+ errorType === "protocol_stream_unsupported" ||
194
+ errorType === "unsupported_protocol" ||
195
+ errorType === "invalid_protocol_config"
196
+ ? 400
197
+ : errorType === "rate_limited"
198
+ ? 429
199
+ : 502;
200
+ reply.code(status).send({ error: { message: "Image variation unavailable", type: errorType } });
201
+ }
202
+ });
203
+ }
204
+ async function pickDefaultDiffusionModel(paths) {
205
+ const smart = await (0, scheduler_1.selectPoolCandidates)(paths, "smart", {
206
+ requiredInput: ["text"],
207
+ requiredOutput: ["image"],
208
+ }, {
209
+ operation: "images_generation",
210
+ stream: false,
211
+ });
212
+ if (smart && smart.candidates.length > 0) {
213
+ return "smart";
214
+ }
215
+ const byCapabilities = await (0, modelRegistry_1.pickBestProviderModelByCapabilities)(paths, { requiredInput: ["text"], requiredOutput: ["image"] }, "diffusion");
216
+ if (byCapabilities) {
217
+ return byCapabilities;
218
+ }
219
+ return null;
220
+ }
221
+ async function pickDefaultImageEditModel(paths) {
222
+ const smart = await (0, scheduler_1.selectPoolCandidates)(paths, "smart", {
223
+ requiredInput: ["image", "text"],
224
+ requiredOutput: ["image"],
225
+ }, {
226
+ operation: "images_edits",
227
+ stream: false,
228
+ });
229
+ if (smart && smart.candidates.length > 0) {
230
+ return "smart";
231
+ }
232
+ const byCapabilities = await (0, modelRegistry_1.pickBestProviderModelByCapabilities)(paths, { requiredInput: ["image"], requiredOutput: ["image"] }, "diffusion");
233
+ if (byCapabilities) {
234
+ return byCapabilities;
235
+ }
236
+ return pickDefaultDiffusionModel(paths);
237
+ }
238
+ function setHeaders(reply, headers) {
239
+ for (const [key, value] of Object.entries(headers)) {
240
+ if (Array.isArray(value)) {
241
+ reply.header(key.toLowerCase(), value.join(", "));
242
+ }
243
+ else {
244
+ reply.header(key.toLowerCase(), value);
245
+ }
246
+ }
247
+ }
248
+ async function readBody(response) {
249
+ const chunks = [];
250
+ for await (const chunk of response.body) {
251
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
252
+ }
253
+ const buffer = Buffer.concat(chunks);
254
+ const contentType = normalizeContentType(response.headers);
255
+ if (contentType.includes("application/json")) {
256
+ try {
257
+ return { payload: JSON.parse(buffer.toString("utf8")) };
258
+ }
259
+ catch {
260
+ return { payload: buffer };
261
+ }
262
+ }
263
+ return { payload: buffer };
264
+ }
265
+ function normalizeContentType(headers) {
266
+ const ct = headers["content-type"] ?? headers["Content-Type"];
267
+ if (Array.isArray(ct))
268
+ return ct.join(", ");
269
+ return ct ?? "";
270
+ }
271
+ function buildLog(requestId, model, outcome, latencyMs, stream) {
272
+ return {
273
+ requestId,
274
+ ts: new Date(),
275
+ route: {
276
+ publicModel: model,
277
+ endpointId: outcome.attempt.endpoint.id,
278
+ endpointName: outcome.attempt.endpoint.name,
279
+ upstreamModel: outcome.attempt.upstreamModel
280
+ },
281
+ request: { stream },
282
+ result: {
283
+ statusCode: outcome.attempt.response.statusCode,
284
+ latencyMs,
285
+ totalTokens: null
286
+ }
287
+ };
288
+ }
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerMcpRoutes = registerMcpRoutes;
4
+ const config_1 = require("../config");
5
+ const registry_1 = require("../mcp/registry");
6
+ const discovery_1 = require("../mcp/discovery");
7
+ /**
8
+ * MCP Routes
9
+ *
10
+ * Admin API for managing MCP servers and tools.
11
+ *
12
+ * Endpoints:
13
+ * GET /admin/mcp/servers - List all MCP servers
14
+ * POST /admin/mcp/servers - Add a new MCP server
15
+ * GET /admin/mcp/servers/:id - Get server by ID
16
+ * PUT /admin/mcp/servers/:id - Update server
17
+ * DELETE /admin/mcp/servers/:id - Remove server
18
+ * POST /admin/mcp/servers/:id/connect - Connect and discover tools
19
+ * POST /admin/mcp/servers/:id/disconnect - Disconnect from server
20
+ *
21
+ * GET /admin/mcp/tools - List all discovered tools
22
+ * POST /admin/mcp/tools/discover - Discover tools from all servers
23
+ * POST /admin/mcp/tools/execute - Execute a tool
24
+ */
25
+ async function registerMcpRoutes(app, paths) {
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+ // Server management
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ app.get("/admin/mcp/servers", async (_req, reply) => {
30
+ try {
31
+ const servers = await (0, registry_1.listMcpServers)(paths);
32
+ const builtin = (0, discovery_1.getBuiltinVirtualServer)();
33
+ return reply.send({
34
+ object: "list",
35
+ data: [
36
+ ...(builtin ? [builtin] : []),
37
+ ...servers.map((s) => ({
38
+ ...s,
39
+ connected: (0, discovery_1.isServerConnected)(s.id),
40
+ })),
41
+ ],
42
+ });
43
+ }
44
+ catch (error) {
45
+ app.log.error(error, "Failed to list MCP servers");
46
+ return reply.status(500).send({
47
+ error: { message: "Failed to list servers", type: "internal_error" },
48
+ });
49
+ }
50
+ });
51
+ app.post("/admin/mcp/servers", async (req, reply) => {
52
+ try {
53
+ if (!req.body?.name || !req.body?.url) {
54
+ return reply.status(400).send({
55
+ error: { message: "name and url are required", type: "invalid_request" },
56
+ });
57
+ }
58
+ const server = await (0, registry_1.addMcpServer)(paths, req.body);
59
+ return reply.status(201).send(server);
60
+ }
61
+ catch (error) {
62
+ if (error.message.includes("already exists")) {
63
+ return reply.status(409).send({
64
+ error: { message: error.message, type: "conflict" },
65
+ });
66
+ }
67
+ app.log.error(error, "Failed to add MCP server");
68
+ return reply.status(500).send({
69
+ error: { message: "Failed to add server", type: "internal_error" },
70
+ });
71
+ }
72
+ });
73
+ app.get("/admin/mcp/servers/:id", async (req, reply) => {
74
+ try {
75
+ if (req.params.id === discovery_1.BUILTIN_SERVER_ID) {
76
+ const builtin = (0, discovery_1.getBuiltinVirtualServer)();
77
+ if (!builtin) {
78
+ return reply.status(404).send({
79
+ error: { message: "Built-in server not yet initialized", type: "not_found" },
80
+ });
81
+ }
82
+ return reply.send({
83
+ ...builtin,
84
+ tools: (0, discovery_1.getCachedToolsForServer)(discovery_1.BUILTIN_SERVER_ID),
85
+ });
86
+ }
87
+ const server = await (0, registry_1.getMcpServer)(paths, req.params.id);
88
+ if (!server) {
89
+ return reply.status(404).send({
90
+ error: { message: "Server not found", type: "not_found" },
91
+ });
92
+ }
93
+ return reply.send({
94
+ ...server,
95
+ connected: (0, discovery_1.isServerConnected)(server.id),
96
+ tools: (0, discovery_1.getCachedToolsForServer)(server.id),
97
+ });
98
+ }
99
+ catch (error) {
100
+ app.log.error(error, "Failed to get MCP server");
101
+ return reply.status(500).send({
102
+ error: { message: "Failed to get server", type: "internal_error" },
103
+ });
104
+ }
105
+ });
106
+ app.put("/admin/mcp/servers/:id", async (req, reply) => {
107
+ try {
108
+ const server = await (0, registry_1.updateMcpServer)(paths, req.params.id, req.body || {});
109
+ if (!server) {
110
+ return reply.status(404).send({
111
+ error: { message: "Server not found", type: "not_found" },
112
+ });
113
+ }
114
+ return reply.send(server);
115
+ }
116
+ catch (error) {
117
+ app.log.error(error, "Failed to update MCP server");
118
+ return reply.status(500).send({
119
+ error: { message: "Failed to update server", type: "internal_error" },
120
+ });
121
+ }
122
+ });
123
+ app.delete("/admin/mcp/servers/:id", async (req, reply) => {
124
+ try {
125
+ if (req.params.id === discovery_1.BUILTIN_SERVER_ID) {
126
+ return reply.status(403).send({
127
+ error: { message: "Cannot delete the built-in waypoi server", type: "forbidden" },
128
+ });
129
+ }
130
+ // Disconnect first
131
+ await (0, discovery_1.disconnectServer)(req.params.id);
132
+ const deleted = await (0, registry_1.removeMcpServer)(paths, req.params.id);
133
+ if (!deleted) {
134
+ return reply.status(404).send({
135
+ error: { message: "Server not found", type: "not_found" },
136
+ });
137
+ }
138
+ return reply.status(204).send();
139
+ }
140
+ catch (error) {
141
+ app.log.error(error, "Failed to remove MCP server");
142
+ return reply.status(500).send({
143
+ error: { message: "Failed to remove server", type: "internal_error" },
144
+ });
145
+ }
146
+ });
147
+ app.post("/admin/mcp/servers/:id/connect", async (req, reply) => {
148
+ try {
149
+ if (req.params.id === discovery_1.BUILTIN_SERVER_ID) {
150
+ const port = config_1.appConfig.port;
151
+ const tools = await (0, discovery_1.discoverBuiltinTools)(paths, `http://localhost:${port}/mcp`);
152
+ return reply.send({
153
+ connected: true,
154
+ toolCount: tools.length,
155
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
156
+ });
157
+ }
158
+ const server = await (0, registry_1.getMcpServer)(paths, req.params.id);
159
+ if (!server) {
160
+ return reply.status(404).send({
161
+ error: { message: "Server not found", type: "not_found" },
162
+ });
163
+ }
164
+ const tools = await (0, discovery_1.discoverServerTools)(paths, server);
165
+ return reply.send({
166
+ connected: true,
167
+ toolCount: tools.length,
168
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
169
+ });
170
+ }
171
+ catch (error) {
172
+ app.log.error(error, "Failed to connect to MCP server");
173
+ return reply.status(500).send({
174
+ error: { message: "Failed to connect", type: "connection_error" },
175
+ });
176
+ }
177
+ });
178
+ app.post("/admin/mcp/servers/:id/disconnect", async (req, reply) => {
179
+ try {
180
+ if (req.params.id === discovery_1.BUILTIN_SERVER_ID) {
181
+ return reply.status(403).send({
182
+ error: { message: "Cannot disconnect the built-in waypoi server", type: "forbidden" },
183
+ });
184
+ }
185
+ await (0, discovery_1.disconnectServer)(req.params.id);
186
+ return reply.send({ disconnected: true });
187
+ }
188
+ catch (error) {
189
+ app.log.error(error, "Failed to disconnect from MCP server");
190
+ return reply.status(500).send({
191
+ error: { message: "Failed to disconnect", type: "internal_error" },
192
+ });
193
+ }
194
+ });
195
+ // ─────────────────────────────────────────────────────────────────────────────
196
+ // Tool management
197
+ // ─────────────────────────────────────────────────────────────────────────────
198
+ app.get("/admin/mcp/tools", async (_req, reply) => {
199
+ try {
200
+ const tools = (0, discovery_1.getCachedTools)();
201
+ return reply.send({
202
+ object: "list",
203
+ data: tools,
204
+ });
205
+ }
206
+ catch (error) {
207
+ app.log.error(error, "Failed to list tools");
208
+ return reply.status(500).send({
209
+ error: { message: "Failed to list tools", type: "internal_error" },
210
+ });
211
+ }
212
+ });
213
+ app.post("/admin/mcp/tools/discover", async (_req, reply) => {
214
+ try {
215
+ const tools = await (0, discovery_1.discoverAllTools)(paths);
216
+ return reply.send({
217
+ discovered: tools.length,
218
+ tools: tools.map((t) => ({
219
+ name: t.name,
220
+ description: t.description,
221
+ serverName: t.serverName,
222
+ })),
223
+ });
224
+ }
225
+ catch (error) {
226
+ app.log.error(error, "Failed to discover tools");
227
+ return reply.status(500).send({
228
+ error: { message: "Failed to discover tools", type: "internal_error" },
229
+ });
230
+ }
231
+ });
232
+ app.post("/admin/mcp/tools/execute", async (req, reply) => {
233
+ try {
234
+ if (!req.body?.name) {
235
+ return reply.status(400).send({
236
+ error: { message: "Tool name is required", type: "invalid_request" },
237
+ });
238
+ }
239
+ const result = await (0, discovery_1.executeTool)(req.body.name, req.body.arguments ?? {});
240
+ if (result.isError) {
241
+ return reply.status(500).send({
242
+ error: { message: result.content, type: "tool_error" },
243
+ });
244
+ }
245
+ return reply.send({
246
+ result: result.content,
247
+ });
248
+ }
249
+ catch (error) {
250
+ app.log.error(error, "Failed to execute tool");
251
+ return reply.status(500).send({
252
+ error: { message: "Failed to execute tool", type: "internal_error" },
253
+ });
254
+ }
255
+ });
256
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerMcpServiceRoutes = registerMcpServiceRoutes;
4
+ exports.closeMcpServiceRoutes = closeMcpServiceRoutes;
5
+ const service_1 = require("../mcp/service");
6
+ let activeMcpService = null;
7
+ async function registerMcpServiceRoutes(app, paths, deps) {
8
+ const mcpService = (0, service_1.createMcpService)(paths, deps);
9
+ activeMcpService = mcpService;
10
+ app.route({
11
+ method: ["GET", "POST", "DELETE"],
12
+ url: "/mcp",
13
+ handler: async (req, reply) => {
14
+ if (!isLocalRequest(req)) {
15
+ reply.code(403).send({
16
+ error: {
17
+ message: "Forbidden: /mcp is restricted to localhost",
18
+ type: "forbidden",
19
+ },
20
+ });
21
+ return;
22
+ }
23
+ if (req.method === "GET") {
24
+ reply.code(405).header("Allow", "POST, DELETE").send("Method Not Allowed");
25
+ return;
26
+ }
27
+ await mcpService.handleRequest(req.raw, reply.raw, req.body);
28
+ reply.hijack();
29
+ },
30
+ });
31
+ }
32
+ async function closeMcpServiceRoutes() {
33
+ if (!activeMcpService) {
34
+ return;
35
+ }
36
+ await activeMcpService.close();
37
+ activeMcpService = null;
38
+ }
39
+ function isLocalRequest(req) {
40
+ const address = normalizeAddress(req.ip ?? req.socket.remoteAddress);
41
+ if (address && !isLocalHost(address)) {
42
+ return false;
43
+ }
44
+ const hostCandidates = [
45
+ firstHeaderValue(req.headers.host),
46
+ firstHeaderValue(req.headers["x-forwarded-host"]),
47
+ extractHostFromOrigin(firstHeaderValue(req.headers.origin)),
48
+ ]
49
+ .map((value) => normalizeHost(value))
50
+ .filter((value) => Boolean(value));
51
+ return hostCandidates.every((host) => isLocalHost(host));
52
+ }
53
+ function firstHeaderValue(header) {
54
+ if (typeof header === "string") {
55
+ return header;
56
+ }
57
+ if (Array.isArray(header) && header.length > 0) {
58
+ return header[0];
59
+ }
60
+ return undefined;
61
+ }
62
+ function extractHostFromOrigin(origin) {
63
+ if (!origin)
64
+ return undefined;
65
+ try {
66
+ const parsed = new URL(origin);
67
+ return parsed.host;
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ }
73
+ function normalizeHost(value) {
74
+ if (!value)
75
+ return null;
76
+ const host = value.split(",")[0].trim().toLowerCase();
77
+ if (!host)
78
+ return null;
79
+ if (host.startsWith("[")) {
80
+ const end = host.indexOf("]");
81
+ if (end > -1) {
82
+ return host.slice(1, end);
83
+ }
84
+ }
85
+ return host.split(":")[0];
86
+ }
87
+ function normalizeAddress(value) {
88
+ if (!value)
89
+ return null;
90
+ const trimmed = value.trim().toLowerCase();
91
+ if (!trimmed)
92
+ return null;
93
+ if (trimmed.startsWith("::ffff:")) {
94
+ return trimmed.replace("::ffff:", "");
95
+ }
96
+ return trimmed;
97
+ }
98
+ function isLocalHost(host) {
99
+ return host === "localhost" || host === "127.0.0.1" || host === "::1";
100
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerModelsRoutes = registerModelsRoutes;
4
+ const modelRegistry_1 = require("../providers/modelRegistry");
5
+ async function registerModelsRoutes(app, paths) {
6
+ app.get("/v1/models", async (req, reply) => {
7
+ const query = (req.query ?? {});
8
+ const availableOnly = query.available_only === true || query.available_only === "true";
9
+ const concrete = await (0, modelRegistry_1.listModelsForApi)(paths, { availableOnly });
10
+ const smart = await (0, modelRegistry_1.getAvailableSmartPool)(paths);
11
+ const poolEntries = smart
12
+ ? [
13
+ {
14
+ id: smart.alias,
15
+ object: "model",
16
+ owned_by: "waypoi",
17
+ endpoint_type: inferEndpointType(smart.capabilities.output),
18
+ capabilities: smart.capabilities,
19
+ slug: smart.alias,
20
+ waypoi_pool: {
21
+ id: smart.id,
22
+ strategy: smart.strategy,
23
+ candidateCount: smart.candidateCount,
24
+ scoreSource: "benchmark.livebench",
25
+ },
26
+ },
27
+ ]
28
+ : [];
29
+ const data = [...concrete, ...poolEntries];
30
+ // Include both `data` and `models` for broader client compatibility.
31
+ reply.send({ object: "list", data, models: data });
32
+ });
33
+ }
34
+ function inferEndpointType(output) {
35
+ if (output.includes("text")) {
36
+ return "llm";
37
+ }
38
+ if (output.includes("embedding")) {
39
+ return "embedding";
40
+ }
41
+ if (output.includes("image")) {
42
+ return "diffusion";
43
+ }
44
+ if (output.includes("audio")) {
45
+ return "audio";
46
+ }
47
+ return "llm";
48
+ }