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,415 @@
1
+ import { getPoolByAlias, listPools } from "../pools/repository";
2
+ import { PoolCandidate } from "../pools/types";
3
+ import { getProtocolAdapter } from "../protocols/registry";
4
+ import { ProtocolOperation } from "../protocols/types";
5
+ import { StoragePaths } from "../storage/files";
6
+ import { EndpointType, ModelCapabilities, ModelModality } from "../types";
7
+ import { supportsRequirements } from "../utils/modelCapabilities";
8
+ import { canonicalProviderModelId, getEffectiveModelInsecureTls, listProviders } from "./repository";
9
+ import { getProviderModelHealthMap } from "./health";
10
+ import { ProviderModelRecord, ProviderRecord } from "./types";
11
+
12
+ const SCORE_FALLBACK = 20;
13
+ const SMART_ALIAS = "smart";
14
+
15
+ export interface RegistryModelEntry {
16
+ id: string;
17
+ provider_id: string;
18
+ model_id: string;
19
+ provider_model_id: string;
20
+ object: "model";
21
+ owned_by: string;
22
+ endpoint_type: EndpointType;
23
+ capabilities: ModelCapabilities;
24
+ enabled: boolean;
25
+ aliases: string[];
26
+ slug: string;
27
+ waypoi_health?: RegistryModelHealth;
28
+ }
29
+
30
+ export interface RegistryModelHealth {
31
+ status: "up" | "down" | "unknown";
32
+ lastCheckedAt?: string;
33
+ consecutiveFailures?: number;
34
+ latencyMsEwma?: number;
35
+ }
36
+
37
+ type CandidateRequirements = {
38
+ requiredInput?: ModelModality[];
39
+ requiredOutput?: ModelModality[];
40
+ };
41
+
42
+ export type ResolveModelResult =
43
+ | { kind: "pool"; alias: string }
44
+ | { kind: "direct"; canonicalId: string; candidates: PoolCandidate[] }
45
+ | { kind: "deprecated_pool_alias"; input: string; replacement: typeof SMART_ALIAS }
46
+ | { kind: "ambiguous"; input: string; matches: string[] }
47
+ | { kind: "none"; input: string };
48
+
49
+ interface FlattenedProviderModel {
50
+ provider: ProviderRecord;
51
+ model: ProviderModelRecord;
52
+ canonicalId: string;
53
+ }
54
+
55
+ export async function listModelsForApi(
56
+ paths: StoragePaths,
57
+ options?: { availableOnly?: boolean }
58
+ ): Promise<RegistryModelEntry[]> {
59
+ const providers = await listProviders(paths);
60
+ const healthMap = await getProviderModelHealthMap(paths);
61
+ const entries: RegistryModelEntry[] = [];
62
+
63
+ for (const provider of providers) {
64
+ for (const model of provider.models) {
65
+ const health = healthMap[model.providerModelId];
66
+ const status: RegistryModelHealth["status"] = health?.status ?? "unknown";
67
+ if (options?.availableOnly && status === "down") {
68
+ continue;
69
+ }
70
+ const canonicalId = canonicalProviderModelId(provider.id, model.modelId);
71
+ entries.push({
72
+ id: canonicalId,
73
+ provider_id: provider.id,
74
+ model_id: model.modelId,
75
+ provider_model_id: model.providerModelId,
76
+ object: "model",
77
+ owned_by: "waypoi",
78
+ endpoint_type: model.endpointType,
79
+ capabilities: model.capabilities,
80
+ enabled: provider.enabled && model.enabled !== false,
81
+ aliases: model.aliases ?? [],
82
+ slug: canonicalId,
83
+ waypoi_health: {
84
+ status,
85
+ lastCheckedAt: health?.lastCheckedAt ? new Date(health.lastCheckedAt).toISOString() : undefined,
86
+ consecutiveFailures: health?.consecutiveFailures,
87
+ latencyMsEwma: health?.latencyMsEwma,
88
+ },
89
+ });
90
+ }
91
+ }
92
+
93
+ return entries.sort((a, b) => a.id.localeCompare(b.id));
94
+ }
95
+
96
+ export async function resolveModel(
97
+ paths: StoragePaths,
98
+ inputId: string,
99
+ requirements: CandidateRequirements,
100
+ routing?: { operation: ProtocolOperation; stream: boolean }
101
+ ): Promise<ResolveModelResult> {
102
+ const pool = await getPoolByAlias(paths, inputId);
103
+ if (pool) {
104
+ return { kind: "pool", alias: inputId };
105
+ }
106
+
107
+ const models = await flattenProviderModels(paths);
108
+ const healthMap = await getProviderModelHealthMap(paths);
109
+ const canonicalMatch = models.find((entry) => {
110
+ return entry.canonicalId === inputId || entry.model.providerModelId === inputId;
111
+ });
112
+ if (canonicalMatch) {
113
+ const candidates = await buildAndFilterCandidates(
114
+ paths,
115
+ [canonicalMatch],
116
+ requirements,
117
+ routing,
118
+ healthMap
119
+ );
120
+ return {
121
+ kind: "direct",
122
+ canonicalId: canonicalMatch.canonicalId,
123
+ candidates,
124
+ };
125
+ }
126
+
127
+ const aliasMatches = models.filter((entry) => {
128
+ if (entry.model.modelId === inputId) {
129
+ return true;
130
+ }
131
+ return Boolean(entry.model.aliases?.includes(inputId));
132
+ });
133
+
134
+ if (aliasMatches.length === 0) {
135
+ return { kind: "none", input: inputId };
136
+ }
137
+ if (aliasMatches.length > 1) {
138
+ return {
139
+ kind: "ambiguous",
140
+ input: inputId,
141
+ matches: aliasMatches.map((entry) => entry.canonicalId).sort(),
142
+ };
143
+ }
144
+
145
+ const winner = aliasMatches[0];
146
+ const candidates = await buildAndFilterCandidates(
147
+ paths,
148
+ [winner],
149
+ requirements,
150
+ routing,
151
+ healthMap
152
+ );
153
+ return {
154
+ kind: "direct",
155
+ canonicalId: winner.canonicalId,
156
+ candidates,
157
+ };
158
+ }
159
+
160
+ export async function pickBestProviderModelByCapabilities(
161
+ paths: StoragePaths,
162
+ requirements: CandidateRequirements,
163
+ preferredEndpointType?: EndpointType
164
+ ): Promise<string | null> {
165
+ const all = await flattenProviderModels(paths);
166
+ const healthMap = await getProviderModelHealthMap(paths);
167
+ const filtered = all
168
+ .filter((entry) => entry.provider.enabled && entry.model.enabled !== false)
169
+ .filter((entry) => {
170
+ const health = healthMap[entry.model.providerModelId];
171
+ return health?.status !== "down";
172
+ })
173
+ .filter((entry) => supportsRequirements(entry.model.capabilities, requirements));
174
+
175
+ if (filtered.length === 0) {
176
+ return null;
177
+ }
178
+
179
+ const ranked = filtered.sort((a, b) => {
180
+ if (preferredEndpointType) {
181
+ const aPreferred = a.model.endpointType === preferredEndpointType ? 1 : 0;
182
+ const bPreferred = b.model.endpointType === preferredEndpointType ? 1 : 0;
183
+ if (aPreferred !== bPreferred) {
184
+ return bPreferred - aPreferred;
185
+ }
186
+ }
187
+ const aScore = typeof a.model.benchmark?.livebench === "number" ? a.model.benchmark.livebench : SCORE_FALLBACK;
188
+ const bScore = typeof b.model.benchmark?.livebench === "number" ? b.model.benchmark.livebench : SCORE_FALLBACK;
189
+ if (aScore !== bScore) {
190
+ return bScore - aScore;
191
+ }
192
+ return a.canonicalId.localeCompare(b.canonicalId);
193
+ });
194
+
195
+ return ranked[0].canonicalId;
196
+ }
197
+
198
+ export async function listModelAliases(paths: StoragePaths): Promise<string[]> {
199
+ const providers = await listProviders(paths);
200
+ const aliases = new Set<string>();
201
+ for (const provider of providers) {
202
+ for (const model of provider.models) {
203
+ for (const alias of model.aliases ?? []) {
204
+ aliases.add(alias);
205
+ }
206
+ }
207
+ }
208
+ const pools = await listPools(paths);
209
+ for (const pool of pools) {
210
+ for (const alias of pool.aliases) {
211
+ if (alias === SMART_ALIAS) {
212
+ aliases.add(alias);
213
+ }
214
+ }
215
+ }
216
+ return Array.from(aliases).sort();
217
+ }
218
+
219
+ export interface SmartPoolAvailability {
220
+ id: string;
221
+ alias: string;
222
+ strategy: "highest_rank_available" | "remaining_limit";
223
+ candidateCount: number;
224
+ capabilities: ModelCapabilities;
225
+ }
226
+
227
+ export async function getAvailableSmartPool(paths: StoragePaths): Promise<SmartPoolAvailability | null> {
228
+ const pool = await getPoolByAlias(paths, SMART_ALIAS);
229
+ if (!pool) {
230
+ return null;
231
+ }
232
+
233
+ const healthMap = await getProviderModelHealthMap(paths);
234
+ const candidates = pool.candidates.filter((candidate) => {
235
+ if (!candidate.providerEnabled || !candidate.modelEnabled || !candidate.supportsRouting) {
236
+ return false;
237
+ }
238
+ if (!candidate.baseUrl) {
239
+ return false;
240
+ }
241
+ if (!getProtocolAdapter(candidate.protocol)) {
242
+ return false;
243
+ }
244
+ if (candidate.providerModelId) {
245
+ const health = healthMap[candidate.providerModelId];
246
+ if (health?.status === "down") {
247
+ return false;
248
+ }
249
+ }
250
+ const requiresApiKey = (candidate.auth?.type ?? "bearer") !== "none";
251
+ if (requiresApiKey && !candidate.apiKey) {
252
+ return false;
253
+ }
254
+ return true;
255
+ });
256
+
257
+ if (candidates.length === 0) {
258
+ return null;
259
+ }
260
+
261
+ return {
262
+ id: pool.id,
263
+ alias: SMART_ALIAS,
264
+ strategy: pool.strategy,
265
+ candidateCount: candidates.length,
266
+ capabilities: unionCapabilities(candidates),
267
+ };
268
+ }
269
+
270
+ async function flattenProviderModels(paths: StoragePaths): Promise<FlattenedProviderModel[]> {
271
+ const providers = await listProviders(paths);
272
+ const flattened: FlattenedProviderModel[] = [];
273
+ for (const provider of providers) {
274
+ for (const model of provider.models) {
275
+ flattened.push({
276
+ provider,
277
+ model,
278
+ canonicalId: canonicalProviderModelId(provider.id, model.modelId),
279
+ });
280
+ }
281
+ }
282
+ return flattened;
283
+ }
284
+
285
+ async function buildAndFilterCandidates(
286
+ paths: StoragePaths,
287
+ entries: FlattenedProviderModel[],
288
+ requirements: CandidateRequirements,
289
+ routing?: { operation: ProtocolOperation; stream: boolean },
290
+ healthMap?: Record<string, { status?: "up" | "down" }>
291
+ ): Promise<PoolCandidate[]> {
292
+ const accepted: PoolCandidate[] = [];
293
+ const modelHealth = healthMap ?? (await getProviderModelHealthMap(paths));
294
+ for (const entry of entries) {
295
+ const candidate = buildCandidate(entry.provider, entry.model);
296
+ if (!candidate.providerEnabled || !candidate.modelEnabled) {
297
+ continue;
298
+ }
299
+ if (candidate.providerModelId) {
300
+ const health = modelHealth[candidate.providerModelId];
301
+ if (health?.status === "down") {
302
+ continue;
303
+ }
304
+ }
305
+ if (!candidate.baseUrl) {
306
+ continue;
307
+ }
308
+ const adapter = getProtocolAdapter(candidate.protocol);
309
+ if (!candidate.supportsRouting || !adapter) {
310
+ continue;
311
+ }
312
+ const requiresApiKey = (candidate.auth?.type ?? "bearer") !== "none";
313
+ if (requiresApiKey && !candidate.apiKey) {
314
+ continue;
315
+ }
316
+ if (!supportsRequirements(candidate.capabilities, requirements)) {
317
+ continue;
318
+ }
319
+ if (routing) {
320
+ const support = adapter.supports({
321
+ operation: routing.operation,
322
+ stream: routing.stream,
323
+ capabilities: candidate.capabilities,
324
+ requiredInput: requirements.requiredInput,
325
+ requiredOutput: requirements.requiredOutput,
326
+ });
327
+ if (!support.supported) {
328
+ continue;
329
+ }
330
+ }
331
+ accepted.push(candidate);
332
+ }
333
+ return accepted.sort((a, b) => {
334
+ if (b.score !== a.score) {
335
+ return b.score - a.score;
336
+ }
337
+ return a.id.localeCompare(b.id);
338
+ });
339
+ }
340
+
341
+ function buildCandidate(provider: ProviderRecord, model: ProviderModelRecord): PoolCandidate {
342
+ const score = model.benchmark?.livebench;
343
+ const baseUrl = model.baseUrl ?? provider.baseUrl;
344
+ return {
345
+ id: model.providerModelId,
346
+ providerModelId: model.providerModelId,
347
+ providerId: provider.id,
348
+ providerName: provider.name,
349
+ providerEnabled: provider.enabled,
350
+ modelEnabled: model.enabled !== false,
351
+ modelId: model.modelId,
352
+ aliases: model.aliases ?? [],
353
+ upstreamModel: model.upstreamModel,
354
+ baseUrl: baseUrl ?? "",
355
+ apiKey: model.apiKey ?? provider.apiKey,
356
+ insecureTls: getEffectiveModelInsecureTls(provider, model),
357
+ autoInsecureTlsDomains: provider.autoInsecureTlsDomains ?? [],
358
+ protocol: provider.protocol,
359
+ protocolConfig: provider.protocolConfig,
360
+ auth: provider.auth,
361
+ supportsRouting: provider.supportsRouting,
362
+ free: model.free,
363
+ endpointType: model.endpointType,
364
+ capabilities: model.capabilities,
365
+ score: typeof score === "number" ? score : SCORE_FALLBACK,
366
+ scoreSource: typeof score === "number" ? "benchmark.livebench" : "fallback",
367
+ limits: {
368
+ requestsPerMinute: model.limits?.requests?.perMinute ?? provider.limits?.requests?.perMinute,
369
+ requestsPerHour: model.limits?.requests?.perHour ?? provider.limits?.requests?.perHour,
370
+ requestsPerDay: model.limits?.requests?.perDay ?? provider.limits?.requests?.perDay,
371
+ requestsPerWeek: model.limits?.requests?.perWeek ?? provider.limits?.requests?.perWeek,
372
+ tokensPerMinute: model.limits?.tokens?.perMinute ?? provider.limits?.tokens?.perMinute,
373
+ tokensPerHour: model.limits?.tokens?.perHour ?? provider.limits?.tokens?.perHour,
374
+ tokensPerDay: model.limits?.tokens?.perDay ?? provider.limits?.tokens?.perDay,
375
+ tokensPerWeek: model.limits?.tokens?.perWeek ?? provider.limits?.tokens?.perWeek,
376
+ },
377
+ };
378
+ }
379
+
380
+ function unionCapabilities(candidates: PoolCandidate[]): ModelCapabilities {
381
+ const input = new Set<ModelModality>();
382
+ const output = new Set<ModelModality>();
383
+ let supportsTools = false;
384
+ let supportsStreaming = false;
385
+
386
+ for (const candidate of candidates) {
387
+ for (const modality of candidate.capabilities.input) {
388
+ input.add(modality);
389
+ }
390
+ for (const modality of candidate.capabilities.output) {
391
+ output.add(modality);
392
+ }
393
+ if (candidate.capabilities.supportsTools) {
394
+ supportsTools = true;
395
+ }
396
+ if (candidate.capabilities.supportsStreaming) {
397
+ supportsStreaming = true;
398
+ }
399
+ }
400
+
401
+ const capabilities: ModelCapabilities = {
402
+ input: Array.from(input).sort(),
403
+ output: Array.from(output).sort(),
404
+ source: "inferred",
405
+ };
406
+
407
+ if (supportsTools) {
408
+ capabilities.supportsTools = true;
409
+ }
410
+ if (supportsStreaming) {
411
+ capabilities.supportsStreaming = true;
412
+ }
413
+
414
+ return capabilities;
415
+ }