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,45 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { rewriteLegacyArgv } from "../cli/legacyRewrite.ts";
|
|
4
|
+
|
|
5
|
+
test("rewrites provider model ls to models <provider>", () => {
|
|
6
|
+
const result = rewriteLegacyArgv(["provider", "model", "ls", "pcai"]);
|
|
7
|
+
assert.equal(result.legacyUsed, true);
|
|
8
|
+
assert.deepEqual(result.argv, ["models", "pcai"]);
|
|
9
|
+
assert.equal(result.ruleId, "provider-model-list");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("rewrites provider models to models <provider>", () => {
|
|
13
|
+
const result = rewriteLegacyArgv(["provider", "models", "pcai"]);
|
|
14
|
+
assert.equal(result.legacyUsed, true);
|
|
15
|
+
assert.deepEqual(result.argv, ["models", "pcai"]);
|
|
16
|
+
assert.equal(result.ruleId, "provider-models-list");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("rewrites provider model show to models show provider/model", () => {
|
|
20
|
+
const result = rewriteLegacyArgv(["provider", "model", "show", "pcai", "gpt-4o"]);
|
|
21
|
+
assert.equal(result.legacyUsed, true);
|
|
22
|
+
assert.deepEqual(result.argv, ["models", "show", "pcai/gpt-4o"]);
|
|
23
|
+
assert.equal(result.ruleId, "provider-model-show");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("rewrites provider model set-key to models set-key provider/model", () => {
|
|
27
|
+
const result = rewriteLegacyArgv([
|
|
28
|
+
"provider",
|
|
29
|
+
"model",
|
|
30
|
+
"set-key",
|
|
31
|
+
"pcai",
|
|
32
|
+
"gpt-4o",
|
|
33
|
+
"--env-var",
|
|
34
|
+
"PCAI_KEY",
|
|
35
|
+
]);
|
|
36
|
+
assert.equal(result.legacyUsed, true);
|
|
37
|
+
assert.deepEqual(result.argv, ["models", "set-key", "pcai/gpt-4o", "--env-var", "PCAI_KEY"]);
|
|
38
|
+
assert.equal(result.ruleId, "provider-model-set-key");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("leaves canonical commands unchanged", () => {
|
|
42
|
+
const result = rewriteLegacyArgv(["models", "pcai"]);
|
|
43
|
+
assert.equal(result.legacyUsed, false);
|
|
44
|
+
assert.deepEqual(result.argv, ["models", "pcai"]);
|
|
45
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { promises as fs } from "fs";
|
|
6
|
+
import {
|
|
7
|
+
normalizeChatImagePayload,
|
|
8
|
+
normalizeImageGenerationPayload,
|
|
9
|
+
} from "../src/services/imageGeneration";
|
|
10
|
+
import { StoragePaths } from "../src/storage/files";
|
|
11
|
+
|
|
12
|
+
function makePaths(baseDir: string): StoragePaths {
|
|
13
|
+
return {
|
|
14
|
+
baseDir,
|
|
15
|
+
configPath: path.join(baseDir, "config.yaml"),
|
|
16
|
+
healthPath: path.join(baseDir, "health.json"),
|
|
17
|
+
providerHealthPath: path.join(baseDir, "providers_health.json"),
|
|
18
|
+
requestLogPath: path.join(baseDir, "request_logs.jsonl"),
|
|
19
|
+
providersPath: path.join(baseDir, "providers.json"),
|
|
20
|
+
poolsPath: path.join(baseDir, "pools.json"),
|
|
21
|
+
poolStatePath: path.join(baseDir, "pool_state.json"),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test("normalizeImageGenerationPayload preserves mixed url+b64 fields", async () => {
|
|
26
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-norm-"));
|
|
27
|
+
const result = await normalizeImageGenerationPayload(
|
|
28
|
+
makePaths(baseDir),
|
|
29
|
+
{
|
|
30
|
+
created: 1730000000,
|
|
31
|
+
data: [
|
|
32
|
+
{
|
|
33
|
+
url: "https://example.com/image.png",
|
|
34
|
+
b64_json: "AAA",
|
|
35
|
+
revised_prompt: "cat",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
"provider/model"
|
|
40
|
+
);
|
|
41
|
+
assert.equal(result.model, "provider/model");
|
|
42
|
+
assert.equal(result.images.length, 1);
|
|
43
|
+
assert.equal(result.images[0].url, "https://example.com/image.png");
|
|
44
|
+
assert.equal(result.images[0].b64_json, "AAA");
|
|
45
|
+
assert.equal(result.images[0].revised_prompt, "cat");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("normalizeImageGenerationPayload adds data url when only b64_json exists", async () => {
|
|
49
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-norm-"));
|
|
50
|
+
const result = await normalizeImageGenerationPayload(
|
|
51
|
+
makePaths(baseDir),
|
|
52
|
+
{
|
|
53
|
+
data: [{ b64_json: "BBB" }],
|
|
54
|
+
},
|
|
55
|
+
"smart"
|
|
56
|
+
);
|
|
57
|
+
assert.equal(result.images.length, 1);
|
|
58
|
+
assert.equal(result.images[0].b64_json, "BBB");
|
|
59
|
+
assert.equal(result.images[0].url, "data:image/png;base64,BBB");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("normalizeImageGenerationPayload extracts b64 from data URL", async () => {
|
|
63
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-norm-"));
|
|
64
|
+
const result = await normalizeImageGenerationPayload(
|
|
65
|
+
makePaths(baseDir),
|
|
66
|
+
{
|
|
67
|
+
data: [{ url: "data:image/png;base64,CCC" }],
|
|
68
|
+
},
|
|
69
|
+
"smart"
|
|
70
|
+
);
|
|
71
|
+
assert.equal(result.images.length, 1);
|
|
72
|
+
assert.equal(result.images[0].b64_json, "CCC");
|
|
73
|
+
assert.equal(result.images[0].url, "data:image/png;base64,CCC");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("normalizeChatImagePayload converts chat multimodal image content", () => {
|
|
77
|
+
const normalized = normalizeChatImagePayload({
|
|
78
|
+
created: 1730000000,
|
|
79
|
+
choices: [
|
|
80
|
+
{
|
|
81
|
+
message: {
|
|
82
|
+
content: [
|
|
83
|
+
{ type: "text", text: "edited result" },
|
|
84
|
+
{ type: "image_url", image_url: { url: "data:image/png;base64,DDD" } },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
}) as {
|
|
90
|
+
created: number;
|
|
91
|
+
data: Array<{ url?: string; revised_prompt?: string }>;
|
|
92
|
+
};
|
|
93
|
+
assert.equal(normalized.created, 1730000000);
|
|
94
|
+
assert.equal(normalized.data.length, 1);
|
|
95
|
+
assert.equal(normalized.data[0].url, "data:image/png;base64,DDD");
|
|
96
|
+
assert.equal(normalized.data[0].revised_prompt, "edited result");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("normalizeChatImagePayload throws when chat payload has no image output", () => {
|
|
100
|
+
assert.throws(
|
|
101
|
+
() =>
|
|
102
|
+
normalizeChatImagePayload({
|
|
103
|
+
choices: [{ message: { content: [{ type: "text", text: "no image" }] } }],
|
|
104
|
+
}),
|
|
105
|
+
/did not return any image output/
|
|
106
|
+
);
|
|
107
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { promises as fs } from "fs";
|
|
6
|
+
import sharp from "sharp";
|
|
7
|
+
import {
|
|
8
|
+
buildImageGeometrySystemMessage,
|
|
9
|
+
imageDataUrlFromPath,
|
|
10
|
+
imageDataUrlWithGeometryFromPath,
|
|
11
|
+
parseImageUnderstandingText,
|
|
12
|
+
} from "../src/services/imageUnderstanding";
|
|
13
|
+
|
|
14
|
+
test("imageDataUrlFromPath reads bytes and builds data URL", async () => {
|
|
15
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-understand-"));
|
|
16
|
+
const filePath = path.join(baseDir, "sample.png");
|
|
17
|
+
await sharp({
|
|
18
|
+
create: {
|
|
19
|
+
width: 4,
|
|
20
|
+
height: 4,
|
|
21
|
+
channels: 3,
|
|
22
|
+
background: { r: 255, g: 0, b: 0 },
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.png()
|
|
26
|
+
.toFile(filePath);
|
|
27
|
+
|
|
28
|
+
const dataUrl = await imageDataUrlFromPath(filePath);
|
|
29
|
+
assert.match(dataUrl, /^data:image\/png;base64,/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("imageDataUrlWithGeometryFromPath preserves identity geometry for small images", async () => {
|
|
33
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-understand-"));
|
|
34
|
+
const filePath = path.join(baseDir, "small.png");
|
|
35
|
+
await sharp({
|
|
36
|
+
create: {
|
|
37
|
+
width: 320,
|
|
38
|
+
height: 240,
|
|
39
|
+
channels: 3,
|
|
40
|
+
background: { r: 0, g: 255, b: 0 },
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
.png()
|
|
44
|
+
.toFile(filePath);
|
|
45
|
+
|
|
46
|
+
const resolved = await imageDataUrlWithGeometryFromPath(filePath);
|
|
47
|
+
assert.match(resolved.imageUrl, /^data:image\/png;base64,/);
|
|
48
|
+
assert.deepEqual(resolved.imageGeometry, {
|
|
49
|
+
original_width: 320,
|
|
50
|
+
original_height: 240,
|
|
51
|
+
uploaded_width: 320,
|
|
52
|
+
uploaded_height: 240,
|
|
53
|
+
scale_x: 1,
|
|
54
|
+
scale_y: 1,
|
|
55
|
+
resized: false,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("imageDataUrlWithGeometryFromPath tracks resized upload geometry", async () => {
|
|
60
|
+
const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "waypoi-img-understand-"));
|
|
61
|
+
const filePath = path.join(baseDir, "large.png");
|
|
62
|
+
await sharp({
|
|
63
|
+
create: {
|
|
64
|
+
width: 2000,
|
|
65
|
+
height: 1200,
|
|
66
|
+
channels: 3,
|
|
67
|
+
background: { r: 0, g: 0, b: 255 },
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
.png()
|
|
71
|
+
.toFile(filePath);
|
|
72
|
+
|
|
73
|
+
const resolved = await imageDataUrlWithGeometryFromPath(filePath);
|
|
74
|
+
assert.match(resolved.imageUrl, /^data:image\/png;base64,/);
|
|
75
|
+
assert.equal(resolved.imageGeometry?.original_width, 2000);
|
|
76
|
+
assert.equal(resolved.imageGeometry?.original_height, 1200);
|
|
77
|
+
assert.ok((resolved.imageGeometry?.uploaded_width ?? 0) < 2000);
|
|
78
|
+
assert.ok((resolved.imageGeometry?.uploaded_height ?? 0) < 1200);
|
|
79
|
+
assert.equal(resolved.imageGeometry?.resized, true);
|
|
80
|
+
assert.ok((resolved.imageGeometry?.scale_x ?? 1) > 1);
|
|
81
|
+
assert.ok((resolved.imageGeometry?.scale_y ?? 1) > 1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("buildImageGeometrySystemMessage describes original coordinate space", () => {
|
|
85
|
+
const message = buildImageGeometrySystemMessage({
|
|
86
|
+
original_width: 2000,
|
|
87
|
+
original_height: 1200,
|
|
88
|
+
uploaded_width: 1080,
|
|
89
|
+
uploaded_height: 648,
|
|
90
|
+
scale_x: 2000 / 1080,
|
|
91
|
+
scale_y: 1200 / 648,
|
|
92
|
+
resized: true,
|
|
93
|
+
});
|
|
94
|
+
assert.match(message, /original image pixel space/i);
|
|
95
|
+
assert.match(message, /Original image size: 2000x1200/);
|
|
96
|
+
assert.match(message, /Uploaded image size: 1080x648/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("parseImageUnderstandingText handles plain text fallback", () => {
|
|
100
|
+
const result = parseImageUnderstandingText("A red stop sign on a city street.");
|
|
101
|
+
assert.equal(result.answer, "A red stop sign on a city street.");
|
|
102
|
+
assert.equal(result.objects.length, 0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("parseImageUnderstandingText handles structured json payload", () => {
|
|
106
|
+
const text = JSON.stringify({
|
|
107
|
+
analysis: {
|
|
108
|
+
answer: "Sign with text",
|
|
109
|
+
ocr_text: "STOP",
|
|
110
|
+
objects: ["sign", "road"],
|
|
111
|
+
scene: "urban street",
|
|
112
|
+
notable_details: ["clear weather"],
|
|
113
|
+
safety_notes: ["traffic nearby"],
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const result = parseImageUnderstandingText(text);
|
|
117
|
+
assert.equal(result.answer, "Sign with text");
|
|
118
|
+
assert.equal(result.ocr_text, "STOP");
|
|
119
|
+
assert.deepEqual(result.objects, ["sign", "road"]);
|
|
120
|
+
assert.equal(result.scene, "urban street");
|
|
121
|
+
assert.deepEqual(result.notable_details, ["clear weather"]);
|
|
122
|
+
assert.deepEqual(result.safety_notes, ["traffic nearby"]);
|
|
123
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import {
|
|
5
|
+
resolveBinaryOutputPolicy,
|
|
6
|
+
validateAtMostOneImageInput,
|
|
7
|
+
validateSingleImageInput,
|
|
8
|
+
} from "../src/mcp/policy";
|
|
9
|
+
|
|
10
|
+
test("resolveBinaryOutputPolicy defaults outputDir to baseDir/generated-images", () => {
|
|
11
|
+
const baseDir = path.join(path.sep, "Users", "example", ".config", "waypoi");
|
|
12
|
+
const resolved = resolveBinaryOutputPolicy({}, { defaultBaseDir: baseDir });
|
|
13
|
+
assert.equal(resolved.outputBaseRoot, baseDir);
|
|
14
|
+
assert.equal(resolved.outputDir, path.join(baseDir, "generated-images"));
|
|
15
|
+
assert.equal(resolved.includeData, false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("resolveBinaryOutputPolicy WAYPOI_MCP_OUTPUT_ROOT overrides baseDir", () => {
|
|
19
|
+
const baseDir = path.join(path.sep, "Users", "example", ".config", "waypoi");
|
|
20
|
+
const customRoot = path.join(path.sep, "Users", "example", "projects");
|
|
21
|
+
const resolved = resolveBinaryOutputPolicy(
|
|
22
|
+
{},
|
|
23
|
+
{ defaultBaseDir: baseDir, env: { WAYPOI_MCP_OUTPUT_ROOT: customRoot } }
|
|
24
|
+
);
|
|
25
|
+
assert.equal(resolved.outputBaseRoot, customRoot);
|
|
26
|
+
assert.equal(resolved.outputDir, path.join(customRoot, "generated-images"));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("resolveBinaryOutputPolicy WAYPOI_MCP_OUTPUT_SUBDIR narrows output path", () => {
|
|
30
|
+
const baseDir = path.join(path.sep, "Users", "example", ".config", "waypoi");
|
|
31
|
+
const resolved = resolveBinaryOutputPolicy(
|
|
32
|
+
{},
|
|
33
|
+
{ defaultBaseDir: baseDir, env: { WAYPOI_MCP_OUTPUT_SUBDIR: "work" } }
|
|
34
|
+
);
|
|
35
|
+
assert.equal(resolved.outputBaseRoot, baseDir);
|
|
36
|
+
assert.equal(resolved.outputDir, path.join(baseDir, "work"));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolveBinaryOutputPolicy strict mode requires WAYPOI_MCP_OUTPUT_ROOT", () => {
|
|
40
|
+
assert.throws(
|
|
41
|
+
() =>
|
|
42
|
+
resolveBinaryOutputPolicy(
|
|
43
|
+
{},
|
|
44
|
+
{ env: { WAYPOI_MCP_STRICT_OUTPUT_ROOT: "true" } }
|
|
45
|
+
),
|
|
46
|
+
/requires WAYPOI_MCP_OUTPUT_ROOT/
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("resolveBinaryOutputPolicy strict mode rejects non-absolute WAYPOI_MCP_OUTPUT_ROOT", () => {
|
|
51
|
+
assert.throws(
|
|
52
|
+
() =>
|
|
53
|
+
resolveBinaryOutputPolicy(
|
|
54
|
+
{},
|
|
55
|
+
{
|
|
56
|
+
env: {
|
|
57
|
+
WAYPOI_MCP_STRICT_OUTPUT_ROOT: "true",
|
|
58
|
+
WAYPOI_MCP_OUTPUT_ROOT: "relative/path",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/must be an absolute path/
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("resolveBinaryOutputPolicy rejects absolute WAYPOI_MCP_OUTPUT_SUBDIR", () => {
|
|
67
|
+
assert.throws(
|
|
68
|
+
() =>
|
|
69
|
+
resolveBinaryOutputPolicy(
|
|
70
|
+
{},
|
|
71
|
+
{ env: { WAYPOI_MCP_OUTPUT_SUBDIR: path.sep + "absolute" } }
|
|
72
|
+
),
|
|
73
|
+
/WAYPOI_MCP_OUTPUT_SUBDIR must be relative/
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("resolveBinaryOutputPolicy defaults includeData to false", () => {
|
|
78
|
+
const resolved = resolveBinaryOutputPolicy({});
|
|
79
|
+
assert.equal(resolved.includeData, false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("resolveBinaryOutputPolicy respects include_data=true", () => {
|
|
83
|
+
const resolved = resolveBinaryOutputPolicy({ include_data: true });
|
|
84
|
+
assert.equal(resolved.includeData, true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("validateSingleImageInput enforces xor behavior", () => {
|
|
88
|
+
assert.throws(() => validateSingleImageInput({}), /Exactly one image source/);
|
|
89
|
+
assert.throws(
|
|
90
|
+
() => validateSingleImageInput({ image_path: "/tmp/a.png", image_url: "https://example.com/a.png" }),
|
|
91
|
+
/Exactly one image source/
|
|
92
|
+
);
|
|
93
|
+
assert.doesNotThrow(() => validateSingleImageInput({ image_path: "/tmp/a.png" }));
|
|
94
|
+
assert.doesNotThrow(() => validateSingleImageInput({ image_url: "https://example.com/a.png" }));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("validateAtMostOneImageInput allows none or one image source", () => {
|
|
98
|
+
assert.doesNotThrow(() => validateAtMostOneImageInput({}));
|
|
99
|
+
assert.doesNotThrow(() => validateAtMostOneImageInput({ image_path: "/tmp/a.png" }));
|
|
100
|
+
assert.doesNotThrow(() => validateAtMostOneImageInput({ image_url: "https://example.com/a.png" }));
|
|
101
|
+
assert.throws(
|
|
102
|
+
() => validateAtMostOneImageInput({ image_path: "/tmp/a.png", image_url: "https://example.com/a.png" }),
|
|
103
|
+
/Provide either image_path or image_url/
|
|
104
|
+
);
|
|
105
|
+
});
|