Skip to content

@ahpd/activepieces ​

npmCIlicense MITnode >=22Agent Host Protocol 0.9.0

Activepieces pieces as connectors for the @ahpd/server daemon. Each piece is an adapter. Each of its actions is a tool that the host offers to a session or a bot that binds a connector of it.

A connector of a piece is one account: a Resend connector is one Resend key, and two Slack connectors are two Slack workspaces. The host keeps each key in the vault, because every secret field says secret.

The pieces run in the engine, the process Activepieces runs piece code in, with no Activepieces server and no Docker. A piece bundles an HTTP client that turns off TLS checks for the process it runs in. The daemon starts one engine for the pieces and pins that setting there.

Part of ahpd. The source is in packages/activepieces.

Install ​

bash
ahpd plugin install @ahpd/activepieces
ahpd restart

The pieces in the table below are dependencies of this package, so they arrive with it. A piece this package does not depend on is installed when its name is saved in pieces. The install goes into the plugin's own installed/ folder, so a name that is not a dependency still arrives. Write a version after the name to pick one, as in @activepieces/piece-postgres@0.5.1; a name with no version installs the newest. The plugin says on its own row how each install ended, and a piece that still does not load is a warning there.

Update, remove and one-run flags are in Plugins.

Options ​

In the configuration file, with three of the pieces named:

json
{ "plugins": [{ "name": "@ahpd/activepieces", "options": { "pieces": ["@activepieces/piece-resend", "@activepieces/piece-telegram-bot", "@activepieces/piece-slack"] } }] }
OptionDefaultWhat it does
pieces-The package of each piece the daemon offers, such as @activepieces/piece-resend. A version may follow the name, as in @activepieces/piece-postgres@0.5.1. Required, and at least one
networkModeUNRESTRICTEDActivepieces' AP_NETWORK_MODE for the engine, with Activepieces' default: UNRESTRICTED refuses no connection a piece makes, so the network the daemon runs in is the boundary, and STRICT refuses every one to a private, loopback, link-local or multicast address
ssrfAllowListnoneActivepieces' AP_SSRF_ALLOW_LIST: the addresses and CIDR ranges a piece may reach under STRICT, such as 10.0.0.5 or 10.0.0.0/8
sandboxPropagatedEnvVarsnoneThe variables of the daemon's environment the engine is given, by name, as Activepieces' AP_SANDBOX_PROPAGATED_ENV_VARS. A proxy or a certificate file a piece needs is listed here

An adapter is named ap- and the piece's own name: ap-resend, ap-telegram-bot and ap-slack. The plugin declares the contract ap-*, so a client sees every adapter it loads.

The pieces this package ships ​

PiecePackageVersionAuthLicense
Resend@activepieces/piece-resend0.6.1SECRET_TEXT, one keyMIT
Telegram Bot@activepieces/piece-telegram-bot0.9.1SECRET_TEXT, one bot tokenMIT
Slack@activepieces/piece-slack0.23.1CUSTOM_AUTH, a bot token and an optional user tokenMIT
GitHub@activepieces/piece-github0.11.0CUSTOM_AUTH, a GitHub App: app id, installation id and private key; or SECRET_TEXT, a personal access tokenMIT
Linear@activepieces/piece-linear0.9.1SECRET_TEXT, one API keyMIT
Jira Cloud@activepieces/piece-jira-cloud0.6.1CUSTOM_AUTH, the site URL, an email and an API tokenMIT
Discord@activepieces/piece-discord0.8.0SECRET_TEXT, one bot tokenMIT
WhatsApp Business@activepieces/piece-whatsapp1.0.1CUSTOM_AUTH, an access token, the business account id and an optional app secretMIT
Notion@activepieces/piece-notion0.8.1CUSTOM_AUTH, an internal integration tokenMIT
Airtable@activepieces/piece-airtable0.9.2SECRET_TEXT, one personal access tokenMIT
SMTP@activepieces/piece-smtp0.5.0CUSTOM_AUTH, the host, port, TLS, and an optional email and passwordMIT
SendGrid@activepieces/piece-sendgrid0.6.3SECRET_TEXT, one API key; or CUSTOM_AUTH, an API key and its data residencyMIT
Stripe@activepieces/piece-stripe0.9.0SECRET_TEXT, one secret keyMIT
HubSpot@activepieces/piece-hubspot0.13.1CUSTOM_AUTH, a private app access tokenMIT

The versions are exact, and the versions @activepieces/pieces-framework at 0.32.0 describes. Each piece is in packages/pieces/community/ or packages/pieces/core/ of github.com/activepieces/activepieces, which is MIT outside its ee folders. Slack, GitHub, Notion and HubSpot declare an OAuth2 connection beside the auth in the table, and this plugin offers only the auth in the table. An action that takes a file, such as Slack's file upload, takes it as a string: An action that takes or writes a file.

A connector ​

json
{
  "id": "mail",
  "adapter": "ap-resend",
  "owner": "host",
  "config": {
    "secret_text": "re_the_resend_key"
  }
}

Sent as the params of x-ahpd/connectors/put, that connector writes the key to the vault and keeps { "$secret": "<name>" } in the record. A bot, or a session config key, that binds mail gets the tool ap_resend_send_email, and a call sends one email through the piece. A Slack connector with botToken and userToken gets ap_slack_slack_post_message and the rest of the piece's actions that are not marked for a person alone.

A connector carries the auth kinds Activepieces names SECRET_TEXT, one secret secret_text; BASIC_AUTH, a username and a secret password; and CUSTOM_AUTH, the piece's own fields. A piece that lists more than one of them is one adapter whose connector picks its kind with type, as a person picks the kind of an Activepieces connection: a GitHub connector is { "type": "SECRET_TEXT", "secret_text": "<token>" } or { "type": "CUSTOM_AUTH", "appId": "...", "installationId": "...", "privateKey": "..." }. An OAUTH2 auth is not carried.

A connector is checked with the piece's own validate, Activepieces' credential check, where the piece has one: its row is ok when the piece takes the connection, and denied with the piece's words when it does not.

An action its piece marks for humans alone, with audience: 'human', is not offered, as Activepieces hides it from agents. An adapter is described in the piece's own words, or by its name where it wrote none.

What a piece runs ​

  • An action runs in the engine with the connector's auth and the call's input, as Activepieces runs a single action outside a flow: its step is step_1, and run.canPause is false, so a waitpoint is refused in the engine's words. An action the piece lacks is refused as the engine refuses it, Action not found for piece <package>@<version>. The result comes back as the tool's answer, and a thrown error comes back as its message only. What the action hands output.update while it runs is the call's partial content, its data as JSON text, each update in place of the last, which a client is shown as chat/toolCallContentChanged where the host can say it.
  • A call may run for 600 seconds, Activepieces' single action run timeout. One that runs longer fails, saying the action may have partially completed and is not to be run again blindly, and the engine stays up for the next one. An engine that exits starts again on the next call, five times, as VS Code restarts its agent host. After that, every call is refused: The engine process stopped after 5 restarts.
  • A call's input is diagnosed, filled, made its props' types and validated as Activepieces makes an action run's, and refused in its words when it cannot run.
  • The engine's environment is Activepieces' sandbox's: a home in the system's temporary folder, the network mode and allow list, and the variables sandboxPropagatedEnvVars names. Nothing else of the daemon's environment reaches a piece.
  • Under STRICT, every connection a piece makes to an address that is not unicast fails with SSRFBlockedError, as in Activepieces' engine.
  • An action may keep a value in store, and the engine keeps it on disk for that connector alone, in the scope the piece names.
  • files writes into the session's attachments, a Buffer or a Readable of at most 25 MB, and answers the name a later FILE value takes.
  • connections and server are not offered.
  • Triggers, OAuth2 and file props wait for a later plan.

Documentation ​

PLUGINS.mdWhat a plugin is, and how a daemon loads one

License ​

MIT.

How a session binds a connector ​

A session names the connectors it may use in the connectors key of its config. A bot names the same map in its connectors field:

json
{ "connectors": { "work": "connectors://ap-resend/mail" } }

That session has the tool ap_resend_send_email with no connector argument, because the alias says whose account the call uses. With two bindings of one adapter, the tool takes a connector argument that names one alias. PLUGINS.md says how a tool is named, offered and checked.

Each piece becomes an adapter, and the adapter carries the logo Activepieces publishes for the piece, as Activepieces shows each piece with its own logo. The plugin fetches each logo once, at load, and keeps it at <configDir>/activepieces/icons/<piece>.<ext>, with the extension of the image type the CDN sent. A client receives it as a data: icon, so a second start makes no fetch. A daemon with no configuration directory to cache in fetches nothing.

A logo is read as VS Code reads a remote image: https: on every hop, five redirects followed by hand, ten seconds for the whole read, and its size checked from content-length and again as the bytes arrive, up to 32 KB, since a logo travels in the adapters list every client receives in full. The options logoLimitBytes (32768, 0 for no limit) and logoTimeoutMs (10000, 0 for no limit) set the two bounds. Each hop's address is checked as the network below checks a piece's, so a logo URL that resolves into the host's own network is blocked. An answer that is not an image, a read that fails and a logo past the bound cost the icon and one line. The adapter is still registered, without it.

The logos are the brands' own marks, and the plugin passes on what Activepieces publishes for each piece. They belong to their owners: a host that shows one is showing the piece, not claiming the mark.

An action that takes or writes a file ​

An action with a FILE prop is offered with that prop as a string. The value is one of three things: a base64 data: URI, a URL, or the name of a file in the session's attachments folder, which stands for Activepieces' upload. The engine turns it into the file the piece reads before the action runs, as Activepieces' engine does. A base64 data: URI is decoded and named unknown.<ext> by the engine's table of media types, bin for a type the table lacks. A URL, data: URIs that are not base64 among them, is fetched and named by its Content-Disposition, else by the last segment of its path when that holds a dot, else unknown.<ext> by its media type. An attachment is resolved inside the folder, by its name or by its absolute path there. A prop the piece declares streaming is given a body to read, and any other the whole file.

A value that names no file, a URL that answers no success, and a name that leaves the folder or names nothing in it are no file. The engine's validator then refuses a required prop in its words: Expected file url or base64 with mimeType, received: <value>, where a URL is named by its host alone, never its path or query, since a signed URL carries its token there, and a data: URI by its scheme. The FILE props of each item of an array are made the same way.

A URL is fetched in the engine, under the guard every connection there takes: the network below.

A piece that calls files.write or files.upload puts the file in the session's attachments folder, under the name it asked for, as the engine's file uploader takes one: its data a Buffer or a Readable, and at most 25 MB, Activepieces' default AP_MAX_FILE_SIZE_MB. Anything else is refused in the engine's words, a larger file with File size is larger than maximum supported size. A name longer than 80 characters is cut, and its extension is kept. A name of a file already there gets a number before its extension, and an existing file is never replaced. write answers the file's name, and upload answers it as its id and its url: the name is what a later FILE value takes, as a later step takes Activepieces' file URL. The written file goes when the session goes, like every other attachment. A call on a session with no folder refuses the write, and a FILE value that names an attachment is no file there.

A connector's store ​

A piece's store keeps its values on disk, one file to a connector in <configDir>/activepieces/stores/, so a value outlives the engine and the daemon, as Activepieces keeps a piece's store past its run. A connector stands for Activepieces' project: a value put in the PROJECT scope is the connector's, and one put in the FLOW scope, the default, is kept under the flow the call runs in, so the two scopes never meet. A key is at most 128 characters and a value at most 512 KB as JSON, Activepieces' limits, each refused in its words.

An action's input ​

A call's input is made as Activepieces makes a single action run's, before the action runs. A key the action has no prop for refuses the call, with the prop the key most likely meant and the action's props listed. A required prop with no value, and a value a static dropdown does not list, refuse it too: Cannot run action: Missing required inputs: ... and Invalid option values: ..., each in Activepieces' words. An empty object or list given for a prop of the other shape is made the prop's own, and an optional list, dynamic prop or checkbox the call leaves out is filled, a checkbox with its default.

Then each value is made its prop's type, as the engine's processors make it: a number from its text, a checkbox from "true", an object or a JSON value from its text, a time as ISO 8601 in UTC, a multi-select's one value as a list, and text from anything else. A value its prop's validator refuses refuses the call, with the engine's JSON of every prop that failed, such as { "count": ["Expected number, received: many"] }.

The network ​

networkMode is the operator's switch for what a piece may reach, Activepieces' AP_NETWORK_MODE, and it is UNRESTRICTED unless the operator sets it, as in Activepieces. Under UNRESTRICTED a piece reaches whatever the network the daemon runs in lets it reach, so that network, the sandbox or host the operator runs the daemon in, is the boundary.

Under STRICT the engine guards every connection a piece makes as Activepieces' engine does. A name that resolves to an address that is not unicast, as ipaddr.js names ranges, fails: loopback, private, link-local, multicast, unspecified, reserved, shared and the like. So does a socket opened to such an address written as one, a URL naming it among them. An IPv4 address written as IPv6 is read as the IPv4 it holds. The failure is SSRFBlockedError, in its words: SSRF protection: refusing to connect to <host> (resolved <address>), private, loopback, link-local, or multicast address.

ssrfAllowList names the addresses and CIDR ranges a piece may reach under STRICT, such as a Jira server at 10.0.0.5. The guard is the engine's in-process one: it stops a piece that fetches a URL it was handed, and it is no boundary against code written to get round it, which is why Activepieces has the boundary drawn in the network and the guard as a second line on top of it (Network Security).