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,192 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRequestCaptureMiddleware = registerRequestCaptureMiddleware;
4
+ exports.setCaptureRouting = setCaptureRouting;
5
+ exports.setCaptureDerivedRequest = setCaptureDerivedRequest;
6
+ exports.setCaptureResponseOverride = setCaptureResponseOverride;
7
+ exports.setCaptureError = setCaptureError;
8
+ exports.startCaptureStreamResponse = startCaptureStreamResponse;
9
+ exports.appendCaptureStreamChunk = appendCaptureStreamChunk;
10
+ const crypto_1 = require("crypto");
11
+ const captureRepository_1 = require("../storage/captureRepository");
12
+ const captureContexts = new WeakMap();
13
+ function meta(reply) {
14
+ return reply;
15
+ }
16
+ async function registerRequestCaptureMiddleware(app, paths) {
17
+ app.addHook("onRequest", async (req) => {
18
+ if (!req.url.startsWith("/v1/"))
19
+ return;
20
+ let enabled = false;
21
+ try {
22
+ enabled = await (0, captureRepository_1.isCaptureEnabled)(paths);
23
+ }
24
+ catch {
25
+ enabled = false;
26
+ }
27
+ captureContexts.set(req, {
28
+ id: (0, crypto_1.randomUUID)(),
29
+ startedAt: Date.now(),
30
+ enabled,
31
+ });
32
+ });
33
+ app.addHook("preHandler", async (req) => {
34
+ const context = captureContexts.get(req);
35
+ if (!context?.enabled)
36
+ return;
37
+ context.requestBody = safeClone(req.body);
38
+ });
39
+ app.addHook("onSend", async (req, reply, payload) => {
40
+ const context = captureContexts.get(req);
41
+ if (!context?.enabled)
42
+ return payload;
43
+ if (!meta(reply).captureResponseOverride) {
44
+ context.responseBody = payloadToBody(payload);
45
+ context.responseHeaders = reply.getHeaders();
46
+ }
47
+ return payload;
48
+ });
49
+ app.addHook("onResponse", async (req, reply) => {
50
+ const context = captureContexts.get(req);
51
+ if (!context)
52
+ return;
53
+ try {
54
+ if (!context.enabled)
55
+ return;
56
+ const replyMeta = meta(reply);
57
+ context.routing = replyMeta.captureRouting;
58
+ context.derivedRequest = replyMeta.captureDerivedRequest;
59
+ context.error = replyMeta.captureError;
60
+ if (replyMeta.captureResponseOverride) {
61
+ context.responseBody = replyMeta.captureResponseOverride.body;
62
+ context.responseHeaders = replyMeta.captureResponseOverride.headers;
63
+ }
64
+ await (0, captureRepository_1.persistCaptureRecord)(paths, {
65
+ route: req.url,
66
+ method: req.method,
67
+ statusCode: reply.statusCode,
68
+ latencyMs: Date.now() - context.startedAt,
69
+ requestHeaders: req.headers,
70
+ responseHeaders: context.responseHeaders ??
71
+ reply.getHeaders(),
72
+ requestBody: context.requestBody,
73
+ responseBody: context.responseBody,
74
+ derivedRequest: context.derivedRequest,
75
+ routing: context.routing,
76
+ error: context.error,
77
+ });
78
+ }
79
+ catch (error) {
80
+ app.log.warn({ err: error }, "Failed to persist request capture");
81
+ }
82
+ finally {
83
+ captureContexts.delete(req);
84
+ }
85
+ });
86
+ }
87
+ function setCaptureRouting(reply, routing) {
88
+ meta(reply).captureRouting = routing;
89
+ const context = captureContexts.get(reply.request);
90
+ if (context?.enabled) {
91
+ context.routing = routing;
92
+ }
93
+ }
94
+ function setCaptureDerivedRequest(reply, payload) {
95
+ meta(reply).captureDerivedRequest = payload;
96
+ const context = captureContexts.get(reply.request);
97
+ if (context?.enabled) {
98
+ context.derivedRequest = payload;
99
+ }
100
+ }
101
+ function setCaptureResponseOverride(reply, body, headers) {
102
+ meta(reply).captureResponseOverride = { body, headers };
103
+ const context = captureContexts.get(reply.request);
104
+ if (context?.enabled) {
105
+ context.responseBody = body;
106
+ if (headers) {
107
+ context.responseHeaders = headers;
108
+ }
109
+ }
110
+ }
111
+ function setCaptureError(reply, error) {
112
+ meta(reply).captureError = error;
113
+ const context = captureContexts.get(reply.request);
114
+ if (context?.enabled) {
115
+ context.error = error;
116
+ }
117
+ }
118
+ function startCaptureStreamResponse(reply, headers, contentType, note) {
119
+ const body = {
120
+ $type: "stream",
121
+ contentType,
122
+ bytes: 0,
123
+ };
124
+ if (note) {
125
+ body.note = note;
126
+ }
127
+ setCaptureResponseOverride(reply, body, headers);
128
+ }
129
+ function appendCaptureStreamChunk(reply, chunk, options) {
130
+ const context = captureContexts.get(reply.request);
131
+ if (!context?.enabled)
132
+ return;
133
+ const body = ensureCaptureStreamBody(context, options?.contentType);
134
+ body.bytes += chunk.byteLength;
135
+ if (isTextLikeStream(body.contentType)) {
136
+ body.text = (body.text ?? "") + chunk.toString("utf8");
137
+ }
138
+ context.responseBody = body;
139
+ if (options?.headers) {
140
+ context.responseHeaders = options.headers;
141
+ }
142
+ meta(reply).captureResponseOverride = {
143
+ body,
144
+ headers: context.responseHeaders,
145
+ };
146
+ }
147
+ function safeClone(value) {
148
+ try {
149
+ return JSON.parse(JSON.stringify(value));
150
+ }
151
+ catch {
152
+ return value;
153
+ }
154
+ }
155
+ function payloadToBody(payload) {
156
+ if (payload === null || payload === undefined)
157
+ return payload;
158
+ if (Buffer.isBuffer(payload)) {
159
+ return { $type: "buffer", base64: payload.toString("base64"), bytes: payload.byteLength };
160
+ }
161
+ if (typeof payload === "string") {
162
+ try {
163
+ return JSON.parse(payload);
164
+ }
165
+ catch {
166
+ return payload;
167
+ }
168
+ }
169
+ return payload;
170
+ }
171
+ function ensureCaptureStreamBody(context, contentType) {
172
+ const existing = context.responseBody;
173
+ if (existing?.$type === "stream") {
174
+ if (contentType) {
175
+ existing.contentType = contentType;
176
+ }
177
+ return existing;
178
+ }
179
+ const body = {
180
+ $type: "stream",
181
+ contentType: contentType ?? "application/octet-stream",
182
+ bytes: 0,
183
+ };
184
+ context.responseBody = body;
185
+ return body;
186
+ }
187
+ function isTextLikeStream(contentType) {
188
+ return (contentType.includes("text/") ||
189
+ contentType.includes("json") ||
190
+ contentType.includes("xml") ||
191
+ contentType.includes("event-stream"));
192
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRequestStatsMiddleware = registerRequestStatsMiddleware;
4
+ exports.setStatsPayload = setStatsPayload;
5
+ const crypto_1 = require("crypto");
6
+ const statsRepository_1 = require("../storage/statsRepository");
7
+ // WeakMap to store request context without polluting request object
8
+ const requestContexts = new WeakMap();
9
+ async function registerRequestStatsMiddleware(app, paths) {
10
+ // Decorate request with stats context
11
+ app.decorateRequest("statsContext", null);
12
+ // Hook: onRequest - capture start time and request size
13
+ app.addHook("onRequest", async (req) => {
14
+ // Only track /v1/* routes
15
+ if (!req.url.startsWith("/v1/")) {
16
+ return;
17
+ }
18
+ const context = {
19
+ requestId: (0, crypto_1.randomUUID)(),
20
+ startTime: Date.now(),
21
+ requestBytes: 0,
22
+ route: req.url,
23
+ method: req.method
24
+ };
25
+ // Estimate request size from content-length header
26
+ const contentLength = req.headers["content-length"];
27
+ if (contentLength) {
28
+ context.requestBytes = parseInt(contentLength, 10) || 0;
29
+ }
30
+ requestContexts.set(req, context);
31
+ });
32
+ // Hook: preHandler - extract model from parsed body
33
+ app.addHook("preHandler", async (req) => {
34
+ const context = requestContexts.get(req);
35
+ if (!context)
36
+ return;
37
+ const body = req.body;
38
+ if (body?.model) {
39
+ context.publicModel = body.model;
40
+ }
41
+ });
42
+ // Hook: onResponse - log the stats
43
+ app.addHook("onResponse", async (req, reply) => {
44
+ const context = requestContexts.get(req);
45
+ if (!context)
46
+ return;
47
+ const latencyMs = Date.now() - context.startTime;
48
+ const statusCode = reply.statusCode;
49
+ // Try to get response size from content-length header
50
+ let responseBytes = 0;
51
+ const respContentLength = reply.getHeader("content-length");
52
+ if (respContentLength) {
53
+ responseBytes = typeof respContentLength === "number"
54
+ ? respContentLength
55
+ : parseInt(String(respContentLength), 10) || 0;
56
+ }
57
+ // Determine if there was an error
58
+ let errorType;
59
+ if (statusCode >= 400) {
60
+ if (statusCode >= 500) {
61
+ errorType = "server_error";
62
+ }
63
+ else if (statusCode === 429) {
64
+ errorType = "rate_limit";
65
+ }
66
+ else if (statusCode === 401 || statusCode === 403) {
67
+ errorType = "auth_error";
68
+ }
69
+ else {
70
+ errorType = "client_error";
71
+ }
72
+ }
73
+ // Extract token info from reply (if stored during route handling)
74
+ const statsPayload = reply.statsPayload;
75
+ const stats = {
76
+ requestId: context.requestId,
77
+ timestamp: new Date(),
78
+ route: context.route,
79
+ method: context.method,
80
+ publicModel: context.publicModel,
81
+ endpointId: statsPayload?.endpointId,
82
+ endpointName: statsPayload?.endpointName,
83
+ upstreamModel: statsPayload?.upstreamModel,
84
+ requestBytes: context.requestBytes,
85
+ responseBytes,
86
+ latencyMs,
87
+ statusCode,
88
+ errorType,
89
+ totalTokens: statsPayload?.totalTokens ?? estimateTokens(context.requestBytes, responseBytes),
90
+ promptTokens: statsPayload?.promptTokens ?? null,
91
+ completionTokens: statsPayload?.completionTokens ?? null
92
+ };
93
+ // Append stats asynchronously (don't block response)
94
+ (0, statsRepository_1.appendStats)(paths, stats).catch((err) => {
95
+ app.log.error({ err }, "Failed to append request stats");
96
+ });
97
+ // Cleanup
98
+ requestContexts.delete(req);
99
+ });
100
+ }
101
+ /**
102
+ * Helper to set stats payload from route handlers
103
+ */
104
+ function setStatsPayload(reply, payload) {
105
+ reply.statsPayload = payload;
106
+ }
107
+ /**
108
+ * Estimate token count from byte sizes when actual usage is not available.
109
+ * Uses rough approximation: ~4 characters per token, ~1 byte per character for English.
110
+ * This is intentionally conservative.
111
+ */
112
+ function estimateTokens(requestBytes, responseBytes) {
113
+ if (requestBytes === 0 && responseBytes === 0) {
114
+ return null;
115
+ }
116
+ // Rough estimate: 4 bytes per token average
117
+ return Math.ceil((requestBytes + responseBytes) / 4);
118
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rebuildDefaultPools = rebuildDefaultPools;
4
+ const repository_1 = require("../providers/repository");
5
+ const modelCapabilities_1 = require("../utils/modelCapabilities");
6
+ const repository_2 = require("./repository");
7
+ const repository_3 = require("../providers/repository");
8
+ const DEFAULT_SCORE_FALLBACK = 20;
9
+ const DEFAULT_POOLS = [
10
+ {
11
+ id: "smart",
12
+ aliases: ["smart"],
13
+ requiredInput: [],
14
+ requiredOutput: [],
15
+ },
16
+ ];
17
+ function buildCandidate(provider, model, scoreFallback) {
18
+ const effectiveBaseUrl = model.baseUrl ?? provider.baseUrl;
19
+ const score = model.benchmark?.livebench;
20
+ return {
21
+ id: model.providerModelId,
22
+ providerModelId: model.providerModelId,
23
+ providerId: provider.id,
24
+ providerName: provider.name,
25
+ providerEnabled: provider.enabled,
26
+ modelEnabled: model.enabled !== false,
27
+ modelId: model.modelId,
28
+ aliases: model.aliases ?? [],
29
+ upstreamModel: model.upstreamModel,
30
+ baseUrl: effectiveBaseUrl ?? "",
31
+ apiKey: model.apiKey ?? provider.apiKey,
32
+ insecureTls: (0, repository_3.getEffectiveModelInsecureTls)(provider, model),
33
+ autoInsecureTlsDomains: provider.autoInsecureTlsDomains ?? [],
34
+ protocol: provider.protocol,
35
+ protocolConfig: provider.protocolConfig,
36
+ auth: provider.auth,
37
+ supportsRouting: provider.supportsRouting,
38
+ free: model.free,
39
+ endpointType: model.endpointType,
40
+ capabilities: model.capabilities,
41
+ score: typeof score === "number" ? score : scoreFallback,
42
+ scoreSource: typeof score === "number" ? "benchmark.livebench" : "fallback",
43
+ limits: {
44
+ requestsPerMinute: model.limits?.requests?.perMinute ?? provider.limits?.requests?.perMinute,
45
+ requestsPerHour: model.limits?.requests?.perHour ?? provider.limits?.requests?.perHour,
46
+ requestsPerDay: model.limits?.requests?.perDay ?? provider.limits?.requests?.perDay,
47
+ requestsPerWeek: model.limits?.requests?.perWeek ?? provider.limits?.requests?.perWeek,
48
+ tokensPerMinute: model.limits?.tokens?.perMinute ?? provider.limits?.tokens?.perMinute,
49
+ tokensPerHour: model.limits?.tokens?.perHour ?? provider.limits?.tokens?.perHour,
50
+ tokensPerDay: model.limits?.tokens?.perDay ?? provider.limits?.tokens?.perDay,
51
+ tokensPerWeek: model.limits?.tokens?.perWeek ?? provider.limits?.tokens?.perWeek,
52
+ },
53
+ };
54
+ }
55
+ async function rebuildDefaultPools(paths, scoreFallback = DEFAULT_SCORE_FALLBACK) {
56
+ const existing = await (0, repository_2.loadPools)(paths);
57
+ const providers = await (0, repository_1.listProviders)(paths);
58
+ const userPools = existing.pools.filter((p) => p.userDefined);
59
+ const autoPools = existing.pools.filter((p) => !p.userDefined);
60
+ const autoPoolIds = new Set(autoPools.map((p) => p.id));
61
+ const pools = DEFAULT_POOLS.map((template) => {
62
+ const existingAuto = autoPools.find((p) => p.id === template.id);
63
+ return {
64
+ id: template.id,
65
+ name: existingAuto?.name ?? template.id,
66
+ aliases: template.aliases,
67
+ enabled: existingAuto?.enabled ?? true,
68
+ strategy: existingAuto?.strategy ?? "highest_rank_available",
69
+ requiredInput: template.requiredInput,
70
+ requiredOutput: template.requiredOutput,
71
+ scoreFallback,
72
+ candidates: [],
73
+ candidateSelection: existingAuto?.candidateSelection ?? [],
74
+ userDefined: false,
75
+ updatedAt: new Date().toISOString(),
76
+ };
77
+ });
78
+ const allCandidates = new Map();
79
+ for (const provider of providers) {
80
+ for (const model of provider.models) {
81
+ const candidate = buildCandidate(provider, model, scoreFallback);
82
+ allCandidates.set(`${provider.id}/${model.modelId}`, candidate);
83
+ }
84
+ }
85
+ for (const pool of pools) {
86
+ for (const [key, candidate] of allCandidates) {
87
+ if (!candidate.modelEnabled || !candidate.providerEnabled)
88
+ continue;
89
+ if (!candidate.free && !pool.userDefined)
90
+ continue;
91
+ if ((0, modelCapabilities_1.supportsRequirements)(candidate.capabilities, {
92
+ requiredInput: pool.requiredInput,
93
+ requiredOutput: pool.requiredOutput,
94
+ })) {
95
+ pool.candidates.push(candidate);
96
+ }
97
+ }
98
+ if (pool.candidateSelection.length > 0) {
99
+ const selected = pool.candidateSelection
100
+ .map((sel) => allCandidates.get(sel))
101
+ .filter((c) => c !== undefined);
102
+ if (selected.length > 0) {
103
+ pool.candidates = selected;
104
+ }
105
+ }
106
+ pool.candidates = pool.candidates.sort((a, b) => {
107
+ if (b.score !== a.score) {
108
+ return b.score - a.score;
109
+ }
110
+ return `${a.providerId}/${a.modelId}`.localeCompare(`${b.providerId}/${b.modelId}`);
111
+ });
112
+ }
113
+ for (const userPool of userPools) {
114
+ if (!allPoolsHasId(pools, userPool.id)) {
115
+ userPool.candidates = (userPool.candidateSelection ?? [])
116
+ .map((sel) => allCandidates.get(sel))
117
+ .filter((c) => c !== undefined);
118
+ userPool.candidates = userPool.candidates.sort((a, b) => {
119
+ if (b.score !== a.score)
120
+ return b.score - a.score;
121
+ return `${a.providerId}/${a.modelId}`.localeCompare(`${b.providerId}/${b.modelId}`);
122
+ });
123
+ userPool.updatedAt = new Date().toISOString();
124
+ pools.push(userPool);
125
+ }
126
+ }
127
+ await (0, repository_2.savePools)(paths, pools);
128
+ return pools;
129
+ }
130
+ function allPoolsHasId(pools, id) {
131
+ return pools.some((p) => p.id === id);
132
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPools = loadPools;
4
+ exports.savePools = savePools;
5
+ exports.listPools = listPools;
6
+ exports.getPoolByAlias = getPoolByAlias;
7
+ exports.loadPoolState = loadPoolState;
8
+ exports.savePoolState = savePoolState;
9
+ const files_1 = require("../storage/files");
10
+ const POOLS_VERSION = 1;
11
+ const STATE_VERSION = 1;
12
+ function defaultPoolStore() {
13
+ return {
14
+ version: POOLS_VERSION,
15
+ updatedAt: new Date().toISOString(),
16
+ pools: [],
17
+ };
18
+ }
19
+ function defaultPoolState() {
20
+ return {
21
+ version: STATE_VERSION,
22
+ updatedAt: new Date().toISOString(),
23
+ candidates: {},
24
+ };
25
+ }
26
+ async function loadPools(paths) {
27
+ const store = await (0, files_1.readJsonFile)(paths.poolsPath, defaultPoolStore());
28
+ if (!Array.isArray(store.pools)) {
29
+ return defaultPoolStore();
30
+ }
31
+ return {
32
+ version: Number.isFinite(store.version) ? store.version : POOLS_VERSION,
33
+ updatedAt: typeof store.updatedAt === "string" ? store.updatedAt : new Date().toISOString(),
34
+ pools: store.pools,
35
+ };
36
+ }
37
+ async function savePools(paths, pools) {
38
+ await (0, files_1.writeJsonFile)(paths.poolsPath, {
39
+ version: POOLS_VERSION,
40
+ updatedAt: new Date().toISOString(),
41
+ pools,
42
+ });
43
+ }
44
+ async function listPools(paths) {
45
+ const store = await loadPools(paths);
46
+ return [...store.pools].sort((a, b) => a.id.localeCompare(b.id));
47
+ }
48
+ async function getPoolByAlias(paths, alias) {
49
+ const pools = await listPools(paths);
50
+ return pools.find((pool) => pool.aliases.includes(alias)) ?? null;
51
+ }
52
+ async function loadPoolState(paths) {
53
+ const state = await (0, files_1.readJsonFile)(paths.poolStatePath, defaultPoolState());
54
+ if (!state.candidates || typeof state.candidates !== "object") {
55
+ return defaultPoolState();
56
+ }
57
+ return {
58
+ version: Number.isFinite(state.version) ? state.version : STATE_VERSION,
59
+ updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : new Date().toISOString(),
60
+ candidates: state.candidates,
61
+ };
62
+ }
63
+ async function savePoolState(paths, state) {
64
+ await (0, files_1.writeJsonFile)(paths.poolStatePath, {
65
+ ...state,
66
+ version: STATE_VERSION,
67
+ updatedAt: new Date().toISOString(),
68
+ });
69
+ }