All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
132 lines
3.2 KiB
TypeScript
132 lines
3.2 KiB
TypeScript
import {
|
|
IsString,
|
|
IsNotEmpty,
|
|
IsEnum,
|
|
ValidateNested,
|
|
IsArray,
|
|
IsOptional,
|
|
ArrayNotEmpty,
|
|
ArrayMaxSize,
|
|
MaxLength,
|
|
IsIn,
|
|
Validate,
|
|
ValidatorConstraint,
|
|
ValidatorConstraintInterface,
|
|
ValidationArguments,
|
|
} from "class-validator";
|
|
import { Type } from "class-transformer";
|
|
import { AgentType } from "../../../spawner/types/agent-spawner.types";
|
|
import { GateProfileType } from "../../../coordinator/types/gate-config.types";
|
|
import { validateBranchName, validateRepositoryUrl } from "../../../git/git-validation.util";
|
|
|
|
/**
|
|
* Custom validator for git branch names
|
|
* Uses whitelist-based validation to prevent command injection
|
|
*/
|
|
@ValidatorConstraint({ name: "isValidBranchName", async: false })
|
|
export class IsValidBranchName implements ValidatorConstraintInterface {
|
|
validate(branchName: string, _args: ValidationArguments): boolean {
|
|
try {
|
|
validateBranchName(branchName);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
defaultMessage(args: ValidationArguments): string {
|
|
try {
|
|
validateBranchName(args.value as string);
|
|
return "Branch name is invalid";
|
|
} catch (error) {
|
|
return error instanceof Error ? error.message : "Branch name is invalid";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom validator for git repository URLs
|
|
* Prevents SSRF and command injection via dangerous protocols
|
|
*/
|
|
@ValidatorConstraint({ name: "isValidRepositoryUrl", async: false })
|
|
export class IsValidRepositoryUrl implements ValidatorConstraintInterface {
|
|
validate(repositoryUrl: string, _args: ValidationArguments): boolean {
|
|
try {
|
|
validateRepositoryUrl(repositoryUrl);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
defaultMessage(args: ValidationArguments): string {
|
|
try {
|
|
validateRepositoryUrl(args.value as string);
|
|
return "Repository URL is invalid";
|
|
} catch (error) {
|
|
return error instanceof Error ? error.message : "Repository URL is invalid";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Context DTO for agent spawn request
|
|
*/
|
|
export class AgentContextDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@Validate(IsValidRepositoryUrl)
|
|
repository!: string;
|
|
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
@Validate(IsValidBranchName)
|
|
branch!: string;
|
|
|
|
@IsArray()
|
|
@ArrayNotEmpty()
|
|
@ArrayMaxSize(50, { message: "workItems must contain at most 50 items" })
|
|
@IsString({ each: true })
|
|
@MaxLength(2000, { each: true, message: "Each work item must be at most 2000 characters" })
|
|
workItems!: string[];
|
|
|
|
@IsArray()
|
|
@IsOptional()
|
|
@ArrayMaxSize(20, { message: "skills must contain at most 20 items" })
|
|
@IsString({ each: true })
|
|
@MaxLength(200, { each: true, message: "Each skill must be at most 200 characters" })
|
|
skills?: string[];
|
|
}
|
|
|
|
/**
|
|
* Request DTO for spawning an agent
|
|
*/
|
|
export class SpawnAgentDto {
|
|
@IsString()
|
|
@IsNotEmpty()
|
|
taskId!: string;
|
|
|
|
@IsEnum(["worker", "reviewer", "tester"])
|
|
agentType!: AgentType;
|
|
|
|
@ValidateNested()
|
|
@Type(() => AgentContextDto)
|
|
context!: AgentContextDto;
|
|
|
|
@IsOptional()
|
|
@IsIn(["strict", "standard", "minimal", "custom"])
|
|
gateProfile?: GateProfileType;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
parentAgentId?: string;
|
|
}
|
|
|
|
/**
|
|
* Response DTO for spawn agent endpoint
|
|
*/
|
|
export class SpawnAgentResponseDto {
|
|
agentId!: string;
|
|
status!: "spawning" | "queued";
|
|
}
|