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:
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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.`
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
@@ -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.",
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user