@world-engines/project-host 0.1.0-alpha.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/LICENSE +46 -0
- package/assets/gui/WELogo-title.svg +11 -0
- package/assets/gui/app.js +1326 -0
- package/assets/gui/index.html +118 -0
- package/assets/gui/local-preview-asset-urls.js +61 -0
- package/assets/gui/local-preview-bridge.js +393 -0
- package/assets/gui/spatial-template-panel.js +233 -0
- package/assets/gui/styles.css +115 -0
- package/assets/gui/terrain-render.js +120 -0
- package/assets/gui/workspace-status.css +17 -0
- package/assets/gui/workspace-status.js +255 -0
- package/assets/gui/world-map-globe.js +187 -0
- package/assets/gui/world-map-worker.js +62 -0
- package/assets/gui/world-map.css +30 -0
- package/assets/gui/world-map.js +2466 -0
- package/dist/account-model-proxy.d.ts +21 -0
- package/dist/account-model-proxy.js +470 -0
- package/dist/authoring-browser-runtime.d.ts +7 -0
- package/dist/authoring-browser-runtime.js +110 -0
- package/dist/authoring-ingestion.d.ts +38 -0
- package/dist/authoring-ingestion.js +410 -0
- package/dist/authoring-model-connection.d.ts +166 -0
- package/dist/authoring-model-connection.js +757 -0
- package/dist/chinese-lexical.d.ts +152 -0
- package/dist/chinese-lexical.js +555 -0
- package/dist/chromium-preview-browser-driver.d.ts +43 -0
- package/dist/chromium-preview-browser-driver.js +575 -0
- package/dist/cli/download.d.ts +12 -0
- package/dist/cli/download.js +50 -0
- package/dist/cli/dump.d.ts +32 -0
- package/dist/cli/dump.js +91 -0
- package/dist/cli/ingest.d.ts +49 -0
- package/dist/cli/ingest.js +225 -0
- package/dist/cli/review.d.ts +10 -0
- package/dist/cli/review.js +44 -0
- package/dist/cli/upload.d.ts +57 -0
- package/dist/cli/upload.js +112 -0
- package/dist/cli-command.d.ts +60 -0
- package/dist/cli-command.js +613 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +182 -0
- package/dist/desktop-authoring-runtime.d.ts +88 -0
- package/dist/desktop-authoring-runtime.js +1602 -0
- package/dist/desktop-sidecar.d.ts +11 -0
- package/dist/desktop-sidecar.js +143 -0
- package/dist/document-dump.d.ts +113 -0
- package/dist/document-dump.js +431 -0
- package/dist/document-graph-extractor.d.ts +125 -0
- package/dist/document-graph-extractor.js +588 -0
- package/dist/document-ingestion-journal.d.ts +68 -0
- package/dist/document-ingestion-journal.js +471 -0
- package/dist/document-ingestion.d.ts +183 -0
- package/dist/document-ingestion.js +976 -0
- package/dist/document-query-path-proof.d.ts +30 -0
- package/dist/document-query-path-proof.js +34 -0
- package/dist/document-query-path-renderer.d.ts +50 -0
- package/dist/document-query-path-renderer.js +75 -0
- package/dist/document-query-proof-paths.d.ts +22 -0
- package/dist/document-query-proof-paths.js +71 -0
- package/dist/document-query.d.ts +97 -0
- package/dist/document-query.js +1106 -0
- package/dist/document-relation-role.d.ts +14 -0
- package/dist/document-relation-role.js +86 -0
- package/dist/document-source.d.ts +107 -0
- package/dist/document-source.js +255 -0
- package/dist/embedding-cache.d.ts +35 -0
- package/dist/embedding-cache.js +317 -0
- package/dist/frozen-review-local-pipeline.d.ts +88 -0
- package/dist/frozen-review-local-pipeline.js +609 -0
- package/dist/frozen-review-source-capture.d.ts +15 -0
- package/dist/frozen-review-source-capture.js +551 -0
- package/dist/frozen-review-wasm-encryptor.d.ts +16 -0
- package/dist/frozen-review-wasm-encryptor.js +72 -0
- package/dist/frozen-review.d.ts +131 -0
- package/dist/frozen-review.js +802 -0
- package/dist/graph-traversal.d.ts +23 -0
- package/dist/graph-traversal.js +100 -0
- package/dist/gui/graph-layout-worker.js +1 -0
- package/dist/gui/graph-layout.wasm +0 -0
- package/dist/gui/graph.css +1 -0
- package/dist/gui/graph.js +76 -0
- package/dist/gui/plane-generation.js +112 -0
- package/dist/gui/plane-generator.js +310 -0
- package/dist/gui/plane-generator_bg.wasm +0 -0
- package/dist/gui/procedural-city.js +595 -0
- package/dist/gui/road-generation.js +129 -0
- package/dist/gui/road-parcels.js +1584 -0
- package/dist/gui/score.css +1 -0
- package/dist/gui/score.js +54 -0
- package/dist/gui/spatial-runtime.js +1231 -0
- package/dist/gui/spatial-templates.js +200 -0
- package/dist/gui/trigger.css +1 -0
- package/dist/gui/trigger.js +142 -0
- package/dist/harness-callback-transport.d.ts +17 -0
- package/dist/harness-callback-transport.js +128 -0
- package/dist/harness-document-ingestion.d.ts +77 -0
- package/dist/harness-document-ingestion.js +343 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +43 -0
- package/dist/ingestion-source-reconciliation.d.ts +88 -0
- package/dist/ingestion-source-reconciliation.js +253 -0
- package/dist/ladybug-runtime.d.ts +117 -0
- package/dist/ladybug-runtime.js +886 -0
- package/dist/local-account-embedding.d.ts +34 -0
- package/dist/local-account-embedding.js +294 -0
- package/dist/local-gallery.d.ts +35 -0
- package/dist/local-gallery.js +227 -0
- package/dist/local-gui-auth.d.ts +96 -0
- package/dist/local-gui-auth.js +703 -0
- package/dist/local-gui-contract.d.ts +53 -0
- package/dist/local-gui-contract.js +5 -0
- package/dist/local-gui-image-assets.d.ts +17 -0
- package/dist/local-gui-image-assets.js +96 -0
- package/dist/local-gui-preview-client.d.ts +3 -0
- package/dist/local-gui-preview-client.js +111 -0
- package/dist/local-gui-score.d.ts +72 -0
- package/dist/local-gui-score.js +184 -0
- package/dist/local-gui-server.d.ts +10 -0
- package/dist/local-gui-server.js +987 -0
- package/dist/local-gui-version.d.ts +17 -0
- package/dist/local-gui-version.js +147 -0
- package/dist/local-gui-workspace.d.ts +14 -0
- package/dist/local-gui-workspace.js +171 -0
- package/dist/local-model-settings.d.ts +28 -0
- package/dist/local-model-settings.js +124 -0
- package/dist/local-preview-assets.d.ts +28 -0
- package/dist/local-preview-assets.js +124 -0
- package/dist/local-preview-bound-resources.d.ts +12 -0
- package/dist/local-preview-bound-resources.js +80 -0
- package/dist/local-preview-message-store.d.ts +63 -0
- package/dist/local-preview-message-store.js +214 -0
- package/dist/local-preview-player-read-models.d.ts +47 -0
- package/dist/local-preview-player-read-models.js +90 -0
- package/dist/local-preview-projections.d.ts +61 -0
- package/dist/local-preview-projections.js +117 -0
- package/dist/local-preview-runtime.d.ts +105 -0
- package/dist/local-preview-runtime.js +553 -0
- package/dist/local-preview-session.d.ts +73 -0
- package/dist/local-preview-session.js +395 -0
- package/dist/local-preview-source.d.ts +40 -0
- package/dist/local-preview-source.js +192 -0
- package/dist/local-preview-view-events.d.ts +101 -0
- package/dist/local-preview-view-events.js +179 -0
- package/dist/local-preview-view-state-sqlite.d.ts +24 -0
- package/dist/local-preview-view-state-sqlite.js +263 -0
- package/dist/local-preview-view-state.d.ts +93 -0
- package/dist/local-preview-view-state.js +108 -0
- package/dist/local-preview-vite-config.d.ts +6 -0
- package/dist/local-preview-vite-config.js +122 -0
- package/dist/local-preview-world-effects.d.ts +9 -0
- package/dist/local-preview-world-effects.js +55 -0
- package/dist/local-preview-worldline-store.d.ts +47 -0
- package/dist/local-preview-worldline-store.js +282 -0
- package/dist/local-preview-worldlines.d.ts +95 -0
- package/dist/local-preview-worldlines.js +136 -0
- package/dist/mcp-async-dispatcher.d.ts +3 -0
- package/dist/mcp-async-dispatcher.js +129 -0
- package/dist/mcp-line-queue.d.ts +14 -0
- package/dist/mcp-line-queue.js +184 -0
- package/dist/mcp-pipe-internal.d.ts +6 -0
- package/dist/mcp-pipe-internal.js +151 -0
- package/dist/mcp-pipe-response.d.ts +14 -0
- package/dist/mcp-pipe-response.js +50 -0
- package/dist/mcp-stdio-response.d.ts +12 -0
- package/dist/mcp-stdio-response.js +27 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +194 -0
- package/dist/prepared-embedding-cache.d.ts +38 -0
- package/dist/prepared-embedding-cache.js +238 -0
- package/dist/preview-browser-adapter.d.ts +53 -0
- package/dist/preview-browser-adapter.js +171 -0
- package/dist/preview-runtime.d.ts +67 -0
- package/dist/preview-runtime.js +460 -0
- package/dist/project-auth-transport.d.ts +27 -0
- package/dist/project-auth-transport.js +121 -0
- package/dist/project-auth.d.ts +51 -0
- package/dist/project-auth.js +108 -0
- package/dist/project-git-capture-recovery.d.ts +16 -0
- package/dist/project-git-capture-recovery.js +347 -0
- package/dist/project-git-cli.d.ts +5 -0
- package/dist/project-git-cli.js +40 -0
- package/dist/project-git-scenario-source.d.ts +10 -0
- package/dist/project-git-scenario-source.js +240 -0
- package/dist/project-git-view-source.d.ts +13 -0
- package/dist/project-git-view-source.js +134 -0
- package/dist/project-git.d.ts +97 -0
- package/dist/project-git.js +656 -0
- package/dist/project-host.d.ts +59 -0
- package/dist/project-host.js +471 -0
- package/dist/project-writer-lease.d.ts +18 -0
- package/dist/project-writer-lease.js +430 -0
- package/dist/public-error.d.ts +7 -0
- package/dist/public-error.js +61 -0
- package/dist/retrieval-benchmark.d.ts +46 -0
- package/dist/retrieval-benchmark.js +58 -0
- package/dist/safe-project-path.d.ts +8 -0
- package/dist/safe-project-path.js +93 -0
- package/dist/scenario-writer.d.ts +32 -0
- package/dist/scenario-writer.js +191 -0
- package/dist/transfer/download.d.ts +159 -0
- package/dist/transfer/download.js +598 -0
- package/dist/transfer/transport.d.ts +26 -0
- package/dist/transfer/transport.js +1 -0
- package/dist/transfer/upload.d.ts +162 -0
- package/dist/transfer/upload.js +354 -0
- package/dist/transfer-types.d.ts +95 -0
- package/dist/transfer-types.js +1 -0
- package/dist/transfer.d.ts +18 -0
- package/dist/transfer.js +273 -0
- package/dist/trigger-runtime.d.ts +121 -0
- package/dist/trigger-runtime.js +153 -0
- package/dist/vendor/wasm-core/asset-hashes.json +5 -0
- package/dist/vendor/wasm-core/wasm_core.d.ts +490 -0
- package/dist/vendor/wasm-core/wasm_core.js +1 -0
- package/dist/vendor/wasm-core/wasm_core_bg.wasm +0 -0
- package/dist/view-watcher.d.ts +9 -0
- package/dist/view-watcher.js +34 -0
- package/package.json +110 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { LocalProjectHostOptions } from "./project-host.js";
|
|
2
|
+
import type { LocalPreviewRuntime } from "./preview-runtime.js";
|
|
3
|
+
import type { LocalGuiAuth, LocalGuiAuthOptions } from "./local-gui-auth.js";
|
|
4
|
+
import type { LocalGuiVersionChecker } from "./local-gui-version.js";
|
|
5
|
+
import type { LocalAccountChatCatalog } from "./local-model-settings.js";
|
|
6
|
+
export declare const LOCAL_GUI_HOST = "127.0.0.1";
|
|
7
|
+
export declare const LOCAL_GUI_PORT = 11451;
|
|
8
|
+
export declare const LOCAL_GUI_ORIGIN = "http://127.0.0.1:11451";
|
|
9
|
+
export declare const LOCAL_GUI_SESSION_COOKIE = "worldengine_gui_session";
|
|
10
|
+
export declare const LOCAL_GUI_MAX_JSON_BODY_BYTES: number;
|
|
11
|
+
export type LocalGuiEnvelope<T> = {
|
|
12
|
+
readonly ok: true;
|
|
13
|
+
readonly result: T;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly error: {
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export interface LocalGuiCommandResult {
|
|
22
|
+
readonly [key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
export type LocalGuiRunCommand = (args: readonly string[], context: Readonly<{
|
|
25
|
+
auth?: LocalGuiAuth;
|
|
26
|
+
}>) => Promise<LocalGuiCommandResult>;
|
|
27
|
+
export type LocalGuiAccountChatCatalogReader = (input: Readonly<{
|
|
28
|
+
projectRoot: string;
|
|
29
|
+
projectId: string;
|
|
30
|
+
}>) => Promise<LocalAccountChatCatalog>;
|
|
31
|
+
export interface StartLocalGuiServerOptions {
|
|
32
|
+
readonly hostOptions?: LocalProjectHostOptions;
|
|
33
|
+
readonly assetsDirectory?: string;
|
|
34
|
+
/** 仅承载构建生成的具名 graph/trigger JS 与 CSS;不会暴露为任意静态目录。 */
|
|
35
|
+
readonly generatedAssetsDirectory?: string;
|
|
36
|
+
readonly runCommand?: LocalGuiRunCommand;
|
|
37
|
+
readonly previewRuntime?: LocalPreviewRuntime;
|
|
38
|
+
/** 提供时启用 /api/auth/* 与受 PKCE/state 约束的 loopback callback。 */
|
|
39
|
+
readonly authOptions?: Omit<LocalGuiAuthOptions, "projectId" | "loopbackOrigin">;
|
|
40
|
+
/** 固定 11450 account-model proxy 的测试 seam;生产默认为该 proxy。 */
|
|
41
|
+
readonly accountChatCatalogReader?: LocalGuiAccountChatCatalogReader;
|
|
42
|
+
/** 仅用于注入版本检查 seam;没有事务 updater,不授予安装能力。 */
|
|
43
|
+
readonly versionChecker?: LocalGuiVersionChecker;
|
|
44
|
+
/** 仅供真实 HTTP 测试让内核分配端口;生产调用方不能选择端口。 */
|
|
45
|
+
readonly testOnlyPort?: 0;
|
|
46
|
+
}
|
|
47
|
+
export interface LocalGuiServer {
|
|
48
|
+
readonly url: string;
|
|
49
|
+
readonly host: typeof LOCAL_GUI_HOST;
|
|
50
|
+
readonly port: number;
|
|
51
|
+
readonly auth?: LocalGuiAuth;
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const LOCAL_GUI_HOST = "127.0.0.1";
|
|
2
|
+
export const LOCAL_GUI_PORT = 11451;
|
|
3
|
+
export const LOCAL_GUI_ORIGIN = `http://${LOCAL_GUI_HOST}:${LOCAL_GUI_PORT}`;
|
|
4
|
+
export const LOCAL_GUI_SESSION_COOKIE = "worldengine_gui_session";
|
|
5
|
+
export const LOCAL_GUI_MAX_JSON_BODY_BYTES = 4 * 1024 * 1024;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const LOCAL_GUI_MAX_IMAGE_ASSET_BYTES: number;
|
|
2
|
+
export declare const LOCAL_GUI_MAX_IMAGE_ASSETS = 2048;
|
|
3
|
+
export interface LocalGuiImageAsset {
|
|
4
|
+
readonly asset_path: string;
|
|
5
|
+
readonly mime: "image/png" | "image/jpeg" | "image/webp" | "image/gif" | "image/avif";
|
|
6
|
+
readonly size: number;
|
|
7
|
+
readonly sha256: string;
|
|
8
|
+
}
|
|
9
|
+
export interface LocalGuiImageAssetReadback extends LocalGuiImageAsset {
|
|
10
|
+
readonly bytes: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
type ImageMime = LocalGuiImageAsset["mime"];
|
|
13
|
+
export declare function localGuiImageMimeForPath(path: string): ImageMime | undefined;
|
|
14
|
+
export declare function localGuiImageBytesMatchMime(bytes: Uint8Array, mime: ImageMime): boolean;
|
|
15
|
+
export declare function readLocalGuiImageAsset(projectRoot: string, assetPath: string): Promise<LocalGuiImageAssetReadback>;
|
|
16
|
+
export declare function listLocalGuiImageAssets(projectRoot: string): Promise<readonly LocalGuiImageAsset[]>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { lstat, readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { extname, isAbsolute, join } from "node:path";
|
|
3
|
+
import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
|
|
4
|
+
import { sha256Hex } from "@world-engines/project-format";
|
|
5
|
+
import { assertSafeExistingProjectPath } from "./safe-project-path.js";
|
|
6
|
+
export const LOCAL_GUI_MAX_IMAGE_ASSET_BYTES = 24 * 1024 * 1024;
|
|
7
|
+
export const LOCAL_GUI_MAX_IMAGE_ASSETS = 2_048;
|
|
8
|
+
const IMAGE_MIME_BY_EXTENSION = Object.freeze({
|
|
9
|
+
".png": "image/png",
|
|
10
|
+
".jpg": "image/jpeg",
|
|
11
|
+
".jpeg": "image/jpeg",
|
|
12
|
+
".webp": "image/webp",
|
|
13
|
+
".gif": "image/gif",
|
|
14
|
+
".avif": "image/avif",
|
|
15
|
+
});
|
|
16
|
+
const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
|
|
17
|
+
function pathFailure(message) {
|
|
18
|
+
throw new AuthoringBridgeError("E_PATH_OUTSIDE_PROJECT", message);
|
|
19
|
+
}
|
|
20
|
+
function normalizedAssetPath(value) {
|
|
21
|
+
if (typeof value !== "string" || !value.startsWith("assets/") || value.includes("\0") || value.includes("\\") || isAbsolute(value)) {
|
|
22
|
+
pathFailure("图片必须使用 assets/ 下的项目相对路径");
|
|
23
|
+
}
|
|
24
|
+
const parts = value.split("/");
|
|
25
|
+
if (parts.length < 2 || parts.some((part) => part === "" || part === "." || part === ".." || part.includes(":") || part.endsWith(".") || part.endsWith(" ") || WINDOWS_DEVICE_NAME.test(part))) {
|
|
26
|
+
pathFailure("图片资源路径无效");
|
|
27
|
+
}
|
|
28
|
+
return { assetPath: parts.join("/"), relativePath: parts.slice(1).join("/") };
|
|
29
|
+
}
|
|
30
|
+
export function localGuiImageMimeForPath(path) {
|
|
31
|
+
return IMAGE_MIME_BY_EXTENSION[extname(path).toLowerCase()];
|
|
32
|
+
}
|
|
33
|
+
export function localGuiImageBytesMatchMime(bytes, mime) {
|
|
34
|
+
if (mime === "image/png")
|
|
35
|
+
return bytes.length >= 8 && [137, 80, 78, 71, 13, 10, 26, 10].every((byte, index) => bytes[index] === byte);
|
|
36
|
+
if (mime === "image/jpeg")
|
|
37
|
+
return bytes.length >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes.at(-2) === 0xff && bytes.at(-1) === 0xd9;
|
|
38
|
+
if (mime === "image/gif")
|
|
39
|
+
return bytes.length >= 6 && (Buffer.from(bytes.subarray(0, 6)).toString("ascii") === "GIF87a" || Buffer.from(bytes.subarray(0, 6)).toString("ascii") === "GIF89a");
|
|
40
|
+
if (mime === "image/webp")
|
|
41
|
+
return bytes.length >= 12 && Buffer.from(bytes.subarray(0, 4)).toString("ascii") === "RIFF" && Buffer.from(bytes.subarray(8, 12)).toString("ascii") === "WEBP";
|
|
42
|
+
return bytes.length >= 12 && Buffer.from(bytes.subarray(4, 8)).toString("ascii") === "ftyp" && ["avif", "avis"].includes(Buffer.from(bytes.subarray(8, 12)).toString("ascii"));
|
|
43
|
+
}
|
|
44
|
+
async function readValidatedImage(projectRoot, assetsRoot, assetPath) {
|
|
45
|
+
const normalized = normalizedAssetPath(assetPath);
|
|
46
|
+
const absolute = await assertSafeExistingProjectPath(projectRoot, join(assetsRoot, ...normalized.relativePath.split("/")));
|
|
47
|
+
const info = await stat(absolute);
|
|
48
|
+
const mime = localGuiImageMimeForPath(normalized.assetPath);
|
|
49
|
+
if (!info.isFile() || mime === undefined || info.size <= 0 || info.size > LOCAL_GUI_MAX_IMAGE_ASSET_BYTES) {
|
|
50
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "图片资源格式或大小无效");
|
|
51
|
+
}
|
|
52
|
+
const bytes = new Uint8Array(await readFile(absolute));
|
|
53
|
+
if (bytes.byteLength > LOCAL_GUI_MAX_IMAGE_ASSET_BYTES) {
|
|
54
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "图片内容超过本地 GUI 上限");
|
|
55
|
+
}
|
|
56
|
+
if (!localGuiImageBytesMatchMime(bytes, mime))
|
|
57
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "图片扩展名与内容不一致");
|
|
58
|
+
return { asset_path: normalized.assetPath, mime, size: bytes.byteLength, sha256: await sha256Hex(bytes), bytes };
|
|
59
|
+
}
|
|
60
|
+
export async function readLocalGuiImageAsset(projectRoot, assetPath) {
|
|
61
|
+
const assetsRoot = await assertSafeExistingProjectPath(projectRoot, join(projectRoot, "scenario", "assets"));
|
|
62
|
+
return readValidatedImage(projectRoot, assetsRoot, assetPath);
|
|
63
|
+
}
|
|
64
|
+
export async function listLocalGuiImageAssets(projectRoot) {
|
|
65
|
+
const assetsRoot = await assertSafeExistingProjectPath(projectRoot, join(projectRoot, "scenario", "assets"));
|
|
66
|
+
const images = [];
|
|
67
|
+
async function walk(directory, prefix) {
|
|
68
|
+
const canonical = await assertSafeExistingProjectPath(projectRoot, directory);
|
|
69
|
+
for (const entry of await readdir(canonical, { withFileTypes: true })) {
|
|
70
|
+
const absolute = join(canonical, entry.name);
|
|
71
|
+
const info = await lstat(absolute);
|
|
72
|
+
if (info.isSymbolicLink())
|
|
73
|
+
pathFailure("图片资源目录不能包含链接");
|
|
74
|
+
const relativePath = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
75
|
+
if (info.isDirectory()) {
|
|
76
|
+
await walk(absolute, relativePath);
|
|
77
|
+
}
|
|
78
|
+
else if (info.isFile() && localGuiImageMimeForPath(entry.name) !== undefined) {
|
|
79
|
+
if (images.length >= LOCAL_GUI_MAX_IMAGE_ASSETS)
|
|
80
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "图片资源数量超过本地 GUI 上限");
|
|
81
|
+
try {
|
|
82
|
+
const { bytes: _bytes, ...image } = await readValidatedImage(projectRoot, assetsRoot, `assets/${relativePath}`);
|
|
83
|
+
void _bytes;
|
|
84
|
+
images.push(image);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof AuthoringBridgeError && error.code === "E_VALIDATION_FAILED")
|
|
88
|
+
continue;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await walk(assetsRoot, "");
|
|
95
|
+
return images.sort((left, right) => left.asset_path.localeCompare(right.asset_path));
|
|
96
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { LOCAL_GUI_ORIGIN, LOCAL_GUI_SESSION_COOKIE } from "./local-gui-contract.js";
|
|
4
|
+
// Node fetch 会强制 sec-fetch-mode=cors;此受限本地 adapter 必须保留导航握手头。
|
|
5
|
+
const requestLocalGui = ((input, init) => new Promise((resolve, reject) => {
|
|
6
|
+
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
7
|
+
if (url.origin !== LOCAL_GUI_ORIGIN) {
|
|
8
|
+
reject(new AuthoringBridgeError("E_PATH_OUTSIDE_PROJECT", "GUI origin 无效"));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const requestHeaders = {};
|
|
12
|
+
new Headers(init?.headers).forEach((value, key) => { requestHeaders[key] = value; });
|
|
13
|
+
const request = httpRequest(url, {
|
|
14
|
+
method: init?.method ?? "GET", headers: requestHeaders,
|
|
15
|
+
...(init?.signal == null ? {} : { signal: init.signal }),
|
|
16
|
+
}, (incoming) => {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
let size = 0;
|
|
19
|
+
incoming.on("data", (value) => {
|
|
20
|
+
size += value.length;
|
|
21
|
+
if (size > 256 * 1024) {
|
|
22
|
+
request.destroy(new AuthoringBridgeError("E_VALIDATION_FAILED", "GUI 响应过大"));
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
chunks.push(value);
|
|
26
|
+
});
|
|
27
|
+
incoming.on("error", reject);
|
|
28
|
+
incoming.on("end", () => {
|
|
29
|
+
const headers = new Headers();
|
|
30
|
+
for (const [key, value] of Object.entries(incoming.headers)) {
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
value.forEach((item) => headers.append(key, item));
|
|
33
|
+
else if (value !== undefined)
|
|
34
|
+
headers.set(key, value);
|
|
35
|
+
}
|
|
36
|
+
const status = incoming.statusCode ?? 502;
|
|
37
|
+
resolve(new Response([204, 205, 304].includes(status) ? null : Buffer.concat(chunks), { status, headers }));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
request.once("error", (error) => reject(new TypeError("GUI request failed", { cause: error })));
|
|
41
|
+
request.end(init?.body);
|
|
42
|
+
}));
|
|
43
|
+
/** Node 作者工具对既有 GUI 的窄 adapter;本地 HttpOnly cookie 仅留在本调用内存。 */
|
|
44
|
+
export async function attachLocalGuiPreview(projectRoot, projectId, request = requestLocalGui) {
|
|
45
|
+
const signal = AbortSignal.timeout(25_000);
|
|
46
|
+
let response;
|
|
47
|
+
try {
|
|
48
|
+
response = await request(`${LOCAL_GUI_ORIGIN}/`, {
|
|
49
|
+
redirect: "manual", signal,
|
|
50
|
+
headers: { "sec-fetch-site": "none", "sec-fetch-mode": "navigate", "sec-fetch-dest": "document" },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const cause = error instanceof Error && "cause" in error ? error.cause : undefined;
|
|
55
|
+
if (cause?.code === "ECONNREFUSED")
|
|
56
|
+
return null;
|
|
57
|
+
throw new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "既有 GUI 无法安全连接");
|
|
58
|
+
}
|
|
59
|
+
const setCookie = response.headers.get("set-cookie");
|
|
60
|
+
await response.body?.cancel();
|
|
61
|
+
const pair = setCookie?.split(";", 1)[0];
|
|
62
|
+
if (!response.ok || !pair?.startsWith(`${LOCAL_GUI_SESSION_COOKIE}=`) || !/;\s*HttpOnly(?:;|$)/iu.test(setCookie)) {
|
|
63
|
+
throw new AuthoringBridgeError("E_PROJECT_BUSY", "11451 不提供当前项目 GUI 会话,拒绝接管");
|
|
64
|
+
}
|
|
65
|
+
const call = async (path, action) => {
|
|
66
|
+
const reply = await request(`${LOCAL_GUI_ORIGIN}${path}`, {
|
|
67
|
+
method: action === undefined ? "GET" : "POST", redirect: "manual", signal,
|
|
68
|
+
headers: { cookie: pair, origin: LOCAL_GUI_ORIGIN, "content-type": "application/json" },
|
|
69
|
+
...(action === undefined ? {} : { body: JSON.stringify({ action }) }),
|
|
70
|
+
});
|
|
71
|
+
const raw = await reply.text();
|
|
72
|
+
if (!reply.ok || raw.length > 256 * 1024)
|
|
73
|
+
throw new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "既有 GUI 预览请求失败");
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "GUI 响应无效");
|
|
80
|
+
}
|
|
81
|
+
if (parsed.ok !== true)
|
|
82
|
+
throw new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "GUI 预览能力不可用");
|
|
83
|
+
return parsed.result;
|
|
84
|
+
};
|
|
85
|
+
const status = await call("/api/status");
|
|
86
|
+
if (status?.project_id !== projectId)
|
|
87
|
+
throw new AuthoringBridgeError("E_PROJECT_BUSY", "11451 属于另一项目,拒绝接管");
|
|
88
|
+
let preview = await call("/api/preview", "status");
|
|
89
|
+
if (preview === null || preview.state === "stopped" || preview.state === "failed") {
|
|
90
|
+
preview = await call("/api/preview", "start");
|
|
91
|
+
}
|
|
92
|
+
while (preview?.state === "starting") {
|
|
93
|
+
if (signal.aborted)
|
|
94
|
+
throw new AuthoringBridgeError("E_PREVIEW_NOT_RUNNING", "GUI 预览就绪超时");
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
96
|
+
preview = await call("/api/preview", "status");
|
|
97
|
+
}
|
|
98
|
+
let url;
|
|
99
|
+
try {
|
|
100
|
+
url = new URL(preview?.url ?? "");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "GUI 预览地址无效");
|
|
104
|
+
}
|
|
105
|
+
if (preview?.state !== "running" || preview.project_root !== projectRoot || !preview.session_id
|
|
106
|
+
|| url.protocol !== "http:" || url.hostname !== "localhost" || Number(url.port) !== preview.port
|
|
107
|
+
|| !/^\/_worldengine_preview\/[a-f0-9]{32}\/$/u.test(url.pathname) || url.search || url.hash) {
|
|
108
|
+
throw new AuthoringBridgeError("E_VALIDATION_FAILED", "GUI 预览未绑定当前项目");
|
|
109
|
+
}
|
|
110
|
+
return preview;
|
|
111
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type LocalStructuralProjection } from "@world-engines/authoring-ui/score";
|
|
2
|
+
import type { LocalGuiAuth } from "./local-gui-auth.js";
|
|
3
|
+
import type { LocalProjectHost } from "./project-host.js";
|
|
4
|
+
export type LocalGuiScoreHost = Pick<LocalProjectHost, "inspect" | "readGraphProjectionReadback">;
|
|
5
|
+
export interface LocalGuiScoreIdentity {
|
|
6
|
+
readonly project_id: string;
|
|
7
|
+
readonly project_revision: number;
|
|
8
|
+
readonly snapshot_digest: string;
|
|
9
|
+
readonly source_revision: number;
|
|
10
|
+
readonly source_digest: string;
|
|
11
|
+
}
|
|
12
|
+
export interface LocalGuiScoreResult {
|
|
13
|
+
readonly identity: LocalGuiScoreIdentity;
|
|
14
|
+
readonly local: LocalStructuralProjection;
|
|
15
|
+
readonly official: {
|
|
16
|
+
readonly status: "available" | "unavailable" | "error";
|
|
17
|
+
readonly structural_score: number | null;
|
|
18
|
+
readonly quality: null;
|
|
19
|
+
readonly base: null;
|
|
20
|
+
readonly radars: LocalGuiOfficialRadars | null;
|
|
21
|
+
readonly reason?: "authentication_required" | "remote_scenario_binding_required" | "remote_score_invalid" | "remote_score_unavailable";
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface LocalGuiTagBenchmarkResult {
|
|
25
|
+
readonly identity: LocalGuiScoreIdentity;
|
|
26
|
+
readonly tag: string;
|
|
27
|
+
readonly status: "available" | "unavailable" | "error";
|
|
28
|
+
readonly benchmark: Readonly<{
|
|
29
|
+
tag: string;
|
|
30
|
+
model: string;
|
|
31
|
+
epoch: number;
|
|
32
|
+
}> | null;
|
|
33
|
+
readonly radars: LocalGuiBenchmarkRadars | null;
|
|
34
|
+
/** 与 benchmark 同一次响应读取的正式 self 快照,前端不得混用另一请求的正式画像。 */
|
|
35
|
+
readonly official: LocalGuiScoreResult["official"];
|
|
36
|
+
readonly comparison_available: boolean;
|
|
37
|
+
readonly reason?: "authentication_required" | "remote_scenario_binding_required" | "remote_score_invalid" | "remote_score_unavailable" | "benchmark_updating" | "score_benchmark_identity_unproven";
|
|
38
|
+
}
|
|
39
|
+
interface LocalGuiOfficialAxis {
|
|
40
|
+
readonly key: string;
|
|
41
|
+
readonly self: number | null;
|
|
42
|
+
readonly eligible: boolean;
|
|
43
|
+
}
|
|
44
|
+
interface LocalGuiOfficialProfile {
|
|
45
|
+
readonly model_version: string;
|
|
46
|
+
readonly source_revision: string;
|
|
47
|
+
readonly ranking_epoch: number | null;
|
|
48
|
+
readonly axes: readonly LocalGuiOfficialAxis[];
|
|
49
|
+
}
|
|
50
|
+
export interface LocalGuiOfficialRadars {
|
|
51
|
+
readonly story: LocalGuiOfficialProfile | null;
|
|
52
|
+
readonly presentation: LocalGuiOfficialProfile | null;
|
|
53
|
+
}
|
|
54
|
+
interface LocalGuiBenchmarkAxis {
|
|
55
|
+
readonly key: string;
|
|
56
|
+
readonly mean: number | null;
|
|
57
|
+
readonly median: number | null;
|
|
58
|
+
readonly sample_state: "available" | "insufficient";
|
|
59
|
+
}
|
|
60
|
+
interface LocalGuiBenchmarkProfile {
|
|
61
|
+
readonly model: string;
|
|
62
|
+
readonly epoch: number;
|
|
63
|
+
readonly axes: readonly LocalGuiBenchmarkAxis[];
|
|
64
|
+
}
|
|
65
|
+
export interface LocalGuiBenchmarkRadars {
|
|
66
|
+
readonly story: LocalGuiBenchmarkProfile;
|
|
67
|
+
readonly presentation: LocalGuiBenchmarkProfile;
|
|
68
|
+
}
|
|
69
|
+
export declare function readLocalGuiScore(host: LocalGuiScoreHost): Promise<LocalGuiScoreResult>;
|
|
70
|
+
export declare function readLocalGuiOfficialScore(auth: LocalGuiAuth | undefined): Promise<LocalGuiScoreResult["official"]>;
|
|
71
|
+
export declare function readLocalGuiTagBenchmark(host: LocalGuiScoreHost, tag: string, auth?: LocalGuiAuth): Promise<LocalGuiTagBenchmarkResult>;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { computeLocalStructuralProjection, } from "@world-engines/authoring-ui/score";
|
|
2
|
+
import { PRESENTATION_SCORE_AXES, STORY_SCORE_AXES } from "@world-engines/authoring-ui/score-presentation";
|
|
3
|
+
import { isKnownScenarioTag } from "@world-engines/authoring-ui/score-tags";
|
|
4
|
+
import { AuthoringBridgeError } from "@world-engines/authoring-bridge";
|
|
5
|
+
function description(metadata) {
|
|
6
|
+
if (typeof metadata.description === "string")
|
|
7
|
+
return metadata.description;
|
|
8
|
+
const nested = metadata.metadata;
|
|
9
|
+
return typeof nested === "object" && nested !== null && !Array.isArray(nested)
|
|
10
|
+
&& typeof nested.description === "string"
|
|
11
|
+
? nested.description
|
|
12
|
+
: "";
|
|
13
|
+
}
|
|
14
|
+
function identity(status, graph) {
|
|
15
|
+
if (status.project_revision !== graph.project_revision)
|
|
16
|
+
throw Object.assign(new Error("评分读取期间项目 revision 已变化"), { code: "E_STALE_PROJECT_REVISION" });
|
|
17
|
+
return {
|
|
18
|
+
project_id: status.project_id,
|
|
19
|
+
project_revision: status.project_revision,
|
|
20
|
+
snapshot_digest: status.snapshot_digest,
|
|
21
|
+
source_revision: graph.project_revision,
|
|
22
|
+
source_digest: graph.source_sha256,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function readLocalGuiScore(host) {
|
|
26
|
+
const graph = await host.readGraphProjectionReadback();
|
|
27
|
+
const status = await host.inspect();
|
|
28
|
+
const freshIdentity = identity(status, graph);
|
|
29
|
+
return {
|
|
30
|
+
identity: freshIdentity,
|
|
31
|
+
local: computeLocalStructuralProjection({
|
|
32
|
+
entities: graph.authoring.entities,
|
|
33
|
+
relations: graph.authoring.relations,
|
|
34
|
+
description: description(graph.authoring.metadata),
|
|
35
|
+
source_revision: graph.project_revision,
|
|
36
|
+
source_digest: graph.source_sha256,
|
|
37
|
+
}),
|
|
38
|
+
official: { status: "unavailable", structural_score: null, quality: null, base: null, radars: null, reason: "authentication_required" },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function record(value) {
|
|
42
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
43
|
+
}
|
|
44
|
+
function finiteScore(value) {
|
|
45
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100 ? value : null;
|
|
46
|
+
}
|
|
47
|
+
async function boundedRemoteScore(auth, request) {
|
|
48
|
+
let timer;
|
|
49
|
+
try {
|
|
50
|
+
return await Promise.race([
|
|
51
|
+
auth.requestScore(request),
|
|
52
|
+
new Promise((_resolve, reject) => { timer = setTimeout(() => reject(Object.assign(new Error("remote score timeout"), { code: "E_OPERATION_UNCERTAIN" })), 5_000); }),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
if (timer !== undefined)
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function scoreValue(value) {
|
|
61
|
+
return value === null ? null : typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100 ? value : undefined;
|
|
62
|
+
}
|
|
63
|
+
function officialRadars(value) {
|
|
64
|
+
const root = record(value);
|
|
65
|
+
if (root === undefined)
|
|
66
|
+
return null;
|
|
67
|
+
const profile = (candidate, expected) => {
|
|
68
|
+
if (candidate === null)
|
|
69
|
+
return null;
|
|
70
|
+
const item = record(candidate);
|
|
71
|
+
if (item === undefined || !Array.isArray(item.axes) || item.axes.length !== expected.length)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (typeof item.model_version !== "string" || item.model_version.length === 0 || typeof item.source_revision !== "string" || item.source_revision.length === 0)
|
|
74
|
+
return undefined;
|
|
75
|
+
const rankingEpoch = item.ranking_epoch === undefined || item.ranking_epoch === null ? null
|
|
76
|
+
: Number.isSafeInteger(item.ranking_epoch) && item.ranking_epoch >= 0 ? item.ranking_epoch : undefined;
|
|
77
|
+
if (rankingEpoch === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const expectedKeys = new Set(expected.map(({ key }) => key));
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const axes = [];
|
|
82
|
+
for (const candidateAxis of item.axes) {
|
|
83
|
+
const axis = record(candidateAxis);
|
|
84
|
+
if (axis === undefined || typeof axis.key !== "string" || !expectedKeys.has(axis.key) || seen.has(axis.key))
|
|
85
|
+
return undefined;
|
|
86
|
+
seen.add(axis.key);
|
|
87
|
+
if (typeof axis.eligible !== "boolean")
|
|
88
|
+
return undefined;
|
|
89
|
+
const score = scoreValue(axis.score);
|
|
90
|
+
if (score === undefined || score === null)
|
|
91
|
+
return undefined;
|
|
92
|
+
axes.push({ key: axis.key, self: axis.eligible ? score : null, eligible: axis.eligible });
|
|
93
|
+
}
|
|
94
|
+
return { model_version: item.model_version, source_revision: item.source_revision, ranking_epoch: rankingEpoch, axes };
|
|
95
|
+
};
|
|
96
|
+
const story = profile(root.story, STORY_SCORE_AXES);
|
|
97
|
+
const presentation = profile(root.presentation, PRESENTATION_SCORE_AXES);
|
|
98
|
+
if (story === undefined || presentation === undefined)
|
|
99
|
+
return null;
|
|
100
|
+
return { story, presentation };
|
|
101
|
+
}
|
|
102
|
+
function benchmarkRadars(value) {
|
|
103
|
+
const root = record(value);
|
|
104
|
+
if (root === undefined)
|
|
105
|
+
return null;
|
|
106
|
+
const profile = (candidate, expected) => {
|
|
107
|
+
const item = record(candidate);
|
|
108
|
+
if (item === undefined || typeof item.model !== "string" || item.model.length === 0 || !Number.isSafeInteger(item.epoch) || item.epoch < 0 || !Array.isArray(item.axes) || item.axes.length !== expected.length)
|
|
109
|
+
return undefined;
|
|
110
|
+
const expectedKeys = new Set(expected.map(({ key }) => key));
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
const axes = [];
|
|
113
|
+
for (const candidateAxis of item.axes) {
|
|
114
|
+
const axis = record(candidateAxis);
|
|
115
|
+
if (axis === undefined || typeof axis.key !== "string" || !expectedKeys.has(axis.key) || seen.has(axis.key))
|
|
116
|
+
return undefined;
|
|
117
|
+
seen.add(axis.key);
|
|
118
|
+
if (axis.sample_state !== "available" && axis.sample_state !== "insufficient")
|
|
119
|
+
return undefined;
|
|
120
|
+
const mean = scoreValue(axis.mean);
|
|
121
|
+
const median = axis.median === undefined ? null : scoreValue(axis.median);
|
|
122
|
+
if (mean === undefined || median === undefined || (axis.sample_state === "available" && mean === null) || (axis.sample_state === "insufficient" && (mean !== null || median !== null)))
|
|
123
|
+
return undefined;
|
|
124
|
+
axes.push({ key: axis.key, mean, median, sample_state: axis.sample_state });
|
|
125
|
+
}
|
|
126
|
+
return { model: item.model, epoch: item.epoch, axes };
|
|
127
|
+
};
|
|
128
|
+
const story = profile(root.story, STORY_SCORE_AXES);
|
|
129
|
+
const presentation = profile(root.presentation, PRESENTATION_SCORE_AXES);
|
|
130
|
+
if (story === undefined || presentation === undefined || story.model !== presentation.model || story.epoch !== presentation.epoch)
|
|
131
|
+
return null;
|
|
132
|
+
return { story, presentation };
|
|
133
|
+
}
|
|
134
|
+
function unavailableReason(error) {
|
|
135
|
+
return error instanceof AuthoringBridgeError && error.code === "E_AUTH_REQUIRED" ? "authentication_required"
|
|
136
|
+
: error instanceof AuthoringBridgeError && error.code === "E_REFERENCE_MISSING" ? "remote_scenario_binding_required"
|
|
137
|
+
: "remote_score_unavailable";
|
|
138
|
+
}
|
|
139
|
+
export async function readLocalGuiOfficialScore(auth) {
|
|
140
|
+
if (auth === undefined)
|
|
141
|
+
return { status: "unavailable", structural_score: null, quality: null, base: null, radars: null, reason: "authentication_required" };
|
|
142
|
+
try {
|
|
143
|
+
const response = await boundedRemoteScore(auth, { kind: "score" });
|
|
144
|
+
const body = record(response.body);
|
|
145
|
+
if (body === undefined)
|
|
146
|
+
return { status: "error", structural_score: null, quality: null, base: null, radars: null, reason: "remote_score_invalid" };
|
|
147
|
+
const structuralScore = finiteScore(body.structural_score);
|
|
148
|
+
if (structuralScore === null)
|
|
149
|
+
return { status: "error", structural_score: null, quality: null, base: null, radars: null, reason: "remote_score_invalid" };
|
|
150
|
+
return { status: "available", structural_score: structuralScore, quality: null, base: null, radars: officialRadars(body.radars) };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return { status: "unavailable", structural_score: null, quality: null, base: null, radars: null, reason: unavailableReason(error) };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export async function readLocalGuiTagBenchmark(host, tag, auth) {
|
|
157
|
+
if (!isKnownScenarioTag(tag)) {
|
|
158
|
+
throw Object.assign(new Error("tag 无效"), { code: "E_SCHEMA_INVALID" });
|
|
159
|
+
}
|
|
160
|
+
const score = await readLocalGuiScore(host);
|
|
161
|
+
if (auth === undefined)
|
|
162
|
+
return { identity: score.identity, tag, status: "unavailable", benchmark: null, radars: null, official: score.official, comparison_available: false, reason: "authentication_required" };
|
|
163
|
+
try {
|
|
164
|
+
const [official, response] = await Promise.all([
|
|
165
|
+
readLocalGuiOfficialScore(auth),
|
|
166
|
+
boundedRemoteScore(auth, { kind: "tag-benchmark", tag }),
|
|
167
|
+
]);
|
|
168
|
+
const body = record(response.body);
|
|
169
|
+
const radars = body === undefined || body.tag !== tag ? null : benchmarkRadars(body);
|
|
170
|
+
const comparable = radars !== null && official.radars?.story !== null && official.radars?.presentation !== null
|
|
171
|
+
&& official.radars?.story !== undefined && official.radars?.presentation !== undefined
|
|
172
|
+
&& official.radars.story.model_version === radars.story.model
|
|
173
|
+
&& official.radars.presentation.model_version === radars.presentation.model
|
|
174
|
+
&& official.radars.story.ranking_epoch === radars.story.epoch
|
|
175
|
+
&& official.radars.presentation.ranking_epoch === radars.presentation.epoch;
|
|
176
|
+
return radars === null
|
|
177
|
+
? { identity: score.identity, tag, status: "error", benchmark: null, radars: null, official, comparison_available: false, reason: "remote_score_invalid" }
|
|
178
|
+
: { identity: score.identity, tag, status: "available", benchmark: { tag, model: radars.story.model, epoch: radars.story.epoch }, radars, official, comparison_available: comparable, ...(comparable ? {} : { reason: "score_benchmark_identity_unproven" }) };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const reason = error instanceof AuthoringBridgeError && error.code === "E_OPERATION_UNCERTAIN" && error.message.includes("基准正在更新") ? "benchmark_updating" : unavailableReason(error);
|
|
182
|
+
return { identity: score.identity, tag, status: "unavailable", benchmark: null, radars: null, official: score.official, comparison_available: false, reason };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
+
import { type LocalGuiServer, type StartLocalGuiServerOptions } from "./local-gui-contract.js";
|
|
3
|
+
/** @internal 供真实 HTTP Range 回归复用;调用方仍负责 session/capability authorization。 */
|
|
4
|
+
export declare function sendPreviewAsset(request: IncomingMessage, response: ServerResponse, asset: Readonly<{
|
|
5
|
+
mime: string;
|
|
6
|
+
sha256: string;
|
|
7
|
+
bytes: Uint8Array;
|
|
8
|
+
}>): void;
|
|
9
|
+
/** 启动绑定一个 canonical project root 的 npm 包内本地 GUI;浏览器不能改选 root。 */
|
|
10
|
+
export declare function startLocalGuiServer(root: string, options?: StartLocalGuiServerOptions): Promise<LocalGuiServer>;
|