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,28 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { skyvernAuth } from '../common/auth';
import { skyvernApiCall } from '../common/client';
export const cancelRunAction = createAction({
auth: skyvernAuth,
name: 'cancel-run',
displayName: 'Cancel Run',
description: 'Cancels a workflow or or task run by ID.',
props: {
runId: Property.ShortText({
displayName: 'Workflow/Task Run ID',
required: true,
}),
},
async run(context) {
const { runId } = context.propsValue;
const response = await skyvernApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/runs/${runId}/cancel`,
});
return response;
},
});

View File

@@ -0,0 +1,50 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { skyvernAuth } from '../common/auth';
import { skyvernApiCall } from '../common/client';
import { ListWorkflowResponse } from '../common/props';
export const findWorkflowAction = createAction({
auth: skyvernAuth,
name: 'find-workflow',
displayName: 'Find Workflow',
description: 'Finds workflow based on title.',
props: {
title: Property.ShortText({
displayName: 'Workflow Title',
required: true,
}),
},
async run(context) {
const { title } = context.propsValue;
let hasMore = true;
let page = 1;
const workflows = [];
do {
const response = await skyvernApiCall<ListWorkflowResponse[]>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/workflows',
query: {
page_size: 100,
page,
title,
},
});
if (isNil(response) || !Array.isArray(response)) break;
workflows.push(...response);
hasMore = response.length > 0;
page++;
} while (hasMore);
return {
found: workflows.length > 0,
result: workflows,
};
},
});

View File

@@ -0,0 +1,28 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { skyvernAuth } from '../common/auth';
import { skyvernApiCall } from '../common/client';
export const getRunAction = createAction({
auth: skyvernAuth,
name: 'get-run',
displayName: 'Get Workflow/Task Run',
description: 'Retrieves a workflow or task run by ID.',
props: {
runId: Property.ShortText({
displayName: 'Workflow/Task Run ID',
required: true,
}),
},
async run(context) {
const { runId } = context.propsValue;
const response = await skyvernApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/runs/${runId}`,
});
return response;
},
});

View File

@@ -0,0 +1,100 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { skyvernAuth } from '../common/auth';
import { skyvernApiCall } from '../common/client';
export const runAgentTaskAction = createAction({
auth: skyvernAuth,
name: 'run-agent-task',
displayName: 'Run Agent Task',
description: 'Runs task with specified prompt.',
props: {
prompt: Property.ShortText({
displayName: 'Prompt',
description: 'The goal or task description for Skyvern to accomplish.',
required: true,
}),
url: Property.ShortText({
displayName: 'URL',
description: 'The starting URL for the task.',
required: false,
}),
engine: Property.StaticDropdown({
displayName: 'Engine',
required: false,
options: {
disabled: false,
options: [
{ label: 'Skyvern 1.0', value: 'skyvern-1.0' },
{ label: 'Skyvern 2.0', value: 'skyvern-2.0' },
{ label: 'OpenAI CUA', value: 'openai-cua' },
{ label: 'Anthropic CUA', value: 'anthropic-cua' },
],
},
}),
webhookUrl: Property.ShortText({
displayName: 'Webhook Callback URL',
description: 'URL to send task status updates to after a run is finished.',
required: false,
}),
proxyLocation: Property.StaticDropdown({
displayName: 'Proxy Location',
required: false,
options: {
disabled: false,
options: [
{ label: 'Residential', value: 'RESIDENTIAL' },
{ label: 'Spain', value: 'RESIDENTIAL_ES' },
{ label: 'Ireland', value: 'RESIDENTIAL_IE' },
{ label: 'United Kingdom', value: 'RESIDENTIAL_GB' },
{ label: 'India', value: 'RESIDENTIAL_IN' },
{ label: 'Japan', value: 'RESIDENTIAL_JP' },
{ label: 'France', value: 'RESIDENTIAL_FR' },
{ label: 'Germany', value: 'RESIDENTIAL_DE' },
{ label: 'New Zealand', value: 'RESIDENTIAL_NZ' },
{ label: 'South Africa', value: 'RESIDENTIAL_ZA' },
{ label: 'Argentina', value: 'RESIDENTIAL_AR' },
{ label: 'ISP Proxy', value: 'RESIDENTIAL_ISP' },
{ label: 'California (US)', value: 'US-CA' },
{ label: 'New York (US)', value: 'US-NY' },
{ label: 'Texas (US)', value: 'US-TX' },
{ label: 'Florida (US)', value: 'US-FL' },
{ label: 'Washington (US)', value: 'US-WA' },
{ label: 'No Proxy', value: 'NONE' },
],
},
}),
maxSteps: Property.Number({
displayName: 'Max Steps',
required: false,
description:
'Maximum number of steps the task can take. Task will fail if it exceeds this number.',
}),
dataExtractionSchema: Property.Json({
displayName: 'Data Extraction Schema',
required: false,
description: 'The schema for data to be extracted from the webpage.',
}),
},
async run(context) {
const { prompt, proxyLocation, url, webhookUrl, dataExtractionSchema, maxSteps, engine } =
context.propsValue;
const response = await skyvernApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: '/run/tasks',
body: {
prompt,
url,
engine,
proxy_location: proxyLocation,
data_extraction_schema: dataExtractionSchema,
max_steps: maxSteps,
webhook_url: webhookUrl,
},
});
return response;
},
});

View File

@@ -0,0 +1,69 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { skyvernAuth } from '../common/auth';
import { skyvernApiCall } from '../common/client';
import { workflowId, workflowParams } from '../common/props';
export const runWorkflowAction = createAction({
auth: skyvernAuth,
name: 'run-workflow',
displayName: 'Run Workflow',
description: 'Runs the workflow.',
props: {
workflowId: workflowId,
title: Property.ShortText({
displayName: 'Workflow Run Title',
required: false,
description: 'The title for this workflow run.',
}),
proxyLocation: Property.StaticDropdown({
displayName: 'Proxy Location',
required: false,
options: {
disabled: false,
options: [
{ label: 'Residential', value: 'RESIDENTIAL' },
{ label: 'Spain', value: 'RESIDENTIAL_ES' },
{ label: 'Ireland', value: 'RESIDENTIAL_IE' },
{ label: 'United Kingdom', value: 'RESIDENTIAL_GB' },
{ label: 'India', value: 'RESIDENTIAL_IN' },
{ label: 'Japan', value: 'RESIDENTIAL_JP' },
{ label: 'France', value: 'RESIDENTIAL_FR' },
{ label: 'Germany', value: 'RESIDENTIAL_DE' },
{ label: 'New Zealand', value: 'RESIDENTIAL_NZ' },
{ label: 'South Africa', value: 'RESIDENTIAL_ZA' },
{ label: 'Argentina', value: 'RESIDENTIAL_AR' },
{ label: 'ISP Proxy', value: 'RESIDENTIAL_ISP' },
{ label: 'California (US)', value: 'US-CA' },
{ label: 'New York (US)', value: 'US-NY' },
{ label: 'Texas (US)', value: 'US-TX' },
{ label: 'Florida (US)', value: 'US-FL' },
{ label: 'Washington (US)', value: 'US-WA' },
{ label: 'No Proxy', value: 'NONE' },
],
},
}),
webhookUrl: Property.ShortText({
displayName: 'Webhook Callback URL',
required: false,
}),
parameters: workflowParams,
},
async run(context) {
const { workflowId, webhookUrl, proxyLocation, parameters } = context.propsValue;
const response = await skyvernApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/run/workflows`,
body: {
workflow_id: workflowId,
proxy_location: proxyLocation,
parameters,
webhook_url: webhookUrl,
},
});
return response;
},
});

View File

@@ -0,0 +1,24 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { PieceAuth } from '@activepieces/pieces-framework';
import { skyvernApiCall } from './client';
export const skyvernAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: `You can obtain your API key by navigating to [Settings](https://app.skyvern.com/settings).`,
required: true,
validate: async ({ auth }) => {
try {
await skyvernApiCall({
apiKey: auth as string,
method: HttpMethod.GET,
resourceUri: '/workflows',
});
return { valid: true };
} catch {
return {
valid: false,
error: 'Invalid API Key.',
};
}
},
});

View File

@@ -0,0 +1,48 @@
import {
httpClient,
HttpMessageBody,
HttpMethod,
HttpRequest,
QueryParams,
} from '@activepieces/pieces-common';
export type SkyvernApiCallParams = {
apiKey: string;
method: HttpMethod;
resourceUri: string;
query?: Record<string, string | number | string[] | undefined>;
body?: any;
};
export const BASE_URL = 'https://api.skyvern.com/v1';
export async function skyvernApiCall<T extends HttpMessageBody>({
apiKey,
method,
resourceUri,
query,
body,
}: SkyvernApiCallParams): Promise<T> {
const qs: QueryParams = {};
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== null && value !== undefined) {
qs[key] = String(value);
}
}
}
const request: HttpRequest = {
method,
url: BASE_URL + resourceUri,
headers: {
'x-api-key': apiKey,
},
queryParams: qs,
body,
};
const response = await httpClient.sendRequest<T>(request);
return response.body;
}

View File

@@ -0,0 +1,175 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { DynamicPropsValue, Property } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { skyvernApiCall } from './client';
import { skyvernAuth } from './auth';
export interface ListWorkflowResponse {
workflow_permanent_id: string;
title: string;
workflow_definition: {
parameters: {
parameter_type: string;
key: string;
workflow_parameter_type: string;
default_value: any;
}[];
};
}
export const workflowId = Property.Dropdown({
auth: skyvernAuth,
displayName: 'Workflow',
refreshers: [],
required: true,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
let hasMore = true;
let page = 1;
const workflows = [];
do {
const response = await skyvernApiCall<ListWorkflowResponse[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/workflows',
query: {
page_size: 100,
page,
},
});
if (isNil(response) || !Array.isArray(response)) break;
workflows.push(...response);
hasMore = response.length > 0;
page++;
} while (hasMore);
return {
disabled: false,
options: workflows.map((workflow) => ({
label: workflow.title,
value: workflow.workflow_permanent_id,
})),
};
},
});
export const workflowParams = Property.DynamicProperties({
auth: skyvernAuth,
displayName: 'Workflow Params',
refreshers: ['workflowId'],
required: false,
props: async ({ auth, workflowId }) => {
if (!auth || !workflowId) return {};
let hasMore = true;
let page = 1;
let matchedWorkflow: ListWorkflowResponse | undefined = undefined;
do {
const response = await skyvernApiCall<ListWorkflowResponse[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/workflows',
query: {
page_size: 100,
page,
},
});
if (isNil(response) || !Array.isArray(response)) break;
matchedWorkflow = response.find(
(workflow) => workflow.workflow_permanent_id === (workflowId as unknown as string),
);
if (matchedWorkflow) break;
hasMore = response.length > 0;
page++;
} while (hasMore);
if (isNil(matchedWorkflow)) return {};
const fields: DynamicPropsValue = {};
for (const field of matchedWorkflow.workflow_definition.parameters) {
if (field.parameter_type !== 'workflow') continue;
const { key, workflow_parameter_type, default_value: defaultValue } = field;
const required = isNil(defaultValue);
const displayName = key;
if (workflow_parameter_type === 'credential_id') {
const response = await skyvernApiCall<{ credential_id: string; name: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/credentials',
query: {
page_size: 100,
},
});
const credentials = response || [];
fields[field.key] = Property.StaticDropdown({
displayName,
required,
defaultValue,
options: {
disabled: false,
options: credentials.map((cred) => ({
label: cred.name,
value: cred.credential_id,
})),
},
});
continue;
}
switch (workflow_parameter_type) {
case 'string':
case 'file_url':
fields[field.key] = Property.ShortText({
displayName,
required,
defaultValue,
});
break;
case 'float':
case 'integer':
fields[field.key] = Property.Number({
displayName,
required,
defaultValue,
});
break;
case 'boolean':
fields[field.key] = Property.Checkbox({
displayName,
required,
defaultValue,
});
break;
case 'json':
fields[field.key] = Property.Json({
displayName,
required,
defaultValue,
});
break;
default:
break;
}
}
return fields;
},
});