gadget/gadget-drone/src/tools/plan/common.ts
2026-05-11 07:08:18 -04:00

65 lines
1.9 KiB
TypeScript

// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0
import path from "node:path";
import { formatError, type IToolError } from "@gadget/ai";
import type { AiToolbox } from "../toolbox.ts";
import { getProjectRoot, toolError } from "../system/common.ts";
export interface ResolvedPlanPath {
inputPath: string;
absolutePath: string;
displayPath: string;
}
export function getGadgetDir(toolbox: AiToolbox): string | undefined {
const projectRoot = getProjectRoot(toolbox);
if (!projectRoot) return undefined;
return path.resolve(projectRoot, ".gadget");
}
export function resolvePlanPath(
toolbox: AiToolbox,
inputPath: string,
): ResolvedPlanPath | string {
const gadgetDir = getGadgetDir(toolbox);
if (!gadgetDir) {
return toolError({
code: "OPERATION_NOT_ALLOWED",
message: "No active project workspace is configured for plan file tools.",
recoveryHint: "Run this tool during an active Agent work order in Plan mode.",
});
}
const trimmedPath = inputPath.trim();
if (!trimmedPath) {
return toolError({
code: "MISSING_PARAMETER",
message: "File path must not be empty.",
parameter: "path",
recoveryHint: "Provide a valid file path within the .gadget directory.",
});
}
const absolutePath = path.isAbsolute(trimmedPath)
? path.resolve(trimmedPath)
: path.resolve(gadgetDir, trimmedPath);
const relative = path.relative(gadgetDir, absolutePath);
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
return {
inputPath: trimmedPath,
absolutePath,
displayPath: relative || ".",
};
}
return toolError({
code: "SECURITY_VIOLATION",
message: `Path is outside the .gadget directory: ${trimmedPath}`,
parameter: "path",
recoveryHint: "Use a relative path inside the project's .gadget directory.",
});
}