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,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inferenceV2ProtocolAdapter = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const stream_1 = require("stream");
|
|
6
|
+
const imageCache_1 = require("../../storage/imageCache");
|
|
7
|
+
const DEFAULT_RESPONSE_TEXT_PATHS = [
|
|
8
|
+
"outputs.0.outputs.text",
|
|
9
|
+
"outputs.0.text",
|
|
10
|
+
"outputs.0.generated_text",
|
|
11
|
+
];
|
|
12
|
+
exports.inferenceV2ProtocolAdapter = {
|
|
13
|
+
id: "inference_v2",
|
|
14
|
+
supportedOperations: ["chat_completions"],
|
|
15
|
+
streamSupportedOperations: [],
|
|
16
|
+
supports(context) {
|
|
17
|
+
if (context.operation !== "chat_completions") {
|
|
18
|
+
return { supported: false, reason: "unsupported_operation" };
|
|
19
|
+
}
|
|
20
|
+
if (context.stream) {
|
|
21
|
+
return { supported: false, reason: "stream_unsupported" };
|
|
22
|
+
}
|
|
23
|
+
return { supported: true };
|
|
24
|
+
},
|
|
25
|
+
async buildRequest(context) {
|
|
26
|
+
const router = typeof context.config?.router === "string" ? context.config.router.trim() : "";
|
|
27
|
+
if (!router) {
|
|
28
|
+
throw protocolError("invalid_protocol_config", "inference_v2 protocol requires provider protocolConfig.router.", false);
|
|
29
|
+
}
|
|
30
|
+
const { text, image } = await extractPromptAndImage(context.paths, context.payload);
|
|
31
|
+
const inferInputs = {
|
|
32
|
+
text,
|
|
33
|
+
max_new_tokens: numberOrUndefined(context.payload.max_tokens),
|
|
34
|
+
temperature: numberOrUndefined(context.payload.temperature),
|
|
35
|
+
top_p: numberOrUndefined(context.payload.top_p),
|
|
36
|
+
};
|
|
37
|
+
if (image) {
|
|
38
|
+
inferInputs.image = image;
|
|
39
|
+
}
|
|
40
|
+
const payload = {
|
|
41
|
+
inputs: [
|
|
42
|
+
{
|
|
43
|
+
model_name: context.upstreamModel,
|
|
44
|
+
inputs: compactObject(inferInputs),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
const requestPath = `/v2/models/${encodeURIComponent(router)}/infer`;
|
|
49
|
+
const auth = buildAuth(context, requestPath);
|
|
50
|
+
return {
|
|
51
|
+
path: auth.path,
|
|
52
|
+
payload,
|
|
53
|
+
headers: auth.headers,
|
|
54
|
+
skipDefaultAuth: auth.skipDefaultAuth,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
async normalizeResponse(context) {
|
|
58
|
+
const responseJson = await readJsonBody(context.upstreamResult);
|
|
59
|
+
const textPaths = normalizeTextPaths(context.config?.responseTextPaths);
|
|
60
|
+
const content = firstStringByPaths(responseJson, textPaths);
|
|
61
|
+
if (!content) {
|
|
62
|
+
throw protocolError("invalid_upstream_response", "Inference v2 response does not contain assistant text at configured paths.", true);
|
|
63
|
+
}
|
|
64
|
+
const promptTokens = numberFromPath(responseJson, "usage.prompt_tokens");
|
|
65
|
+
const completionTokens = numberFromPath(responseJson, "usage.completion_tokens");
|
|
66
|
+
const totalTokensCandidate = numberFromPath(responseJson, "usage.total_tokens");
|
|
67
|
+
const totalTokens = totalTokensCandidate ??
|
|
68
|
+
(promptTokens !== undefined && completionTokens !== undefined
|
|
69
|
+
? promptTokens + completionTokens
|
|
70
|
+
: undefined);
|
|
71
|
+
const normalized = {
|
|
72
|
+
id: `chatcmpl-infer-${Date.now().toString(36)}`,
|
|
73
|
+
object: "chat.completion",
|
|
74
|
+
created: Math.floor(Date.now() / 1000),
|
|
75
|
+
model: context.publicModel,
|
|
76
|
+
choices: [
|
|
77
|
+
{
|
|
78
|
+
index: 0,
|
|
79
|
+
message: {
|
|
80
|
+
role: "assistant",
|
|
81
|
+
content,
|
|
82
|
+
},
|
|
83
|
+
finish_reason: "stop",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
usage: promptTokens !== undefined || completionTokens !== undefined || totalTokens !== undefined
|
|
87
|
+
? {
|
|
88
|
+
prompt_tokens: promptTokens ?? 0,
|
|
89
|
+
completion_tokens: completionTokens ?? 0,
|
|
90
|
+
total_tokens: totalTokens ?? 0,
|
|
91
|
+
}
|
|
92
|
+
: undefined,
|
|
93
|
+
waypoi_adapter: {
|
|
94
|
+
protocol: "inference_v2",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const buffer = Buffer.from(JSON.stringify(normalized), "utf8");
|
|
98
|
+
const headers = {
|
|
99
|
+
...context.upstreamResult.headers,
|
|
100
|
+
"content-type": "application/json",
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
statusCode: context.upstreamResult.statusCode,
|
|
104
|
+
headers,
|
|
105
|
+
body: stream_1.Readable.from([buffer]),
|
|
106
|
+
rawBody: buffer,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
async function extractPromptAndImage(paths, payload) {
|
|
111
|
+
const messages = Array.isArray(payload.messages)
|
|
112
|
+
? payload.messages
|
|
113
|
+
: [];
|
|
114
|
+
const message = [...messages]
|
|
115
|
+
.reverse()
|
|
116
|
+
.find((item) => item &&
|
|
117
|
+
typeof item === "object" &&
|
|
118
|
+
item.role === "user") ?? messages[messages.length - 1];
|
|
119
|
+
if (!message || typeof message !== "object") {
|
|
120
|
+
return { text: "" };
|
|
121
|
+
}
|
|
122
|
+
const content = message.content;
|
|
123
|
+
if (typeof content === "string") {
|
|
124
|
+
return { text: content };
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(content)) {
|
|
127
|
+
return { text: "" };
|
|
128
|
+
}
|
|
129
|
+
const textParts = [];
|
|
130
|
+
let image;
|
|
131
|
+
for (const part of content) {
|
|
132
|
+
if (!part || typeof part !== "object") {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const typed = part;
|
|
136
|
+
const type = typeof typed.type === "string" ? typed.type : "";
|
|
137
|
+
if (type === "text" || type === "input_text" || type === "output_text") {
|
|
138
|
+
const text = typed.text;
|
|
139
|
+
if (typeof text === "string" && text.length > 0) {
|
|
140
|
+
textParts.push(text);
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (!image && type === "image_url") {
|
|
145
|
+
const imageUrl = typed.image_url;
|
|
146
|
+
if (typeof imageUrl?.url === "string") {
|
|
147
|
+
image = await resolveImageToBase64(paths, imageUrl.url);
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!image && type === "image") {
|
|
152
|
+
const value = typed.image;
|
|
153
|
+
if (typeof value === "string") {
|
|
154
|
+
image = await resolveImageToBase64(paths, value);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!image && type === "input_image") {
|
|
159
|
+
const imageValue = typed.image ?? typed.image_url?.url;
|
|
160
|
+
if (typeof imageValue === "string") {
|
|
161
|
+
image = await resolveImageToBase64(paths, imageValue);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
text: textParts.join("\n").trim(),
|
|
167
|
+
image,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async function resolveImageToBase64(paths, value) {
|
|
171
|
+
if (value.startsWith("data:")) {
|
|
172
|
+
const match = value.match(/^data:[^;]+;base64,(.+)$/i);
|
|
173
|
+
if (!match) {
|
|
174
|
+
throw protocolError("invalid_request", "Invalid data URL for image input.", false);
|
|
175
|
+
}
|
|
176
|
+
return match[1].replace(/\s+/g, "");
|
|
177
|
+
}
|
|
178
|
+
if (looksLikeBase64(value)) {
|
|
179
|
+
return value.replace(/\s+/g, "");
|
|
180
|
+
}
|
|
181
|
+
const hash = extractLocalMediaHash(value);
|
|
182
|
+
if (!hash) {
|
|
183
|
+
throw protocolError("invalid_request", "Only local /admin/media or /admin/images URLs are allowed for image input.", false);
|
|
184
|
+
}
|
|
185
|
+
const mediaPath = await (0, imageCache_1.getMediaPath)(paths, hash);
|
|
186
|
+
if (!mediaPath) {
|
|
187
|
+
throw protocolError("invalid_request", "Referenced image not found in cache.", false);
|
|
188
|
+
}
|
|
189
|
+
const buffer = await fs_1.promises.readFile(mediaPath);
|
|
190
|
+
return buffer.toString("base64");
|
|
191
|
+
}
|
|
192
|
+
function normalizeTextPaths(raw) {
|
|
193
|
+
if (Array.isArray(raw)) {
|
|
194
|
+
const values = raw.filter((value) => typeof value === "string" && value.length > 0);
|
|
195
|
+
if (values.length > 0) {
|
|
196
|
+
return values;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return DEFAULT_RESPONSE_TEXT_PATHS;
|
|
200
|
+
}
|
|
201
|
+
function firstStringByPaths(source, paths) {
|
|
202
|
+
for (const path of paths) {
|
|
203
|
+
const value = getByPath(source, path);
|
|
204
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
205
|
+
return value.trim();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
function numberFromPath(source, path) {
|
|
211
|
+
const value = getByPath(source, path);
|
|
212
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
function getByPath(source, path) {
|
|
218
|
+
const segments = path
|
|
219
|
+
.replace(/\[(\d+)\]/g, ".$1")
|
|
220
|
+
.split(".")
|
|
221
|
+
.map((segment) => segment.trim())
|
|
222
|
+
.filter((segment) => segment.length > 0);
|
|
223
|
+
let cursor = source;
|
|
224
|
+
for (const segment of segments) {
|
|
225
|
+
if (cursor === null || cursor === undefined) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(cursor)) {
|
|
229
|
+
const index = Number(segment);
|
|
230
|
+
if (!Number.isInteger(index) || index < 0 || index >= cursor.length) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
cursor = cursor[index];
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (typeof cursor !== "object") {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
cursor = cursor[segment];
|
|
240
|
+
}
|
|
241
|
+
return cursor;
|
|
242
|
+
}
|
|
243
|
+
function compactObject(input) {
|
|
244
|
+
const output = {};
|
|
245
|
+
for (const [key, value] of Object.entries(input)) {
|
|
246
|
+
if (value !== undefined) {
|
|
247
|
+
output[key] = value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return output;
|
|
251
|
+
}
|
|
252
|
+
function numberOrUndefined(value) {
|
|
253
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
function looksLikeBase64(value) {
|
|
259
|
+
if (value.length < 32) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return /^[A-Za-z0-9+/=\s]+$/.test(value);
|
|
263
|
+
}
|
|
264
|
+
function extractLocalMediaHash(url) {
|
|
265
|
+
const normalized = normalizeLocalUrl(url);
|
|
266
|
+
if (!normalized) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const match = normalized.match(/^\/admin\/(media|images)\/([a-f0-9]{16})$/i);
|
|
270
|
+
return match ? match[2] : null;
|
|
271
|
+
}
|
|
272
|
+
function normalizeLocalUrl(url) {
|
|
273
|
+
if (url.startsWith("/")) {
|
|
274
|
+
return url;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const parsed = new URL(url);
|
|
278
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
if (!["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
return parsed.pathname;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function buildAuth(context, defaultPath) {
|
|
291
|
+
const authType = context.auth?.type ?? "bearer";
|
|
292
|
+
if (authType === "none") {
|
|
293
|
+
return { path: defaultPath, skipDefaultAuth: true };
|
|
294
|
+
}
|
|
295
|
+
if (authType === "query") {
|
|
296
|
+
const keyParam = context.auth?.keyParam ?? "api_key";
|
|
297
|
+
const apiKey = context.endpoint.apiKey;
|
|
298
|
+
if (!apiKey) {
|
|
299
|
+
return { path: defaultPath, skipDefaultAuth: true };
|
|
300
|
+
}
|
|
301
|
+
const parsed = new URL(defaultPath, "http://placeholder.local");
|
|
302
|
+
parsed.searchParams.set(keyParam, apiKey);
|
|
303
|
+
return { path: `${parsed.pathname}${parsed.search}`, skipDefaultAuth: true };
|
|
304
|
+
}
|
|
305
|
+
if (authType === "header") {
|
|
306
|
+
const headerName = context.auth?.headerName ?? context.auth?.keyParam ?? "x-api-key";
|
|
307
|
+
const apiKey = context.endpoint.apiKey;
|
|
308
|
+
if (!apiKey) {
|
|
309
|
+
return { path: defaultPath, skipDefaultAuth: true };
|
|
310
|
+
}
|
|
311
|
+
const prefix = context.auth?.keyPrefix ? `${context.auth.keyPrefix} ` : "";
|
|
312
|
+
return {
|
|
313
|
+
path: defaultPath,
|
|
314
|
+
headers: {
|
|
315
|
+
[headerName]: `${prefix}${apiKey}`,
|
|
316
|
+
},
|
|
317
|
+
skipDefaultAuth: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return { path: defaultPath, skipDefaultAuth: false };
|
|
321
|
+
}
|
|
322
|
+
async function readJsonBody(result) {
|
|
323
|
+
if (result.rawBody) {
|
|
324
|
+
return safeJsonParse(result.rawBody.toString("utf8"));
|
|
325
|
+
}
|
|
326
|
+
const chunks = [];
|
|
327
|
+
for await (const chunk of result.body) {
|
|
328
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
329
|
+
}
|
|
330
|
+
const buffer = Buffer.concat(chunks);
|
|
331
|
+
return safeJsonParse(buffer.toString("utf8"));
|
|
332
|
+
}
|
|
333
|
+
function safeJsonParse(text) {
|
|
334
|
+
try {
|
|
335
|
+
return JSON.parse(text);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
throw protocolError("invalid_upstream_response", `Inference v2 response is not valid JSON: ${error.message}`, true);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function protocolError(type, message, retryable) {
|
|
342
|
+
const error = new Error(message);
|
|
343
|
+
error.type = type;
|
|
344
|
+
error.retryable = retryable;
|
|
345
|
+
return error;
|
|
346
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openAiProtocolAdapter = void 0;
|
|
4
|
+
const ALL_OPERATIONS = [
|
|
5
|
+
"chat_completions",
|
|
6
|
+
"embeddings",
|
|
7
|
+
"images_generation",
|
|
8
|
+
"images_edits",
|
|
9
|
+
"images_variations",
|
|
10
|
+
"audio_transcriptions",
|
|
11
|
+
"audio_translations",
|
|
12
|
+
"audio_speech",
|
|
13
|
+
];
|
|
14
|
+
exports.openAiProtocolAdapter = {
|
|
15
|
+
id: "openai",
|
|
16
|
+
supportedOperations: [...ALL_OPERATIONS],
|
|
17
|
+
streamSupportedOperations: [...ALL_OPERATIONS],
|
|
18
|
+
supports(_context) {
|
|
19
|
+
return { supported: true };
|
|
20
|
+
},
|
|
21
|
+
async buildRequest(context) {
|
|
22
|
+
return {
|
|
23
|
+
path: context.path,
|
|
24
|
+
payload: context.payload,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalizeProtocol = canonicalizeProtocol;
|
|
4
|
+
exports.getProtocolAdapter = getProtocolAdapter;
|
|
5
|
+
exports.hasProtocolAdapter = hasProtocolAdapter;
|
|
6
|
+
exports.listAdapterOperations = listAdapterOperations;
|
|
7
|
+
exports.listAllProtocolAdapters = listAllProtocolAdapters;
|
|
8
|
+
exports.routePathToOperation = routePathToOperation;
|
|
9
|
+
const inferenceV2_1 = require("./adapters/inferenceV2");
|
|
10
|
+
const openai_1 = require("./adapters/openai");
|
|
11
|
+
const dashscope_1 = require("./adapters/dashscope");
|
|
12
|
+
const PROTOCOL_ALIASES = {
|
|
13
|
+
openai: "openai",
|
|
14
|
+
inference_v2: "inference_v2",
|
|
15
|
+
"kserve-v2": "inference_v2",
|
|
16
|
+
kserve_v2: "inference_v2",
|
|
17
|
+
ray_infer_v2: "inference_v2",
|
|
18
|
+
"ray-infer-v2": "inference_v2",
|
|
19
|
+
v2_infer: "inference_v2",
|
|
20
|
+
"v2-infer": "inference_v2",
|
|
21
|
+
dashscope: "dashscope",
|
|
22
|
+
};
|
|
23
|
+
const ADAPTERS = new Map([
|
|
24
|
+
[openai_1.openAiProtocolAdapter.id, openai_1.openAiProtocolAdapter],
|
|
25
|
+
[inferenceV2_1.inferenceV2ProtocolAdapter.id, inferenceV2_1.inferenceV2ProtocolAdapter],
|
|
26
|
+
[dashscope_1.dashscopeProtocolAdapter.id, dashscope_1.dashscopeProtocolAdapter],
|
|
27
|
+
]);
|
|
28
|
+
const PROTOCOL_METADATA = {
|
|
29
|
+
openai: {
|
|
30
|
+
label: "OpenAI Compatible",
|
|
31
|
+
description: "Standard OpenAI API format. Supports chat, embeddings, images, and audio.",
|
|
32
|
+
},
|
|
33
|
+
inference_v2: {
|
|
34
|
+
label: "Inference V2 (KServe/Ray)",
|
|
35
|
+
description: "KServe v2 / Ray Serve inference format. Chat only, no streaming.",
|
|
36
|
+
},
|
|
37
|
+
dashscope: {
|
|
38
|
+
label: "DashScope (Alibaba ModelStudio)",
|
|
39
|
+
description: "Alibaba Cloud ModelStudio native API. Supports image generation, video generation, and async task-based operations.",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
function canonicalizeProtocol(raw) {
|
|
43
|
+
const normalized = (raw ?? "unknown").trim().toLowerCase();
|
|
44
|
+
return (PROTOCOL_ALIASES[normalized] ?? normalized) || "unknown";
|
|
45
|
+
}
|
|
46
|
+
function getProtocolAdapter(protocol) {
|
|
47
|
+
const canonical = canonicalizeProtocol(protocol);
|
|
48
|
+
return ADAPTERS.get(canonical) ?? null;
|
|
49
|
+
}
|
|
50
|
+
function hasProtocolAdapter(protocol) {
|
|
51
|
+
return getProtocolAdapter(protocol) !== null;
|
|
52
|
+
}
|
|
53
|
+
function listAdapterOperations(protocol) {
|
|
54
|
+
const adapter = getProtocolAdapter(protocol);
|
|
55
|
+
if (!adapter) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
operations: [...adapter.supportedOperations],
|
|
60
|
+
streamOperations: [...adapter.streamSupportedOperations],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function listAllProtocolAdapters() {
|
|
64
|
+
return Array.from(ADAPTERS.entries()).map(([id, adapter]) => {
|
|
65
|
+
const meta = PROTOCOL_METADATA[id] ?? { label: id, description: "" };
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
label: meta.label,
|
|
69
|
+
description: meta.description,
|
|
70
|
+
operations: [...adapter.supportedOperations],
|
|
71
|
+
streamOperations: [...adapter.streamSupportedOperations],
|
|
72
|
+
supportsRouting: true,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function routePathToOperation(path) {
|
|
77
|
+
switch (path) {
|
|
78
|
+
case "/v1/chat/completions":
|
|
79
|
+
return "chat_completions";
|
|
80
|
+
case "/v1/embeddings":
|
|
81
|
+
return "embeddings";
|
|
82
|
+
case "/v1/images/generations":
|
|
83
|
+
return "images_generation";
|
|
84
|
+
case "/v1/images/edits":
|
|
85
|
+
return "images_edits";
|
|
86
|
+
case "/v1/images/variations":
|
|
87
|
+
return "images_variations";
|
|
88
|
+
case "/v1/audio/transcriptions":
|
|
89
|
+
return "audio_transcriptions";
|
|
90
|
+
case "/v1/audio/translations":
|
|
91
|
+
return "audio_translations";
|
|
92
|
+
case "/v1/audio/speech":
|
|
93
|
+
return "audio_speech";
|
|
94
|
+
case "/v1/videos/generations":
|
|
95
|
+
return "video_generations";
|
|
96
|
+
default:
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultProviderModelHealth = defaultProviderModelHealth;
|
|
4
|
+
exports.getProviderModelHealthMap = getProviderModelHealthMap;
|
|
5
|
+
exports.updateProviderModelHealthCheck = updateProviderModelHealthCheck;
|
|
6
|
+
exports.probeProviderModels = probeProviderModels;
|
|
7
|
+
const undici_1 = require("undici");
|
|
8
|
+
const files_1 = require("../storage/files");
|
|
9
|
+
const repository_1 = require("./repository");
|
|
10
|
+
const repository_2 = require("./repository");
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
12
|
+
function defaultProviderModelHealth() {
|
|
13
|
+
return {
|
|
14
|
+
status: "up",
|
|
15
|
+
consecutiveFailures: 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function getProviderModelHealthMap(paths) {
|
|
19
|
+
const health = await (0, files_1.loadProviderHealth)(paths);
|
|
20
|
+
return health.models;
|
|
21
|
+
}
|
|
22
|
+
async function updateProviderModelHealthCheck(paths, providerModelId, status, latencyMs, lastStatusCode, lastError) {
|
|
23
|
+
const health = await (0, files_1.loadProviderHealth)(paths);
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const current = health.models[providerModelId] ?? defaultProviderModelHealth();
|
|
26
|
+
let consecutiveFailures = current.consecutiveFailures;
|
|
27
|
+
let nextStatus = current.status;
|
|
28
|
+
if (status === "up") {
|
|
29
|
+
consecutiveFailures = 0;
|
|
30
|
+
nextStatus = "up";
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
consecutiveFailures = current.consecutiveFailures + 1;
|
|
34
|
+
if (consecutiveFailures >= 3) {
|
|
35
|
+
nextStatus = "down";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const next = {
|
|
39
|
+
...current,
|
|
40
|
+
status: nextStatus,
|
|
41
|
+
consecutiveFailures,
|
|
42
|
+
lastCheckedAt: now,
|
|
43
|
+
lastStatusCode,
|
|
44
|
+
lastError,
|
|
45
|
+
};
|
|
46
|
+
if (status === "up" && latencyMs !== null) {
|
|
47
|
+
next.latencyMsEwma = ewma(current.latencyMsEwma, latencyMs);
|
|
48
|
+
next.lastSuccessAt = now;
|
|
49
|
+
next.lastFailureAt = undefined;
|
|
50
|
+
next.lastError = undefined;
|
|
51
|
+
}
|
|
52
|
+
else if (status === "down") {
|
|
53
|
+
next.lastFailureAt = now;
|
|
54
|
+
}
|
|
55
|
+
health.models[providerModelId] = next;
|
|
56
|
+
await (0, files_1.saveProviderHealth)(paths, health);
|
|
57
|
+
}
|
|
58
|
+
async function probeProviderModels(paths, options) {
|
|
59
|
+
const providers = await (0, repository_1.listProviders)(paths);
|
|
60
|
+
const targets = collectTargets(providers);
|
|
61
|
+
const results = [];
|
|
62
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
63
|
+
await Promise.all(Array.from(targets.values()).map(async (target) => {
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
try {
|
|
66
|
+
const dispatcher = target.insecureTls
|
|
67
|
+
? new undici_1.Agent({ connect: { rejectUnauthorized: false } })
|
|
68
|
+
: undefined;
|
|
69
|
+
const headers = {};
|
|
70
|
+
if (target.apiKey) {
|
|
71
|
+
headers.authorization = `Bearer ${target.apiKey}`;
|
|
72
|
+
}
|
|
73
|
+
const response = await (0, undici_1.request)(new URL("/v1/models", target.baseUrl).toString(), {
|
|
74
|
+
method: "GET",
|
|
75
|
+
headers,
|
|
76
|
+
headersTimeout: timeoutMs,
|
|
77
|
+
bodyTimeout: timeoutMs,
|
|
78
|
+
dispatcher,
|
|
79
|
+
});
|
|
80
|
+
const latency = Date.now() - start;
|
|
81
|
+
response.body.resume();
|
|
82
|
+
const ok = response.statusCode >= 200 && response.statusCode < 300;
|
|
83
|
+
for (const model of target.models) {
|
|
84
|
+
await updateProviderModelHealthCheck(paths, model.providerModelId, ok ? "up" : "down", ok ? latency : null, response.statusCode, ok ? undefined : `status ${response.statusCode}`);
|
|
85
|
+
results.push({
|
|
86
|
+
providerModelId: model.providerModelId,
|
|
87
|
+
providerId: model.providerId,
|
|
88
|
+
modelId: model.modelId,
|
|
89
|
+
baseUrl: target.baseUrl,
|
|
90
|
+
status: ok ? "up" : "down",
|
|
91
|
+
latencyMs: ok ? latency : null,
|
|
92
|
+
statusCode: response.statusCode,
|
|
93
|
+
error: ok ? undefined : `status ${response.statusCode}`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const errorMsg = error.message || "unknown error";
|
|
99
|
+
for (const model of target.models) {
|
|
100
|
+
await updateProviderModelHealthCheck(paths, model.providerModelId, "down", null, undefined, errorMsg);
|
|
101
|
+
results.push({
|
|
102
|
+
providerModelId: model.providerModelId,
|
|
103
|
+
providerId: model.providerId,
|
|
104
|
+
modelId: model.modelId,
|
|
105
|
+
baseUrl: target.baseUrl,
|
|
106
|
+
status: "down",
|
|
107
|
+
latencyMs: null,
|
|
108
|
+
error: errorMsg,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
function collectTargets(providers) {
|
|
116
|
+
const targets = new Map();
|
|
117
|
+
for (const provider of providers) {
|
|
118
|
+
if (!provider.enabled) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const model of provider.models) {
|
|
122
|
+
if (model.enabled === false) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const baseUrl = model.baseUrl ?? provider.baseUrl;
|
|
126
|
+
if (!baseUrl) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const apiKey = model.apiKey ?? provider.apiKey;
|
|
130
|
+
const insecureTls = (0, repository_2.getEffectiveModelInsecureTls)(provider, model);
|
|
131
|
+
const key = `${baseUrl}@@${apiKey ? "key" : "nokey"}@@${insecureTls ? "insecure" : "secure"}`;
|
|
132
|
+
const entry = targets.get(key) ?? {
|
|
133
|
+
baseUrl,
|
|
134
|
+
apiKey,
|
|
135
|
+
insecureTls,
|
|
136
|
+
models: [],
|
|
137
|
+
};
|
|
138
|
+
entry.models.push({
|
|
139
|
+
providerModelId: model.providerModelId,
|
|
140
|
+
providerId: provider.id,
|
|
141
|
+
modelId: model.modelId,
|
|
142
|
+
});
|
|
143
|
+
targets.set(key, entry);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return targets;
|
|
147
|
+
}
|
|
148
|
+
function ewma(previous, next, alpha = 0.2) {
|
|
149
|
+
if (previous === undefined) {
|
|
150
|
+
return next;
|
|
151
|
+
}
|
|
152
|
+
return alpha * next + (1 - alpha) * previous;
|
|
153
|
+
}
|