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,43 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { AgentXAuth } from '../common/auth';
|
||||
import { AgentIdDropdown } from '../common/dropdown';
|
||||
import { makeRequest } from '../common/client';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
|
||||
|
||||
export const createConversationWithSingleAgent = createAction({
|
||||
auth: AgentXAuth,
|
||||
name: 'createConversationWithSingleAgent',
|
||||
displayName: 'Create Conversation With Single Agent',
|
||||
description: 'Create a new conversation with a specific Agent by ID.',
|
||||
props: {
|
||||
agentId: AgentIdDropdown,
|
||||
type: Property.StaticDropdown({
|
||||
displayName: "Conversation Type",
|
||||
description: "Choose the type of conversation",
|
||||
required: true,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: "Chat", value: "chat" },
|
||||
{ label: "Search", value: "search" },
|
||||
{ label: "Ecommerce", value: "ecommerce" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const { agentId, type } = propsValue;
|
||||
|
||||
const response = await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.POST,
|
||||
`/agents/${agentId}/conversations/new`,
|
||||
{
|
||||
type
|
||||
}
|
||||
)
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { createAction, Property } from "@activepieces/pieces-framework";
|
||||
import { makeRequest } from "../common/client";
|
||||
import { AgentXAuth } from "../common/auth";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
import { AgentIdDropdown, ConversationIdDropdown } from "../common/dropdown";
|
||||
|
||||
export const findConversation = createAction({
|
||||
auth: AgentXAuth,
|
||||
name: "find_conversation",
|
||||
displayName: "Find Conversation",
|
||||
description:
|
||||
"Looks up an existing conversation by Agent ID (optionally by conversation ID or Name).",
|
||||
props: {
|
||||
agentId: AgentIdDropdown,
|
||||
conversationId: Property.ShortText({
|
||||
displayName: "Conversation ID",
|
||||
description: "Search by exact Conversation ID.",
|
||||
required: false,
|
||||
}),
|
||||
conversationName: Property.ShortText({
|
||||
displayName: "Conversation Name,tittle or ID",
|
||||
description:
|
||||
"Search conversation by its name (derived from the first message).",
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run({ auth, propsValue }) {
|
||||
const { agentId, conversationId, conversationName } = propsValue;
|
||||
|
||||
const addName = (conversation: any) => ({
|
||||
...conversation,
|
||||
name:
|
||||
conversation.messages?.[0]?.text?.slice(0, 30) ||
|
||||
`Conversation ${conversation._id || conversation.id}`,
|
||||
});
|
||||
|
||||
|
||||
const conversations = await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.GET,
|
||||
`/agents/${agentId}/conversations`
|
||||
);
|
||||
|
||||
if (!Array.isArray(conversations)) {
|
||||
throw new Error(
|
||||
"Unexpected response from AgentX API: expected an array of conversations."
|
||||
);
|
||||
}
|
||||
|
||||
const conversationsWithName = conversations.map(addName);
|
||||
|
||||
if (conversationName) {
|
||||
return conversationsWithName.filter((c) =>
|
||||
c.name.toLowerCase().includes(conversationName.toLowerCase())
|
||||
);
|
||||
}
|
||||
if (conversationId) {
|
||||
return conversationsWithName.filter(
|
||||
(c) => c.id === conversationId || c._id === conversationId
|
||||
);
|
||||
}
|
||||
|
||||
return conversationsWithName;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { createAction, Property } from "@activepieces/pieces-framework";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
import { makeRequest } from "../common/client";
|
||||
import { AgentXAuth } from "../common/auth";
|
||||
import { AgentIdDropdown, ConversationIdDropdown } from "../common/dropdown";
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
text?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
type ConversationDetail = {
|
||||
id: string;
|
||||
messages: Message[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export const findMessage = createAction({
|
||||
auth: AgentXAuth,
|
||||
name: "find_message",
|
||||
displayName: "Find Message",
|
||||
description:
|
||||
"Searches for a specific message by ID (or searches text within messages) inside a conversation.",
|
||||
|
||||
props: {
|
||||
agentId: AgentIdDropdown,
|
||||
conversationId: ConversationIdDropdown,
|
||||
searchTerm: Property.ShortText({
|
||||
displayName: "Search Term",
|
||||
description:
|
||||
"Text to search for in message content. Leave blank if searching by Message ID.",
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run({ auth, propsValue }) {
|
||||
const { agentId, conversationId, searchTerm } = propsValue;
|
||||
|
||||
const conversation = await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.GET,
|
||||
`/agents/${agentId}/conversations/${conversationId}`
|
||||
) as ConversationDetail;
|
||||
|
||||
const allMessages = conversation?.messages || [];
|
||||
|
||||
if (!searchTerm) {
|
||||
return allMessages;
|
||||
}
|
||||
|
||||
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
||||
return allMessages.filter(
|
||||
(m: any) =>
|
||||
(m.text && m.text.toLowerCase().includes(lowerCaseSearchTerm)) ||
|
||||
(m.content && m.content.toLowerCase().includes(lowerCaseSearchTerm))
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createAction, Property } from "@activepieces/pieces-framework";
|
||||
import { makeRequest } from "../common/client";
|
||||
import { AgentXAuth } from "../common/auth";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
|
||||
export const searchAgents = createAction({
|
||||
auth: AgentXAuth,
|
||||
name: "search_agents",
|
||||
displayName: "Search Agents",
|
||||
description: "Find agents by name or ID using search filters.",
|
||||
props: {
|
||||
name: Property.ShortText({
|
||||
displayName: "Agent Name",
|
||||
description: "Search by agent name (partial matches allowed).",
|
||||
required: false,
|
||||
}),
|
||||
id: Property.ShortText({
|
||||
displayName: "Agent ID",
|
||||
description: "Search by exact Agent ID.",
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run({ auth, propsValue }) {
|
||||
const { name, id } = propsValue;
|
||||
|
||||
const agents = await makeRequest(auth.secret_text, HttpMethod.GET, "/agents");
|
||||
|
||||
if (!Array.isArray(agents)) {
|
||||
throw new Error("Unexpected response from AgentX API: expected an array of agents.");
|
||||
}
|
||||
|
||||
const filtered = agents.filter((agent: any) => {
|
||||
let matches = true;
|
||||
|
||||
if (id) {
|
||||
matches = matches && agent._id === id;
|
||||
}
|
||||
|
||||
if (name && agent.name) {
|
||||
matches = matches && agent.name.toLowerCase().includes(name.toLowerCase());
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { AgentXAuth } from '../common/auth';
|
||||
import { makeRequest } from '../common/client';
|
||||
import { HttpMethod } from '@activepieces/pieces-common';
|
||||
import { ConversationIdDropdown } from '../common/dropdown';
|
||||
import { AgentIdDropdown } from '../common/dropdown';
|
||||
|
||||
export const sendMessageToExistingConversation = createAction({
|
||||
auth: AgentXAuth,
|
||||
name: 'sendMessageToExistingConversation',
|
||||
displayName: 'Send Message to Existing Conversation',
|
||||
description: 'Send a message to an existing conversation with an agent.',
|
||||
props: {
|
||||
agentId: AgentIdDropdown,
|
||||
conversationId: ConversationIdDropdown,
|
||||
agentMode: Property.StaticDropdown({
|
||||
displayName: "Agent Mode",
|
||||
description: "Choose how the agent should respond",
|
||||
required: true,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: "Chat", value: "chat" },
|
||||
{ label: "Search", value: "search" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
message: Property.LongText({
|
||||
displayName: "Message",
|
||||
description: "The message you want to send to the agent.",
|
||||
required: true,
|
||||
}),
|
||||
context: Property.Number({
|
||||
displayName: "Memory Context",
|
||||
description:
|
||||
"Number of previous messages to include. 0 = as much as possible, 1 = only last message, etc.",
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run({ auth, propsValue }) {
|
||||
const { conversationId, agentMode, message, context } = propsValue;
|
||||
|
||||
const response = await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.POST,
|
||||
`/conversations/${conversationId}/message`,
|
||||
{
|
||||
conversationId,
|
||||
agentMode,
|
||||
message,
|
||||
context,
|
||||
}
|
||||
)
|
||||
return response;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { PieceAuth } from "@activepieces/pieces-framework";
|
||||
import { makeRequest } from "./client";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
|
||||
export const AgentXAuth = PieceAuth.SecretText({
|
||||
displayName: 'AgentX API Key',
|
||||
description: `**Enter your AgentX API Key**
|
||||
---
|
||||
### How to obtain your API key
|
||||
1. Visit [AgentX](https://www.agentx.so/) and log in.
|
||||
2. Click on your avatar (bottom-left corner).
|
||||
3. Copy your API Key from the popup window.
|
||||
4. Paste it here.
|
||||
|
||||
`,
|
||||
required: true,
|
||||
validate: async ({ auth }) => {
|
||||
if (auth) {
|
||||
try {
|
||||
await makeRequest(auth as string, HttpMethod.GET, "/agents");
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Invalid API Key or authentication failed.",
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
error: "API Key is required.",
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { HttpMethod, httpClient } from "@activepieces/pieces-common";
|
||||
|
||||
export const BASE_URL = `https://api.agentx.so/api/v1/access`;
|
||||
|
||||
export async function makeRequest(
|
||||
apiKey: string,
|
||||
method: HttpMethod,
|
||||
path: string,
|
||||
body?: unknown
|
||||
) {
|
||||
try {
|
||||
const response = await httpClient.sendRequest({
|
||||
method,
|
||||
url: `${BASE_URL}${path}`,
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body,
|
||||
});
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
throw new Error(`AgentX API error: ${error.message || String(error)}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Property } from "@activepieces/pieces-framework";
|
||||
import { makeRequest } from "./client";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
import { AgentXAuth } from "./auth";
|
||||
|
||||
export const AgentIdDropdown = Property.Dropdown<string,true,typeof AgentXAuth>({
|
||||
auth: AgentXAuth,
|
||||
displayName: "Agent",
|
||||
description: "Select an AgentX agent",
|
||||
required: true, // ensures the value is always a string, not undefined
|
||||
refreshers: ["auth"],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: "Please connect your AgentX account first",
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const agents = await makeRequest(auth.secret_text, HttpMethod.GET, "/agents");
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: agents.map((agent: any) => ({
|
||||
label: agent.name || `Agent ${agent.id}`,
|
||||
value: agent._id, // must be string
|
||||
})),
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: `Failed to fetch agents: ${error.message || error}`,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const ConversationIdDropdown = Property.Dropdown({
|
||||
auth: AgentXAuth,
|
||||
displayName: "Conversation",
|
||||
description: "Select a conversation for the chosen Agent",
|
||||
required: false,
|
||||
refreshers: ["auth", "agentId"],
|
||||
options: async ({ auth, agentId }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: "Please connect your AgentX account first",
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!agentId) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: "Select an Agent first",
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const conversations = await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.GET,
|
||||
`/agents/${agentId}/conversations`
|
||||
);
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: conversations.map((c: any) => ({
|
||||
label: c.title || `Conversation ${c._id}`,
|
||||
value: c._id,
|
||||
})),
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: `Failed to fetch conversations: ${error.message || error}`,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import {createTrigger,TriggerStrategy, AppConnectionValueForAuthProperty,} from "@activepieces/pieces-framework";
|
||||
import {DedupeStrategy,Polling,pollingHelper,} from "@activepieces/pieces-common";
|
||||
import dayjs from "dayjs";
|
||||
import { makeRequest } from "../common/client";
|
||||
import { HttpMethod } from "@activepieces/pieces-common";
|
||||
import { AgentXAuth } from "../common/auth";
|
||||
|
||||
type Agent = {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof AgentXAuth>,
|
||||
Record<string, never>
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth }) => {
|
||||
const agents = (await makeRequest(auth.secret_text, HttpMethod.GET, "/agents")) as Agent[];
|
||||
|
||||
const sortedAgents = agents.sort((a, b) =>
|
||||
dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf()
|
||||
);
|
||||
|
||||
return sortedAgents.map((agent) => ({
|
||||
epochMilliSeconds: dayjs(agent.createdAt).valueOf(),
|
||||
data: agent,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const newAgent = createTrigger({
|
||||
auth: AgentXAuth,
|
||||
name: "new_agent",
|
||||
displayName: "New Agent",
|
||||
description: "Triggers when a new AgentX agent is created.",
|
||||
props: {},
|
||||
sampleData: {
|
||||
_id: "agt_1234567890abcdef",
|
||||
name: "Customer Support Bot",
|
||||
created_at: "2025-09-08T10:00:00Z",
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onEnable(polling, { store, auth, propsValue });
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { store, auth, propsValue } = context;
|
||||
await pollingHelper.onDisable(polling, { store, auth, propsValue });
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
return await pollingHelper.poll(polling, context);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
createTrigger,
|
||||
TriggerStrategy,
|
||||
AppConnectionValueForAuthProperty,
|
||||
} from "@activepieces/pieces-framework";
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
HttpMethod,
|
||||
} from "@activepieces/pieces-common";
|
||||
import dayjs from "dayjs";
|
||||
import { makeRequest } from "../common/client";
|
||||
import { AgentXAuth } from "../common/auth";
|
||||
import { AgentIdDropdown } from "../common/dropdown";
|
||||
|
||||
|
||||
type Conversation = {
|
||||
_id: string;
|
||||
type?: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof AgentXAuth>,
|
||||
{ agentId?: string }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue }) => {
|
||||
const { agentId } = propsValue;
|
||||
|
||||
if (!agentId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const conversations = (await makeRequest(
|
||||
auth.secret_text,
|
||||
HttpMethod.GET,
|
||||
`/agents/${agentId}/conversations`
|
||||
)) as Conversation[];
|
||||
|
||||
const sortedConversations = conversations.sort((a, b) =>
|
||||
dayjs(b.createdAt).valueOf() - dayjs(a.createdAt).valueOf()
|
||||
);
|
||||
|
||||
return sortedConversations.map((conv) => ({
|
||||
epochMilliSeconds: dayjs(conv.createdAt).valueOf(),
|
||||
data: conv,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error(`Error fetching conversations for agent ${agentId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const newConversation = createTrigger({
|
||||
auth: AgentXAuth,
|
||||
name: "new_conversation",
|
||||
displayName: "New Conversation",
|
||||
description: "Triggers when a new conversation begins with a specific Agent. Only detects conversations created after the trigger is enabled.",
|
||||
type: TriggerStrategy.POLLING,
|
||||
|
||||
props: {
|
||||
agentId: AgentIdDropdown,
|
||||
},
|
||||
|
||||
sampleData: {
|
||||
id: "conv_1234567890abcdef",
|
||||
type: "chat",
|
||||
created_at: "2025-09-08T11:45:00Z",
|
||||
},
|
||||
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
await pollingHelper.onEnable(polling, context);
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
await pollingHelper.onDisable(polling, context);
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
return await pollingHelper.poll(polling, context);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user