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,460 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createMcpService = createMcpService;
7
+ const crypto_1 = require("crypto");
8
+ const fs_1 = require("fs");
9
+ const path_1 = __importDefault(require("path"));
10
+ const zod_1 = require("zod");
11
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
12
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
13
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
14
+ const imageGeneration_1 = require("../services/imageGeneration");
15
+ const imageUnderstanding_1 = require("../services/imageUnderstanding");
16
+ const videoGeneration_1 = require("../services/videoGeneration");
17
+ const policy_1 = require("./policy");
18
+ const defaultDeps = {
19
+ runImageGeneration: imageGeneration_1.runImageGeneration,
20
+ normalizeImageGenerationPayload: imageGeneration_1.normalizeImageGenerationPayload,
21
+ runImageUnderstanding: imageUnderstanding_1.runImageUnderstanding,
22
+ runVideoGeneration: videoGeneration_1.runVideoGeneration,
23
+ resolveVideoGenerationModel: videoGeneration_1.resolveVideoGenerationModel,
24
+ };
25
+ const GENERATE_IMAGE_TOOL_DESCRIPTION = "Generate or edit images from a text prompt. Supports text-to-image and image-to-image editing. Successful calls always write files to the waypoi config directory (~/.config/waypoi/generated-images by default; override with WAYPOI_MCP_OUTPUT_ROOT). Report file_path or file_paths to the user. Use include_data=true only when inline image data is also needed.";
26
+ const UNDERSTAND_IMAGE_TOOL_DESCRIPTION = "Analyze one image and return structured text. Provide exactly one of image_path (local file) or image_url (http/https or data URL). Use the instruction parameter to specify what analysis you need (e.g., 'describe the image', 'find all objects and their bounding boxes', 'extract text via OCR'). If returning points or boxes, use original-image pixel coordinates. Keep instruction concise and specify the output format you expect.";
27
+ const GENERATE_VIDEO_TOOL_DESCRIPTION = "Generate videos from text prompts or images. Supports text-to-video and image-to-video generation using Alibaba Cloud ModelStudio (Wan models). Videos are generated asynchronously and may take 1-5 minutes. Returns a URL to the generated MP4 video (H.264 encoding). Provide a detailed prompt describing the desired video content, style, and camera movement. Optionally provide an image_url to use as the first frame for image-to-video generation.";
28
+ function createMcpService(paths, deps = {}) {
29
+ const resolvedDeps = {
30
+ ...defaultDeps,
31
+ ...deps,
32
+ };
33
+ const sessions = new Map();
34
+ const createServer = () => {
35
+ const server = new mcp_js_1.McpServer({
36
+ name: "waypoi-mcp",
37
+ version: "0.7.1",
38
+ }, {
39
+ capabilities: {},
40
+ });
41
+ server.registerTool("generate_image", {
42
+ description: GENERATE_IMAGE_TOOL_DESCRIPTION,
43
+ inputSchema: {
44
+ prompt: zod_1.z.string().min(1),
45
+ model: zod_1.z.string().optional(),
46
+ image_path: zod_1.z.string().optional(),
47
+ image_url: zod_1.z.string().optional(),
48
+ n: zod_1.z.number().int().min(1).max(4).optional(),
49
+ size: zod_1.z.string().optional(),
50
+ quality: zod_1.z.string().optional(),
51
+ style: zod_1.z.string().optional(),
52
+ response_format: zod_1.z.enum(["url", "b64_json"]).optional(),
53
+ include_data: zod_1.z.boolean().optional(),
54
+ },
55
+ }, async (args) => {
56
+ const controller = new AbortController();
57
+ const timeout = setTimeout(() => controller.abort(), 60_000);
58
+ try {
59
+ (0, policy_1.validateAtMostOneImageInput)({
60
+ image_path: args.image_path,
61
+ image_url: args.image_url,
62
+ });
63
+ const resolvedImageUrl = await resolveOptionalImageInputToUrl({
64
+ image_path: args.image_path,
65
+ image_url: args.image_url,
66
+ });
67
+ const filePolicy = (0, policy_1.resolveBinaryOutputPolicy)({ include_data: args.include_data }, { defaultBaseDir: paths.baseDir });
68
+ const hasFileOutput = true;
69
+ // File output requires decodable bytes; force b64_json upstream even if caller asks for "url".
70
+ const responseFormat = hasFileOutput ? "b64_json" : (args.response_format ?? "b64_json");
71
+ const request = {
72
+ prompt: args.prompt,
73
+ model: args.model,
74
+ n: args.n,
75
+ size: args.size,
76
+ quality: args.quality,
77
+ style: args.style,
78
+ response_format: responseFormat,
79
+ image_url: resolvedImageUrl,
80
+ };
81
+ const generated = await resolvedDeps.runImageGeneration(paths, request, {}, controller.signal);
82
+ const normalized = await resolvedDeps.normalizeImageGenerationPayload(paths, generated.payload, generated.model);
83
+ const artifacts = await materializeImagesToFiles(normalized.images, normalized.created, {
84
+ outputDir: filePolicy.outputDir,
85
+ includeData: filePolicy.includeData,
86
+ outputBaseRoot: filePolicy.outputBaseRoot,
87
+ });
88
+ const filePaths = artifacts.map((artifact) => artifact.file_path);
89
+ const summary = buildGenerateImageSummary(filePaths.length);
90
+ const output = {
91
+ ok: true,
92
+ summary,
93
+ model: normalized.model,
94
+ created: normalized.created,
95
+ ...(filePaths.length === 1
96
+ ? { file_path: filePaths[0] }
97
+ : { file_paths: filePaths }),
98
+ artifacts,
99
+ };
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: JSON.stringify(buildGenerateImageTextPayload(output)),
105
+ },
106
+ ],
107
+ structuredContent: output,
108
+ };
109
+ }
110
+ catch (error) {
111
+ const typed = error;
112
+ const type = typed.type ?? "upstream_error";
113
+ const message = type === "no_diffusion_model"
114
+ ? "No diffusion model available. Add or enable a provider model."
115
+ : typed.message || "Image generation failed";
116
+ const output = {
117
+ ok: false,
118
+ error: {
119
+ type,
120
+ message,
121
+ },
122
+ };
123
+ return {
124
+ isError: true,
125
+ content: [
126
+ {
127
+ type: "text",
128
+ text: JSON.stringify(output),
129
+ },
130
+ ],
131
+ structuredContent: output,
132
+ };
133
+ }
134
+ finally {
135
+ clearTimeout(timeout);
136
+ }
137
+ });
138
+ server.registerTool("understand_image", {
139
+ description: UNDERSTAND_IMAGE_TOOL_DESCRIPTION,
140
+ inputSchema: {
141
+ image_path: zod_1.z.string().optional(),
142
+ image_url: zod_1.z.string().optional(),
143
+ instruction: zod_1.z.string().optional(),
144
+ model: zod_1.z.string().optional(),
145
+ max_tokens: zod_1.z.number().int().min(1).max(4096).optional(),
146
+ temperature: zod_1.z.number().min(0).max(2).optional(),
147
+ },
148
+ }, async (args) => {
149
+ const controller = new AbortController();
150
+ const timeout = setTimeout(() => controller.abort(), 60_000);
151
+ try {
152
+ (0, policy_1.validateSingleImageInput)({
153
+ image_path: args.image_path,
154
+ image_url: args.image_url,
155
+ });
156
+ const result = await resolvedDeps.runImageUnderstanding(paths, {
157
+ image_path: args.image_path,
158
+ image_url: args.image_url,
159
+ instruction: args.instruction,
160
+ model: args.model,
161
+ max_tokens: args.max_tokens,
162
+ temperature: args.temperature,
163
+ }, controller.signal);
164
+ const summary = "Image analyzed.";
165
+ const output = {
166
+ ok: true,
167
+ summary,
168
+ model: result.model,
169
+ text: result.raw_text,
170
+ result: result.analysis,
171
+ ...(result.image_geometry ? { image_geometry: result.image_geometry } : {}),
172
+ ...(result.usage ? { usage: result.usage } : {}),
173
+ };
174
+ return {
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: JSON.stringify({
179
+ ok: true,
180
+ summary,
181
+ model: result.model,
182
+ text: result.raw_text,
183
+ }),
184
+ },
185
+ ],
186
+ structuredContent: output,
187
+ };
188
+ }
189
+ catch (error) {
190
+ const typed = error;
191
+ const type = typed.type ?? "upstream_error";
192
+ const output = {
193
+ ok: false,
194
+ error: {
195
+ type,
196
+ message: typed.message || "Image understanding failed",
197
+ },
198
+ };
199
+ return {
200
+ isError: true,
201
+ content: [{ type: "text", text: JSON.stringify(output) }],
202
+ structuredContent: output,
203
+ };
204
+ }
205
+ finally {
206
+ clearTimeout(timeout);
207
+ }
208
+ });
209
+ server.registerTool("generate_video", {
210
+ description: GENERATE_VIDEO_TOOL_DESCRIPTION,
211
+ inputSchema: {
212
+ prompt: zod_1.z.string().min(1),
213
+ model: zod_1.z.string().optional(),
214
+ image_url: zod_1.z.string().optional(),
215
+ audio_url: zod_1.z.string().optional(),
216
+ duration: zod_1.z.number().int().min(2).max(15).optional(),
217
+ resolution: zod_1.z.enum(["480P", "720P", "1080P"]).optional(),
218
+ negative_prompt: zod_1.z.string().optional(),
219
+ seed: zod_1.z.number().int().min(0).max(2147483647).optional(),
220
+ watermark: zod_1.z.boolean().optional(),
221
+ prompt_extend: zod_1.z.boolean().optional(),
222
+ },
223
+ }, async (args) => {
224
+ const controller = new AbortController();
225
+ const timeout = setTimeout(() => controller.abort(), 300_000);
226
+ try {
227
+ const resolvedModel = await resolvedDeps.resolveVideoGenerationModel(paths, args.model);
228
+ if (!resolvedModel) {
229
+ throw (0, policy_1.typedError)("no_video_model", "No video generation model available. Add or enable a provider model.");
230
+ }
231
+ const request = {
232
+ prompt: args.prompt,
233
+ model: resolvedModel,
234
+ image_url: args.image_url,
235
+ audio_url: args.audio_url,
236
+ duration: args.duration,
237
+ resolution: args.resolution,
238
+ negative_prompt: args.negative_prompt,
239
+ seed: args.seed,
240
+ watermark: args.watermark,
241
+ prompt_extend: args.prompt_extend,
242
+ };
243
+ const generated = await resolvedDeps.runVideoGeneration(paths, request, {}, controller.signal);
244
+ const payload = generated.payload;
245
+ const data = payload.data ?? [];
246
+ const usage = payload.usage ?? {};
247
+ if (data.length === 0) {
248
+ throw (0, policy_1.typedError)("no_video_output", "Video generation completed but no video URL was returned.");
249
+ }
250
+ const videoData = data[0];
251
+ const output = {
252
+ ok: true,
253
+ summary: "Generated 1 video.",
254
+ model: generated.route.upstreamModel,
255
+ url: videoData.url,
256
+ ...(videoData.revised_prompt ? { revised_prompt: videoData.revised_prompt } : {}),
257
+ ...(usage.video_count ? { video_count: usage.video_count } : {}),
258
+ ...(usage.duration ? { duration: usage.duration } : {}),
259
+ ...(usage.resolution ? { resolution: usage.resolution } : {}),
260
+ };
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: JSON.stringify(output),
266
+ },
267
+ ],
268
+ structuredContent: output,
269
+ };
270
+ }
271
+ catch (error) {
272
+ const typed = error;
273
+ const type = typed.type ?? "upstream_error";
274
+ const message = type === "no_video_model"
275
+ ? "No video generation model available. Add or enable a provider model."
276
+ : typed.message || "Video generation failed";
277
+ const output = {
278
+ ok: false,
279
+ error: {
280
+ type,
281
+ message,
282
+ },
283
+ };
284
+ return {
285
+ isError: true,
286
+ content: [
287
+ {
288
+ type: "text",
289
+ text: JSON.stringify(output),
290
+ },
291
+ ],
292
+ structuredContent: output,
293
+ };
294
+ }
295
+ finally {
296
+ clearTimeout(timeout);
297
+ }
298
+ });
299
+ return server;
300
+ };
301
+ const close = async () => {
302
+ const entries = Array.from(sessions.values());
303
+ sessions.clear();
304
+ await Promise.allSettled(entries.map(async (entry) => {
305
+ await entry.transport.close();
306
+ await entry.server.close();
307
+ }));
308
+ };
309
+ const handleRequest = async (req, res, parsedBody) => {
310
+ const sessionIdHeader = req.headers["mcp-session-id"];
311
+ const sessionId = typeof sessionIdHeader === "string"
312
+ ? sessionIdHeader
313
+ : Array.isArray(sessionIdHeader)
314
+ ? sessionIdHeader[0]
315
+ : undefined;
316
+ let entry;
317
+ if (sessionId) {
318
+ entry = sessions.get(sessionId);
319
+ }
320
+ if (!entry) {
321
+ if (sessionId || !(0, types_js_1.isInitializeRequest)(parsedBody)) {
322
+ if (!res.headersSent) {
323
+ res.writeHead(400, { "Content-Type": "application/json" });
324
+ res.end(JSON.stringify({
325
+ jsonrpc: "2.0",
326
+ error: {
327
+ code: -32000,
328
+ message: "Bad Request: No valid MCP session ID provided",
329
+ },
330
+ id: null,
331
+ }));
332
+ }
333
+ return;
334
+ }
335
+ const server = createServer();
336
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
337
+ sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
338
+ enableJsonResponse: true,
339
+ onsessioninitialized: (newSessionId) => {
340
+ sessions.set(newSessionId, { server, transport });
341
+ },
342
+ onsessionclosed: (closedSessionId) => {
343
+ sessions.delete(closedSessionId);
344
+ },
345
+ });
346
+ transport.onclose = () => {
347
+ const sid = transport.sessionId;
348
+ if (sid) {
349
+ sessions.delete(sid);
350
+ }
351
+ };
352
+ await server.connect(transport);
353
+ entry = { server, transport };
354
+ }
355
+ await entry.transport.handleRequest(req, res, parsedBody);
356
+ };
357
+ return {
358
+ handleRequest,
359
+ close,
360
+ };
361
+ }
362
+ async function materializeImagesToFiles(images, created, options) {
363
+ const output = [];
364
+ for (const image of images) {
365
+ const payload = decodeImagePayload(image);
366
+ if (!payload) {
367
+ throw (0, policy_1.typedError)("invalid_request", `Image ${image.index} has no decodable bytes for file output.`);
368
+ }
369
+ const extension = extensionForMime(payload.mimeType);
370
+ const resolvedPath = resolveOutputPath(image.index, created, extension, options);
371
+ await fs_1.promises.mkdir(path_1.default.dirname(resolvedPath), { recursive: true });
372
+ await fs_1.promises.writeFile(resolvedPath, payload.buffer);
373
+ const entry = {
374
+ index: image.index,
375
+ file_path: toRelativeFilePath(options.outputBaseRoot, resolvedPath),
376
+ mime_type: payload.mimeType,
377
+ bytes: payload.buffer.length,
378
+ };
379
+ if (image.revised_prompt) {
380
+ entry.revised_prompt = image.revised_prompt;
381
+ }
382
+ if (options.includeData) {
383
+ if (image.url)
384
+ entry.url = image.url;
385
+ if (image.b64_json)
386
+ entry.b64_json = image.b64_json;
387
+ }
388
+ output.push(entry);
389
+ }
390
+ return output;
391
+ }
392
+ function decodeImagePayload(image) {
393
+ if (image.b64_json) {
394
+ const mimeType = extractMimeFromDataUrl(image.url) ?? "image/png";
395
+ return { buffer: Buffer.from(image.b64_json, "base64"), mimeType };
396
+ }
397
+ if (image.url?.startsWith("data:")) {
398
+ const match = image.url.match(/^data:([^;]+);base64,(.+)$/i);
399
+ if (!match) {
400
+ return null;
401
+ }
402
+ return { buffer: Buffer.from(match[2], "base64"), mimeType: match[1] };
403
+ }
404
+ return null;
405
+ }
406
+ function extractMimeFromDataUrl(url) {
407
+ if (!url?.startsWith("data:")) {
408
+ return null;
409
+ }
410
+ const match = url.match(/^data:([^;]+);base64,/i);
411
+ return match ? match[1] : null;
412
+ }
413
+ function extensionForMime(mimeType) {
414
+ switch (mimeType.toLowerCase()) {
415
+ case "image/jpeg":
416
+ return "jpg";
417
+ case "image/webp":
418
+ return "webp";
419
+ case "image/gif":
420
+ return "gif";
421
+ default:
422
+ return "png";
423
+ }
424
+ }
425
+ function resolveOutputPath(index, created, extension, options) {
426
+ return path_1.default.join(options.outputDir, `image-${created}-${index}.${extension}`);
427
+ }
428
+ function toRelativeFilePath(outputBaseRoot, resolvedPath) {
429
+ const relative = path_1.default.relative(outputBaseRoot, resolvedPath);
430
+ if (!relative || relative === "") {
431
+ return ".";
432
+ }
433
+ return relative.split(path_1.default.sep).join("/");
434
+ }
435
+ async function resolveOptionalImageInputToUrl(input) {
436
+ if (input.image_path) {
437
+ return (0, imageUnderstanding_1.imageDataUrlFromPath)(input.image_path);
438
+ }
439
+ if (!input.image_url) {
440
+ return undefined;
441
+ }
442
+ if (input.image_url.startsWith("data:image/") ||
443
+ input.image_url.startsWith("http://") ||
444
+ input.image_url.startsWith("https://")) {
445
+ return input.image_url;
446
+ }
447
+ throw (0, policy_1.typedError)("invalid_request", "image_url must be an http(s) URL or data:image/* URL.");
448
+ }
449
+ function buildGenerateImageSummary(count) {
450
+ return count === 1 ? "Generated 1 image file." : `Generated ${count} image files.`;
451
+ }
452
+ function buildGenerateImageTextPayload(output) {
453
+ return {
454
+ ok: output.ok,
455
+ summary: output.summary,
456
+ ...(output.file_path ? { file_path: output.file_path } : {}),
457
+ ...(output.file_paths ? { file_paths: output.file_paths } : {}),
458
+ model: output.model,
459
+ };
460
+ }
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadAuthConfig = loadAuthConfig;
4
+ exports.updateAuthConfig = updateAuthConfig;
5
+ exports.getAuthConfig = getAuthConfig;
6
+ exports.authGuard = authGuard;
7
+ exports.apiKeyGuard = apiKeyGuard;
8
+ exports.registerAuthHooks = registerAuthHooks;
9
+ exports.requireAuth = requireAuth;
10
+ exports.requireRole = requireRole;
11
+ const files_1 = require("../storage/files");
12
+ let authConfig = {
13
+ enabled: false,
14
+ };
15
+ /**
16
+ * Load auth configuration from the main config file.
17
+ */
18
+ async function loadAuthConfig(paths) {
19
+ try {
20
+ const config = await (0, files_1.loadConfig)(paths);
21
+ return {
22
+ enabled: config.authEnabled ?? false,
23
+ };
24
+ }
25
+ catch {
26
+ return { enabled: false };
27
+ }
28
+ }
29
+ /**
30
+ * Update auth config (e.g., after config hot-reload).
31
+ */
32
+ function updateAuthConfig(config) {
33
+ authConfig = config;
34
+ }
35
+ /**
36
+ * Get current auth config.
37
+ */
38
+ function getAuthConfig() {
39
+ return authConfig;
40
+ }
41
+ /**
42
+ * Auth guard for protected routes.
43
+ *
44
+ * When auth is disabled: passes through all requests.
45
+ * When auth is enabled: checks for valid authentication.
46
+ */
47
+ function authGuard(req, reply, done) {
48
+ // Auth disabled - pass through
49
+ if (!authConfig.enabled) {
50
+ done();
51
+ return;
52
+ }
53
+ // ─────────────────────────────────────────────────────────────────────────
54
+ // Auth enabled - implement your auth logic here
55
+ // ─────────────────────────────────────────────────────────────────────────
56
+ // Example: Check for Authorization header
57
+ const authHeader = req.headers.authorization;
58
+ if (!authHeader) {
59
+ reply.status(401).send({
60
+ error: {
61
+ message: "Authentication required",
62
+ type: "auth_error",
63
+ code: "missing_auth",
64
+ },
65
+ });
66
+ return;
67
+ }
68
+ // Placeholder validation - replace with real auth logic
69
+ // For now, any Bearer token is accepted
70
+ if (authHeader.startsWith("Bearer ")) {
71
+ const token = authHeader.slice(7);
72
+ // TODO: Validate token (JWT decode, database lookup, etc.)
73
+ // For now, we just set a placeholder user
74
+ req.user = {
75
+ id: "placeholder",
76
+ email: undefined,
77
+ roles: ["user"],
78
+ };
79
+ done();
80
+ return;
81
+ }
82
+ reply.status(401).send({
83
+ error: {
84
+ message: "Invalid authentication",
85
+ type: "auth_error",
86
+ code: "invalid_auth",
87
+ },
88
+ });
89
+ }
90
+ /**
91
+ * Optional: API key auth guard for simpler use cases.
92
+ */
93
+ function apiKeyGuard(validKeys) {
94
+ return (req, reply, done) => {
95
+ if (!authConfig.enabled) {
96
+ done();
97
+ return;
98
+ }
99
+ const authHeader = req.headers.authorization;
100
+ const apiKey = req.headers["x-api-key"];
101
+ // Check X-API-Key header
102
+ if (apiKey && validKeys.has(apiKey)) {
103
+ req.user = { id: "api-key-user", roles: ["api"] };
104
+ done();
105
+ return;
106
+ }
107
+ // Check Bearer token as API key
108
+ if (authHeader?.startsWith("Bearer ")) {
109
+ const key = authHeader.slice(7);
110
+ if (validKeys.has(key)) {
111
+ req.user = { id: "api-key-user", roles: ["api"] };
112
+ done();
113
+ return;
114
+ }
115
+ }
116
+ reply.status(401).send({
117
+ error: {
118
+ message: "Invalid API key",
119
+ type: "auth_error",
120
+ code: "invalid_api_key",
121
+ },
122
+ });
123
+ };
124
+ }
125
+ /**
126
+ * Register auth hooks on protected route prefixes.
127
+ *
128
+ * Usage:
129
+ * await registerAuthHooks(app, paths, ["/admin", "/ui"]);
130
+ */
131
+ async function registerAuthHooks(app, paths, protectedPrefixes = ["/admin", "/ui"]) {
132
+ // Load initial config
133
+ authConfig = await loadAuthConfig(paths);
134
+ app.log.info({ authEnabled: authConfig.enabled, protectedPrefixes }, "Auth middleware initialized");
135
+ // Add hook for protected routes
136
+ app.addHook("onRequest", (req, reply, done) => {
137
+ const isProtected = protectedPrefixes.some((prefix) => req.url.startsWith(prefix));
138
+ if (isProtected) {
139
+ authGuard(req, reply, done);
140
+ }
141
+ else {
142
+ done();
143
+ }
144
+ });
145
+ }
146
+ /**
147
+ * Middleware factory for route-level auth.
148
+ *
149
+ * Usage in route handlers:
150
+ * app.get("/admin/something", { preHandler: [requireAuth()] }, handler);
151
+ */
152
+ function requireAuth() {
153
+ return (req, reply, done) => {
154
+ authGuard(req, reply, done);
155
+ };
156
+ }
157
+ /**
158
+ * Check if a user has a specific role.
159
+ */
160
+ function requireRole(role) {
161
+ return (req, reply, done) => {
162
+ // First check auth
163
+ if (authConfig.enabled) {
164
+ if (!req.user) {
165
+ reply.status(401).send({
166
+ error: { message: "Authentication required", type: "auth_error" },
167
+ });
168
+ return;
169
+ }
170
+ if (!req.user.roles?.includes(role)) {
171
+ reply.status(403).send({
172
+ error: { message: "Insufficient permissions", type: "auth_error" },
173
+ });
174
+ return;
175
+ }
176
+ }
177
+ done();
178
+ };
179
+ }