import type { ChannelAttachmentDto } from '@mosaicstack/types'; import { Transform, Type } from 'class-transformer'; import { IsNotEmpty, IsObject, IsOptional, IsString, IsUUID, MaxLength, ValidateNested, } from 'class-validator'; export class ChatRequestDto { @IsOptional() @IsUUID() conversationId?: string; @IsString() @MaxLength(10_000) content!: string; } export class ChatSocketMessageDto { @IsOptional() @IsUUID() conversationId?: string; @IsString() @MaxLength(10_000) content!: string; @IsOptional() @IsString() @MaxLength(255) provider?: string; @IsOptional() @IsString() @MaxLength(255) modelId?: string; @IsOptional() @IsUUID() agentId?: string; /** Validated channel attachment references; binary content is not embedded. */ attachments?: readonly ChannelAttachmentDto[]; } /** * Task Five, group 2 — the frozen pi-rpc `turn:send` selection triple. * * Each id is a required, non-empty, bounded string. There is no `@IsOptional` and no extra * field: under `forbidNonWhitelisted` an unknown selection key is rejected, and a missing id * fails `@IsString` (undefined is not a string) rather than silently passing. */ export class HarnessTurnSelectionDto { @IsString() @IsNotEmpty() @MaxLength(255) harnessId!: string; @IsString() @IsNotEmpty() @MaxLength(255) providerId!: string; @IsString() @IsNotEmpty() @MaxLength(255) modelId!: string; } /** * Task Five, group 2 — the frozen wire contract for a pi-rpc `turn:send`. * * Validated through the production `ValidationPipe({ whitelist, forbidNonWhitelisted, transform })`: * a UUID conversation id; `content` trimmed then bounded to 1..10_000 characters (whitespace-only * collapses to empty and fails `@IsNotEmpty`); a nested `selection` object recursed with an * explicit `@Type` (a bare `@ValidateNested` is masked green by class-validator's empty-metadata * `unknownValue`); and a UUID-v4 idempotency key. No `provider`/`modelId`/`attachments` or other * authority field is declared, so `forbidNonWhitelisted` rejects every unknown top-level key. */ export class HarnessTurnSendDto { @IsUUID() conversationId!: string; @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value)) @IsString() @IsNotEmpty() @MaxLength(10_000) content!: string; @IsObject() @ValidateNested() @Type(() => HarnessTurnSelectionDto) selection!: HarnessTurnSelectionDto; @IsUUID('4') idempotencyKey!: string; }