Files
PaperClipAI/packages/shared/src/validators/chat-channels.ts
T
DottaandPaperclip f329b24ee1 feat: give tasks durable project-optional workspaces (#15749)
Give tasks durable company-scoped workspace bindings independent of project assignment, channel defaults and authorized repository preparation. Reuse verified input seeds and sparse output recovery while preserving trust, privacy and controller provisioning boundaries.

Full CI and E2E, Superagent, legacy checks and Greptile Apex 5/5 passed on reviewed head 753680b61c. All review findings are resolved; the PR records focused verification and historical acceptance limits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-12 20:45:18 -05:00

239 lines
8.0 KiB
TypeScript

import { z } from "zod";
import { multilineTextSchema } from "./text.js";
import { taskWorkspaceSelectionSchema } from "./task-workspace.js";
export const chatExecutionDefaultsSchema = z.object({
projectId: z.string().uuid().nullable().optional(),
workspace: taskWorkspaceSelectionSchema.nullable().optional(),
}).strict();
import {
CHAT_CONCURRENCY_POLICIES,
CHAT_DELIVERY_STATES,
CHAT_ENDPOINT_STATUSES,
CHAT_EVENT_KINDS,
CHAT_FILE_TRANSFER_PHASES,
CHAT_IDENTITY_LINK_STATUSES,
CHAT_PRINCIPAL_KINDS,
CHAT_PROVIDERS,
CHAT_PUBLICATION_STATES,
CHAT_RESOURCE_AVAILABILITIES,
} from "../types/chat-channels.js";
export const chatProviderSchema = z.enum(CHAT_PROVIDERS);
export const chatEndpointStatusSchema = z.enum(CHAT_ENDPOINT_STATUSES);
export const chatConcurrencyPolicySchema = z.enum(CHAT_CONCURRENCY_POLICIES);
export const chatEventKindSchema = z.enum(CHAT_EVENT_KINDS);
export const chatDeliveryStateSchema = z.enum(CHAT_DELIVERY_STATES);
export const chatPublicationStateSchema = z.enum(CHAT_PUBLICATION_STATES);
export const chatPrincipalKindSchema = z.enum(CHAT_PRINCIPAL_KINDS);
export const chatIdentityLinkStatusSchema = z.enum(CHAT_IDENTITY_LINK_STATUSES);
export const chatResourceAvailabilitySchema = z.enum(
CHAT_RESOURCE_AVAILABILITIES,
);
/**
* Microsoft emits Entra application and tenant identifiers in canonical UUID
* form in Bot Framework activities. Tenant aliases such as `common` or an
* `onmicrosoft.com` domain can be accepted by the token endpoint, but cannot
* be compared safely with the activity tenant id used by Paperclip's runtime
* fence. Normalize the UUIDs at the API boundary instead.
*/
export const microsoftTeamsCredentialIdSchema = z
.string()
.trim()
.uuid()
.refine((value) => value !== "00000000-0000-0000-0000-000000000000", {
message: "Microsoft Teams credential IDs cannot be the nil UUID",
})
.transform((value) => value.toLowerCase());
const chatEndpointCredentialsSchema = z
.record(z.string(), z.string().min(1))
.superRefine((credentials, ctx) => {
for (const key of ["clientId", "tenantId"] as const) {
const value = credentials[key];
if (value === undefined) continue;
const parsed = microsoftTeamsCredentialIdSchema.safeParse(value);
if (parsed.success) continue;
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [key],
message: `${key} must be a canonical Microsoft Entra UUID`,
});
}
})
.transform((credentials) => {
const normalized = { ...credentials };
for (const key of ["clientId", "tenantId"] as const) {
const value = normalized[key];
if (value !== undefined) {
normalized[key] = microsoftTeamsCredentialIdSchema.parse(value);
}
}
return normalized;
});
export const slackAppConfigurationSchema = z.object({
appName: z.string().trim().min(1, "Enter a Slack app name.").max(35),
botName: z.string().trim().min(1).max(80).regex(/^[a-z0-9._-]+$/, "Use lowercase letters, numbers, dots, hyphens, or underscores for the bot name."),
command: z.string().trim().min(2).max(32).regex(/^\/[a-z0-9_-]+$/, "Start the command with / and use lowercase letters, numbers, hyphens, or underscores."),
}).strict();
export const createChatEndpointSchema = z
.object({
provider: chatProviderSchema.exclude(["agentmail"]),
assignedAgentId: z.string().uuid(),
applicationId: z.string().uuid().optional(),
name: z.string().trim().min(1).max(160).optional(),
slackApp: slackAppConfigurationSchema.optional(),
slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(),
})
.strict()
.refine(value => (!value.slackApp && !value.slackSetupMethod) || value.provider === "slack", {
message: "Slack app details only apply to Slack connections", path: ["slackApp"],
});
export const updateChatEndpointSchema = z
.object({
executionDefaults: chatExecutionDefaultsSchema.nullable().optional(),
communicationInstructions: multilineTextSchema.pipe(z.string().trim().max(4000)).optional(),
slackApp: slackAppConfigurationSchema.optional(),
slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(),
allowDirectMessages: z.boolean().optional(),
requireAtMention: z.boolean().optional(),
allowGroupChats: z.boolean().optional(),
allowUnlinkedPeople: z.boolean().optional(),
})
.strict()
.refine((value) => Object.keys(value).length > 0, {
message: "At least one chat endpoint field is required",
});
export const photonProjectIdSchema = z.string().trim().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/);
export const photonLineIdSchema = z.string().trim().min(1).max(63).regex(/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/);
export const photonChannelConfigurationSchema = z.union([
z.object({ allocation: z.literal("dedicated").default("dedicated"), projectId: photonProjectIdSchema, lineId: photonLineIdSchema }).strict(),
z.object({ allocation: z.literal("shared"), projectId: photonProjectIdSchema }).strict(),
]);
export const inspectPhotonProjectSchema = z.object({
projectId: photonProjectIdSchema,
projectSecret: z.string().min(1).max(4096),
}).strict();
export const configureChatEndpointSchema = z
.object({
action: z.enum([
"configure",
"verify",
"pause",
"resume",
"reconnect",
"remove",
]),
credentials: chatEndpointCredentialsSchema.optional(),
photon: photonChannelConfigurationSchema.optional(),
})
.strict()
.superRefine((value, ctx) => {
if (
(value.credentials || value.photon) &&
value.action !== "configure" &&
value.action !== "reconnect"
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["credentials"],
message: `Credentials are not accepted for the ${value.action} action`,
});
}
});
export const replaceChatEndpointResourcesSchema = z
.object({
resources: z
.array(
z
.object({
id: z.string().uuid(),
enabled: z.boolean().optional(),
executionDefaults: chatExecutionDefaultsSchema.nullable().optional(),
})
.strict(),
)
.max(500),
})
.strict();
export const gitHubRepositoryPageQuerySchema = z.object({
limit: z.coerce.number().int().min(1).max(100).default(20),
offset: z.coerce.number().int().min(0).max(2_147_483_647).default(0),
search: z.string().trim().max(200).default(""),
}).strict();
export const toggleAllGitHubRepositoriesSchema = z.object({
enabled: z.boolean(),
}).strict();
export const publishChatCommentSchema = z
.object({
commentId: z.string().uuid(),
})
.strict();
export const publishChatBoardMessageSchema = z
.object({
body: multilineTextSchema.pipe(z.string().trim().min(1).max(100_000)),
idempotencyKey: z.string().trim().min(16).max(200),
attachmentIds: z
.array(z.string().uuid())
.max(20)
.refine((ids) => new Set(ids).size === ids.length, {
message: "Attachment ids must be unique",
})
.optional(),
})
.strict();
export const publishChatPublicationSchema = z.union([
publishChatCommentSchema,
publishChatBoardMessageSchema,
]);
export const resolveChatPublicationSchema = z
.object({
action: z.enum(["mark_delivered", "retry_anyway", "cancel"]),
fileTransfer: z
.object({
phase: z.enum(CHAT_FILE_TRANSFER_PHASES),
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
})
.strict()
.optional(),
})
.strict();
export const resolveChatActionSchema = z
.object({
action: z.enum(["mark_delivered", "retry_anyway", "cancel"]),
})
.strict();
export const createChatIdentityLinkIntentSchema = z
.object({
expiresInSeconds: z.number().int().min(300).max(86_400).default(1_800),
})
.strict()
.default({ expiresInSeconds: 1_800 });
export const confirmChatIdentityLinkSchema = z
.object({
token: z.string().min(32).max(4096),
})
.strict();
export const replayChatDeliverySchema = z.object({}).strict();
export const chatPublicEndpointIdSchema = z
.string()
.regex(/^[a-zA-Z0-9_-]{32,128}$/);