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,1503 @@
1
+ /**
2
+ * Waypoi API Client
3
+ *
4
+ * Centralized API layer for communicating with the Waypoi proxy server.
5
+ */
6
+
7
+ const API_BASE = '';
8
+
9
+ export class ApiError extends Error {
10
+ constructor(
11
+ public status: number,
12
+ public statusText: string,
13
+ public body?: unknown
14
+ ) {
15
+ super(`API Error: ${status} ${statusText}`);
16
+ this.name = 'ApiError';
17
+ }
18
+ }
19
+
20
+ async function handleResponse<T>(response: Response): Promise<T> {
21
+ if (!response.ok) {
22
+ let body: unknown;
23
+ try {
24
+ body = await response.json();
25
+ } catch {
26
+ body = await response.text();
27
+ }
28
+ throw new ApiError(response.status, response.statusText, body);
29
+ }
30
+ return response.json();
31
+ }
32
+
33
+ export interface AdminMeta {
34
+ name: string;
35
+ version: string;
36
+ now: string;
37
+ }
38
+
39
+ export async function getAdminMeta(): Promise<AdminMeta> {
40
+ const response = await fetch(`${API_BASE}/admin/meta`);
41
+ return handleResponse<AdminMeta>(response);
42
+ }
43
+
44
+ export interface ProtocolInfo {
45
+ id: string;
46
+ label: string;
47
+ description: string;
48
+ operations: string[];
49
+ streamOperations: string[];
50
+ supportsRouting: boolean;
51
+ }
52
+
53
+ export interface ProtocolsResponse {
54
+ data: ProtocolInfo[];
55
+ }
56
+
57
+ export async function listProtocols(): Promise<ProtocolInfo[]> {
58
+ const response = await fetch(`${API_BASE}/admin/protocols`);
59
+ const result = await handleResponse<ProtocolsResponse>(response);
60
+ return result.data;
61
+ }
62
+
63
+ // ========================================
64
+ // Providers API
65
+ // ========================================
66
+
67
+ export interface ProviderModel {
68
+ providerModelId: string;
69
+ providerId: string;
70
+ modelId: string;
71
+ upstreamModel: string;
72
+ baseUrl?: string;
73
+ apiKey?: string;
74
+ insecureTls?: boolean;
75
+ enabled?: boolean;
76
+ aliases?: string[];
77
+ free: boolean;
78
+ modalities: string[];
79
+ capabilities: ModelCapabilities;
80
+ endpointType: EndpointType;
81
+ benchmark?: {
82
+ livebench?: number;
83
+ };
84
+ limits?: ProviderLimits;
85
+ }
86
+
87
+ export interface DiscoveredProviderModel {
88
+ id: string;
89
+ capabilities?: ModelCapabilities;
90
+ }
91
+
92
+ export interface ProviderModelDiscoveryResponse {
93
+ baseUrl: string;
94
+ models: DiscoveredProviderModel[];
95
+ }
96
+
97
+ export interface ProviderAuthConfig {
98
+ type: 'bearer' | 'query' | 'header' | 'none';
99
+ keyParam?: string;
100
+ headerName?: string;
101
+ keyPrefix?: string;
102
+ }
103
+
104
+ export interface ProviderProtocolConfig {
105
+ router?: string;
106
+ responseTextPaths?: string[];
107
+ [key: string]: unknown;
108
+ }
109
+
110
+ export interface ProviderLimits {
111
+ requests?: {
112
+ perMinute?: number;
113
+ perHour?: number;
114
+ perDay?: number;
115
+ perWeek?: number;
116
+ perMonth?: number;
117
+ };
118
+ tokens?: {
119
+ perMinute?: number;
120
+ perHour?: number;
121
+ perDay?: number;
122
+ perWeek?: number;
123
+ perMonth?: number;
124
+ };
125
+ concurrent?: number;
126
+ }
127
+
128
+ export interface Provider {
129
+ id: string;
130
+ name: string;
131
+ description?: string;
132
+ docs?: string;
133
+ protocol: string;
134
+ protocolRaw?: string;
135
+ protocolConfig?: ProviderProtocolConfig;
136
+ baseUrl: string;
137
+ insecureTls?: boolean;
138
+ autoInsecureTlsDomains?: string[];
139
+ enabled: boolean;
140
+ supportsRouting: boolean;
141
+ auth?: ProviderAuthConfig;
142
+ envVar?: string;
143
+ apiKey?: string;
144
+ limits?: ProviderLimits;
145
+ models: ProviderModel[];
146
+ warnings?: string[];
147
+ importedAt?: string;
148
+ }
149
+
150
+ export async function listProviders(): Promise<Provider[]> {
151
+ const response = await fetch(`${API_BASE}/admin/providers`);
152
+ return handleResponse<Provider[]>(response);
153
+ }
154
+
155
+ export async function addProvider(payload: Partial<Provider>): Promise<Provider> {
156
+ const response = await fetch(`${API_BASE}/admin/providers`, {
157
+ method: 'POST',
158
+ headers: { 'Content-Type': 'application/json' },
159
+ body: JSON.stringify(payload),
160
+ });
161
+ return handleResponse<Provider>(response);
162
+ }
163
+
164
+ export async function updateProvider(providerId: string, payload: Partial<Provider>): Promise<Provider> {
165
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}`, {
166
+ method: 'PATCH',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify(payload),
169
+ });
170
+ return handleResponse<Provider>(response);
171
+ }
172
+
173
+ export async function deleteProvider(providerId: string): Promise<{ deleted: string }> {
174
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}`, {
175
+ method: 'DELETE',
176
+ });
177
+ return handleResponse<{ deleted: string }>(response);
178
+ }
179
+
180
+ export async function enableProvider(providerId: string): Promise<Provider> {
181
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/enable`, {
182
+ method: 'POST',
183
+ });
184
+ return handleResponse<Provider>(response);
185
+ }
186
+
187
+ export async function disableProvider(providerId: string): Promise<Provider> {
188
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/disable`, {
189
+ method: 'POST',
190
+ });
191
+ return handleResponse<Provider>(response);
192
+ }
193
+
194
+ export async function listProviderModels(providerId: string): Promise<ProviderModel[]> {
195
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models`);
196
+ return handleResponse<ProviderModel[]>(response);
197
+ }
198
+
199
+ export async function addProviderModel(
200
+ providerId: string,
201
+ payload: Partial<ProviderModel>
202
+ ): Promise<ProviderModel> {
203
+ const response = await fetch(`${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models`, {
204
+ method: 'POST',
205
+ headers: { 'Content-Type': 'application/json' },
206
+ body: JSON.stringify(payload),
207
+ });
208
+ return handleResponse<ProviderModel>(response);
209
+ }
210
+
211
+ export async function updateProviderModel(
212
+ providerId: string,
213
+ modelRef: string,
214
+ payload: Partial<ProviderModel>
215
+ ): Promise<ProviderModel> {
216
+ const response = await fetch(
217
+ `${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models/${encodeURIComponent(modelRef)}`,
218
+ {
219
+ method: 'PATCH',
220
+ headers: { 'Content-Type': 'application/json' },
221
+ body: JSON.stringify(payload),
222
+ }
223
+ );
224
+ return handleResponse<ProviderModel>(response);
225
+ }
226
+
227
+ export async function deleteProviderModel(providerId: string, modelRef: string): Promise<{ deleted: string }> {
228
+ const response = await fetch(
229
+ `${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models/${encodeURIComponent(modelRef)}`,
230
+ { method: 'DELETE' }
231
+ );
232
+ return handleResponse<{ deleted: string }>(response);
233
+ }
234
+
235
+ export async function enableProviderModel(providerId: string, modelRef: string): Promise<ProviderModel> {
236
+ const response = await fetch(
237
+ `${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models/${encodeURIComponent(modelRef)}/enable`,
238
+ { method: 'POST' }
239
+ );
240
+ return handleResponse<ProviderModel>(response);
241
+ }
242
+
243
+ export async function disableProviderModel(providerId: string, modelRef: string): Promise<ProviderModel> {
244
+ const response = await fetch(
245
+ `${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models/${encodeURIComponent(modelRef)}/disable`,
246
+ { method: 'POST' }
247
+ );
248
+ return handleResponse<ProviderModel>(response);
249
+ }
250
+
251
+ export async function discoverProviderModels(
252
+ providerId: string,
253
+ payload?: { baseUrl?: string; apiKey?: string; insecureTls?: boolean }
254
+ ): Promise<ProviderModelDiscoveryResponse> {
255
+ const response = await fetch(
256
+ `${API_BASE}/admin/providers/${encodeURIComponent(providerId)}/models/discover`,
257
+ {
258
+ method: 'POST',
259
+ headers: { 'Content-Type': 'application/json' },
260
+ body: JSON.stringify(payload ?? {}),
261
+ }
262
+ );
263
+ return handleResponse<ProviderModelDiscoveryResponse>(response);
264
+ }
265
+
266
+ // ========================================
267
+ // Models API
268
+ // ========================================
269
+
270
+ export type EndpointType = 'llm' | 'diffusion' | 'audio' | 'embedding';
271
+ export type ModelModality = 'text' | 'image' | 'audio' | 'embedding';
272
+
273
+ export interface ModelCapabilities {
274
+ input: ModelModality[];
275
+ output: ModelModality[];
276
+ supportsTools?: boolean;
277
+ supportsStreaming?: boolean;
278
+ source?: 'configured' | 'inferred';
279
+ }
280
+
281
+ export interface Model {
282
+ id: string;
283
+ object: 'model';
284
+ created?: number;
285
+ owned_by?: string;
286
+ endpoint_type?: EndpointType;
287
+ capabilities?: ModelCapabilities;
288
+ waypoi_health?: {
289
+ status: 'up' | 'down' | 'unknown';
290
+ lastCheckedAt?: string;
291
+ consecutiveFailures?: number;
292
+ latencyMsEwma?: number;
293
+ };
294
+ waypoi_pool?: {
295
+ id: string;
296
+ strategy: string;
297
+ candidateCount: number;
298
+ scoreSource: string;
299
+ };
300
+ }
301
+
302
+ export interface ModelsResponse {
303
+ object: 'list';
304
+ data: Model[];
305
+ }
306
+
307
+ export async function listModels(options?: { availableOnly?: boolean }): Promise<ModelsResponse> {
308
+ const params = new URLSearchParams();
309
+ if (options?.availableOnly) {
310
+ params.set('available_only', 'true');
311
+ }
312
+ const qs = params.toString();
313
+ const response = await fetch(`${API_BASE}/v1/models${qs ? `?${qs}` : ''}`);
314
+ return handleResponse<ModelsResponse>(response);
315
+ }
316
+
317
+ // ========================================
318
+ // Stats API
319
+ // ========================================
320
+
321
+ export interface StatsAggregation {
322
+ window: string;
323
+ timeZone?: string;
324
+ total: number;
325
+ success: number;
326
+ errors: number;
327
+ avgLatencyMs: number | null;
328
+ p50LatencyMs: number | null;
329
+ p95LatencyMs: number | null;
330
+ p99LatencyMs: number | null;
331
+ totalTokens: number;
332
+ tokensPerHour: number | null;
333
+ byModel: Record<string, { count: number; avgLatencyMs: number; tokens: number }>;
334
+ byEndpoint: Record<string, { count: number; avgLatencyMs: number; tokens: number; errors: number }>;
335
+ }
336
+
337
+ export async function getStats(window: string = '24h', options?: { timeZone?: string }): Promise<StatsAggregation> {
338
+ const params = new URLSearchParams({ window });
339
+ if (options?.timeZone) params.set('timeZone', options.timeZone);
340
+ const response = await fetch(`${API_BASE}/admin/stats?${params.toString()}`);
341
+ return handleResponse<StatsAggregation>(response);
342
+ }
343
+
344
+ export interface LatencyDistribution {
345
+ window: string;
346
+ timeZone?: string;
347
+ count: number;
348
+ min: number | null;
349
+ max: number | null;
350
+ avg: number | null;
351
+ p50: number | null;
352
+ p95: number | null;
353
+ p99: number | null;
354
+ histogram: Record<string, number>;
355
+ }
356
+
357
+ export async function getLatencyDistribution(window: string = '7d', options?: { timeZone?: string }): Promise<LatencyDistribution> {
358
+ const params = new URLSearchParams({ window });
359
+ if (options?.timeZone) params.set('timeZone', options.timeZone);
360
+ const response = await fetch(`${API_BASE}/admin/stats/latency?${params.toString()}`);
361
+ return handleResponse<LatencyDistribution>(response);
362
+ }
363
+
364
+ export interface TokenUsage {
365
+ window: string;
366
+ totalTokens: number;
367
+ totalInputTokens: number;
368
+ totalOutputTokens: number;
369
+ totalRequests: number;
370
+ avgTokensPerRequest: number;
371
+ tokenEstimatedCount?: number;
372
+ tokenEstimatedRate?: number;
373
+ splitUnknownCount?: number;
374
+ splitUnknownRate?: number;
375
+ bucketGranularity?: 'hour' | 'day';
376
+ bucketTimeZone?: string;
377
+ byDay: Array<{
378
+ date: string;
379
+ count: number;
380
+ tokens: number;
381
+ estimated: number;
382
+ inputTokens: number;
383
+ outputTokens: number;
384
+ splitUnknown: number;
385
+ }>;
386
+ }
387
+
388
+ export async function getTokenUsage(window: string = '7d', options?: { timeZone?: string }): Promise<TokenUsage> {
389
+ const params = new URLSearchParams({ window });
390
+ if (options?.timeZone) params.set('timeZone', options.timeZone);
391
+ const response = await fetch(`${API_BASE}/admin/stats/tokens?${params.toString()}`);
392
+ return handleResponse<TokenUsage>(response);
393
+ }
394
+
395
+ // ========================================
396
+ // Chat Completions API (for Playground)
397
+ // ========================================
398
+
399
+ // Content can be a string or array of content parts (multimodal)
400
+ export type ContentPart =
401
+ | { type: 'text'; text: string }
402
+ | { type: 'image_url'; image_url: { url: string } }
403
+ | { type: 'input_audio'; input_audio: { url?: string; data?: string; format?: string } }
404
+ | { type: 'audio'; audio: { url?: string; data?: string; format?: string } }
405
+
406
+ export interface ChatMessage {
407
+ role: 'user' | 'assistant' | 'system' | 'tool';
408
+ content: string | ContentPart[] | null;
409
+ name?: string;
410
+ tool_calls?: Array<{
411
+ id: string;
412
+ type: 'function';
413
+ function: { name: string; arguments: string };
414
+ }>;
415
+ tool_call_id?: string;
416
+ }
417
+
418
+ export interface ChatCompletionRequest {
419
+ model: string;
420
+ messages: ChatMessage[];
421
+ stream?: boolean;
422
+ temperature?: number;
423
+ top_p?: number;
424
+ max_tokens?: number;
425
+ presence_penalty?: number;
426
+ frequency_penalty?: number;
427
+ seed?: number;
428
+ stop?: string | string[];
429
+ tools?: unknown[];
430
+ tool_choice?: unknown;
431
+ }
432
+
433
+ export interface ChatCompletionChoice {
434
+ index: number;
435
+ message: ChatMessage;
436
+ finish_reason: string | null;
437
+ }
438
+
439
+ export interface ChatCompletionResponse {
440
+ id: string;
441
+ object: 'chat.completion';
442
+ created: number;
443
+ model: string;
444
+ choices: ChatCompletionChoice[];
445
+ usage?: {
446
+ prompt_tokens: number;
447
+ completion_tokens: number;
448
+ total_tokens: number;
449
+ };
450
+ }
451
+
452
+ export interface ChatCompletionRawResponse {
453
+ id?: string;
454
+ object?: string;
455
+ created?: number;
456
+ model?: string;
457
+ choices?: Array<{
458
+ index?: number;
459
+ finish_reason?: string | null;
460
+ message?: {
461
+ role?: string;
462
+ content?: string | ContentPart[] | null;
463
+ audio?: { url?: string; data?: string; format?: string };
464
+ [key: string]: unknown;
465
+ };
466
+ [key: string]: unknown;
467
+ }>;
468
+ usage?: {
469
+ prompt_tokens?: number;
470
+ completion_tokens?: number;
471
+ total_tokens?: number;
472
+ };
473
+ [key: string]: unknown;
474
+ }
475
+
476
+ export async function createChatCompletion(
477
+ request: ChatCompletionRequest
478
+ ): Promise<ChatCompletionResponse> {
479
+ const response = await fetch(`${API_BASE}/v1/chat/completions`, {
480
+ method: 'POST',
481
+ headers: { 'Content-Type': 'application/json' },
482
+ body: JSON.stringify({ ...request, stream: false }),
483
+ });
484
+ return handleResponse<ChatCompletionResponse>(response);
485
+ }
486
+
487
+ export async function createChatCompletionRaw(
488
+ request: ChatCompletionRequest
489
+ ): Promise<ChatCompletionRawResponse> {
490
+ const response = await fetch(`${API_BASE}/v1/chat/completions`, {
491
+ method: 'POST',
492
+ headers: { 'Content-Type': 'application/json' },
493
+ body: JSON.stringify({ ...request, stream: false }),
494
+ });
495
+ return handleResponse<ChatCompletionRawResponse>(response);
496
+ }
497
+
498
+ export interface StreamChunk {
499
+ content: string;
500
+ reasoning?: string;
501
+ }
502
+
503
+ /**
504
+ * Stream chat completion using Server-Sent Events
505
+ * Yields chunks containing both content and optional reasoning content
506
+ */
507
+ export async function* streamChatCompletion(
508
+ request: ChatCompletionRequest,
509
+ signal?: AbortSignal
510
+ ): AsyncGenerator<StreamChunk, void, unknown> {
511
+ const response = await fetch(`${API_BASE}/v1/chat/completions`, {
512
+ method: 'POST',
513
+ headers: { 'Content-Type': 'application/json' },
514
+ body: JSON.stringify({ ...request, stream: true }),
515
+ signal,
516
+ });
517
+
518
+ if (!response.ok) {
519
+ throw new ApiError(response.status, response.statusText);
520
+ }
521
+
522
+ const reader = response.body?.getReader();
523
+ if (!reader) throw new Error('No response body');
524
+
525
+ const decoder = new TextDecoder();
526
+ let buffer = '';
527
+
528
+ while (true) {
529
+ const { done, value } = await reader.read();
530
+ if (done) break;
531
+
532
+ buffer += decoder.decode(value, { stream: true });
533
+ const lines = buffer.split('\n');
534
+ buffer = lines.pop() || '';
535
+
536
+ for (const line of lines) {
537
+ if (line.startsWith('data: ')) {
538
+ const data = line.slice(6);
539
+ if (data === '[DONE]') return;
540
+
541
+ try {
542
+ const parsed = JSON.parse(data);
543
+ const delta = parsed.choices?.[0]?.delta;
544
+ const content = delta?.content;
545
+ // Support both reasoning_content (DeepSeek) and reasoning (other providers)
546
+ const reasoning = delta?.reasoning_content || delta?.reasoning;
547
+
548
+ if (content || reasoning) {
549
+ yield {
550
+ content: content || '',
551
+ reasoning: reasoning || undefined,
552
+ };
553
+ }
554
+ } catch {
555
+ // Skip malformed JSON
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+
562
+ // ========================================
563
+ // Image Generation API
564
+ // ========================================
565
+
566
+ export interface ImageGenerationRequest {
567
+ model?: string;
568
+ prompt: string;
569
+ image_url?: string;
570
+ n?: number;
571
+ size?: string;
572
+ quality?: string;
573
+ style?: string;
574
+ response_format?: 'url' | 'b64_json';
575
+ }
576
+
577
+ export interface ImageObject {
578
+ url?: string;
579
+ b64_json?: string;
580
+ revised_prompt?: string;
581
+ }
582
+
583
+ export interface ImageGenerationResponse {
584
+ created: number;
585
+ data: ImageObject[];
586
+ }
587
+
588
+ export async function generateImage(
589
+ request: ImageGenerationRequest
590
+ ): Promise<ImageGenerationResponse> {
591
+ const response = await fetch(`${API_BASE}/v1/images/generations`, {
592
+ method: 'POST',
593
+ headers: { 'Content-Type': 'application/json' },
594
+ body: JSON.stringify(request),
595
+ });
596
+ return handleResponse<ImageGenerationResponse>(response);
597
+ }
598
+
599
+ // ========================================
600
+ // Sessions API (Playground)
601
+ // ========================================
602
+
603
+ export interface ChatSessionMessage {
604
+ role: 'user' | 'assistant' | 'system' | 'tool';
605
+ content: string | ContentPart[] | null;
606
+ images?: string[];
607
+ toolCalls?: Array<{
608
+ id: string;
609
+ name: string;
610
+ arguments: string;
611
+ result?: string;
612
+ }>;
613
+ // New API uses createdAt; timestamp is preserved for legacy payloads.
614
+ timestamp?: string;
615
+ createdAt?: string;
616
+ model?: string;
617
+ }
618
+
619
+ export interface ChatSession {
620
+ id: string;
621
+ name: string;
622
+ model?: string;
623
+ titleStatus?: 'pending' | 'generated' | 'manual' | 'failed';
624
+ titleUpdatedAt?: string;
625
+ storageVersion?: number;
626
+ messages: ChatSessionMessage[];
627
+ createdAt: string;
628
+ updatedAt: string;
629
+ }
630
+
631
+ export interface SessionListItem {
632
+ id: string;
633
+ name: string;
634
+ model?: string;
635
+ titleStatus?: 'pending' | 'generated' | 'manual' | 'failed';
636
+ titleUpdatedAt?: string;
637
+ storageVersion?: number;
638
+ messageCount: number;
639
+ createdAt: string;
640
+ updatedAt: string;
641
+ }
642
+
643
+ export interface SessionsListResponse {
644
+ object: 'list';
645
+ data: SessionListItem[];
646
+ }
647
+
648
+ export async function listSessions(): Promise<SessionsListResponse> {
649
+ const response = await fetch(`${API_BASE}/admin/sessions`);
650
+ return handleResponse<SessionsListResponse>(response);
651
+ }
652
+
653
+ export async function getSession(sessionId: string): Promise<ChatSession> {
654
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}`);
655
+ const session = await handleResponse<ChatSession>(response);
656
+ return {
657
+ ...session,
658
+ messages: session.messages.map(normalizeSessionMessageMedia),
659
+ };
660
+ }
661
+
662
+ export async function createSession(name?: string, model?: string): Promise<ChatSession> {
663
+ const response = await fetch(`${API_BASE}/admin/sessions`, {
664
+ method: 'POST',
665
+ headers: { 'Content-Type': 'application/json' },
666
+ body: JSON.stringify({ name, model }),
667
+ });
668
+ return handleResponse<ChatSession>(response);
669
+ }
670
+
671
+ export async function updateSession(
672
+ sessionId: string,
673
+ updates: { name?: string; model?: string }
674
+ ): Promise<ChatSession> {
675
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}`, {
676
+ method: 'PUT',
677
+ headers: { 'Content-Type': 'application/json' },
678
+ body: JSON.stringify(updates),
679
+ });
680
+ return handleResponse<ChatSession>(response);
681
+ }
682
+
683
+ export async function autoTitleSession(
684
+ sessionId: string,
685
+ payload: { model?: string; seedText?: string }
686
+ ): Promise<{
687
+ id: string;
688
+ name: string;
689
+ titleStatus?: 'pending' | 'generated' | 'manual' | 'failed';
690
+ titleUpdatedAt?: string;
691
+ generated: boolean;
692
+ model?: string;
693
+ }> {
694
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}/auto-title`, {
695
+ method: 'POST',
696
+ headers: { 'Content-Type': 'application/json' },
697
+ body: JSON.stringify(payload),
698
+ });
699
+ return handleResponse<{
700
+ id: string;
701
+ name: string;
702
+ titleStatus?: 'pending' | 'generated' | 'manual' | 'failed';
703
+ titleUpdatedAt?: string;
704
+ generated: boolean;
705
+ model?: string;
706
+ }>(response);
707
+ }
708
+
709
+ export async function deleteSession(sessionId: string): Promise<void> {
710
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}`, {
711
+ method: 'DELETE',
712
+ });
713
+ if (!response.ok) {
714
+ throw new ApiError(response.status, response.statusText);
715
+ }
716
+ }
717
+
718
+ export async function addMessageToSession(
719
+ sessionId: string,
720
+ message: ChatSessionMessage
721
+ ): Promise<{ messageId?: string; createdAt?: string }> {
722
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}/messages`, {
723
+ method: 'POST',
724
+ headers: { 'Content-Type': 'application/json' },
725
+ body: JSON.stringify(message),
726
+ });
727
+ return handleResponse<{ messageId?: string; createdAt?: string }>(response);
728
+ }
729
+
730
+ export async function appendMessageContent(
731
+ sessionId: string,
732
+ messageIndex: number,
733
+ content: string
734
+ ): Promise<void> {
735
+ const response = await fetch(`${API_BASE}/admin/sessions/${sessionId}/messages/${messageIndex}`, {
736
+ method: 'PATCH',
737
+ headers: { 'Content-Type': 'application/json' },
738
+ body: JSON.stringify({ content }),
739
+ });
740
+ if (!response.ok) {
741
+ throw new ApiError(response.status, response.statusText);
742
+ }
743
+ }
744
+
745
+ // ========================================
746
+ // Image Cache API
747
+ // ========================================
748
+
749
+ export interface ImageCacheStats {
750
+ count: number;
751
+ totalSizeBytes: number;
752
+ oldestEntry?: string;
753
+ newestEntry?: string;
754
+ }
755
+
756
+ export type MediaCacheStats = ImageCacheStats;
757
+
758
+ export async function getImageCacheStats(): Promise<ImageCacheStats> {
759
+ const response = await fetch(`${API_BASE}/admin/images/stats`);
760
+ return handleResponse<ImageCacheStats>(response);
761
+ }
762
+
763
+ export async function getMediaCacheStats(): Promise<MediaCacheStats> {
764
+ const response = await fetch(`${API_BASE}/admin/media/stats`);
765
+ return handleResponse<MediaCacheStats>(response);
766
+ }
767
+
768
+ export function getCachedImageUrl(hash: string): string {
769
+ return `${API_BASE}/admin/images/${hash}`;
770
+ }
771
+
772
+ export function resolveMediaUrl(hashOrUrl: string): string {
773
+ const value = hashOrUrl.trim();
774
+ if (value.length === 0) {
775
+ return value;
776
+ }
777
+ if (/^https?:\/\//i.test(value) || /^data:/i.test(value) || value.startsWith('/')) {
778
+ return value;
779
+ }
780
+ if (value.startsWith('admin/')) {
781
+ return `${API_BASE}/${value}`;
782
+ }
783
+ if (value.startsWith('media/')) {
784
+ return `${API_BASE}/admin/${value}`;
785
+ }
786
+ if (value.startsWith('images/')) {
787
+ return `${API_BASE}/admin/${value}`;
788
+ }
789
+ return `${API_BASE}/admin/media/${value}`;
790
+ }
791
+
792
+ export function normalizeSessionMessageMedia(message: ChatSessionMessage): ChatSessionMessage {
793
+ const normalizedContent = normalizeContentMedia(message.content);
794
+ const normalizedImages = message.images?.map(resolveMediaUrl);
795
+ return {
796
+ ...message,
797
+ content: normalizedContent,
798
+ images: normalizedImages,
799
+ };
800
+ }
801
+
802
+ export function normalizeContentMedia(
803
+ content: string | ContentPart[] | null
804
+ ): string | ContentPart[] | null {
805
+ if (!Array.isArray(content)) {
806
+ return content;
807
+ }
808
+ return content.map((part) => {
809
+ if (part.type === 'image_url') {
810
+ return {
811
+ ...part,
812
+ image_url: { ...part.image_url, url: resolveMediaUrl(part.image_url.url) },
813
+ };
814
+ }
815
+ if (part.type === 'input_audio' && part.input_audio?.url) {
816
+ return {
817
+ ...part,
818
+ input_audio: { ...part.input_audio, url: resolveMediaUrl(part.input_audio.url) },
819
+ };
820
+ }
821
+ if (part.type === 'audio' && part.audio?.url) {
822
+ return {
823
+ ...part,
824
+ audio: { ...part.audio, url: resolveMediaUrl(part.audio.url) },
825
+ };
826
+ }
827
+ return part;
828
+ });
829
+ }
830
+
831
+ export async function storeMedia(
832
+ data: string,
833
+ model?: string,
834
+ mimeType?: string
835
+ ): Promise<{ hash: string; url: string; mimeType?: string; evicted: string[] }> {
836
+ const response = await fetch(`${API_BASE}/admin/media`, {
837
+ method: 'POST',
838
+ headers: { 'Content-Type': 'application/json' },
839
+ body: JSON.stringify({ data, model, mimeType }),
840
+ });
841
+ return handleResponse<{ hash: string; url: string; mimeType?: string; evicted: string[] }>(response);
842
+ }
843
+
844
+ export async function storeImage(
845
+ data: string,
846
+ model?: string
847
+ ): Promise<{ hash: string; url: string; evicted: string[] }> {
848
+ const result = await storeMedia(data, model);
849
+ return { hash: result.hash, url: result.url, evicted: result.evicted };
850
+ }
851
+
852
+ export async function clearImageCache(): Promise<{ deleted: number }> {
853
+ const response = await fetch(`${API_BASE}/admin/images`, {
854
+ method: 'DELETE',
855
+ });
856
+ return handleResponse<{ deleted: number }>(response);
857
+ }
858
+
859
+ export interface BenchmarkRunSummary {
860
+ id: string;
861
+ status: 'running' | 'completed' | 'failed';
862
+ createdAt: string;
863
+ startedAt?: string;
864
+ finishedAt?: string;
865
+ suite?: string;
866
+ exampleId?: string;
867
+ profile?: string;
868
+ scenarioPath?: string;
869
+ succeeded?: number;
870
+ failed?: number;
871
+ successRate?: number;
872
+ artifactPath?: string;
873
+ }
874
+
875
+ export interface BenchmarkExampleSummary {
876
+ id: string
877
+ suite: string
878
+ mode: string
879
+ title: string
880
+ summary: string
881
+ userVisibleGoal: string
882
+ exampleSource: 'opencode' | 'builtin' | 'file' | 'huggingface'
883
+ inputPreview: string
884
+ successCriteria: string
885
+ expectedHighlights: string[]
886
+ requiresAvailableTools: boolean
887
+ model?: string
888
+ }
889
+
890
+ export type BenchmarkCapabilityKey =
891
+ | 'chat_basic'
892
+ | 'chat_streaming'
893
+ | 'chat_tool_calls'
894
+ | 'chat_vision_input'
895
+ | 'images_generation'
896
+ | 'images_edit'
897
+ | 'embeddings'
898
+ | 'audio_transcription'
899
+ | 'audio_speech'
900
+ | 'responses_compat'
901
+
902
+ export type BenchmarkCapabilityStatus = 'supported' | 'unsupported' | 'unknown' | 'misconfigured'
903
+
904
+ export interface BenchmarkCapabilityFinding {
905
+ capability: BenchmarkCapabilityKey
906
+ status: BenchmarkCapabilityStatus
907
+ confidence: number
908
+ evidence: string
909
+ scenarioId?: string
910
+ statusCode?: number
911
+ observedAt: string
912
+ }
913
+
914
+ export interface BenchmarkModelCapabilitySnapshot {
915
+ model: string
916
+ providerId: string
917
+ modelId: string
918
+ configFingerprint: string
919
+ confidence: number
920
+ lastVerifiedAt: string
921
+ expiresAt: string
922
+ freshness: 'fresh' | 'stale'
923
+ findings: Record<BenchmarkCapabilityKey, BenchmarkCapabilityFinding>
924
+ }
925
+
926
+ export interface BenchmarkCapabilityMatrix {
927
+ generatedAt: string
928
+ ttlDays: number
929
+ models: BenchmarkModelCapabilitySnapshot[]
930
+ }
931
+
932
+ export interface BenchmarkRunEvent {
933
+ type: string;
934
+ timestamp: string;
935
+ runId?: string;
936
+ scenarioId?: string;
937
+ scenarioIndex?: number;
938
+ totalScenarios?: number;
939
+ runIndex?: number;
940
+ totalRuns?: number;
941
+ phase?: 'warmup' | 'measured';
942
+ scenario?: BenchmarkExampleSummary;
943
+ warning?: string;
944
+ summary?: {
945
+ total: number;
946
+ executed: number;
947
+ succeeded: number;
948
+ failed: number;
949
+ successRate: number;
950
+ };
951
+ exchange?: {
952
+ mode: string;
953
+ model: string;
954
+ scenarioInput: string;
955
+ requestPreview: string;
956
+ responsePreview: string;
957
+ requestPath: string;
958
+ statusCode: number;
959
+ contentType: string;
960
+ endpointId?: string;
961
+ endpointName?: string;
962
+ upstreamModel?: string;
963
+ toolTrace: Array<{
964
+ kind: 'tool_call' | 'tool_result';
965
+ toolName: string;
966
+ toolCallId?: string;
967
+ argumentsText?: string;
968
+ contentText?: string;
969
+ }>;
970
+ requestRaw: unknown;
971
+ requestSanitized: unknown;
972
+ responseRaw: unknown;
973
+ responseSanitized: unknown;
974
+ };
975
+ }
976
+
977
+ export interface BenchmarkScenarioDetail {
978
+ id: string
979
+ suite?: string
980
+ example?: BenchmarkExampleSummary
981
+ model: string
982
+ status: 'passed' | 'failed' | 'skipped'
983
+ verdict: string
984
+ exchanges: Array<{
985
+ timestamp?: string
986
+ mode: string
987
+ model: string
988
+ requestPath: string
989
+ statusCode: number
990
+ contentType: string
991
+ requestSanitized: unknown
992
+ responseSanitized: unknown
993
+ requestPreview: string
994
+ responsePreview: string
995
+ endpointId?: string
996
+ endpointName?: string
997
+ upstreamModel?: string
998
+ toolTrace: Array<{
999
+ kind: 'tool_call' | 'tool_result'
1000
+ toolName: string
1001
+ toolCallId?: string
1002
+ argumentsText?: string
1003
+ contentText?: string
1004
+ }>
1005
+ }>
1006
+ finalResponsePreview: string
1007
+ usedToolNames: string[]
1008
+ }
1009
+
1010
+ export interface BenchmarkReport {
1011
+ id: string
1012
+ profile: string
1013
+ executionMode: 'showcase' | 'diagnostic'
1014
+ suite?: string
1015
+ exampleId?: string
1016
+ scenarioPath?: string
1017
+ modelOverride?: string
1018
+ total: number
1019
+ executed: number
1020
+ skipped: number
1021
+ succeeded: number
1022
+ failed: number
1023
+ successRate: number
1024
+ avgLatencyMs: number
1025
+ p95LatencyMs: number
1026
+ totalTokens: number
1027
+ totalToolCalls: number
1028
+ avgThroughputTokensPerSec: number
1029
+ results: Array<{
1030
+ id: string
1031
+ mode: string
1032
+ title?: string
1033
+ model: string
1034
+ status: 'passed' | 'failed' | 'skipped'
1035
+ passRate: number
1036
+ outputPreview: string
1037
+ verdict: string
1038
+ usedToolNames: string[]
1039
+ errorReasons: string[]
1040
+ skippedReason?: string
1041
+ totalTokens: number
1042
+ failovers: number
1043
+ p95LatencyMs: number
1044
+ }>
1045
+ scenarioDetails: BenchmarkScenarioDetail[]
1046
+ capabilityMatrix?: BenchmarkCapabilityMatrix
1047
+ gateResults: {
1048
+ hard: { passed: boolean; messages: string[] }
1049
+ soft: { passed: boolean; messages: string[] }
1050
+ }
1051
+ warnings: string[]
1052
+ }
1053
+
1054
+ export interface BenchmarkRunRecord extends BenchmarkRunSummary {
1055
+ request?: {
1056
+ suite?: string;
1057
+ exampleId?: string;
1058
+ scenarioPath?: string;
1059
+ modelOverride?: string;
1060
+ outPath?: string;
1061
+ configPath?: string;
1062
+ profile?: string;
1063
+ baselinePath?: string;
1064
+ executionMode?: 'showcase' | 'diagnostic';
1065
+ updateCapCache?: boolean;
1066
+ capTtlDays?: number;
1067
+ temperature?: number;
1068
+ top_p?: number;
1069
+ max_tokens?: number;
1070
+ presence_penalty?: number;
1071
+ frequency_penalty?: number;
1072
+ seed?: number;
1073
+ stop?: string | string[];
1074
+ };
1075
+ progress?: {
1076
+ totalScenarios: number;
1077
+ completedScenarios: number;
1078
+ currentScenarioId?: string;
1079
+ currentScenarioIndex?: number;
1080
+ currentRunIndex?: number;
1081
+ totalRuns?: number;
1082
+ phase?: 'warmup' | 'measured';
1083
+ };
1084
+ report?: BenchmarkReport;
1085
+ events?: BenchmarkRunEvent[];
1086
+ error?: string;
1087
+ }
1088
+
1089
+ export async function startBenchmarkRun(payload: {
1090
+ suite?: string;
1091
+ exampleId?: string;
1092
+ scenarioPath?: string;
1093
+ modelOverride?: string;
1094
+ configPath?: string;
1095
+ profile?: string;
1096
+ baselinePath?: string;
1097
+ executionMode?: 'showcase' | 'diagnostic';
1098
+ updateCapCache?: boolean;
1099
+ capTtlDays?: number;
1100
+ temperature?: number;
1101
+ top_p?: number;
1102
+ max_tokens?: number;
1103
+ presence_penalty?: number;
1104
+ frequency_penalty?: number;
1105
+ seed?: number;
1106
+ stop?: string | string[];
1107
+ }): Promise<BenchmarkRunRecord> {
1108
+ const response = await fetch(`${API_BASE}/admin/benchmarks/runs`, {
1109
+ method: 'POST',
1110
+ headers: { 'Content-Type': 'application/json' },
1111
+ body: JSON.stringify(payload),
1112
+ });
1113
+ return handleResponse<BenchmarkRunRecord>(response);
1114
+ }
1115
+
1116
+ export async function listBenchmarkExamples(suite = 'showcase'): Promise<{ object: 'list'; suite: string; data: BenchmarkExampleSummary[] }> {
1117
+ const query = `?suite=${encodeURIComponent(suite)}`
1118
+ const response = await fetch(`${API_BASE}/admin/benchmarks/examples${query}`)
1119
+ return handleResponse<{ object: 'list'; suite: string; data: BenchmarkExampleSummary[] }>(response)
1120
+ }
1121
+
1122
+ export async function listBenchmarkCapabilities(ttlDays?: number): Promise<BenchmarkCapabilityMatrix> {
1123
+ const query = typeof ttlDays === 'number' ? `?ttlDays=${encodeURIComponent(String(ttlDays))}` : ''
1124
+ const response = await fetch(`${API_BASE}/admin/benchmarks/capabilities${query}`)
1125
+ return handleResponse<BenchmarkCapabilityMatrix>(response)
1126
+ }
1127
+
1128
+ export async function getBenchmarkCapability(modelId: string, ttlDays?: number): Promise<BenchmarkModelCapabilitySnapshot> {
1129
+ const query = typeof ttlDays === 'number' ? `?ttlDays=${encodeURIComponent(String(ttlDays))}` : ''
1130
+ const response = await fetch(
1131
+ `${API_BASE}/admin/benchmarks/capabilities/${encodeURIComponent(modelId)}${query}`
1132
+ )
1133
+ return handleResponse<BenchmarkModelCapabilitySnapshot>(response)
1134
+ }
1135
+
1136
+ export async function listBenchmarkRuns(): Promise<{ object: 'list'; data: BenchmarkRunSummary[] }> {
1137
+ const response = await fetch(`${API_BASE}/admin/benchmarks/runs`);
1138
+ return handleResponse<{ object: 'list'; data: BenchmarkRunSummary[] }>(response);
1139
+ }
1140
+
1141
+ export async function getBenchmarkRun(runId: string): Promise<BenchmarkRunRecord> {
1142
+ const response = await fetch(`${API_BASE}/admin/benchmarks/runs/${encodeURIComponent(runId)}`);
1143
+ return handleResponse<BenchmarkRunRecord>(response);
1144
+ }
1145
+
1146
+ // ========================================
1147
+ // MCP API (Model Context Protocol)
1148
+ // ========================================
1149
+
1150
+ /** Reserved ID for the built-in waypoi MCP server. */
1151
+ export const BUILTIN_SERVER_ID = "builtin";
1152
+
1153
+ export interface McpServer {
1154
+ id: string;
1155
+ name: string;
1156
+ url: string;
1157
+ enabled: boolean;
1158
+ status: 'connected' | 'disconnected' | 'error' | 'unknown';
1159
+ connected: boolean;
1160
+ toolCount?: number;
1161
+ lastConnectedAt?: string;
1162
+ lastError?: string;
1163
+ createdAt: string;
1164
+ updatedAt: string;
1165
+ }
1166
+
1167
+ export interface McpTool {
1168
+ name: string;
1169
+ description?: string;
1170
+ inputSchema: Record<string, unknown>;
1171
+ serverId: string;
1172
+ serverName: string;
1173
+ serverUrl: string;
1174
+ }
1175
+
1176
+ export interface McpServersResponse {
1177
+ object: 'list';
1178
+ data: McpServer[];
1179
+ }
1180
+
1181
+ export interface McpToolsResponse {
1182
+ object: 'list';
1183
+ data: McpTool[];
1184
+ }
1185
+
1186
+ export async function listMcpServers(): Promise<McpServersResponse> {
1187
+ const response = await fetch(`${API_BASE}/admin/mcp/servers`);
1188
+ return handleResponse<McpServersResponse>(response);
1189
+ }
1190
+
1191
+ export async function getMcpServer(serverId: string): Promise<McpServer & { tools: McpTool[] }> {
1192
+ const response = await fetch(`${API_BASE}/admin/mcp/servers/${serverId}`);
1193
+ return handleResponse<McpServer & { tools: McpTool[] }>(response);
1194
+ }
1195
+
1196
+ export async function addMcpServer(
1197
+ name: string,
1198
+ url: string,
1199
+ enabled?: boolean
1200
+ ): Promise<McpServer> {
1201
+ const response = await fetch(`${API_BASE}/admin/mcp/servers`, {
1202
+ method: 'POST',
1203
+ headers: { 'Content-Type': 'application/json' },
1204
+ body: JSON.stringify({ name, url, enabled }),
1205
+ });
1206
+ return handleResponse<McpServer>(response);
1207
+ }
1208
+
1209
+ export async function updateMcpServer(
1210
+ serverId: string,
1211
+ updates: { name?: string; url?: string; enabled?: boolean }
1212
+ ): Promise<McpServer> {
1213
+ const response = await fetch(`${API_BASE}/admin/mcp/servers/${serverId}`, {
1214
+ method: 'PUT',
1215
+ headers: { 'Content-Type': 'application/json' },
1216
+ body: JSON.stringify(updates),
1217
+ });
1218
+ return handleResponse<McpServer>(response);
1219
+ }
1220
+
1221
+ export async function deleteMcpServer(serverId: string): Promise<void> {
1222
+ const response = await fetch(`${API_BASE}/admin/mcp/servers/${serverId}`, {
1223
+ method: 'DELETE',
1224
+ });
1225
+ if (!response.ok) {
1226
+ throw new ApiError(response.status, response.statusText);
1227
+ }
1228
+ }
1229
+
1230
+ export async function connectMcpServer(
1231
+ serverId: string
1232
+ ): Promise<{ connected: boolean; toolCount: number; tools: { name: string; description?: string }[] }> {
1233
+ const response = await fetch(`${API_BASE}/admin/mcp/servers/${serverId}/connect`, {
1234
+ method: 'POST',
1235
+ });
1236
+ return handleResponse<{ connected: boolean; toolCount: number; tools: { name: string; description?: string }[] }>(response);
1237
+ }
1238
+
1239
+ export async function disconnectMcpServer(serverId: string): Promise<{ disconnected: boolean }> {
1240
+ const response = await fetch(`${API_BASE}/admin/mcp/servers/${serverId}/disconnect`, {
1241
+ method: 'POST',
1242
+ });
1243
+ return handleResponse<{ disconnected: boolean }>(response);
1244
+ }
1245
+
1246
+ export async function listMcpTools(): Promise<McpToolsResponse> {
1247
+ const response = await fetch(`${API_BASE}/admin/mcp/tools`);
1248
+ return handleResponse<McpToolsResponse>(response);
1249
+ }
1250
+
1251
+ export async function discoverMcpTools(): Promise<{ discovered: number; tools: { name: string; description?: string; serverName: string }[] }> {
1252
+ const response = await fetch(`${API_BASE}/admin/mcp/tools/discover`, {
1253
+ method: 'POST',
1254
+ });
1255
+ return handleResponse<{ discovered: number; tools: { name: string; description?: string; serverName: string }[] }>(response);
1256
+ }
1257
+
1258
+ export async function executeMcpTool(
1259
+ name: string,
1260
+ args: Record<string, unknown>
1261
+ ): Promise<{ result: string }> {
1262
+ const response = await fetch(`${API_BASE}/admin/mcp/tools/execute`, {
1263
+ method: 'POST',
1264
+ headers: { 'Content-Type': 'application/json' },
1265
+ body: JSON.stringify({ name, arguments: args }),
1266
+ });
1267
+ return handleResponse<{ result: string }>(response);
1268
+ }
1269
+
1270
+ // ========================================
1271
+ // Virtual Models (Pools) API
1272
+ // ========================================
1273
+
1274
+ export interface VirtualModel {
1275
+ id: string;
1276
+ name: string;
1277
+ aliases: string[];
1278
+ enabled: boolean;
1279
+ strategy: 'highest_rank_available' | 'remaining_limit';
1280
+ requiredInput: string[];
1281
+ requiredOutput: string[];
1282
+ scoreFallback: number;
1283
+ candidates: Array<{
1284
+ id: string;
1285
+ providerId: string;
1286
+ modelId: string;
1287
+ score: number;
1288
+ scoreSource: string;
1289
+ }>;
1290
+ candidateSelection: string[];
1291
+ userDefined: boolean;
1292
+ updatedAt: string;
1293
+ }
1294
+
1295
+ export async function listVirtualModels(): Promise<VirtualModel[]> {
1296
+ const response = await fetch(`${API_BASE}/admin/pools`);
1297
+ return handleResponse<VirtualModel[]>(response);
1298
+ }
1299
+
1300
+ export async function createVirtualModel(payload: {
1301
+ id: string;
1302
+ name?: string;
1303
+ aliases?: string[];
1304
+ strategy?: 'highest_rank_available' | 'remaining_limit';
1305
+ candidateSelection?: string[];
1306
+ }): Promise<VirtualModel> {
1307
+ const response = await fetch(`${API_BASE}/admin/pools`, {
1308
+ method: 'POST',
1309
+ headers: { 'Content-Type': 'application/json' },
1310
+ body: JSON.stringify(payload),
1311
+ });
1312
+ return handleResponse<VirtualModel>(response);
1313
+ }
1314
+
1315
+ export async function updateVirtualModel(
1316
+ id: string,
1317
+ payload: Partial<VirtualModel>
1318
+ ): Promise<VirtualModel> {
1319
+ const response = await fetch(`${API_BASE}/admin/pools/${encodeURIComponent(id)}`, {
1320
+ method: 'PUT',
1321
+ headers: { 'Content-Type': 'application/json' },
1322
+ body: JSON.stringify(payload),
1323
+ });
1324
+ return handleResponse<VirtualModel>(response);
1325
+ }
1326
+
1327
+ export async function deleteVirtualModel(id: string): Promise<{ deleted: string }> {
1328
+ const response = await fetch(`${API_BASE}/admin/pools/${encodeURIComponent(id)}`, {
1329
+ method: 'DELETE',
1330
+ });
1331
+ return handleResponse<{ deleted: string }>(response);
1332
+ }
1333
+
1334
+ export async function toggleVirtualModel(id: string): Promise<VirtualModel> {
1335
+ const response = await fetch(`${API_BASE}/admin/pools/${encodeURIComponent(id)}/toggle`, {
1336
+ method: 'POST',
1337
+ });
1338
+ return handleResponse<VirtualModel>(response);
1339
+ }
1340
+
1341
+ // ========================================
1342
+ // Capture API
1343
+ // ========================================
1344
+
1345
+ export interface CaptureConfig {
1346
+ enabled: boolean;
1347
+ retentionDays: number;
1348
+ maxBytes: number;
1349
+ }
1350
+
1351
+ export interface CaptureRecordSummary {
1352
+ id: string;
1353
+ timestamp: string;
1354
+ route: string;
1355
+ method: string;
1356
+ statusCode: number;
1357
+ latencyMs: number;
1358
+ model?: string;
1359
+ }
1360
+
1361
+ export interface CaptureTimelineEntry {
1362
+ direction: 'request' | 'response';
1363
+ kind: 'message' | 'tool_definition' | 'tool_call' | 'tool_result' | 'reasoning' | 'instructions' | 'stream_preview' | 'error';
1364
+ index: number;
1365
+ sourcePath: string;
1366
+ role?: 'system' | 'user' | 'assistant' | 'tool' | 'developer';
1367
+ content?: string;
1368
+ name?: string;
1369
+ arguments?: string;
1370
+ toolCallId?: string;
1371
+ metadata?: Record<string, unknown>;
1372
+ }
1373
+
1374
+ export interface CaptureCalendarDaySummary {
1375
+ date: string;
1376
+ count: number;
1377
+ }
1378
+
1379
+ export interface CaptureRecordDetail {
1380
+ id: string;
1381
+ timestamp: string;
1382
+ route: string;
1383
+ method: string;
1384
+ captureEnabledSnapshot: boolean;
1385
+ statusCode: number;
1386
+ latencyMs: number;
1387
+ request: {
1388
+ headers: Record<string, string>;
1389
+ body?: unknown;
1390
+ derived?: Record<string, unknown>;
1391
+ };
1392
+ response: {
1393
+ headers: Record<string, string>;
1394
+ body?: unknown;
1395
+ error?: { type?: string; message?: string };
1396
+ };
1397
+ routing: {
1398
+ publicModel?: string;
1399
+ endpointId?: string;
1400
+ endpointName?: string;
1401
+ upstreamModel?: string;
1402
+ };
1403
+ analysis: {
1404
+ systemMessages: CaptureTextMessage[];
1405
+ userMessages: CaptureTextMessage[];
1406
+ assistantMessages: CaptureAssistantMessage[];
1407
+ toolMessages?: CaptureToolMessage[];
1408
+ requestTimeline?: CaptureTimelineEntry[];
1409
+ responseTimeline?: CaptureTimelineEntry[];
1410
+ tools: Array<{ name: string; description?: string }>;
1411
+ mcpToolDescriptions: string[];
1412
+ agentsMdHints: string[];
1413
+ rawSections: string[];
1414
+ tokenFlow?: {
1415
+ eligible: boolean;
1416
+ reason?: string;
1417
+ method: 'exact_totals_estimated_categories' | 'estimated_only' | 'unavailable';
1418
+ totals: {
1419
+ inputTokens: number | null;
1420
+ outputTokens: number | null;
1421
+ totalTokens: number | null;
1422
+ };
1423
+ input: Array<{ key: string; label: string; tokens: number }>;
1424
+ output: Array<{ key: string; label: string; tokens: number }>;
1425
+ notes?: string[];
1426
+ };
1427
+ };
1428
+ artifacts: Array<{
1429
+ hash: string;
1430
+ mime: string;
1431
+ bytes: number;
1432
+ blobRef: string;
1433
+ kind: 'image' | 'audio' | 'binary';
1434
+ }>;
1435
+ }
1436
+
1437
+ export interface CaptureTextMessage {
1438
+ content: string;
1439
+ truncated?: boolean;
1440
+ originalLength?: number;
1441
+ }
1442
+
1443
+ export interface CaptureAssistantMessage extends CaptureTextMessage {
1444
+ reasoningContent?: string;
1445
+ toolCalls?: CaptureToolCall[];
1446
+ asksForClarification?: boolean;
1447
+ }
1448
+
1449
+ export interface CaptureToolMessage extends CaptureTextMessage {
1450
+ toolCallId?: string;
1451
+ }
1452
+
1453
+ export interface CaptureToolCall {
1454
+ id?: string;
1455
+ type?: string;
1456
+ function?: {
1457
+ name?: string;
1458
+ arguments?: string;
1459
+ };
1460
+ }
1461
+
1462
+ export async function getCaptureConfig(): Promise<CaptureConfig> {
1463
+ const response = await fetch(`${API_BASE}/admin/capture/config`);
1464
+ return handleResponse<CaptureConfig>(response);
1465
+ }
1466
+
1467
+ export async function updateCaptureConfig(
1468
+ patch: Partial<CaptureConfig>
1469
+ ): Promise<CaptureConfig> {
1470
+ const response = await fetch(`${API_BASE}/admin/capture/config`, {
1471
+ method: 'PUT',
1472
+ headers: { 'Content-Type': 'application/json' },
1473
+ body: JSON.stringify(patch),
1474
+ });
1475
+ return handleResponse<CaptureConfig>(response);
1476
+ }
1477
+
1478
+ export async function listCaptureRecords(
1479
+ options: { limit?: number; offset?: number; date?: string; timeZone?: string } = {}
1480
+ ): Promise<{ object: 'list'; data: CaptureRecordSummary[]; total: number }> {
1481
+ const params = new URLSearchParams()
1482
+ if (options.limit !== undefined) params.set('limit', String(options.limit))
1483
+ if (options.offset !== undefined) params.set('offset', String(options.offset))
1484
+ if (options.date) params.set('date', options.date)
1485
+ if (options.timeZone) params.set('timeZone', options.timeZone)
1486
+ const response = await fetch(`${API_BASE}/admin/capture/records?${params.toString()}`);
1487
+ return handleResponse<{ object: 'list'; data: CaptureRecordSummary[]; total: number }>(response);
1488
+ }
1489
+
1490
+ export async function getCaptureRecord(id: string): Promise<CaptureRecordDetail> {
1491
+ const response = await fetch(`${API_BASE}/admin/capture/records/${encodeURIComponent(id)}`);
1492
+ return handleResponse<CaptureRecordDetail>(response);
1493
+ }
1494
+
1495
+ export async function getCaptureCalendar(
1496
+ month: string,
1497
+ options: { timeZone?: string } = {}
1498
+ ): Promise<{ month: string; days: CaptureCalendarDaySummary[] }> {
1499
+ const params = new URLSearchParams({ month })
1500
+ if (options.timeZone) params.set('timeZone', options.timeZone)
1501
+ const response = await fetch(`${API_BASE}/admin/capture/calendar?${params.toString()}`);
1502
+ return handleResponse<{ month: string; days: CaptureCalendarDaySummary[] }>(response);
1503
+ }