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,290 @@
import { claudeAuth } from '../../index';
import { createAction, Property } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import Anthropic from '@anthropic-ai/sdk';
import { TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources';
import Ajv from 'ajv';
import mime from 'mime-types';
import { modelOptions } from '../common/common';
export const extractStructuredDataAction = createAction({
auth: claudeAuth,
name: 'extract-structured-data',
displayName: 'Extract Structured Data',
description: 'Extract structured data from provided text,image or PDF.',
props: {
model: Property.StaticDropdown({
displayName: 'Model',
required: true,
description:
'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.',
defaultValue: 'claude-3-haiku-20240307',
options: {
disabled: false,
options: modelOptions,
},
}),
text: Property.LongText({
displayName: 'Text',
description: 'Text to extract structured data from.',
required: false,
}),
image: Property.File({
displayName: 'Image/PDF',
description: 'Image or PDF to extract structured data from.',
required: false,
}),
prompt: Property.LongText({
displayName: 'Guide Prompt',
description: 'Prompt to guide the AI.',
defaultValue: 'Extract the following data from the provided data.',
required: false,
}),
mode: Property.StaticDropdown<'simple' | 'advanced'>({
displayName: 'Data Schema Type',
description: 'For complex schema, you can use advanced mode.',
required: true,
defaultValue: 'simple',
options: {
disabled: false,
options: [
{ label: 'Simple', value: 'simple' },
{ label: 'Advanced', value: 'advanced' },
],
},
}),
schema: Property.DynamicProperties({
auth: claudeAuth,
displayName: 'Data Definition',
required: true,
refreshers: ['mode'],
props: async (propsValue) => {
const mode = propsValue['mode'] as unknown as 'simple' | 'advanced';
if (mode === 'advanced') {
return {
fields: Property.Json({
displayName: 'JSON Schema',
description:
'Learn more about JSON Schema here: https://json-schema.org/learn/getting-started-step-by-step',
required: true,
defaultValue: {
type: 'object',
properties: {
name: {
type: 'string',
},
age: {
type: 'number',
},
},
required: ['name'],
},
}),
};
}
return {
fields: Property.Array({
displayName: 'Data Definition',
required: true,
properties: {
name: Property.ShortText({
displayName: 'Name',
description:
'Provide the name of the value you want to extract from the unstructured text. The name should be unique and short. ',
required: true,
}),
description: Property.LongText({
displayName: 'Description',
description:
'Brief description of the data, this hints for the AI on what to look for',
required: false,
}),
type: Property.StaticDropdown({
displayName: 'Data Type',
description: 'Type of parameter.',
required: true,
defaultValue: 'string',
options: {
disabled: false,
options: [
{ label: 'Text', value: 'string' },
{ label: 'Number', value: 'number' },
{ label: 'Boolean', value: 'boolean' },
],
},
}),
isRequired: Property.Checkbox({
displayName: 'Fail if Not present?',
required: true,
defaultValue: false,
}),
},
}),
};
},
}),
maxTokens: Property.Number({
displayName: 'Maximum Tokens',
required: false,
description:
"The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)",
}),
},
async run(context) {
const { model, text, image, schema, prompt, maxTokens } =
context.propsValue;
if (!text && !image) {
throw new Error('Please provide text or image/PDF to extract data from.');
}
let params: AIFunctionArgumentDefinition;
if (context.propsValue.mode === 'advanced') {
const ajv = new Ajv();
const isValidSchema = ajv.validateSchema(schema);
if (!isValidSchema) {
throw new Error(
JSON.stringify({
message: 'Invalid JSON schema',
errors: ajv.errors,
})
);
}
params = schema['fields'] as AIFunctionArgumentDefinition;
} else {
params = {
type: 'object',
properties: (
schema['fields'] as Array<{
name: string;
description?: string;
type: string;
isRequired: boolean;
}>
).reduce((acc, field) => {
acc[field.name] = {
type: field.type,
description: field.description,
};
return acc;
}, {} as Record<string, { type: string; description?: string }>),
required: (
schema['fields'] as Array<{
name: string;
description?: string;
type: string;
isRequired: boolean;
}>
)
.filter((field) => field.isRequired)
.map((field) => field.name),
};
}
const anthropic = new Anthropic({
apiKey: context.auth.secret_text,
});
const messages: Anthropic.Messages.MessageParam[] = [
{
role: 'user',
content:
prompt ??
'Use optical character recognition (OCR) to extract from provided data.',
},
];
if (!isNil(text) && text !== '') {
messages.push({
role: 'user',
content: text,
});
}
if (image) {
const mediaType = image.extension
? mime.lookup(image.extension)
: 'image/jpeg';
if (mediaType === 'application/pdf') {
messages.push({
role: 'user',
content: [
{
type: 'document',
source: {
type: 'base64',
media_type: 'application/pdf',
data: image.base64,
},
},
],
});
} else {
messages.push({
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: mediaType as
| 'image/jpeg'
| 'image/png'
| 'image/gif'
| 'image/webp',
data: image.base64,
},
},
],
});
}
}
const response = await anthropic.messages.create({
model: model,
messages,
tools: [
{
name: 'extract_structured_data',
description: 'Extract the following data from the provided data.',
input_schema: params,
},
],
tool_choice: { type: 'tool', name: 'extract_structured_data' },
max_tokens: maxTokens ?? 2000,
});
const toolCallsResponse = response.content.filter(
(choice): choice is ToolUseBlock => choice.type === 'tool_use'
);
const toolCall = toolCallsResponse[0];
const choices = response.content
.filter((choice): choice is TextBlock => choice.type === 'text')
.map((choice: TextBlock) => ({
content: choice.text,
role: 'assistant',
}));
const args = toolCall.input;
if (isNil(args)) {
throw new Error(
JSON.stringify({
message:
choices[0].content ??
'Failed to extract structured data from the input.',
})
);
}
return args;
},
});
type AIFunctionArgumentDefinition = {
type: 'object';
properties?: unknown | null;
required?: string[];
[k: string]: unknown;
};

View File

@@ -0,0 +1,243 @@
import {
createAction,
DynamicPropsValue,
Property,
} from '@activepieces/pieces-framework';
import Anthropic from '@anthropic-ai/sdk';
import mime from 'mime-types';
import { claudeAuth } from '../..';
import { TextBlock } from '@anthropic-ai/sdk/resources';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
import { billingIssueMessage, modelOptions, unauthorizedMessage } from '../common/common';
const DEFAULT_TOKENS_FOR_THINKING_MODE = 1024;
export const askClaude = createAction({
auth: claudeAuth,
name: 'ask_claude',
displayName: 'Ask Claude',
description: 'Ask Claude anything you want!',
props: {
model: Property.StaticDropdown({
displayName: 'Model',
required: true,
description:
'The model which will generate the completion. Some models are suitable for natural language tasks, others specialize in code.',
defaultValue: 'claude-3-haiku-20240307',
options: {
disabled: false,
options: modelOptions,
},
}),
systemPrompt: Property.LongText({
displayName: 'System Prompt',
required: false,
defaultValue: "You're a helpful assistant.",
}),
temperature: Property.Number({
displayName: 'Temperature',
required: false,
description:
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
}),
maxTokens: Property.Number({
displayName: 'Maximum Tokens',
required: false,
description:
"The maximum number of tokens to generate. Requests can use up to 2,048 or 4,096 tokens shared between prompt and completion, don't set the value to maximum and leave some tokens for the input. The exact limit varies by model. (One token is roughly 4 characters for normal English text)",
}),
prompt: Property.LongText({
displayName: 'Question',
required: true,
}),
image: Property.File({
displayName: 'Image (URL)',
required: false,
description: 'URL of image to be used as input for the model.',
}),
roles: Property.Json({
displayName: 'Roles',
required: false,
defaultValue: [],
description: `Array of roles to specify more accurate response.Please check [guide to Input Messages](https://docs.anthropic.com/en/api/messages-examples#vision).`,
}),
thinkingMode: Property.Checkbox({
displayName: 'Extended Thinking Mode',
required: false,
defaultValue: false,
description:
'Uses claude 3.7 sonnet enhanced reasoning capabilities for complex tasks.',
}),
thinkingModeParams: Property.DynamicProperties({
auth: claudeAuth,
displayName: '',
refreshers: ['thinkingMode'],
required: false,
props: async ({ auth, thinkingMode }) => {
if (!auth || !thinkingMode) return {};
const props: DynamicPropsValue = {};
props['budgetTokens'] = Property.Number({
displayName: 'Budget Tokens',
required: true,
defaultValue: DEFAULT_TOKENS_FOR_THINKING_MODE,
description:
'This parameter determines the maximum number of tokens Claude is allowed to use for its internal reasoning process.Your budget tokens must always be less than the max tokens specified.',
});
return props;
},
}),
},
async run({ auth, propsValue }) {
await propsValidation.validateZod(propsValue, {
temperature: z.number().min(0).max(1.0).optional(),
});
const anthropic = new Anthropic({
apiKey: auth.secret_text,
});
let billingIssue = false;
let unauthorized = false;
let model = 'claude-3-haiku-20240307';
if (propsValue.model) {
model = propsValue.model;
}
let temperature = 0.5;
if (propsValue.temperature) {
temperature = Number(propsValue.temperature);
}
let maxTokens = 1000;
if (propsValue.maxTokens) {
maxTokens = Number(propsValue.maxTokens);
}
let systemPrompt = 'You are a helpful assistant.';
if (propsValue.systemPrompt) {
systemPrompt = propsValue.systemPrompt;
}
type Content =
| { type: 'text'; text: string }
| {
type: 'image';
source: { type: 'base64'; media_type: string; data: string };
};
const rolesArray = propsValue.roles
? (propsValue.roles as unknown as Array<Content>)
: [];
const rolesEnum = ['user', 'assistant'];
const roles = rolesArray.map((item: any) => {
if (!rolesEnum.includes(item.role)) {
throw new Error('The only available roles are: [user, assistant]');
}
return item;
});
const defaultMimeType = 'image/jpeg';
roles.unshift({
role: 'user',
content: [
{
type: 'text',
text: propsValue['prompt'],
},
...(propsValue.image
? [
{
type: 'image',
source: {
type: 'base64',
media_type: propsValue.image.extension
? mime.lookup(propsValue.image.extension) || defaultMimeType
: defaultMimeType,
data: propsValue.image.base64,
},
},
]
: []),
],
});
const maxRetries = 4;
let retries = 0;
let response: string | undefined;
while (retries < maxRetries) {
try {
if (propsValue.thinkingMode) {
const budgetTokens = propsValue.thinkingModeParams
? propsValue.thinkingModeParams['budgetTokens']
: 1024;
const req = await anthropic.messages.create({
model: 'claude-3-7-sonnet-20250219',
max_tokens: maxTokens,
system: systemPrompt,
thinking: {
type: 'enabled',
budget_tokens: budgetTokens ?? DEFAULT_TOKENS_FOR_THINKING_MODE,
},
messages: roles,
});
response = req.content
.filter((block) => block.type === 'text')[0]
.text.trim();
} else {
const req = await anthropic?.messages.create({
model: model,
max_tokens: maxTokens,
temperature: temperature,
system: systemPrompt,
messages: roles,
});
response = (req?.content[0] as TextBlock).text?.trim();
}
break; // Break out of the loop if the request is successful
} catch (e: any) {
if (e?.type?.includes('rate_limit_error')) {
billingIssue = true;
if (retries + 1 === maxRetries) {
throw e;
}
// Calculate the time delay for the next retry using exponential backoff
const delay = Math.pow(6, retries) * 1000;
console.log(`Retrying in ${delay} milliseconds...`);
await sleep(delay); // Wait for the calculated delay
retries++;
break;
} else {
if (e?.error?.type?.includes('not_found_error')) {
unauthorized = true;
throw e;
}
const new_error = e as {
type: string;
error: {
type: string;
message: string;
};
};
throw e;
// throw {
// error: new_error.error.message,
// };
}
}
}
if (billingIssue) {
throw new Error(billingIssueMessage);
}
if (unauthorized) {
throw new Error(unauthorizedMessage);
}
return response;
},
});
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,25 @@
export const baseUrl = 'https://api.anthropic.com/v1';
export const billingIssueMessage = `Error Occurred: 429 \n
1. Ensure that you have enough tokens on your Anthropic platform. \n
2. Generate a new API key. \n
3. Attempt the process again. \n
For guidance, visit: https://console.anthropic.com/settings/plans`;
export const unauthorizedMessage = `Error Occurred: 401 \n
Ensure that your API key is valid. \n
`;
export const modelOptions = [
{value:'claude-opus-4-5-20251101',label:'Claude 4.5 Opus'},
{ value: 'claude-sonnet-4-5-20250929', label: 'Claude 4.5 Sonnet' },
{ value: 'claude-haiku-4-5-20251001', label: 'Claude 4.5 Haiku' },
{ value: 'claude-opus-4-1-20250805', label: 'Claude 4.1 Opus' },
{ value: 'claude-sonnet-4-20250514', label: 'Claude 4 Sonnet' },
{ value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' },
{ value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku' },
]