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