import { KRISP_ACTION_ITEM_SCHEMA, KRISP_IMPORT_SCHEMA, KRISP_IMPORT_STATUS_SCHEMA, KRISP_MEETING_DETAIL_SCHEMA, KRISP_MEETING_SCHEMA, KRISP_PROFILE_SCHEMA, KRISP_TAG_SCHEMA, } from "@automate.ax/integration-contracts/krisp" import * as z from "zod" import { defineAction } from "../../automation/actions" import { getKrispActionApi } from "./lib" const KRISP_READ_ACCOUNT = { connections: [{ connectionMethodId: "api-key", requiredScope: "read" }], } as const const KRISP_WRITE_ACCOUNT = { connections: [{ connectionMethodId: "api-key", requiredScope: "write" }], } as const const MEETING_FIELDS = [ "title", "started_at", "duration", "status", "source", "tags", "ownership", ] as const const MEETING_DETAIL_FIELDS = [ ...MEETING_FIELDS, "meeting_type", "language", "participants", ] as const const LIST_MEETINGS_RESPONSE_SCHEMA = z.object({ meetings: KRISP_MEETING_SCHEMA.array(), nextCursor: z.string().nullable(), total: z.number().int().nonnegative(), }) const LIST_ACTION_ITEMS_RESPONSE_SCHEMA = z.object({ actionItems: KRISP_ACTION_ITEM_SCHEMA.array(), nextCursor: z.string().nullable(), total: z.number().int().nonnegative(), }) const LIST_ACTION_ITEMS_OUTPUT_SCHEMA = z.object({ items: KRISP_ACTION_ITEM_SCHEMA.array(), nextCursor: z.string().nullable(), total: z.number().int().nonnegative(), }) const LIST_TAGS_OUTPUT_SCHEMA = z.object({ items: KRISP_TAG_SCHEMA.array() }) const IMPORT_RECORDING_OUTPUT_SCHEMA = KRISP_IMPORT_SCHEMA.omit({ url: true }) /** Returns the Krisp user and workspace represented by the connected key. */ export const getKrispProfile = defineAction("Get Krisp profile") .describe( "Returns the Krisp user, team, and workspace for the connected key.", ) .account("krisp", KRISP_READ_ACCOUNT) .input(z.object({})) .output(KRISP_PROFILE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account }) => getKrispActionApi(account.secret).request("me", { responseSchema: KRISP_PROFILE_SCHEMA, }), ) /** Lists processed Krisp meetings visible to the connected user. */ export const listKrispMeetings = defineAction("List Krisp meetings") .describe( "Lists processed Krisp meetings owned by or shared directly with the connected user.", ) .account("krisp", KRISP_READ_ACCOUNT) .input( z.object({ cursor: z.string().min(1).optional(), from: z.iso.datetime({ offset: true }).optional(), includeParticipants: z.boolean().prefault(false), limit: z.number().int().min(1).max(100).prefault(20), order: z.enum(["newest", "oldest"]).prefault("newest"), ownership: z.enum(["all", "mine", "shared"]).prefault("all"), query: z.string().min(1).optional(), sortBy: z.enum(["date", "duration", "lastModified"]).prefault("date"), tagIds: z.string().min(1).array().optional(), to: z.iso.datetime({ offset: true }).optional(), }), ) .output(LIST_MEETINGS_RESPONSE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => { const { includeParticipants, query, sortBy, tagIds, ...queryOptions } = input return getKrispActionApi(account.secret).request("meetings", { query: { ...queryOptions, fields: [ ...MEETING_FIELDS, ...(includeParticipants ? (["participants"] as const) : []), ], q: query, sortBy: sortBy === "lastModified" ? "last_modified" : sortBy, tags: tagIds, }, responseSchema: LIST_MEETINGS_RESPONSE_SCHEMA, }) }) /** Gets one processed Krisp meeting with optional transcript and notes. */ export const getKrispMeeting = defineAction("Get Krisp meeting") .describe( "Returns one processed Krisp meeting and can include its transcript and generated notes.", ) .account("krisp", KRISP_READ_ACCOUNT) .input( z.object({ includeNotes: z.boolean().prefault(false), includeTranscript: z.boolean().prefault(false), meetingId: z.string().min(1), }), ) .output(KRISP_MEETING_DETAIL_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => getKrispActionApi(account.secret).request( `meetings/${encodeURIComponent(input.meetingId)}`, { query: { fields: [ ...MEETING_DETAIL_FIELDS, ...(input.includeTranscript ? (["transcript"] as const) : []), ...(input.includeNotes ? (["notes"] as const) : []), ], }, responseSchema: KRISP_MEETING_DETAIL_SCHEMA, }, ), ) /** Lists extracted action items across visible Krisp meetings. */ export const listKrispActionItems = defineAction("List Krisp action items") .describe("Lists extracted action items across visible Krisp meetings.") .account("krisp", KRISP_READ_ACCOUNT) .input( z.object({ assignedToMe: z.boolean().optional(), completed: z.boolean().optional(), cursor: z.string().min(1).optional(), limit: z.number().int().min(1).max(200).prefault(50), }), ) .output(LIST_ACTION_ITEMS_OUTPUT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const result = await getKrispActionApi(account.secret).request( "action-items", { query: input, responseSchema: LIST_ACTION_ITEMS_RESPONSE_SCHEMA, }, ) return { items: result.actionItems, nextCursor: result.nextCursor, total: result.total, } }) /** Lists tags available to the connected Krisp user. */ export const listKrispTags = defineAction("List Krisp tags") .describe( "Lists up to 100 Krisp meeting tags, optionally filtered by prefix.", ) .account("krisp", KRISP_READ_ACCOUNT) .input(z.object({ query: z.string().min(1).optional() })) .output(LIST_TAGS_OUTPUT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ({ items: await getKrispActionApi(account.secret).request("tags", { query: { q: input.query }, responseSchema: KRISP_TAG_SCHEMA.array(), }), })) /** Uploads a recording to Krisp for transcription and note generation. */ export const importKrispRecording = defineAction("Import Krisp recording") .describe( "Uploads a recording to Krisp and returns an import ID for processing-status checks.", ) .account("krisp", KRISP_WRITE_ACCOUNT) .input( z.object({ durationSeconds: z.number().nonnegative().optional(), file: z.instanceof(File).refine((file) => file.size <= 1_000_000_000, { message: "Krisp recordings cannot exceed 1 GB.", }), language: z.string().min(1).optional(), title: z.string().min(1).optional(), }), ) .output(IMPORT_RECORDING_OUTPUT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const upload = await getKrispActionApi(account.secret).request("import", { body: { ...(input.durationSeconds !== undefined && { duration: input.durationSeconds, }), size: input.file.size, ...(input.language !== undefined && { language: input.language }), ...(input.title !== undefined && { title: input.title }), }, method: "POST", responseSchema: KRISP_IMPORT_SCHEMA, }) const uploadUrl = new URL(upload.url) if (uploadUrl.protocol !== "https:") { throw new Error("Krisp returned a non-HTTPS recording upload URL.") } const response = await fetch(uploadUrl, { body: input.file, method: "PUT" }) if (!response.ok) { throw new Error( `Krisp recording upload failed with status ${response.status}.`, ) } return { expiresAt: upload.expiresAt, importId: upload.importId } }) /** Returns the current state and resulting meeting ID for a Krisp import. */ export const getKrispImportStatus = defineAction("Get Krisp import status") .describe( "Returns the processing state and resulting meeting ID for a Krisp import.", ) .account("krisp", KRISP_READ_ACCOUNT) .input(z.object({ importId: z.string().min(1) })) .output(KRISP_IMPORT_STATUS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => getKrispActionApi(account.secret).request( `import/${encodeURIComponent(input.importId)}/status`, { responseSchema: KRISP_IMPORT_STATUS_SCHEMA }, ), )