fix(runtime): 本机估计运行时 - 兼容 Windows 路径覆盖并允许零人数姿态结果
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import type { Request, RequestHandler, Response } from "express";
|
||||
|
||||
import type { LocalRuntimeModule } from "../local-runtime.js";
|
||||
import { resolveEnvPath } from "./runtime-env-path.js";
|
||||
|
||||
const DEFAULT_MODEL_ID = "depth-anything/Depth-Anything-V2-Small-hf";
|
||||
const MAX_INPUT_BYTES = 12 * 1024 * 1024;
|
||||
@@ -103,12 +104,12 @@ export function resolveDepthRuntimePaths(env: NodeJS.ProcessEnv = process.env):
|
||||
const moduleRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const configuredProjectRoot = env.CANVAS_PROJECT_ROOT?.trim();
|
||||
const projectRoots = [
|
||||
configuredProjectRoot ? path.resolve(configuredProjectRoot) : undefined,
|
||||
configuredProjectRoot ? resolveEnvPath(configuredProjectRoot) : undefined,
|
||||
path.resolve(moduleRoot, ".."),
|
||||
path.resolve(process.cwd()),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
const projectRoot = configuredProjectRoot
|
||||
? path.resolve(configuredProjectRoot)
|
||||
? resolveEnvPath(configuredProjectRoot)
|
||||
: projectRoots.find((value) => fs.existsSync(path.join(value, ".local"))) || projectRoots[0]!;
|
||||
const scriptCandidates = [
|
||||
path.join(moduleRoot, "python", "depth_anything_runtime.py"),
|
||||
@@ -116,12 +117,12 @@ export function resolveDepthRuntimePaths(env: NodeJS.ProcessEnv = process.env):
|
||||
];
|
||||
const scriptPath = scriptCandidates.find((value) => fs.existsSync(value)) || scriptCandidates[0]!;
|
||||
const pythonCandidates = env.CANVAS_DEPTH_PYTHON?.trim()
|
||||
? [path.resolve(env.CANVAS_DEPTH_PYTHON.trim())]
|
||||
? [resolveEnvPath(env.CANVAS_DEPTH_PYTHON)]
|
||||
: process.platform === "win32"
|
||||
? [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "Scripts", "python.exe")]
|
||||
: [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "bin", "python")];
|
||||
const pythonPath = pythonCandidates[0]!;
|
||||
const hfHome = path.resolve(env.CANVAS_DEPTH_HF_HOME?.trim() || path.join(projectRoot, ".local", "cache", "huggingface"));
|
||||
const hfHome = resolveEnvPath(env.CANVAS_DEPTH_HF_HOME?.trim() || path.join(projectRoot, ".local", "cache", "huggingface"));
|
||||
const modelId = env.CANVAS_DEPTH_MODEL_ID?.trim() || DEFAULT_MODEL_ID;
|
||||
const modelCacheDir = path.join(hfHome, "hub", `models--${modelId.replaceAll("/", "--")}`);
|
||||
const workerExists = fs.existsSync(scriptPath);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import type { Request, RequestHandler, Response } from "express";
|
||||
|
||||
import type { LocalRuntimeModule } from "../local-runtime.js";
|
||||
import { resolveEnvPath } from "./runtime-env-path.js";
|
||||
|
||||
const DEFAULT_MODEL_ID = "lllyasviel/Annotators";
|
||||
const MAX_INPUT_BYTES = 12 * 1024 * 1024;
|
||||
@@ -103,12 +104,12 @@ export function resolveLineartRuntimePaths(env: NodeJS.ProcessEnv = process.env)
|
||||
const moduleRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const configuredProjectRoot = env.CANVAS_PROJECT_ROOT?.trim();
|
||||
const projectRoots = [
|
||||
configuredProjectRoot ? path.resolve(configuredProjectRoot) : undefined,
|
||||
configuredProjectRoot ? resolveEnvPath(configuredProjectRoot) : undefined,
|
||||
path.resolve(moduleRoot, ".."),
|
||||
path.resolve(process.cwd()),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
const projectRoot = configuredProjectRoot
|
||||
? path.resolve(configuredProjectRoot)
|
||||
? resolveEnvPath(configuredProjectRoot)
|
||||
: projectRoots.find((value) => fs.existsSync(path.join(value, ".local"))) || projectRoots[0]!;
|
||||
const scriptCandidates = [
|
||||
path.join(moduleRoot, "python", "lineart_runtime.py"),
|
||||
@@ -116,12 +117,12 @@ export function resolveLineartRuntimePaths(env: NodeJS.ProcessEnv = process.env)
|
||||
];
|
||||
const scriptPath = scriptCandidates.find((value) => fs.existsSync(value)) || scriptCandidates[0]!;
|
||||
const pythonCandidates = env.CANVAS_DEPTH_PYTHON?.trim()
|
||||
? [path.resolve(env.CANVAS_DEPTH_PYTHON.trim())]
|
||||
? [resolveEnvPath(env.CANVAS_DEPTH_PYTHON)]
|
||||
: process.platform === "win32"
|
||||
? [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "Scripts", "python.exe")]
|
||||
: [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "bin", "python")];
|
||||
const pythonPath = pythonCandidates[0]!;
|
||||
const hfHome = path.resolve(env.CANVAS_LINEART_HF_HOME?.trim() || env.CANVAS_DEPTH_HF_HOME?.trim() || path.join(projectRoot, ".local", "cache", "huggingface"));
|
||||
const hfHome = resolveEnvPath(env.CANVAS_LINEART_HF_HOME?.trim() || env.CANVAS_DEPTH_HF_HOME?.trim() || path.join(projectRoot, ".local", "cache", "huggingface"));
|
||||
const modelId = env.CANVAS_LINEART_MODEL_ID?.trim() || DEFAULT_MODEL_ID;
|
||||
const modelCacheDir = path.join(hfHome, "hub", `models--${modelId.replaceAll("/", "--")}`);
|
||||
const workerExists = fs.existsSync(scriptPath);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import type { Request, RequestHandler, Response } from "express";
|
||||
|
||||
import type { LocalRuntimeModule } from "../local-runtime.js";
|
||||
import { resolveEnvPath } from "./runtime-env-path.js";
|
||||
|
||||
const DEFAULT_MODEL_ID = "lllyasviel/Annotators";
|
||||
const DEFAULT_BACKEND = "openpose-body" as const;
|
||||
@@ -108,12 +109,12 @@ export function resolvePoseRuntimePaths(env: NodeJS.ProcessEnv = process.env): P
|
||||
const moduleRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const configuredProjectRoot = env.CANVAS_PROJECT_ROOT?.trim();
|
||||
const projectRoots = [
|
||||
configuredProjectRoot ? path.resolve(configuredProjectRoot) : undefined,
|
||||
configuredProjectRoot ? resolveEnvPath(configuredProjectRoot) : undefined,
|
||||
path.resolve(moduleRoot, ".."),
|
||||
path.resolve(process.cwd()),
|
||||
].filter((value): value is string => Boolean(value));
|
||||
const projectRoot = configuredProjectRoot
|
||||
? path.resolve(configuredProjectRoot)
|
||||
? resolveEnvPath(configuredProjectRoot)
|
||||
: projectRoots.find((value) => fs.existsSync(path.join(value, ".local"))) || projectRoots[0]!;
|
||||
const scriptCandidates = [
|
||||
path.join(moduleRoot, "python", "pose_runtime.py"),
|
||||
@@ -121,12 +122,12 @@ export function resolvePoseRuntimePaths(env: NodeJS.ProcessEnv = process.env): P
|
||||
];
|
||||
const scriptPath = scriptCandidates.find((value) => fs.existsSync(value)) || scriptCandidates[0]!;
|
||||
const pythonCandidates = env.CANVAS_POSE_PYTHON?.trim()
|
||||
? [path.resolve(env.CANVAS_POSE_PYTHON.trim())]
|
||||
? [resolveEnvPath(env.CANVAS_POSE_PYTHON)]
|
||||
: process.platform === "win32"
|
||||
? [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "Scripts", "python.exe")]
|
||||
: [path.join(projectRoot, ".local", "depth-anything-v2", "venv", "bin", "python")];
|
||||
const pythonPath = pythonCandidates[0]!;
|
||||
const hfHome = path.resolve(
|
||||
const hfHome = resolveEnvPath(
|
||||
env.CANVAS_POSE_HF_HOME?.trim()
|
||||
|| env.CANVAS_LINEART_HF_HOME?.trim()
|
||||
|| env.CANVAS_DEPTH_HF_HOME?.trim()
|
||||
@@ -278,7 +279,7 @@ export class PoseEstimationWorker {
|
||||
|| typeof value.modelId !== "string"
|
||||
|| value.device !== "cpu"
|
||||
|| value.backend !== DEFAULT_BACKEND
|
||||
|| !isPositiveInteger(value.personCount)
|
||||
|| !isNonNegativeInteger(value.personCount)
|
||||
|| typeof value.pngBase64 !== "string"
|
||||
|| !value.pngBase64) {
|
||||
pending.reject(new PoseRuntimeError("pose_response_invalid", "本机姿态响应无效", 500));
|
||||
@@ -355,3 +356,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function isPositiveInteger(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
function isNonNegativeInteger(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
||||
}
|
||||
|
||||
12
canvas-agent/src/modules/runtime-env-path.ts
Normal file
12
canvas-agent/src/modules/runtime-env-path.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Resolve env-configured filesystem paths without mangling Windows absolutes on POSIX hosts.
|
||||
* `path.resolve("C:\\foo")` on macOS/Linux incorrectly joins cwd, which breaks overrides and tests.
|
||||
*/
|
||||
export function resolveEnvPath(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
if (/^[A-Za-z]:[\\/]/.test(trimmed) || trimmed.startsWith("\\\\")) return trimmed;
|
||||
return path.resolve(trimmed);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ async function boundedText(response: Response, limit: number) {
|
||||
}
|
||||
|
||||
function parseResult(value: Record<string, unknown>): LocalPoseResult {
|
||||
if (value.mimeType !== "image/png" || !isPositiveInteger(value.width) || !isPositiveInteger(value.height) || typeof value.modelId !== "string" || value.device !== "cpu" || value.backend !== "openpose-body" || !isPositiveInteger(value.personCount) || typeof value.pngBase64 !== "string") {
|
||||
if (value.mimeType !== "image/png" || !isPositiveInteger(value.width) || !isPositiveInteger(value.height) || typeof value.modelId !== "string" || value.device !== "cpu" || value.backend !== "openpose-body" || !isNonNegativeInteger(value.personCount) || typeof value.pngBase64 !== "string") {
|
||||
throw new LocalRuntimeClientError("runtime_response_invalid", "本机姿态结果无效");
|
||||
}
|
||||
const bytes = bytesFromBase64(value.pngBase64, "runtime_response_invalid", "本机姿态结果无效");
|
||||
@@ -207,3 +207,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function isPositiveInteger(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
function isNonNegativeInteger(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user