Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4d17500
chore: update version to 0.82.0 in package.json
amrdb Apr 19, 2026
122f444
Merge remote-tracking branch 'origin/main' into release/v0.82.0
abuaboud Apr 21, 2026
5b06785
docs: update architecture
abuaboud Apr 21, 2026
1682897
Merge remote-tracking branch 'origin/main' into release/v0.82.0
abuaboud Apr 21, 2026
b2883b7
Merge remote-tracking branch 'origin/main' into release/v0.82.0
abuaboud Apr 21, 2026
edcee7a
Merge remote-tracking branch 'origin/main' into release/v0.82.0
abuaboud Apr 21, 2026
c59fd0d
fix: bump version
abuaboud Apr 21, 2026
a136729
ci(release): build server-utils and reorder safety checks
abuaboud Apr 23, 2026
5e3a034
ci: remove continuous-delivery-release workflow
abuaboud Apr 23, 2026
488903e
ci(release): bump release-drafter v5 -> v7
abuaboud Apr 23, 2026
4133d02
ci: restore continuous-delivery-release workflow
abuaboud Apr 23, 2026
e09ce78
ci(cloud): deploy release-candidate to canary before production
abuaboud Apr 23, 2026
886a48a
Merge branch 'main' into release/v0.82.0
abuaboud Apr 23, 2026
618c8a9
ci(cloud): call canary workflow instead of inlining deploy steps
abuaboud Apr 23, 2026
8e03f96
Merge branch 'release/v0.82.0' of https://github.com/activepieces/act…
abuaboud Apr 23, 2026
2e0b130
ci: remove release-rc workflow
abuaboud Apr 23, 2026
9cae4f6
ci(release): fix duplicate step id and digest-guard empty-output bug
abuaboud Apr 23, 2026
85924d2
Merge branch 'main' into release/v0.82.0
abuaboud Apr 23, 2026
afe852f
fix: get file or throw should throw if fileId in null
MrChaker Apr 23, 2026
06ffaaf
fix: stop constantly showing failed to fetch data dialog for platform…
AbdulTheActivePiecer Apr 24, 2026
75b8278
Merge branch 'main' into deploy/cloud/2026-04-26
hazemadelkhalel Apr 27, 2026
691721a
fix: bun.lock error generated by npm
hazemadelkhalel Apr 27, 2026
bc96356
Merge branch 'main' into deploy/cloud/2026-04-26
hazemadelkhalel Apr 27, 2026
00c56e5
Merge branch 'main' into deploy/cloud/2026-04-26
hazemadelkhalel Apr 28, 2026
e486b89
Merge branch 'main' into deploy/cloud/2026-04-26
hazemadelkhalel Apr 28, 2026
1810730
fix(sso): accept SAML IdP metadata URL and auto-fetch the XML (#13010)
abuaboud Apr 29, 2026
45c0a73
fix(slack): use user token for usergroups.users.update (#13011)
sanket-a11y Apr 29, 2026
c70d245
fix(mcp): skip flaky ap_run_action tests that hang on CI (#13031)
hazemadelkhalel Apr 29, 2026
19f69f7
Merge branch 'deploy/cloud/2026-04-26' into main
AbdulTheActivePiecer Apr 29, 2026
2b6d97f
fix(web): limit error dialog to table-related queries (#13033)
AbdulTheActivePiecer Apr 29, 2026
1a20ada
feat(ai): add input images support for generate image action (#13032)
AhmadTash Apr 29, 2026
f5b4ea7
feat: add runs stats visual (#12804)
MrChaker Apr 29, 2026
b8453e6
feat(baserow): restore JWT auth + Row Event trigger + auto-create sel…
bst1n Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions docs/admin-guide/guides/sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,47 @@ Activepieces supports multiple SSO providers to integrate with your existing ide
</Step>
</Steps>

### SAML with Microsoft Entra ID (Azure AD)

<Steps>
<Step title="Create an Enterprise Application">
Go to the [Azure Portal](https://portal.azure.com/) → **Microsoft Entra ID** → **Enterprise applications** → **New application** → **Create your own application**.

Name it (e.g., "Activepieces") and select **Integrate any other application you don't find in the gallery (Non-gallery)**.
</Step>
<Step title="Configure SAML Single Sign-On">
Open the application → **Single sign-on** → select **SAML**.
</Step>
<Step title="Set Identifier and Reply URL">
Edit **Basic SAML Configuration**:
- **Identifier (Entity ID)**: `Activepieces`
- **Reply URL (Assertion Consumer Service URL)**: paste the SSO URL from the Activepieces configuration screen
</Step>
<Step title="Configure User Attributes & Claims">
Edit **Attributes & Claims** and add these additional claims (leave **Namespace** empty):

| Claim name | Source attribute |
|------------|------------------|
| `firstName` | `user.givenname` |
| `lastName` | `user.surname` |
| `email` | `user.mail` |
</Step>
<Step title="Copy the Federation Metadata">
In the **SAML Certificates** section, copy the **App Federation Metadata Url**.

You can paste this URL directly into the **IdP Metadata** field in Activepieces — Activepieces will fetch the metadata XML automatically. Alternatively, open the URL in a browser, save the XML, and paste its contents.
</Step>
<Step title="Copy the Signing Certificate">
Download the **Certificate (Base64)** from the **SAML Certificates** section. Open the file and copy its contents (including the `-----BEGIN CERTIFICATE-----` / `-----END CERTIFICATE-----` markers) into the **Signing Key** field in Activepieces.
</Step>
<Step title="Assign Users">
Go to **Users and groups** in the application and assign the users or groups that should be allowed to sign in.
</Step>
<Step title="Save Configuration">
Click **Save** in Activepieces to complete the setup.
</Step>
</Steps>

### SAML with JumpCloud

<Steps>
Expand Down Expand Up @@ -208,6 +249,7 @@ Activepieces supports multiple SSO providers to integrate with your existing ide
</Accordion>
<Accordion title="SAML authentication fails">
- Confirm the IdP metadata is complete and correctly formatted
- If you pasted a metadata URL, make sure it is publicly reachable (Activepieces fetches it server-side)
- Verify the signing certificate is properly formatted with BEGIN/END markers
- Ensure all required attributes (firstName, lastName, email) are mapped
</Accordion>
Expand Down
2 changes: 1 addition & 1 deletion packages/pieces/community/ai/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@activepieces/piece-ai",
"version": "0.3.9",
"version": "0.4.0",
"type": "commonjs",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
Expand Down
3 changes: 3 additions & 0 deletions packages/pieces/community/ai/src/i18n/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"Web Search Options": "Web Search Options",
"Text": "Text",
"Advanced Options": "Advanced Options",
"Input Images": "Input Images",
"Image File": "Image File",
"Provide images for editing, variation, or merging. Support depends on the selected model.": "Provide images for editing, variation, or merging. Support depends on the selected model.",
"Text to Classify": "Text to Classify",
"Categories": "Categories",
"Files": "Files",
Expand Down
177 changes: 128 additions & 49 deletions packages/pieces/community/ai/src/lib/actions/image/generate-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ export const generateImageAction = createAction({
displayName: 'Prompt',
required: true,
}),
inputImages: Property.Array({
displayName: 'Input Images',
description:
'Provide images for editing, variation, or merging. Support depends on the selected model.',
required: false,
properties: {
file: Property.File({
displayName: 'Image File',
required: true,
}),
},
}),
advancedOptions: Property.DynamicProperties({
displayName: 'Advanced Options',
required: false,
Expand Down Expand Up @@ -120,25 +132,6 @@ export const generateImageAction = createAction({
return options;
}

if (
providerId === AIProviderName.GOOGLE &&
modelId === 'gemini-2.5-flash-image-preview'
) {
options = {
image: Property.Array({
displayName: 'Images',
required: false,
properties: {
file: Property.File({
displayName: 'Image File',
required: true,
}),
},
description: 'The image(s) you want to edit/merge',
}),
};
}

return options;
},
}),
Expand All @@ -147,12 +140,18 @@ export const generateImageAction = createAction({
const provider = context.propsValue.provider;
const modelId = context.propsValue.model;

const inputImages = collectInputImages({
inputImages: context.propsValue.inputImages,
advancedOptions: context.propsValue.advancedOptions,
});

const image = await getGeneratedImage({
provider: provider as AIProviderName,
modelId,
engineToken: context.server.token,
apiUrl: context.server.apiUrl,
prompt: context.propsValue.prompt,
inputImages,
projectId: context.project.id,
flowId: context.flows.current.id,
runId: context.run.id,
Expand All @@ -171,12 +170,44 @@ export const generateImageAction = createAction({
},
});

const collectInputImages = ({
inputImages,
advancedOptions,
}: {
inputImages?: unknown;
advancedOptions?: DynamicPropsValue;
}): ApFile[] => {
const fromTopLevel = extractImageFiles(inputImages);
if (fromTopLevel.length > 0) {
return fromTopLevel;
}
return extractImageFiles(advancedOptions?.['image']);
};

const extractImageFiles = (value: unknown): ApFile[] => {
if (!Array.isArray(value)) {
return [];
}
return value.flatMap((entry) => {
if (
entry &&
typeof entry === 'object' &&
'file' in entry &&
entry.file
) {
return [entry.file as ApFile];
}
return [];
});
};

const getGeneratedImage = async ({
provider,
modelId,
engineToken,
apiUrl,
prompt,
inputImages,
projectId,
flowId,
runId,
Expand All @@ -187,6 +218,7 @@ const getGeneratedImage = async ({
engineToken: string;
apiUrl: string;
prompt: string;
inputImages: ApFile[];
projectId: string;
flowId: string;
runId: string;
Expand All @@ -206,49 +238,78 @@ const getGeneratedImage = async ({
const { provider: effectiveProvider } = getEffectiveProviderAndModel({ provider, model: modelId });
const resolvedProvider = (effectiveProvider ?? provider) as AIProviderName;

switch (resolvedProvider) {
case AIProviderName.GOOGLE:
case AIProviderName.ACTIVEPIECES:
case AIProviderName.OPENROUTER:
case AIProviderName.CLOUDFLARE_GATEWAY:
return generateImageUsingGenerateText({
model: model as unknown as LanguageModel,
prompt,
advancedOptions,
});
default: {
const { image } = await generateImage({
model,
prompt,
providerOptions: {
[resolvedProvider]: { ...advancedOptions },
},
});
return image
};
const hasInputImages = inputImages.length > 0;

return withImageInputErrorContext({ modelId, hasInputImages }, async () => {
switch (resolvedProvider) {
case AIProviderName.GOOGLE:
case AIProviderName.ACTIVEPIECES:
case AIProviderName.OPENROUTER:
case AIProviderName.CLOUDFLARE_GATEWAY:
return generateImageUsingGenerateText({
model: model as unknown as LanguageModel,
prompt,
inputImages,
});
default: {
const sanitizedAdvancedOptions = stripLegacyImageField(advancedOptions);
const sdkImages = inputImages.map((file) =>
Buffer.from(file.base64, 'base64'),
);
const { image } = await generateImage({
model,
prompt: hasInputImages
? { text: prompt, images: sdkImages }
: prompt,
providerOptions: {
[resolvedProvider]: { ...sanitizedAdvancedOptions },
},
});
return image;
}
}
});
};

const withImageInputErrorContext = async <T>(
{ modelId, hasInputImages }: { modelId: string; hasInputImages: boolean },
run: () => Promise<T>,
): Promise<T> => {
try {
return await run();
} catch (error) {
if (!hasInputImages) {
throw error;
}
const original = error instanceof Error ? error.message : String(error);
throw new Error(
`Image generation failed for model "${modelId}". ` +
`This model may not support input images. Try a model that supports image editing — ` +
`for example gpt-image-1, dall-e-2, or a Gemini Nano Banana model — or remove the input images. ` +
`Original error: ${original}`,
);
}
};

const generateImageUsingGenerateText = async ({
model,
prompt,
advancedOptions,
inputImages,
}: {
model: LanguageModel;
prompt: string;
advancedOptions?: DynamicPropsValue;
inputImages: ApFile[];
}): Promise<GeneratedFile> => {
const images =
(advancedOptions?.['image'] as Array<{ file: ApFile }> | undefined) ?? [];

const imageFiles = images.map<ImagePart>((image) => {
const fileType = image.file.extension
? mime.lookup(image.file.extension)
: 'image/jpeg';
const imageFiles = inputImages.map<ImagePart>((file) => {
const detected = file.extension ? mime.lookup(file.extension) : false;
const fileType =
detected && ALLOWED_IMAGE_MIME_TYPES.has(detected)
? detected
: 'image/jpeg';

return {
type: 'image',
image: `data:${fileType || 'image/jpeg'};base64,${image.file.base64}`,
image: `data:${fileType};base64,${file.base64}`,
};
});

Expand All @@ -271,6 +332,24 @@ const generateImageUsingGenerateText = async ({
return result.files[0];
};

const stripLegacyImageField = (
advancedOptions: DynamicPropsValue | undefined,
): DynamicPropsValue | undefined => {
if (isNil(advancedOptions)) {
return advancedOptions;
}
const { image: _legacy, ...rest } = advancedOptions as Record<string, unknown>;
return rest as DynamicPropsValue;
};

const ALLOWED_IMAGE_MIME_TYPES: ReadonlySet<string> = new Set([
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/avif',
]);

const assertImageGenerationSuccess = (
result: GenerateTextResult<ToolSet, never>
): void => {
Expand Down
2 changes: 1 addition & 1 deletion packages/pieces/community/baserow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@activepieces/piece-baserow",
"version": "0.7.0",
"version": "0.8.0",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"scripts": {
Expand Down
13 changes: 11 additions & 2 deletions packages/pieces/community/baserow/src/i18n/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"Open-source online database tool, alternative to Airtable": "Open-source online database tool, alternative to Airtable",
"API URL": "API URL",
"Database Token": "Database Token",
"\n 1. Log in to your Baserow Account.\n 2. Click on your profile-pic(top-left) and navigate to **Settings->Database tokens**.\n 3. Create new token with any name and appropriate workspace.\n 4. After token creation,click on **:** right beside token name and copy database token.\n 5. Enter your Baserow API URL.If you are using baserow.io, you can leave the default one.": "\n 1. Log in to your Baserow Account.\n 2. Click on your profile-pic(top-left) and navigate to **Settings->Database tokens**.\n 3. Create new token with any name and appropriate workspace.\n 4. After token creation,click on **:** right beside token name and copy database token.\n 5. Enter your Baserow API URL.If you are using baserow.io, you can leave the default one.",
"Create Row": "Create Row",
"Delete Row": "Delete Row",
"Get Row": "Get Row",
Expand Down Expand Up @@ -111,5 +110,15 @@
"OR": "OR",
"Filters": "Filters",
"List of filters. Each filter is an object with \"field\" (field ID as number), \"type\" (operator), and \"value\" (filter value).": "List of filters. Each filter is an object with \"field\" (field ID as number), \"type\" (operator), and \"value\" (filter value).",
"Authenticate with your Baserow email and password. This mode enables automatic webhook registration for triggers — no manual setup needed.\n\n**Note:** Two-factor authentication (2FA) is not supported. If your Baserow account has 2FA enabled, use the Database Token authentication instead.": "Authenticate with your Baserow email and password. This mode enables automatic webhook registration for triggers — no manual setup needed.\n\n**Note:** Two-factor authentication (2FA) is not supported. If your Baserow account has 2FA enabled, use the Database Token authentication instead."
"Row Event": "Row Event",
"Triggers when a row is created, updated, or deleted in a Baserow table. To react to only one event type, use the dedicated Row Created, Row Updated, or Row Deleted triggers.": "Triggers when a row is created, updated, or deleted in a Baserow table. To react to only one event type, use the dedicated Row Created, Row Updated, or Row Deleted triggers.",
"Create missing select options": "Create missing select options",
"When enabled, single/multi-select values that do not yet exist in the field will be added before creating the row. Existing options are preserved.": "When enabled, single/multi-select values that do not yet exist in the field will be added before creating the row. Existing options are preserved.",
"When enabled, single/multi-select values that do not yet exist in the field will be added before updating the row. Existing options are preserved.": "When enabled, single/multi-select values that do not yet exist in the field will be added before updating the row. Existing options are preserved.",
"Authentication": "Authentication",
"Authentication Method": "Authentication Method",
"Choose how you want to authenticate with Baserow:\n\n**Database Token** — recommended. Per-table CRUD scoping, compatible with 2FA accounts. Triggers require manual webhook setup.\n 1. Log in to your Baserow account.\n 2. Click on your profile picture (top-left) and go to **Settings → Database tokens**.\n 3. Create a new token, then click **:** beside the token name to copy it.\n 4. Paste it into **Database Token** below. Leave **Email** and **Password** empty.\n\n**Email & Password (JWT)** — workspace-wide access, enables automatic webhook registration for triggers. Not compatible with accounts that have 2FA enabled.\n 1. Fill in **Email** and **Password** with your Baserow login credentials. Leave **Database Token** empty.\n\nIn both modes, set **API URL** to your Baserow instance (default: `https://api.baserow.io`).": "Choose how you want to authenticate with Baserow:\n\n**Database Token** — recommended. Per-table CRUD scoping, compatible with 2FA accounts. Triggers require manual webhook setup.\n 1. Log in to your Baserow account.\n 2. Click on your profile picture (top-left) and go to **Settings → Database tokens**.\n 3. Create a new token, then click **:** beside the token name to copy it.\n 4. Paste it into **Database Token** below. Leave **Email** and **Password** empty.\n\n**Email & Password (JWT)** — workspace-wide access, enables automatic webhook registration for triggers. Not compatible with accounts that have 2FA enabled.\n 1. Fill in **Email** and **Password** with your Baserow login credentials. Leave **Database Token** empty.\n\nIn both modes, set **API URL** to your Baserow instance (default: `https://api.baserow.io`).",
"Database Token is recommended. Use Email & Password (JWT) only if you need automatic webhook registration on triggers.": "Database Token is recommended. Use Email & Password (JWT) only if you need automatic webhook registration on triggers.",
"Required if Authentication Method is **Database Token**. Leave empty for JWT.": "Required if Authentication Method is **Database Token**. Leave empty for JWT.",
"Required if Authentication Method is **Email & Password (JWT)**. Leave empty for Database Token.": "Required if Authentication Method is **Email & Password (JWT)**. Leave empty for Database Token."
}
Loading
Loading