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,52 @@
|
||||
import { propsValidation } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { PromptHubClient } from '../common/client';
|
||||
import { getProjectHeadProps, getProjectHeadSchema, sanitizeVariables } from '../common/props';
|
||||
import { prompthubAuth } from '../..';
|
||||
|
||||
export const getProjectHead = createAction({
|
||||
name: 'get_project_head',
|
||||
displayName: 'Get Project Head',
|
||||
description: 'Get the production-ready version of a PromptHub project (typically the last commit on master/main branch). Useful for integrating prompts into your application.',
|
||||
props: getProjectHeadProps,
|
||||
auth: prompthubAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
await propsValidation.validateZod(propsValue, getProjectHeadSchema);
|
||||
const client = new PromptHubClient(auth.secret_text);
|
||||
const q: Record<string, any> = {};
|
||||
|
||||
if (propsValue['branch']) {
|
||||
q['branch'] = propsValue['branch'];
|
||||
}
|
||||
|
||||
if (propsValue['fallback'] !== undefined) {
|
||||
q['fallback'] = propsValue['fallback'] ? 1 : 0;
|
||||
}
|
||||
|
||||
const vars = sanitizeVariables(propsValue['variables'] ?? {});
|
||||
for (const [k, v] of Object.entries(vars)) {
|
||||
q[`variables[${encodeURIComponent(k)}]`] = encodeURIComponent(String(v));
|
||||
}
|
||||
|
||||
const result = await client.getProjectHead(propsValue['projectId'], q);
|
||||
const data = result?.data ?? result;
|
||||
|
||||
return {
|
||||
id: data?.id,
|
||||
provider: data?.provider,
|
||||
model: data?.model,
|
||||
prompt: data?.prompt,
|
||||
system_message: data?.system_message,
|
||||
formatted_request: data?.formatted_request,
|
||||
hash: data?.hash,
|
||||
commit_title: data?.commit_title,
|
||||
commit_description: data?.commit_description,
|
||||
variables: data?.variables,
|
||||
project: data?.project,
|
||||
branch: data?.branch,
|
||||
configuration: data?.configuration,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { propsValidation } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { PromptHubClient } from '../common/client';
|
||||
import { listProjectsProps, listProjectsSchema } from '../common/props';
|
||||
import { prompthubAuth } from '../..';
|
||||
|
||||
export const listProjects = createAction({
|
||||
name: 'list_projects',
|
||||
displayName: 'List Projects',
|
||||
description: 'List PromptHub projects for a team. Returns information about each project\'s head revision and groups.',
|
||||
props: listProjectsProps,
|
||||
auth: prompthubAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
await propsValidation.validateZod(propsValue, listProjectsSchema);
|
||||
const client = new PromptHubClient(auth.secret_text);
|
||||
const result = await client.listProjects(propsValue['teamId'], {
|
||||
group: propsValue['group'],
|
||||
model: propsValue['model'],
|
||||
provider: propsValue['provider'],
|
||||
});
|
||||
|
||||
const data = result?.data ?? result;
|
||||
return Array.isArray(data)
|
||||
? data.map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
head: p.head ? {
|
||||
provider: p.head.provider,
|
||||
model: p.head.model,
|
||||
prompt: p.head.prompt,
|
||||
system_message: p.head.system_message,
|
||||
formatted_request: p.head.formatted_request,
|
||||
hash: p.head.hash,
|
||||
commit_title: p.head.commit_title,
|
||||
commit_description: p.head.commit_description,
|
||||
variables: p.head.variables,
|
||||
branch: p.head.branch,
|
||||
configuration: p.head.configuration,
|
||||
} : null,
|
||||
groups: p.groups || [],
|
||||
}))
|
||||
: data;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { propsValidation } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { PromptHubClient } from '../common/client';
|
||||
import { runPromptProps, runPromptSchema, sanitizeVariables } from '../common/props';
|
||||
import { prompthubAuth } from '../..';
|
||||
|
||||
export const runPrompt = createAction({
|
||||
name: 'run_prompt',
|
||||
displayName: 'Run Prompt',
|
||||
description: 'Run a PromptHub project with optional variables, branch/hash, and chat payload',
|
||||
props: runPromptProps,
|
||||
auth: prompthubAuth,
|
||||
async run({ auth, propsValue }) {
|
||||
await propsValidation.validateZod(propsValue, runPromptSchema);
|
||||
const client = new PromptHubClient(auth.secret_text);
|
||||
const body: Record<string, any> = {};
|
||||
if (propsValue['branch']) body['branch'] = propsValue['branch'];
|
||||
if (propsValue['hash']) body['hash'] = propsValue['hash'];
|
||||
if (propsValue['variables']) body['variables'] = sanitizeVariables(propsValue['variables']);
|
||||
if (propsValue['messages']) body['messages'] = propsValue['messages'];
|
||||
if (propsValue['prompt']) body['prompt'] = propsValue['prompt'];
|
||||
if (propsValue['metadata']) body['metadata'] = propsValue['metadata'];
|
||||
const timeoutMs = propsValue['timeoutSeconds'] ? propsValue['timeoutSeconds'] * 1000 : undefined;
|
||||
const result = await client.runProject(propsValue['projectId'], body, timeoutMs);
|
||||
return result?.data ?? result;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { httpClient, HttpMethod, HttpRequest, AuthenticationType } from '@activepieces/pieces-common';
|
||||
|
||||
export interface PromptHubClientOptions {
|
||||
baseUrl?: string;
|
||||
timeoutMs?: number;
|
||||
maxRetries?: number;
|
||||
initialBackoffMs?: number;
|
||||
}
|
||||
|
||||
export class PromptHubClient {
|
||||
private readonly baseUrl: string;
|
||||
private readonly timeoutMs: number;
|
||||
private readonly maxRetries: number;
|
||||
private readonly initialBackoffMs: number;
|
||||
|
||||
constructor(private readonly apiKey: string, options?: PromptHubClientOptions) {
|
||||
this.baseUrl = (options?.baseUrl ?? 'https://app.prompthub.us/api/v1').replace(/\/$/, '');
|
||||
this.timeoutMs = options?.timeoutMs ?? 30000;
|
||||
this.maxRetries = options?.maxRetries ?? 3;
|
||||
this.initialBackoffMs = options?.initialBackoffMs ?? 500;
|
||||
}
|
||||
|
||||
async validateToken(): Promise<boolean> {
|
||||
try {
|
||||
await this.get('/me');
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
if (e?.statusCode === 401 || e?.statusCode === 403) return false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async listProjects(teamId: number, query?: Record<string, string | number | boolean | undefined>) {
|
||||
return this.get(`/teams/${teamId}/projects`, query);
|
||||
}
|
||||
|
||||
async getProjectHead(projectId: number, query?: Record<string, string | number | boolean | undefined>) {
|
||||
return this.get(`/projects/${projectId}/head`, query);
|
||||
}
|
||||
|
||||
async runProject(projectId: number, body: any, timeoutMs?: number) {
|
||||
return this.post(`/projects/${projectId}/run`, body, timeoutMs);
|
||||
}
|
||||
|
||||
private async get(path: string, query?: Record<string, string | number | boolean | undefined>) {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
const req: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url,
|
||||
queryParams: this.buildQuery(query),
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey,
|
||||
},
|
||||
timeout: this.timeoutMs,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
};
|
||||
return this.sendWithRetry(req);
|
||||
}
|
||||
|
||||
private async post(path: string, body: unknown, timeoutOverrideMs?: number) {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
const req: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url,
|
||||
body,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey,
|
||||
},
|
||||
timeout: timeoutOverrideMs ?? this.timeoutMs,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
};
|
||||
return this.sendWithRetry(req);
|
||||
}
|
||||
|
||||
private async sendWithRetry(request: HttpRequest): Promise<any> {
|
||||
let attempt = 0;
|
||||
let lastError: any;
|
||||
while (attempt <= this.maxRetries) {
|
||||
try {
|
||||
const res = await httpClient.sendRequest<any>(request);
|
||||
const status = res.status ?? 200;
|
||||
if (status >= 200 && status < 300) {
|
||||
return res.body;
|
||||
}
|
||||
const error = new Error(`PromptHub API error ${status}`) as any;
|
||||
error.statusCode = status;
|
||||
error.body = res.body;
|
||||
throw error;
|
||||
} catch (err: any) {
|
||||
lastError = err;
|
||||
const status = err?.statusCode ?? err?.status;
|
||||
const isRateLimit = status === 429;
|
||||
const isAuth = status === 401 || status === 403;
|
||||
const isServer = status >= 500 && status < 600;
|
||||
if (isAuth) {
|
||||
err.message = 'Unauthorized or forbidden. Check PromptHub token and permissions (team/project access).';
|
||||
throw err;
|
||||
}
|
||||
if (!(isRateLimit || isServer)) {
|
||||
throw err;
|
||||
}
|
||||
if (attempt === this.maxRetries) {
|
||||
break;
|
||||
}
|
||||
const backoff = this.exponentialBackoffWithJitter(attempt);
|
||||
await this.sleep(backoff);
|
||||
attempt++;
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
private buildQuery(query?: Record<string, string | number | boolean | undefined>): Record<string, string> | undefined {
|
||||
if (!query) return undefined;
|
||||
const out: Record<string, string> = {};
|
||||
Object.entries(query).forEach(([k, v]) => {
|
||||
if (v === undefined || v === null) return;
|
||||
out[k] = String(v);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
private exponentialBackoffWithJitter(attempt: number): number {
|
||||
const base = this.initialBackoffMs * Math.pow(2, attempt);
|
||||
const jitter = Math.floor(Math.random() * this.initialBackoffMs);
|
||||
return base + jitter;
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import z, { ZodTypeAny } from 'zod';
|
||||
|
||||
export const listProjectsProps = {
|
||||
teamId: Property.Number({
|
||||
displayName: 'Team ID',
|
||||
description: 'The ID of the team. Can be found in your browser\'s URL bar when viewing Team Settings, or use current_team_id from /me endpoint.',
|
||||
required: true
|
||||
}),
|
||||
group: Property.ShortText({
|
||||
displayName: 'Group Name',
|
||||
description: 'Filter projects from a specific group. Must be URL-encoded if the group name contains spaces.',
|
||||
required: false
|
||||
}),
|
||||
model: Property.ShortText({
|
||||
displayName: 'Model',
|
||||
description: 'Filter projects where the head uses a specific model (e.g., gpt-4, claude-2, etc.).',
|
||||
required: false,
|
||||
}),
|
||||
provider: Property.StaticDropdown({
|
||||
displayName: 'Provider',
|
||||
description: 'Filter projects where the head uses a specific model provider.',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'OpenAI', value: 'OpenAI' },
|
||||
{ label: 'Anthropic', value: 'Anthropic' },
|
||||
{ label: 'Azure', value: 'Azure' },
|
||||
{ label: 'Google', value: 'Google' },
|
||||
{ label: 'Amazon', value: 'Amazon' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const getProjectHeadProps = {
|
||||
projectId: Property.Number({
|
||||
displayName: 'Project ID',
|
||||
description: 'The ID of the project. Can be found in your browser\'s URL bar when viewing that project.',
|
||||
required: true
|
||||
}),
|
||||
branch: Property.ShortText({
|
||||
displayName: 'Branch',
|
||||
description: 'Use the head of a specific branch. Defaults to your project\'s master/main branch. Must be URL-encoded if the branch name contains spaces or special characters.',
|
||||
required: false
|
||||
}),
|
||||
fallback: Property.Checkbox({
|
||||
displayName: 'Use Fallback',
|
||||
description: 'When enabled, any placeholders not provided in variables will fall back to your Project/Team defaults. Your variable overrides always take precedence.',
|
||||
required: false
|
||||
}),
|
||||
variables: Property.Object({
|
||||
displayName: 'Variables',
|
||||
description: 'Key-value pairs to override placeholders in the prompt. Both keys and values will be URL-encoded automatically.',
|
||||
required: false
|
||||
}),
|
||||
};
|
||||
|
||||
export const runPromptProps = {
|
||||
projectId: Property.Number({
|
||||
displayName: 'Project ID',
|
||||
description: 'The ID of the project to run.',
|
||||
required: true
|
||||
}),
|
||||
branch: Property.ShortText({
|
||||
displayName: 'Branch',
|
||||
description: 'The branch name to run from (defaults to main/master).',
|
||||
required: false
|
||||
}),
|
||||
hash: Property.ShortText({
|
||||
displayName: 'Hash',
|
||||
description: 'Specific commit hash to run from.',
|
||||
required: false
|
||||
}),
|
||||
variables: Property.Object({
|
||||
displayName: 'Variables',
|
||||
description: 'Key-value pairs to pass as variables to the prompt.',
|
||||
required: false
|
||||
}),
|
||||
messages: Property.Array({
|
||||
displayName: 'Messages',
|
||||
description: 'Chat messages for chat-based projects.',
|
||||
required: false,
|
||||
properties: {
|
||||
role: Property.StaticDropdown({
|
||||
displayName: 'Role',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'system', value: 'system' },
|
||||
{ label: 'user', value: 'user' },
|
||||
{ label: 'assistant', value: 'assistant' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
content: Property.LongText({ displayName: 'Content', required: true }),
|
||||
},
|
||||
}),
|
||||
prompt: Property.LongText({
|
||||
displayName: 'Prompt',
|
||||
description: 'Override prompt text for the project.',
|
||||
required: false
|
||||
}),
|
||||
metadata: Property.Object({
|
||||
displayName: 'Metadata',
|
||||
description: 'Additional metadata to include with the run.',
|
||||
required: false
|
||||
}),
|
||||
timeoutSeconds: Property.Number({
|
||||
displayName: 'Timeout Seconds',
|
||||
description: 'Request timeout in seconds (max 600).',
|
||||
required: false
|
||||
}),
|
||||
};
|
||||
|
||||
export const listProjectsSchema: Record<string, ZodTypeAny> = {
|
||||
teamId: z.number().int().positive(),
|
||||
group: z.string().optional(),
|
||||
|
||||
};
|
||||
|
||||
export const getProjectHeadSchema: Record<string, ZodTypeAny> = {
|
||||
projectId: z.number().int().positive(),
|
||||
branch: z.string().optional(),
|
||||
variables: z.record(z.any()).optional(),
|
||||
fallback: z.boolean().optional(),
|
||||
};
|
||||
|
||||
export const runPromptSchema: Record<string, ZodTypeAny> = {
|
||||
projectId: z.number().int().positive(),
|
||||
branch: z.string().optional(),
|
||||
hash: z.string().optional(),
|
||||
variables: z.record(z.any()).optional(),
|
||||
messages: z.array(z.object({
|
||||
role: z.enum(['system', 'user', 'assistant']),
|
||||
content: z.string(),
|
||||
})).optional(),
|
||||
prompt: z.string().optional(),
|
||||
metadata: z.record(z.any()).optional(),
|
||||
timeoutSeconds: z.number().int().positive().max(600).optional(),
|
||||
};
|
||||
|
||||
export function sanitizeVariables(vars: Record<string, unknown>): Record<string, string | number | boolean | null> {
|
||||
const out: Record<string, string | number | boolean | null> = {};
|
||||
for (const [k, v] of Object.entries(vars)) {
|
||||
if (v === undefined) continue;
|
||||
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null) {
|
||||
out[k] = v;
|
||||
} else if (typeof v === 'object') {
|
||||
out[k] = JSON.stringify(v);
|
||||
} else {
|
||||
out[k] = String(v);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user