mirror of https://github.com/openclaw/openclaw.git
184 lines
5.9 KiB
TypeScript
184 lines
5.9 KiB
TypeScript
import path from "node:path";
|
|
import { buildPluginConfigSchema, type OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
|
import { z } from "openclaw/plugin-sdk/zod";
|
|
|
|
export type OpenShellPluginConfig = {
|
|
mode?: "mirror" | "remote";
|
|
command?: string;
|
|
gateway?: string;
|
|
gatewayEndpoint?: string;
|
|
from?: string;
|
|
policy?: string;
|
|
providers?: string[];
|
|
gpu?: boolean;
|
|
autoProviders?: boolean;
|
|
remoteWorkspaceDir?: string;
|
|
remoteAgentWorkspaceDir?: string;
|
|
timeoutSeconds?: number;
|
|
};
|
|
|
|
export type ResolvedOpenShellPluginConfig = {
|
|
mode: "mirror" | "remote";
|
|
command: string;
|
|
gateway?: string;
|
|
gatewayEndpoint?: string;
|
|
from: string;
|
|
policy?: string;
|
|
providers: string[];
|
|
gpu: boolean;
|
|
autoProviders: boolean;
|
|
remoteWorkspaceDir: string;
|
|
remoteAgentWorkspaceDir: string;
|
|
timeoutMs: number;
|
|
};
|
|
|
|
const DEFAULT_COMMAND = "openshell";
|
|
const DEFAULT_MODE = "mirror";
|
|
const DEFAULT_SOURCE = "openclaw";
|
|
const DEFAULT_REMOTE_WORKSPACE_DIR = "/sandbox";
|
|
const DEFAULT_REMOTE_AGENT_WORKSPACE_DIR = "/agent";
|
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
|
|
function normalizeProviders(value: string[] | undefined): string[] {
|
|
const seen = new Set<string>();
|
|
const providers: string[] = [];
|
|
for (const entry of value ?? []) {
|
|
const normalized = entry.trim();
|
|
if (seen.has(normalized)) {
|
|
continue;
|
|
}
|
|
seen.add(normalized);
|
|
providers.push(normalized);
|
|
}
|
|
return providers;
|
|
}
|
|
|
|
const nonEmptyTrimmedString = (message: string) =>
|
|
z.string({ error: message }).trim().min(1, { error: message });
|
|
|
|
const OpenShellPluginConfigSchema = z.strictObject({
|
|
mode: z.enum(["mirror", "remote"], { error: "mode must be one of mirror, remote" }).optional(),
|
|
command: nonEmptyTrimmedString("command must be a non-empty string").optional(),
|
|
gateway: nonEmptyTrimmedString("gateway must be a non-empty string").optional(),
|
|
gatewayEndpoint: nonEmptyTrimmedString("gatewayEndpoint must be a non-empty string").optional(),
|
|
from: nonEmptyTrimmedString("from must be a non-empty string").optional(),
|
|
policy: nonEmptyTrimmedString("policy must be a non-empty string").optional(),
|
|
providers: z
|
|
.array(
|
|
z.string({ error: "providers must be an array of strings" }).trim().min(1, {
|
|
error: "providers must be an array of strings",
|
|
}),
|
|
{
|
|
error: "providers must be an array of strings",
|
|
},
|
|
)
|
|
.optional(),
|
|
gpu: z.boolean({ error: "gpu must be a boolean" }).optional(),
|
|
autoProviders: z.boolean({ error: "autoProviders must be a boolean" }).optional(),
|
|
remoteWorkspaceDir: nonEmptyTrimmedString(
|
|
"remoteWorkspaceDir must be a non-empty string",
|
|
).optional(),
|
|
remoteAgentWorkspaceDir: nonEmptyTrimmedString(
|
|
"remoteAgentWorkspaceDir must be a non-empty string",
|
|
).optional(),
|
|
timeoutSeconds: z
|
|
.number({ error: "timeoutSeconds must be a number >= 1" })
|
|
.min(1, { error: "timeoutSeconds must be a number >= 1" })
|
|
.optional(),
|
|
});
|
|
|
|
function formatOpenShellConfigIssue(issue: z.ZodIssue | undefined): string {
|
|
if (!issue) {
|
|
return "invalid config";
|
|
}
|
|
if (issue.code === "unrecognized_keys" && issue.keys.length > 0) {
|
|
return `unknown config key: ${issue.keys[0]}`;
|
|
}
|
|
if (issue.code === "invalid_type" && issue.path.length === 0) {
|
|
return "expected config object";
|
|
}
|
|
return issue.message;
|
|
}
|
|
|
|
function normalizeRemotePath(value: string | undefined, fallback: string): string {
|
|
const candidate = value ?? fallback;
|
|
const normalized = path.posix.normalize(candidate.trim() || fallback);
|
|
if (!normalized.startsWith("/")) {
|
|
throw new Error(`OpenShell remote path must be absolute: ${candidate}`);
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function createOpenShellPluginConfigSchema(): OpenClawPluginConfigSchema {
|
|
return buildPluginConfigSchema(OpenShellPluginConfigSchema, {
|
|
safeParse(value) {
|
|
if (value === undefined) {
|
|
return { success: true, data: undefined };
|
|
}
|
|
const parsed = OpenShellPluginConfigSchema.safeParse(value);
|
|
if (parsed.success) {
|
|
return { success: true, data: parsed.data };
|
|
}
|
|
return {
|
|
success: false,
|
|
error: {
|
|
issues: parsed.error.issues.map((issue) => ({
|
|
path: issue.path.filter((segment): segment is string | number => {
|
|
const kind = typeof segment;
|
|
return kind === "string" || kind === "number";
|
|
}),
|
|
message: formatOpenShellConfigIssue(issue),
|
|
})),
|
|
},
|
|
};
|
|
},
|
|
});
|
|
}
|
|
|
|
export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellPluginConfig {
|
|
if (value === undefined) {
|
|
return {
|
|
mode: DEFAULT_MODE,
|
|
command: DEFAULT_COMMAND,
|
|
gateway: undefined,
|
|
gatewayEndpoint: undefined,
|
|
from: DEFAULT_SOURCE,
|
|
policy: undefined,
|
|
providers: [],
|
|
gpu: false,
|
|
autoProviders: true,
|
|
remoteWorkspaceDir: DEFAULT_REMOTE_WORKSPACE_DIR,
|
|
remoteAgentWorkspaceDir: DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
|
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
};
|
|
}
|
|
|
|
const parsed = OpenShellPluginConfigSchema.safeParse(value);
|
|
if (!parsed.success) {
|
|
const message = formatOpenShellConfigIssue(parsed.error.issues[0]);
|
|
throw new Error(`Invalid openshell plugin config: ${message}`);
|
|
}
|
|
const cfg = parsed.data as OpenShellPluginConfig;
|
|
const mode = cfg.mode ?? DEFAULT_MODE;
|
|
return {
|
|
mode,
|
|
command: cfg.command ?? DEFAULT_COMMAND,
|
|
gateway: cfg.gateway,
|
|
gatewayEndpoint: cfg.gatewayEndpoint,
|
|
from: cfg.from ?? DEFAULT_SOURCE,
|
|
policy: cfg.policy,
|
|
providers: normalizeProviders(cfg.providers),
|
|
gpu: cfg.gpu ?? false,
|
|
autoProviders: cfg.autoProviders ?? true,
|
|
remoteWorkspaceDir: normalizeRemotePath(cfg.remoteWorkspaceDir, DEFAULT_REMOTE_WORKSPACE_DIR),
|
|
remoteAgentWorkspaceDir: normalizeRemotePath(
|
|
cfg.remoteAgentWorkspaceDir,
|
|
DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
|
|
),
|
|
timeoutMs:
|
|
typeof cfg.timeoutSeconds === "number"
|
|
? Math.floor(cfg.timeoutSeconds * 1000)
|
|
: DEFAULT_TIMEOUT_MS,
|
|
};
|
|
}
|