Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,33 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
import { botIdDropdown } from '../common/props';
export const createConversationAction = createAction({
auth: codyAuth,
name: 'create_conversation',
displayName: 'Create Conversation',
description: 'Creates a new conversation with a bot.',
props: {
bot_id: botIdDropdown,
name: Property.ShortText({
displayName: 'Conversation Name',
description: 'The name for the new conversation.',
required: true,
}),
document_ids: Property.Array({
displayName: 'Document IDs (Focus Mode)',
description:
"A list of document IDs to limit the bot's knowledge base for this conversation.",
required: false,
}),
},
async run(context) {
const { bot_id, name, document_ids } = context.propsValue;
const apiKey = context.auth;
const docIds = document_ids as string[] | undefined;
return await codyClient.createConversation(apiKey, bot_id, name, docIds);
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
import { folderIdDropdown } from '../common/props';
export const createDocumentFromText = createAction({
auth: codyAuth,
name: 'create_document_from_text',
displayName: 'Create Document From Text',
description: 'Upload text content to create a new document within the Cody knowledge base.',
props: {
folder_id: folderIdDropdown,
name: Property.ShortText({
displayName: 'Document Name',
description: 'The name or title of the new document.',
required: true,
}),
content: Property.LongText({
displayName: 'Content',
description: 'The text or HTML content to be added to the document. Max 768 KB.',
required: true,
}),
},
async run(context) {
const { folder_id, name, content } = context.propsValue;
const apiKey = context.auth;
return await codyClient.createDocument(apiKey, name, folder_id, content);
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
export const findBotAction = createAction({
auth: codyAuth,
name: 'find_bot',
displayName: 'Find Bot',
description: 'Finds a bot based on its name.',
props: {
name: Property.ShortText({
displayName: 'Bot Name',
description: 'The name of the bot to search for. The search is case-insensitive and partial.',
required: true,
})
},
async run(context) {
const { name } = context.propsValue;
const apiKey = context.auth;
const bots = await codyClient.listBots(apiKey, name);
// The API returns an array of matching bots.
// We will return the full array to the user.
return {
found: bots.length > 0,
bots: bots,
};
},
});

View File

@@ -0,0 +1,41 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
import { botIdDropdown } from '../common/props';
export const findConversationAction = createAction({
auth: codyAuth,
name: 'find_conversation',
displayName: 'Find Conversation',
description: 'Finds a conversation based on its name and/or the bot it belongs to.',
props: {
bot_id: {
...botIdDropdown,
required: false,
},
name: Property.ShortText({
displayName: 'Conversation Name',
description: 'The name of the conversation to search for (partial match).',
required: false,
})
},
async run(context) {
const { bot_id, name } = context.propsValue;
const apiKey = context.auth;
if (!bot_id && !name) {
throw new Error("To find a conversation, please provide a Bot or a Conversation Name to search.");
}
const conversations = await codyClient.listConversations(apiKey, {
botId: bot_id,
keyword: name,
});
return {
found: conversations.length > 0,
conversations: conversations,
};
},
});

View File

@@ -0,0 +1,25 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
import { conversationIdDropdown } from '../common/props';
export const sendMessageAction = createAction({
auth: codyAuth,
name: 'send_message',
displayName: 'Send Message',
description: 'Send your message and receive the AI-generated response.',
props: {
conversation_id: conversationIdDropdown,
content: Property.LongText({
displayName: 'Message',
description: 'The message to send to the conversation. (Max 2000 characters)',
required: true,
}),
},
async run(context) {
const { conversation_id, content } = context.propsValue;
const apiKey = context.auth;
return await codyClient.sendMessage(apiKey, conversation_id, content);
},
});

View File

@@ -0,0 +1,45 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { codyAuth } from '../..';
import { codyClient } from '../common/client';
import { folderIdDropdown } from '../common/props';
import mime from 'mime-types';
export const uploadFileAction = createAction({
auth: codyAuth,
name: 'upload_file',
displayName: 'Upload File to Knowledge Base',
description: 'Add a file directly into a specific folder in the knowledge base.',
props: {
folder_id: folderIdDropdown,
file: Property.File({
displayName: 'File',
description: 'The file to upload (e.g., txt, md, rtf, pdf, ppt, docx).',
required: true,
})
},
async run(context) {
const { folder_id, file } = context.propsValue;
const apiKey = context.auth;
// Step 1: Determine Content-Type from filename using the mime-types library
const contentType = mime.lookup(file.filename) || 'application/octet-stream';
// Step 2: Get a Signed URL from the Cody API
const { url: signedUrl, key } = await codyClient.getSignedUrl(
apiKey,
file.filename,
contentType
);
// Step 3: Upload the actual file data to the signed URL
await codyClient.uploadFileToS3(signedUrl, file.data, contentType);
// Step 4: Finalize the document creation in Cody
await codyClient.createDocumentFromFile(apiKey, folder_id, key);
return {
success: true,
message: `File '${file.filename}' uploaded successfully and is being processed.`
};
},
});

View File

@@ -0,0 +1,234 @@
import { HttpMethod, httpClient, HttpRequest, AuthenticationType } from "@activepieces/pieces-common";
import { AppConnectionValueForAuthProperty } from "@activepieces/pieces-framework";
import { codyAuth } from "../..";
export const CODY_BASE_URL = "https://getcody.ai/api/v1";
export interface CodyFolder {
id: string;
name: string;
}
export interface CodyListFoldersResponse {
data: CodyFolder[];
}
export interface CodyCreateDocumentResponse {
data: {
id: string;
name: string;
status: string;
content_url: string;
folder_id: string;
created_at: number;
}
}
// New interfaces for file upload
export interface CodySignedUrlResponse {
data: {
url: string;
key: string;
}
}
export interface CodyConversation {
id: string;
name: string;
}
export interface CodyListConversationsResponse {
data: CodyConversation[];
}
export interface CodyMessageResponse {
data: {
id: string;
content: string;
conversation_id: string;
machine: boolean;
failed_responding: boolean;
flagged: boolean;
created_at: number;
}
}
export interface CodyBot {
id: string;
name: string;
}
export interface CodyListBotsResponse {
data: CodyBot[];
}
export interface CodyCreateConversationResponse {
data: CodyConversation;
}
export const codyClient = {
async listFolders({secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>): Promise<CodyFolder[]> {
const response = await httpClient.sendRequest<CodyListFoldersResponse>({
method: HttpMethod.GET,
url: `${CODY_BASE_URL}/folders`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
});
return response.body.data;
},
async createDocument(
{secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>,
name: string,
folderId: string,
content: string
): Promise<CodyCreateDocumentResponse> {
const request: HttpRequest = {
method: HttpMethod.POST,
url: `${CODY_BASE_URL}/documents`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
body: {
name,
folder_id: folderId,
content,
},
};
const response = await httpClient.sendRequest<CodyCreateDocumentResponse>(request);
return response.body;
},
// New function to get the signed URL
async getSignedUrl({secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>, fileName: string, contentType: string): Promise<CodySignedUrlResponse['data']> {
const response = await httpClient.sendRequest<CodySignedUrlResponse>({
method: HttpMethod.POST,
url: `${CODY_BASE_URL}/uploads/signed-url`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
body: {
file_name: fileName,
content_type: contentType
}
});
return response.body.data;
},
// New function to upload the file buffer to S3
async uploadFileToS3(signedUrl: string, fileBuffer: Buffer, contentType: string): Promise<void> {
await httpClient.sendRequest({
method: HttpMethod.PUT,
url: signedUrl,
body: fileBuffer,
headers: {
'Content-Type': contentType,
},
});
},
// New function to finalize the document creation
async createDocumentFromFile({secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>, folderId: string, key: string): Promise<void> {
await httpClient.sendRequest({
method: HttpMethod.POST,
// This endpoint was in your first documentation dump.
url: `${CODY_BASE_URL}/documents/file`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
body: {
folder_id: folderId,
key: key,
},
});
},
async listConversations(
{secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>,
params?: { botId?: string; keyword?: string }
): Promise<CodyConversation[]> {
const queryParams: Record<string, string> = {};
if (params?.botId) {
queryParams['bot_id'] = params.botId;
}
if (params?.keyword) {
queryParams['keyword'] = params.keyword;
}
const response = await httpClient.sendRequest<CodyListConversationsResponse>({
method: HttpMethod.GET,
url: `${CODY_BASE_URL}/conversations`,
queryParams: queryParams,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
});
return response.body.data;
},
// New function to send a message
async sendMessage({secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>, conversationId: string, content: string): Promise<CodyMessageResponse> {
const response = await httpClient.sendRequest<CodyMessageResponse>({
method: HttpMethod.POST,
url: `${CODY_BASE_URL}/messages`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
body: {
conversation_id: conversationId,
content: content,
}
});
return response.body;
},
async listBots({secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>, keyword?: string): Promise<CodyBot[]> { // Add optional keyword parameter
const queryParams: Record<string, string> = {};
if (keyword) {
queryParams['keyword'] = keyword;
}
const response = await httpClient.sendRequest<CodyListBotsResponse>({
method: HttpMethod.GET,
url: `${CODY_BASE_URL}/bots`,
queryParams: queryParams, // Add queryParams to the request
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
});
return response.body.data;
},
// New function to create a conversation
async createConversation(
{secret_text}: AppConnectionValueForAuthProperty<typeof codyAuth>,
botId: string,
name: string,
documentIds?: string[]
): Promise<CodyCreateConversationResponse> {
const response = await httpClient.sendRequest<CodyCreateConversationResponse>({
method: HttpMethod.POST,
url: `${CODY_BASE_URL}/conversations`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secret_text,
},
body: {
bot_id: botId,
name: name,
document_ids: documentIds,
}
});
return response.body;
},
};

View File

@@ -0,0 +1,122 @@
import { Property } from '@activepieces/pieces-framework';
import { codyClient } from './client';
import { codyAuth } from '../..';
export const folderIdDropdown = Property.Dropdown({
auth: codyAuth,
displayName: 'Folder',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Cody AI account first.',
options: [],
};
}
try {
// Note: Comment removed as the endpoint is now confirmed.
const folders = await codyClient.listFolders(auth);
if (folders.length === 0) {
return {
disabled: true,
options: [],
placeholder: "No folders found. Please create a folder in Cody first.",
};
}
return {
disabled: false,
options: folders.map((folder) => ({
label: folder.name,
value: folder.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: "Error listing folders. Check connection or API key permissions.",
};
}
},
});
export const conversationIdDropdown = Property.Dropdown({
displayName: 'Conversation',
auth: codyAuth,
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Cody AI account first.',
options: [],
};
}
try {
const conversations = await codyClient.listConversations(auth);
if (conversations.length === 0) {
return {
disabled: true,
options: [],
placeholder: "No conversations found. Please create one in Cody first.",
};
}
return {
disabled: false,
options: conversations.map((convo) => ({
label: convo.name,
value: convo.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: "Error listing conversations.",
};
}
},
});
// Add the new dropdown for bots
export const botIdDropdown = Property.Dropdown({
auth: codyAuth,
displayName: 'Bot',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your Cody AI account first.',
options: [],
};
}
try {
const bots = await codyClient.listBots(auth);
if (bots.length === 0) {
return {
disabled: true,
options: [],
placeholder: "No bots found. Please create one in Cody first.",
};
}
return {
disabled: false,
options: bots.map((bot) => ({
label: bot.name,
value: bot.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: "Error listing bots.",
};
}
},
});