import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getLinearApi } from "@automate.ax/integration-contracts/linear" import { LINEAR_COMMENT_FIELDS } from "../lib/fragments" import { LINEAR_COMMENT_SCHEMA, LINEAR_PAGE_INFO_SCHEMA, LINEAR_PAGE_INPUT_SCHEMA, linearConnectionSchema, } from "@automate.ax/integration-contracts/linear" import { LINEAR_COMMENT_CREATE_ACCOUNT_OPTIONS, LINEAR_READ_ACCOUNT_OPTIONS, LINEAR_WRITE_ACCOUNT_OPTIONS, } from "../lib/scopes" const COMMENT_RESPONSE_SCHEMA = z.object({ comment: LINEAR_COMMENT_SCHEMA }) const COMMENT_CONNECTION_SCHEMA = linearConnectionSchema(LINEAR_COMMENT_SCHEMA) const COMMENT_PAGE_SCHEMA = z.object({ comments: LINEAR_COMMENT_SCHEMA.array(), pageInfo: LINEAR_PAGE_INFO_SCHEMA, }) const COMMENT_MUTATION_RESPONSE_SCHEMA = z.object({ commentMutation: z.object({ comment: LINEAR_COMMENT_SCHEMA, success: z.literal(true), }), }) const COMMENT_DELETE_RESPONSE_SCHEMA = z.object({ commentMutation: z.object({ entityId: z.string(), success: z.literal(true), }), }) /** Lists comments attached to one Linear issue. */ export const listLinearIssueComments = defineAction( "List Linear issue comments", ) .describe("Lists a cursor-paginated page of comments on one Linear issue.") .account("linear", LINEAR_READ_ACCOUNT_OPTIONS) .input( LINEAR_PAGE_INPUT_SCHEMA.extend({ /** UUID or human-readable identifier of the parent issue. */ issueId: z.string().min(1), /** Sort by creation time or most recent update. */ orderBy: z.enum(["createdAt", "updatedAt"]).prefault("createdAt"), }), ) .output(COMMENT_PAGE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { comments } = await getLinearApi(account.secret).graphql( `query LinearIssueComments( $first: Int! $after: String $includeArchived: Boolean $orderBy: PaginationOrderBy! $filter: CommentFilter! ) { comments( first: $first after: $after includeArchived: $includeArchived orderBy: $orderBy filter: $filter ) { nodes { ${LINEAR_COMMENT_FIELDS} } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } } }`, { responseSchema: z.object({ comments: COMMENT_CONNECTION_SCHEMA }), variables: { filter: { issue: { id: { eq: input.issueId } } }, first: input.limit, orderBy: input.orderBy, ...(input.after && { after: input.after }), ...(input.includeArchived !== undefined && { includeArchived: input.includeArchived, }), }, }, ) return { comments: comments.nodes, pageInfo: comments.pageInfo! } }) /** Gets one Linear comment by ID. */ export const getLinearComment = defineAction("Get Linear comment") .describe("Gets one Linear comment by ID.") .account("linear", LINEAR_READ_ACCOUNT_OPTIONS) .input( z.object({ /** Stable Linear comment ID. */ commentId: z.string().min(1), }), ) .output(LINEAR_COMMENT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getLinearApi(account.secret) .graphql( `query LinearComment($id: String!) { comment(id: $id) { ${LINEAR_COMMENT_FIELDS} } }`, { responseSchema: COMMENT_RESPONSE_SCHEMA, variables: { id: input.commentId }, }, ) .then(({ comment }) => comment), ) /** Creates a Markdown comment or threaded reply on a Linear issue. */ export const createLinearIssueComment = defineAction( "Create Linear issue comment", ) .describe("Creates a Markdown comment or threaded reply on a Linear issue.") .account("linear", LINEAR_COMMENT_CREATE_ACCOUNT_OPTIONS) .input( z.object({ /** Markdown comment body. */ body: z.string().min(1), /** Do not automatically subscribe the actor to issue activity. */ doNotSubscribeToIssue: z.boolean().optional(), /** UUID or human-readable identifier of the issue to comment on. */ issueId: z.string().min(1), /** Parent comment ID when creating a threaded reply. */ parentId: z.string().min(1).optional(), }), ) .output(LINEAR_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { commentMutation } = await getLinearApi(account.secret).graphql( `mutation LinearCommentCreate($input: CommentCreateInput!) { commentMutation: commentCreate(input: $input) { success comment { ${LINEAR_COMMENT_FIELDS} } } }`, { responseSchema: COMMENT_MUTATION_RESPONSE_SCHEMA, variables: { input }, }, ) return commentMutation.comment }) /** Updates the Markdown body of a Linear comment. */ export const updateLinearComment = defineAction("Update Linear comment") .describe("Updates a Linear comment and records its edited timestamp.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( z.object({ /** Replacement Markdown body. */ body: z.string().min(1), /** Comment ID to update. */ commentId: z.string().min(1), }), ) .output(LINEAR_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { commentMutation } = await getLinearApi(account.secret).graphql( `mutation LinearCommentUpdate($id: String!, $input: CommentUpdateInput!) { commentMutation: commentUpdate(id: $id, input: $input) { success comment { ${LINEAR_COMMENT_FIELDS} } } }`, { responseSchema: COMMENT_MUTATION_RESPONSE_SCHEMA, variables: { id: input.commentId, input: { body: input.body } }, }, ) return commentMutation.comment }) /** Resolves a Linear comment thread. */ export const resolveLinearComment = defineAction("Resolve Linear comment") .describe("Marks a Linear comment thread as resolved.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( z.object({ /** Root comment thread to resolve. */ commentId: z.string().min(1), /** Child comment that resolved the thread, when applicable. */ resolvingCommentId: z.string().min(1).optional(), }), ) .output(LINEAR_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { commentMutation } = await getLinearApi(account.secret).graphql( `mutation LinearCommentResolve( $id: String! $resolvingCommentId: String ) { commentMutation: commentResolve( id: $id resolvingCommentId: $resolvingCommentId ) { success comment { ${LINEAR_COMMENT_FIELDS} } } }`, { responseSchema: COMMENT_MUTATION_RESPONSE_SCHEMA, variables: { id: input.commentId, ...(input.resolvingCommentId && { resolvingCommentId: input.resolvingCommentId, }), }, }, ) return commentMutation.comment }) /** Reopens a resolved Linear comment thread. */ export const unresolveLinearComment = defineAction("Unresolve Linear comment") .describe("Clears the resolved state from a Linear comment thread.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input(z.object({ commentId: z.string().min(1) })) .output(LINEAR_COMMENT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { commentMutation } = await getLinearApi(account.secret).graphql( `mutation LinearCommentUnresolve($id: String!) { commentMutation: commentUnresolve(id: $id) { success comment { ${LINEAR_COMMENT_FIELDS} } } }`, { responseSchema: COMMENT_MUTATION_RESPONSE_SCHEMA, variables: { id: input.commentId }, }, ) return commentMutation.comment }) /** Deletes a Linear comment. */ export const deleteLinearComment = defineAction("Delete Linear comment") .describe("Permanently deletes a Linear comment by ID.") .account("linear", LINEAR_WRITE_ACCOUNT_OPTIONS) .input( z.object({ /** Stable Linear comment ID. */ commentId: z.string().min(1), }), ) .output( z.object({ /** ID of the deleted Linear comment. */ commentId: z.string(), }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { commentMutation } = await getLinearApi(account.secret).graphql( `mutation LinearCommentDelete($id: String!) { commentMutation: commentDelete(id: $id) { success entityId } }`, { responseSchema: COMMENT_DELETE_RESPONSE_SCHEMA, variables: { id: input.commentId }, }, ) return { commentId: commentMutation.entityId } })