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,33 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../../../.eslintrc.base.json"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"!**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# pieces-letta
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build pieces-letta` to build the library.
|
||||
17
activepieces-fork/packages/pieces/community/letta/bun.lock
Normal file
17
activepieces-fork/packages/pieces/community/letta/bun.lock
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@activepieces/piece-letta",
|
||||
"dependencies": {
|
||||
"@letta-ai/letta-client": "^1.3.1",
|
||||
"tslib": "^2.3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@letta-ai/letta-client": ["@letta-ai/letta-client@1.3.1", "", {}, "sha512-L+R7Zjsy2flB0CsGn/a6LAtRpC4HPBXpfU6/K6Xfxl8tHJXzgCEOjAxAuC7SxF0sfSRMF72a/Ves2A15NrCSag=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@activepieces/piece-letta",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs",
|
||||
"main": "./src/index.js",
|
||||
"types": "./src/index.d.ts",
|
||||
"dependencies": {
|
||||
"@letta-ai/letta-client": "^1.3.1",
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "pieces-letta",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/letta/src",
|
||||
"projectType": "library",
|
||||
"release": {
|
||||
"version": {
|
||||
"manifestRootsToUpdate": [
|
||||
"dist/{projectRoot}"
|
||||
],
|
||||
"currentVersionResolver": "git-tag",
|
||||
"fallbackCurrentVersionResolver": "disk"
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/letta",
|
||||
"tsConfig": "packages/pieces/community/letta/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/letta/package.json",
|
||||
"main": "packages/pieces/community/letta/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/letta/*.md",
|
||||
{
|
||||
"input": "packages/pieces/community/letta/src/i18n",
|
||||
"output": "./src/i18n",
|
||||
"glob": "**/!(i18n.json)"
|
||||
}
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"prebuild",
|
||||
"^build"
|
||||
]
|
||||
},
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
},
|
||||
"prebuild": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/letta",
|
||||
"command": "bun install --no-save --silent"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createPiece } from "@activepieces/pieces-framework";
|
||||
import { PieceCategory } from "@activepieces/shared";
|
||||
import { lettaAuth } from "./lib/common/auth";
|
||||
import { createAgentFromTemplate } from "./lib/actions/create-agent-from-template";
|
||||
import { createIdentity } from "./lib/actions/create-identity";
|
||||
import { sendMessageToAgent } from "./lib/actions/send-message-to-agent";
|
||||
import { getIdentities } from "./lib/actions/get-identities";
|
||||
import { newAgent } from "./lib/triggers/new-agent";
|
||||
import { newMessage } from "./lib/triggers/new-message";
|
||||
|
||||
export const letta = createPiece({
|
||||
displayName: "Letta",
|
||||
description: "Letta is the platform for building stateful agents: open AI with advanced memory that can learn and self-improve over time.",
|
||||
auth: lettaAuth,
|
||||
minimumSupportedRelease: '0.36.1',
|
||||
logoUrl: "https://cdn.activepieces.com/pieces/letta.png",
|
||||
categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
|
||||
authors: ["onyedikachi-david"],
|
||||
actions: [
|
||||
createAgentFromTemplate,
|
||||
createIdentity,
|
||||
sendMessageToAgent,
|
||||
getIdentities,
|
||||
],
|
||||
triggers: [
|
||||
newAgent,
|
||||
newMessage,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import { identityIdsDropdown } from '../common/props';
|
||||
import type {
|
||||
AgentCreateParams,
|
||||
AgentCreateResponse,
|
||||
} from '../common/types';
|
||||
|
||||
export const createAgentFromTemplate = createAction({
|
||||
auth: lettaAuth,
|
||||
name: 'createAgentFromTemplate',
|
||||
displayName: 'Create Agent From Template',
|
||||
description: 'Creates an agent from a template',
|
||||
props: {
|
||||
templateVersion: Property.ShortText({
|
||||
displayName: 'Template Version',
|
||||
description: 'The template version ID to create the agent from',
|
||||
required: true,
|
||||
}),
|
||||
agentName: Property.ShortText({
|
||||
displayName: 'Agent Name',
|
||||
description: 'The name of the agent (optional, a random name will be assigned if not provided)',
|
||||
required: false,
|
||||
}),
|
||||
identityIds: identityIdsDropdown,
|
||||
tags: Property.Array({
|
||||
displayName: 'Tags',
|
||||
description: 'Tags to assign to the agent',
|
||||
required: false,
|
||||
properties: {
|
||||
tag: Property.ShortText({
|
||||
displayName: 'Tag',
|
||||
description: 'Tag name',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
memoryVariables: Property.Object({
|
||||
displayName: 'Memory Variables',
|
||||
description: 'Memory variables to assign to the agent (key-value pairs)',
|
||||
required: false,
|
||||
}),
|
||||
toolVariables: Property.Object({
|
||||
displayName: 'Tool Variables',
|
||||
description: 'Tool variables to assign to the agent (key-value pairs)',
|
||||
required: false,
|
||||
}),
|
||||
initialMessageSequence: Property.Array({
|
||||
displayName: 'Initial Message Sequence',
|
||||
description: 'Initial sequence of messages to start the agent with',
|
||||
required: false,
|
||||
properties: {
|
||||
content: Property.LongText({
|
||||
displayName: 'Content',
|
||||
description: 'Message content',
|
||||
required: true,
|
||||
}),
|
||||
role: Property.StaticDropdown({
|
||||
displayName: 'Role',
|
||||
description: 'Message role',
|
||||
required: true,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'User', value: 'user' },
|
||||
{ label: 'System', value: 'system' },
|
||||
{ label: 'Assistant', value: 'assistant' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Optional name of the participant',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
templateVersion,
|
||||
agentName,
|
||||
identityIds,
|
||||
tags,
|
||||
memoryVariables,
|
||||
toolVariables,
|
||||
initialMessageSequence,
|
||||
} = context.propsValue;
|
||||
|
||||
const client = getLettaClient(context.auth.props);
|
||||
|
||||
const body: AgentCreateParams = {};
|
||||
|
||||
if (agentName) {
|
||||
body.agent_name = agentName;
|
||||
}
|
||||
|
||||
if (identityIds && identityIds.length > 0) {
|
||||
body.identity_ids = identityIds;
|
||||
}
|
||||
|
||||
if (tags && tags.length > 0) {
|
||||
body.tags = tags.map((tagObj: any) => tagObj.tag).filter(Boolean);
|
||||
}
|
||||
|
||||
if (memoryVariables && Object.keys(memoryVariables).length > 0) {
|
||||
const memoryVars: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(memoryVariables)) {
|
||||
memoryVars[key] = String(value);
|
||||
}
|
||||
body.memory_variables = memoryVars;
|
||||
}
|
||||
|
||||
if (toolVariables && Object.keys(toolVariables).length > 0) {
|
||||
const toolVars: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(toolVariables)) {
|
||||
toolVars[key] = String(value);
|
||||
}
|
||||
body.tool_variables = toolVars;
|
||||
}
|
||||
|
||||
if (initialMessageSequence && initialMessageSequence.length > 0) {
|
||||
body.initial_message_sequence = initialMessageSequence.map((msg: any) => ({
|
||||
content: msg.content,
|
||||
role: msg.role,
|
||||
name: msg.name || undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
const response: AgentCreateResponse = await client.templates.agents.create(
|
||||
templateVersion,
|
||||
body
|
||||
);
|
||||
|
||||
return {
|
||||
agentIds: response.agent_ids,
|
||||
deploymentId: response.deployment_id,
|
||||
groupId: response.group_id,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import type {
|
||||
IdentityCreateParams,
|
||||
Identity,
|
||||
IdentityProperty,
|
||||
} from '../common/types';
|
||||
|
||||
export const createIdentity = createAction({
|
||||
auth: lettaAuth,
|
||||
name: 'createIdentity',
|
||||
displayName: 'Create Identity',
|
||||
description: 'Creates a Letta identity',
|
||||
props: {
|
||||
identifierKey: Property.ShortText({
|
||||
displayName: 'Identifier Key',
|
||||
description: 'External, user-generated identifier key of the identity',
|
||||
required: true,
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'The name of the identity',
|
||||
required: true,
|
||||
}),
|
||||
identityType: Property.StaticDropdown({
|
||||
displayName: 'Identity Type',
|
||||
description: 'The type of the identity',
|
||||
required: true,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'Organization', value: 'org' },
|
||||
{ label: 'User', value: 'user' },
|
||||
{ label: 'Other', value: 'other' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
projectId: Property.ShortText({
|
||||
displayName: 'Project ID',
|
||||
description: 'The project ID of the identity (optional)',
|
||||
required: false,
|
||||
}),
|
||||
properties: Property.Array({
|
||||
displayName: 'Properties',
|
||||
description: 'List of properties associated with the identity',
|
||||
required: false,
|
||||
properties: {
|
||||
key: Property.ShortText({
|
||||
displayName: 'Key',
|
||||
description: 'Property key',
|
||||
required: true,
|
||||
}),
|
||||
type: Property.StaticDropdown({
|
||||
displayName: 'Type',
|
||||
description: 'Property type',
|
||||
required: true,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'String', value: 'string' },
|
||||
{ label: 'Number', value: 'number' },
|
||||
{ label: 'Boolean', value: 'boolean' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
value: Property.ShortText({
|
||||
displayName: 'Value',
|
||||
description: 'Property value (for JSON type, enter valid JSON string)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
identifierKey,
|
||||
name,
|
||||
identityType,
|
||||
projectId,
|
||||
properties,
|
||||
} = context.propsValue;
|
||||
|
||||
const client = getLettaClient(context.auth.props);
|
||||
|
||||
const body: IdentityCreateParams = {
|
||||
identifier_key: identifierKey,
|
||||
name: name,
|
||||
identity_type: identityType as 'org' | 'user' | 'other',
|
||||
};
|
||||
|
||||
if (projectId) {
|
||||
body.project_id = projectId;
|
||||
}
|
||||
|
||||
if (properties && properties.length > 0) {
|
||||
body.properties = properties.map((prop: any) => {
|
||||
let parsedValue: string | number | boolean | { [key: string]: unknown };
|
||||
|
||||
switch (prop.type) {
|
||||
case 'number':
|
||||
parsedValue = Number(prop.value);
|
||||
break;
|
||||
case 'boolean':
|
||||
parsedValue = prop.value === 'true' || prop.value === true;
|
||||
break;
|
||||
case 'json':
|
||||
try {
|
||||
parsedValue = JSON.parse(prop.value);
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid JSON in property "${prop.key}": ${e instanceof Error ? e.message : 'Unknown error'}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
parsedValue = String(prop.value);
|
||||
}
|
||||
|
||||
const identityProperty: IdentityProperty = {
|
||||
key: prop.key,
|
||||
type: prop.type as 'string' | 'number' | 'boolean' | 'json',
|
||||
value: parsedValue,
|
||||
};
|
||||
|
||||
return identityProperty;
|
||||
});
|
||||
}
|
||||
|
||||
const response: Identity = await client.identities.create(body);
|
||||
|
||||
return {
|
||||
id: response.id,
|
||||
identifierKey: response.identifier_key,
|
||||
name: response.name,
|
||||
identityType: response.identity_type,
|
||||
projectId: response.project_id,
|
||||
properties: response.properties,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import type {
|
||||
IdentityListParams,
|
||||
Identity,
|
||||
} from '../common/types';
|
||||
|
||||
export const getIdentities = createAction({
|
||||
auth: lettaAuth,
|
||||
name: 'getIdentities',
|
||||
displayName: 'Get Identities',
|
||||
description: 'Searches for identities in your Letta Project',
|
||||
props: {
|
||||
identifierKey: Property.ShortText({
|
||||
displayName: 'Identifier Key',
|
||||
description: 'Filter by identifier key',
|
||||
required: false,
|
||||
}),
|
||||
name: Property.ShortText({
|
||||
displayName: 'Name',
|
||||
description: 'Filter by identity name',
|
||||
required: false,
|
||||
}),
|
||||
identityType: Property.StaticDropdown({
|
||||
displayName: 'Identity Type',
|
||||
description: 'Filter by identity type',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: 'Organization', value: 'org' },
|
||||
{ label: 'User', value: 'user' },
|
||||
{ label: 'Other', value: 'other' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
projectId: Property.ShortText({
|
||||
displayName: 'Project ID',
|
||||
description: 'Filter by project ID',
|
||||
required: false,
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'Maximum number of identities to return',
|
||||
required: false,
|
||||
defaultValue: 100,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
identifierKey,
|
||||
name,
|
||||
identityType,
|
||||
projectId,
|
||||
limit,
|
||||
} = context.propsValue;
|
||||
|
||||
const client = getLettaClient(context.auth.props);
|
||||
|
||||
const query: IdentityListParams = {};
|
||||
|
||||
if (identifierKey) {
|
||||
query.identifier_key = identifierKey;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
query.name = name;
|
||||
}
|
||||
|
||||
if (identityType) {
|
||||
query.identity_type = identityType as 'org' | 'user' | 'other';
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
query.project_id = projectId;
|
||||
}
|
||||
|
||||
if (limit !== undefined && limit !== null) {
|
||||
query.limit = limit;
|
||||
}
|
||||
|
||||
const identitiesPage = await client.identities.list(query);
|
||||
|
||||
const identities: Identity[] = [];
|
||||
for await (const identity of identitiesPage) {
|
||||
identities.push(identity);
|
||||
}
|
||||
|
||||
return {
|
||||
identities,
|
||||
count: identities.length,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import { agentIdDropdown } from '../common/props';
|
||||
import type {
|
||||
MessageCreateParamsNonStreaming,
|
||||
LettaResponse,
|
||||
} from '../common/types';
|
||||
|
||||
export const sendMessageToAgent = createAction({
|
||||
auth: lettaAuth,
|
||||
name: 'sendMessageToAgent',
|
||||
displayName: 'Send Message to Agent',
|
||||
description: 'Send message to an agent',
|
||||
props: {
|
||||
agentId: agentIdDropdown,
|
||||
input: Property.LongText({
|
||||
displayName: 'Message',
|
||||
description: 'The message content to send to the agent',
|
||||
required: true,
|
||||
}),
|
||||
maxSteps: Property.Number({
|
||||
displayName: 'Max Steps',
|
||||
description: 'Maximum number of steps the agent should take to process the request',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
agentId,
|
||||
input,
|
||||
maxSteps,
|
||||
} = context.propsValue;
|
||||
|
||||
const client = getLettaClient(context.auth.props);
|
||||
|
||||
const body: MessageCreateParamsNonStreaming = {
|
||||
streaming: false,
|
||||
input: input,
|
||||
};
|
||||
|
||||
if (maxSteps !== undefined && maxSteps !== null) {
|
||||
body.max_steps = maxSteps;
|
||||
}
|
||||
|
||||
const response: LettaResponse = await client.agents.messages.create(
|
||||
agentId,
|
||||
body
|
||||
);
|
||||
|
||||
return {
|
||||
messages: response.messages,
|
||||
stopReason: response.stop_reason,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { PieceAuth, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
Letta,
|
||||
AuthenticationError,
|
||||
PermissionDeniedError,
|
||||
APIConnectionError,
|
||||
APIConnectionTimeoutError,
|
||||
LettaError,
|
||||
} from '@letta-ai/letta-client';
|
||||
import type {
|
||||
ClientOptions,
|
||||
AgentListParams,
|
||||
} from './types';
|
||||
|
||||
const markdown = `
|
||||
To authenticate with Letta:
|
||||
|
||||
1. **For Letta Cloud**: Get your API key from [Letta Cloud](https://cloud.letta.ai)
|
||||
2. **For Self-hosted**: Leave API key empty and provide your server URL (e.g., http://localhost:8283)
|
||||
`;
|
||||
|
||||
export const lettaAuth = PieceAuth.CustomAuth({
|
||||
description: markdown,
|
||||
required: true,
|
||||
props: {
|
||||
apiKey: PieceAuth.SecretText({
|
||||
displayName: 'API Key',
|
||||
description: 'Your Letta API key (required for Letta Cloud, leave empty for self-hosted)',
|
||||
required: false,
|
||||
}),
|
||||
baseUrl: Property.ShortText({
|
||||
displayName: 'Base URL',
|
||||
description: 'Server URL for self-hosted instances (e.g., http://localhost:8283). Leave empty to use Letta Cloud.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
validate: async ({ auth }) => {
|
||||
const { apiKey, baseUrl } = auth;
|
||||
|
||||
if (!apiKey && !baseUrl) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Please provide either an API key (for Letta Cloud) or a base URL (for self-hosted).',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const clientConfig: ClientOptions = {};
|
||||
if (apiKey) {
|
||||
clientConfig.apiKey = apiKey;
|
||||
}
|
||||
if (baseUrl) {
|
||||
clientConfig.baseURL = baseUrl;
|
||||
}
|
||||
|
||||
const client = new Letta(clientConfig);
|
||||
|
||||
if (apiKey) {
|
||||
const listParams: AgentListParams = { limit: 1 };
|
||||
await client.agents.list(listParams);
|
||||
} else {
|
||||
await client.health();
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid API key. Please check your credentials.',
|
||||
};
|
||||
}
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Permission denied. Please check your API key permissions.',
|
||||
};
|
||||
}
|
||||
if (error instanceof APIConnectionError || error instanceof APIConnectionTimeoutError) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Connection failed. Please check your base URL and ensure the server is running.',
|
||||
};
|
||||
}
|
||||
if (error instanceof LettaError) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}. Please verify your credentials.`,
|
||||
};
|
||||
}
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return {
|
||||
valid: false,
|
||||
error: `Authentication failed: ${errorMessage}. Please verify your credentials.`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export type LettaAuthType = {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Letta } from '@letta-ai/letta-client';
|
||||
import type { ClientOptions } from './types';
|
||||
import { LettaAuthType } from './auth';
|
||||
|
||||
|
||||
export function getLettaClient(auth: LettaAuthType): Letta {
|
||||
const clientConfig: ClientOptions = {};
|
||||
|
||||
if (auth.apiKey) {
|
||||
clientConfig.apiKey = auth.apiKey;
|
||||
}
|
||||
|
||||
if (auth.baseUrl) {
|
||||
clientConfig.baseURL = auth.baseUrl;
|
||||
}
|
||||
|
||||
return new Letta(clientConfig);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Property } from '@activepieces/pieces-framework';
|
||||
import { getLettaClient } from './client';
|
||||
import { lettaAuth, type LettaAuthType } from './auth';
|
||||
|
||||
|
||||
export const identityIdsDropdown = Property.MultiSelectDropdown({
|
||||
displayName: 'Identities',
|
||||
description: 'Select identities to assign to the agent',
|
||||
auth: lettaAuth,
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = getLettaClient(auth as LettaAuthType);
|
||||
const identitiesPage = await client.identities.list();
|
||||
|
||||
const identities: Array<{ label: string; value: string }> = [];
|
||||
for await (const identity of identitiesPage) {
|
||||
identities.push({
|
||||
label: identity.name || identity.identifier_key || identity.id,
|
||||
value: identity.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (identities.length === 0) {
|
||||
return {
|
||||
disabled: false,
|
||||
options: [],
|
||||
placeholder: 'No identities found',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: identities,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load identities. Please check your connection.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const agentIdDropdown = Property.Dropdown({
|
||||
auth: lettaAuth,
|
||||
displayName: 'Agent',
|
||||
description: 'Select the agent to send a message to',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = getLettaClient(auth as LettaAuthType);
|
||||
const agentsPage = await client.agents.list();
|
||||
|
||||
const agents: Array<{ label: string; value: string }> = [];
|
||||
for await (const agent of agentsPage) {
|
||||
agents.push({
|
||||
label: agent.name || agent.id,
|
||||
value: agent.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (agents.length === 0) {
|
||||
return {
|
||||
disabled: false,
|
||||
options: [],
|
||||
placeholder: 'No agents found',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: false,
|
||||
options: agents,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load agents. Please check your connection.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
export type { ClientOptions } from '@letta-ai/letta-client';
|
||||
|
||||
export type {
|
||||
AgentState,
|
||||
AgentListParams,
|
||||
} from '@letta-ai/letta-client/resources/agents/agents';
|
||||
|
||||
export type {
|
||||
AgentCreateParams,
|
||||
AgentCreateResponse,
|
||||
} from '@letta-ai/letta-client/resources/templates/agents';
|
||||
|
||||
export type {
|
||||
Identity,
|
||||
IdentityCreateParams,
|
||||
IdentityListParams,
|
||||
IdentityProperty,
|
||||
IdentityType,
|
||||
} from '@letta-ai/letta-client/resources/identities/identities';
|
||||
|
||||
export type {
|
||||
Message,
|
||||
MessageCreateParamsNonStreaming,
|
||||
MessageListParams,
|
||||
ToolCallMessage,
|
||||
LettaResponse,
|
||||
} from '@letta-ai/letta-client/resources/agents/messages';
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
AppConnectionValueForAuthProperty,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import type {
|
||||
AgentState,
|
||||
AgentListParams,
|
||||
} from '../common/types';
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof lettaAuth>,
|
||||
Record<string, never>
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, lastFetchEpochMS }) => {
|
||||
const client = getLettaClient(auth.props);
|
||||
|
||||
const query: AgentListParams = {
|
||||
limit: 100,
|
||||
};
|
||||
|
||||
const agentsPage = await client.agents.list(query);
|
||||
|
||||
const agents: AgentState[] = [];
|
||||
for await (const agent of agentsPage) {
|
||||
if (agent.created_at) {
|
||||
const createdAtEpoch = Date.parse(agent.created_at);
|
||||
|
||||
if (createdAtEpoch > lastFetchEpochMS) {
|
||||
agents.push(agent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return agents
|
||||
.sort((a, b) => {
|
||||
const aTime = a.created_at ? Date.parse(a.created_at) : 0;
|
||||
const bTime = b.created_at ? Date.parse(b.created_at) : 0;
|
||||
return bTime - aTime;
|
||||
})
|
||||
.map((agent) => ({
|
||||
epochMilliSeconds: agent.created_at
|
||||
? Date.parse(agent.created_at)
|
||||
: Date.now(),
|
||||
data: agent,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const newAgent = createTrigger({
|
||||
auth: lettaAuth,
|
||||
name: 'newAgent',
|
||||
displayName: 'New Agent',
|
||||
description: 'Triggers when a new agent is created',
|
||||
type: TriggerStrategy.POLLING,
|
||||
props: {},
|
||||
sampleData: {},
|
||||
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);
|
||||
},
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
TriggerStrategy,
|
||||
createTrigger,
|
||||
PiecePropValueSchema,
|
||||
AppConnectionValueForAuthProperty,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { lettaAuth } from '../common/auth';
|
||||
import { getLettaClient } from '../common/client';
|
||||
import { agentIdDropdown } from '../common/props';
|
||||
import type {
|
||||
Message,
|
||||
MessageListParams,
|
||||
ToolCallMessage,
|
||||
} from '../common/types';
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof lettaAuth>,
|
||||
{ agentId: string }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const { agentId } = propsValue;
|
||||
const client = getLettaClient(auth.props);
|
||||
|
||||
const query: MessageListParams = {
|
||||
limit: 100,
|
||||
};
|
||||
|
||||
const messagesPage = await client.agents.messages.list(agentId, query);
|
||||
|
||||
const sendMessageToolCalls: ToolCallMessage[] = [];
|
||||
for await (const message of messagesPage) {
|
||||
if (message.message_type === 'tool_call_message') {
|
||||
const toolCallMessage = message as ToolCallMessage;
|
||||
|
||||
let hasSendMessage = false;
|
||||
|
||||
if (toolCallMessage.tool_calls) {
|
||||
if (Array.isArray(toolCallMessage.tool_calls)) {
|
||||
hasSendMessage = toolCallMessage.tool_calls.some(
|
||||
(tc) => tc.name === 'send_message'
|
||||
);
|
||||
} else {
|
||||
const delta = toolCallMessage.tool_calls;
|
||||
hasSendMessage = delta.name === 'send_message';
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSendMessage && toolCallMessage.tool_call) {
|
||||
const toolCall = toolCallMessage.tool_call;
|
||||
if ('name' in toolCall && toolCall.name) {
|
||||
hasSendMessage = toolCall.name === 'send_message';
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSendMessage && toolCallMessage.date) {
|
||||
const messageDateEpoch = Date.parse(toolCallMessage.date);
|
||||
|
||||
if (messageDateEpoch > lastFetchEpochMS) {
|
||||
sendMessageToolCalls.push(toolCallMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sendMessageToolCalls
|
||||
.sort((a, b) => {
|
||||
const aTime = a.date ? Date.parse(a.date) : 0;
|
||||
const bTime = b.date ? Date.parse(b.date) : 0;
|
||||
return bTime - aTime;
|
||||
})
|
||||
.map((message) => ({
|
||||
epochMilliSeconds: message.date ? Date.parse(message.date) : Date.now(),
|
||||
data: message,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const newMessage = createTrigger({
|
||||
auth: lettaAuth,
|
||||
name: 'newMessage',
|
||||
displayName: 'New Message',
|
||||
description: 'Triggers when an agent uses send_message',
|
||||
type: TriggerStrategy.POLLING,
|
||||
props: {
|
||||
agentId: agentIdDropdown,
|
||||
},
|
||||
sampleData: {},
|
||||
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);
|
||||
},
|
||||
async test(context) {
|
||||
return await pollingHelper.test(polling, context);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"importHelpers": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user