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.
- package/.github/instructions/ui.instructions.md +42 -0
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +71 -0
- package/.github/workflows/release.yml +48 -0
- package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
- package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
- package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
- package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
- package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
- package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
- package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
- package/AGENTS.md +48 -0
- package/CHANGELOG.md +131 -0
- package/README.md +552 -0
- package/assets/agent-mode.png +0 -0
- package/assets/categorize.png +0 -0
- package/assets/dashboard.png +0 -0
- package/assets/endpoint-proxy.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/mcp-generate-image.png +0 -0
- package/assets/mcp-understand-image.png +0 -0
- package/assets/peek-token-flow.png +0 -0
- package/assets/playground.png +0 -0
- package/assets/sankey.png +0 -0
- package/cli/index.ts +2805 -0
- package/cli/legacyRewrite.ts +108 -0
- package/cli/modelRef.ts +24 -0
- package/dist/cli/index.js +2536 -0
- package/dist/cli/legacyRewrite.js +92 -0
- package/dist/cli/modelRef.js +20 -0
- package/dist/src/benchmark/artifacts.js +131 -0
- package/dist/src/benchmark/capabilityClassifier.js +81 -0
- package/dist/src/benchmark/capabilityStore.js +144 -0
- package/dist/src/benchmark/config.js +238 -0
- package/dist/src/benchmark/gates.js +118 -0
- package/dist/src/benchmark/jobs.js +252 -0
- package/dist/src/benchmark/runner.js +1847 -0
- package/dist/src/benchmark/schema.js +353 -0
- package/dist/src/benchmark/suites.js +314 -0
- package/dist/src/benchmark/tinyQaDataset.js +422 -0
- package/dist/src/benchmark/types.js +25 -0
- package/dist/src/config.js +47 -0
- package/dist/src/index.js +178 -0
- package/dist/src/mcp/client.js +215 -0
- package/dist/src/mcp/discovery.js +226 -0
- package/dist/src/mcp/policy.js +65 -0
- package/dist/src/mcp/registry.js +129 -0
- package/dist/src/mcp/service.js +460 -0
- package/dist/src/middleware/auth.js +179 -0
- package/dist/src/middleware/requestCapture.js +192 -0
- package/dist/src/middleware/requestStats.js +118 -0
- package/dist/src/pools/builder.js +132 -0
- package/dist/src/pools/repository.js +69 -0
- package/dist/src/pools/scheduler.js +360 -0
- package/dist/src/pools/types.js +2 -0
- package/dist/src/protocols/adapters/dashscope.js +267 -0
- package/dist/src/protocols/adapters/inferenceV2.js +346 -0
- package/dist/src/protocols/adapters/openai.js +27 -0
- package/dist/src/protocols/registry.js +99 -0
- package/dist/src/protocols/types.js +2 -0
- package/dist/src/providers/health.js +153 -0
- package/dist/src/providers/importer.js +289 -0
- package/dist/src/providers/modelRegistry.js +313 -0
- package/dist/src/providers/repository.js +361 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/routes/admin.js +531 -0
- package/dist/src/routes/audio.js +295 -0
- package/dist/src/routes/chat.js +240 -0
- package/dist/src/routes/embeddings.js +157 -0
- package/dist/src/routes/images.js +288 -0
- package/dist/src/routes/mcp.js +256 -0
- package/dist/src/routes/mcpService.js +100 -0
- package/dist/src/routes/models.js +48 -0
- package/dist/src/routes/responses.js +711 -0
- package/dist/src/routes/sessions.js +450 -0
- package/dist/src/routes/stats.js +270 -0
- package/dist/src/routes/ui.js +97 -0
- package/dist/src/routes/videos.js +107 -0
- package/dist/src/routing/router.js +338 -0
- package/dist/src/services/imageGeneration.js +280 -0
- package/dist/src/services/imageUnderstanding.js +352 -0
- package/dist/src/services/videoGeneration.js +79 -0
- package/dist/src/storage/captureRepository.js +1591 -0
- package/dist/src/storage/files.js +157 -0
- package/dist/src/storage/imageCache.js +346 -0
- package/dist/src/storage/repositories.js +388 -0
- package/dist/src/storage/sessionRepository.js +370 -0
- package/dist/src/storage/statsRepository.js +204 -0
- package/dist/src/transport/httpClient.js +126 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/messageMedia.js +285 -0
- package/dist/src/utils/modelCapabilities.js +108 -0
- package/dist/src/utils/modelDiscovery.js +170 -0
- package/dist/src/version.js +5 -0
- package/dist/src/workers/captureRetention.js +25 -0
- package/dist/src/workers/configWatcher.js +91 -0
- package/dist/src/workers/healthChecker.js +21 -0
- package/dist/src/workers/statsRotation.js +41 -0
- package/docs/LLM/output_schema.md +312 -0
- package/docs/benchmark.md +208 -0
- package/docs/mcp-guidelines.md +125 -0
- package/docs/mcp-service.md +178 -0
- package/docs/opencode.md +86 -0
- package/docs/providers.md +79 -0
- package/examples/benchmark.config.yaml +28 -0
- package/examples/providers/alibaba-dashscope.yaml +88 -0
- package/examples/providers/alibaba-llm.yaml +64 -0
- package/examples/providers/alibaba-registry.yaml +7 -0
- package/examples/providers/inference-v2-ray.yaml +29 -0
- package/examples/scenarios/assets/omni-call-sample.wav +0 -0
- package/examples/scenarios/custom.jsonl +5 -0
- package/examples/scenarios/custom.yaml +40 -0
- package/model-form-v2.png +0 -0
- package/package.json +66 -0
- package/provider-form-v2.png +0 -0
- package/provider-form.png +0 -0
- package/scripts/manual-test.sh +11 -0
- package/scripts/version-from-git.js +23 -0
- package/src/benchmark/artifacts.ts +149 -0
- package/src/benchmark/capabilityClassifier.ts +99 -0
- package/src/benchmark/capabilityStore.ts +174 -0
- package/src/benchmark/config.ts +337 -0
- package/src/benchmark/gates.ts +164 -0
- package/src/benchmark/jobs.ts +312 -0
- package/src/benchmark/runner.ts +2519 -0
- package/src/benchmark/schema.ts +443 -0
- package/src/benchmark/suites.ts +323 -0
- package/src/benchmark/tinyQaDataset.ts +428 -0
- package/src/benchmark/types.ts +442 -0
- package/src/config.ts +44 -0
- package/src/index.ts +195 -0
- package/src/mcp/client.ts +305 -0
- package/src/mcp/discovery.ts +266 -0
- package/src/mcp/policy.ts +105 -0
- package/src/mcp/registry.ts +164 -0
- package/src/mcp/service.ts +611 -0
- package/src/middleware/auth.ts +251 -0
- package/src/middleware/requestCapture.ts +245 -0
- package/src/middleware/requestStats.ts +163 -0
- package/src/pools/builder.ts +159 -0
- package/src/pools/repository.ts +71 -0
- package/src/pools/scheduler.ts +425 -0
- package/src/pools/types.ts +117 -0
- package/src/protocols/adapters/dashscope.ts +335 -0
- package/src/protocols/adapters/inferenceV2.ts +428 -0
- package/src/protocols/adapters/openai.ts +32 -0
- package/src/protocols/registry.ts +117 -0
- package/src/protocols/types.ts +81 -0
- package/src/providers/health.ts +207 -0
- package/src/providers/importer.ts +402 -0
- package/src/providers/modelRegistry.ts +415 -0
- package/src/providers/repository.ts +439 -0
- package/src/providers/types.ts +113 -0
- package/src/routes/admin.ts +666 -0
- package/src/routes/audio.ts +372 -0
- package/src/routes/chat.ts +301 -0
- package/src/routes/embeddings.ts +197 -0
- package/src/routes/images.ts +356 -0
- package/src/routes/mcp.ts +320 -0
- package/src/routes/mcpService.ts +114 -0
- package/src/routes/models.ts +50 -0
- package/src/routes/responses.ts +872 -0
- package/src/routes/sessions.ts +558 -0
- package/src/routes/stats.ts +312 -0
- package/src/routes/ui.ts +96 -0
- package/src/routes/videos.ts +132 -0
- package/src/routing/router.ts +501 -0
- package/src/services/imageGeneration.ts +396 -0
- package/src/services/imageUnderstanding.ts +449 -0
- package/src/services/videoGeneration.ts +127 -0
- package/src/storage/captureRepository.ts +1835 -0
- package/src/storage/files.ts +178 -0
- package/src/storage/imageCache.ts +405 -0
- package/src/storage/repositories.ts +494 -0
- package/src/storage/sessionRepository.ts +419 -0
- package/src/storage/statsRepository.ts +238 -0
- package/src/transport/httpClient.ts +145 -0
- package/src/types.ts +322 -0
- package/src/utils/messageMedia.ts +293 -0
- package/src/utils/modelCapabilities.ts +161 -0
- package/src/utils/modelDiscovery.ts +203 -0
- package/src/workers/captureRetention.ts +25 -0
- package/src/workers/configWatcher.ts +115 -0
- package/src/workers/healthChecker.ts +22 -0
- package/src/workers/statsRotation.ts +49 -0
- package/tests/benchmarkAdminRoutes.test.ts +82 -0
- package/tests/benchmarkBasics.test.ts +116 -0
- package/tests/captureAdminRoutes.test.ts +420 -0
- package/tests/captureRepository.test.ts +797 -0
- package/tests/cliLegacyRewrite.test.ts +45 -0
- package/tests/imageGeneration.service.test.ts +107 -0
- package/tests/imageUnderstanding.service.test.ts +123 -0
- package/tests/mcpPolicy.test.ts +105 -0
- package/tests/mcpService.test.ts +1245 -0
- package/tests/modelRef.test.ts +23 -0
- package/tests/modelsRoutes.test.ts +154 -0
- package/tests/sessionMediaCache.test.ts +167 -0
- package/tests/statsRoutes.test.ts +323 -0
- package/tsconfig.json +15 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +8521 -0
- package/ui/package.json +52 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/assets/apple-touch-icon.png +0 -0
- package/ui/public/assets/favicon-16.png +0 -0
- package/ui/public/assets/favicon-32.png +0 -0
- package/ui/public/assets/icon-192.png +0 -0
- package/ui/public/assets/icon-512.png +0 -0
- package/ui/src/App.tsx +27 -0
- package/ui/src/api/client.ts +1503 -0
- package/ui/src/components/EndpointUsageGuide.tsx +361 -0
- package/ui/src/components/Layout.tsx +124 -0
- package/ui/src/components/MessageContent.tsx +365 -0
- package/ui/src/components/ToolCallMessage.tsx +179 -0
- package/ui/src/components/ToolPicker.tsx +442 -0
- package/ui/src/components/messageContentParser.test.ts +41 -0
- package/ui/src/components/messageContentParser.ts +73 -0
- package/ui/src/components/thinkingPreview.test.ts +27 -0
- package/ui/src/components/thinkingPreview.ts +15 -0
- package/ui/src/components/toMermaidSankey.test.ts +78 -0
- package/ui/src/components/toMermaidSankey.ts +56 -0
- package/ui/src/components/ui/button.tsx +58 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/textarea.tsx +21 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +9 -0
- package/ui/src/pages/AgentPlayground.tsx +2010 -0
- package/ui/src/pages/Benchmark.tsx +988 -0
- package/ui/src/pages/Dashboard.tsx +581 -0
- package/ui/src/pages/Peek.tsx +962 -0
- package/ui/src/pages/Settings.tsx +2013 -0
- package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
- package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
- package/ui/src/pages/agentThinkingContent.test.ts +50 -0
- package/ui/src/pages/agentThinkingContent.ts +57 -0
- package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
- package/ui/src/pages/dashboardTokenUsage.ts +36 -0
- package/ui/src/pages/imageUpload.test.ts +39 -0
- package/ui/src/pages/imageUpload.ts +71 -0
- package/ui/src/pages/peekFilters.test.ts +29 -0
- package/ui/src/pages/peekFilters.ts +13 -0
- package/ui/src/pages/peekMedia.test.ts +58 -0
- package/ui/src/pages/peekMedia.ts +148 -0
- package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
- package/ui/src/pages/sessionAutoTitle.ts +106 -0
- package/ui/src/stores/settings.ts +58 -0
- package/ui/src/styles/globals.css +223 -0
- package/ui/src/vite-env.d.ts +8 -0
- package/ui/tailwind.config.js +106 -0
- package/ui/tsconfig.json +32 -0
- 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
|
+
}
|