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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,299 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const createIndex = createAction({
auth: pineconeAuth,
name: 'create_index',
displayName: 'Create Index',
description: 'Creates a new Pinecone index with custom settings.',
props: {
name: Property.ShortText({
displayName: 'Index Name',
description:
'You must pass a non-empty string for name in order to create an index',
required: true
}),
dimension: Property.Number({
displayName: 'Dimension',
description:
'You must pass a positive integer for dimension in order to create an index. For dense indexes, this is required.',
required: true
}),
indexType: Property.StaticDropdown({
displayName: 'Index Type',
description: 'Choose between serverless or pod-based index deployment',
required: true,
options: {
options: [
{ label: 'Serverless', value: 'serverless' },
{ label: 'Pod-based', value: 'pod' }
]
},
defaultValue: 'serverless'
}),
cloud: Property.StaticDropdown({
displayName: 'Cloud Provider',
description:
'The public cloud where you would like your index hosted (for serverless)',
required: false,
options: {
options: [
{ label: 'AWS', value: 'aws' },
{ label: 'GCP', value: 'gcp' },
{ label: 'Azure', value: 'azure' }
]
},
defaultValue: 'aws'
}),
region: Property.ShortText({
displayName: 'Region',
description:
'The region where you would like your index to be created (for serverless)',
required: false,
defaultValue: 'us-west-2'
}),
environment: Property.ShortText({
displayName: 'Environment',
description:
'The environment where the index is hosted (for pod-based indexes)',
required: false
}),
podType: Property.StaticDropdown({
displayName: 'Pod Type',
description: 'The type of pod to use',
required: false,
options: {
options: [
{ label: 's1.x1', value: 's1.x1' },
{ label: 's1.x2', value: 's1.x2' },
{ label: 's1.x4', value: 's1.x4' },
{ label: 's1.x8', value: 's1.x8' },
{ label: 'p1.x1', value: 'p1.x1' },
{ label: 'p1.x2', value: 'p1.x2' },
{ label: 'p1.x4', value: 'p1.x4' },
{ label: 'p1.x8', value: 'p1.x8' },
{ label: 'p2.x1', value: 'p2.x1' },
{ label: 'p2.x2', value: 'p2.x2' },
{ label: 'p2.x4', value: 'p2.x4' },
{ label: 'p2.x8', value: 'p2.x8' }
]
},
defaultValue: 'p1.x1'
}),
replicas: Property.Number({
displayName: 'Replicas',
description:
'The number of replicas. Replicas duplicate your index for higher availability and throughput.',
required: false
}),
shards: Property.Number({
displayName: 'Shards',
description:
'The number of shards. Shards split your data across multiple pods.',
required: false
}),
pods: Property.Number({
displayName: 'Pods',
description:
'The number of pods to be used in the index. This should be equal to shards x replicas.',
required: false
}),
metric: Property.StaticDropdown({
displayName: 'Metric',
description:
'The distance metric to use. Defaults to cosine for dense indexes, dotproduct for sparse indexes.',
required: false,
options: {
options: [
{ label: 'Cosine', value: 'cosine' },
{ label: 'Euclidean', value: 'euclidean' },
{ label: 'Dot Product', value: 'dotproduct' }
]
}
}),
vectorType: Property.StaticDropdown({
displayName: 'Vector Type',
description:
'The type of vectors to store. Dense is default for most use cases.',
required: false,
options: {
options: [
{ label: 'Dense', value: 'dense' },
{ label: 'Sparse', value: 'sparse' }
]
},
defaultValue: 'dense'
}),
deletionProtection: Property.Checkbox({
displayName: 'Deletion Protection',
description: 'Enable deletion protection for the index',
required: false,
defaultValue: false
}),
waitUntilReady: Property.Checkbox({
displayName: 'Wait Until Ready',
description:
'Wait until the index is ready to receive data before completing',
required: false,
defaultValue: false
}),
suppressConflicts: Property.Checkbox({
displayName: 'Suppress Conflicts',
description:
'Do not throw if you attempt to create an index that already exists',
required: false,
defaultValue: false
}),
tags: Property.Json({
displayName: 'Tags',
description:
'Optional tags for the index (e.g., {"team": "data-science"})',
required: false
}),
sourceCollection: Property.ShortText({
displayName: 'Source Collection',
description:
'The name of the collection to be used as the source for the index',
required: false
})
},
async run(context) {
const {
name,
dimension,
indexType,
cloud,
region,
environment,
podType,
replicas,
shards,
pods,
metric,
vectorType,
deletionProtection,
waitUntilReady,
suppressConflicts,
tags,
sourceCollection
} = context.propsValue;
if (!name) {
throw new Error(
'You must pass a non-empty string for `name` in order to create an index.'
);
}
if (dimension && dimension <= 0) {
throw new Error(
'You must pass a positive integer for `dimension` in order to create an index.'
);
}
const vType = vectorType?.toLowerCase() || 'dense';
if (vType === 'sparse') {
if (dimension && dimension > 0) {
throw new Error('Sparse indexes cannot have a `dimension`.');
}
if (metric && metric !== 'dotproduct') {
throw new Error('Sparse indexes must have a `metric` of `dotproduct`.');
}
} else if (vType === 'dense') {
if (!dimension || dimension <= 0) {
throw new Error(
'You must pass a positive `dimension` when creating a dense index.'
);
}
}
const pc = createPineconeClientFromAuth(context.auth);
try {
let spec: any;
if (indexType === 'serverless') {
if (!cloud) {
throw new Error(
'You must pass a `cloud` for the serverless `spec` object in order to create an index.'
);
}
if (!region) {
throw new Error(
'You must pass a `region` for the serverless `spec` object in order to create an index.'
);
}
spec = {
serverless: {
cloud: cloud,
region: region,
...(sourceCollection && { sourceCollection })
}
};
} else if (indexType === 'pod') {
if (!environment) {
throw new Error(
'You must pass an `environment` for the pod `spec` object in order to create an index.'
);
}
if (!podType) {
throw new Error(
'You must pass a `podType` for the pod `spec` object in order to create an index.'
);
}
spec = {
pod: {
environment: environment,
podType: podType,
...(replicas && { replicas }),
...(shards && { shards }),
...(pods && { pods }),
...(sourceCollection && { sourceCollection })
}
};
}
const createIndexOptions: any = {
name: name,
dimension: dimension,
spec: spec,
...(metric && { metric }),
...(vectorType && { vector_type: vectorType }),
...(deletionProtection !== undefined && {
deletionProtection: deletionProtection ? 'enabled' : 'disabled'
}),
...(tags && { tags }),
waitUntilReady: waitUntilReady || false,
suppressConflicts: suppressConflicts || false
};
if (!createIndexOptions.metric) {
if (vType === 'sparse') {
createIndexOptions.metric = 'dotproduct';
} else {
createIndexOptions.metric = 'cosine';
}
}
const response = await pc.createIndex(createIndexOptions);
return {
success: true,
indexName: name,
dimension: dimension,
indexType: indexType,
spec: spec,
metric: createIndexOptions.metric,
vectorType: vType,
...(tags && { tags }),
message: 'Index created successfully',
...(response && { response })
};
} catch (caught) {
console.log('Failed to create index.', caught);
return caught;
}
}
});

View File

@@ -0,0 +1,207 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const deleteVector = createAction({
auth: pineconeAuth,
name: 'delete_vector',
displayName: 'Delete a Vector',
description: 'Delete vectors by ID from a namespace.',
props: {
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the index to delete vectors from',
required: true
}),
indexHost: Property.ShortText({
displayName: 'Index Host',
description:
'The unique host for the index (optional, see Pinecone docs for targeting an index)',
required: false
}),
namespace: Property.ShortText({
displayName: 'Namespace',
description:
'The namespace to delete vectors from (e.g., "example-namespace")',
required: false,
defaultValue: 'example-namespace'
}),
deleteMode: Property.StaticDropdown({
displayName: 'Delete Mode',
description: 'Choose how to delete vectors',
required: true,
options: {
options: [
{ label: 'Delete One Vector', value: 'one' },
{ label: 'Delete Multiple Vectors', value: 'many' },
{ label: 'Delete All Vectors', value: 'all' },
{ label: 'Delete by Filter', value: 'filter' }
]
},
defaultValue: 'one'
}),
id: Property.ShortText({
displayName: 'Vector ID',
description:
'The ID of the vector to delete (for single vector deletion)',
required: false
}),
ids: Property.Array({
displayName: 'Vector IDs',
description: 'Array of vector IDs to delete (e.g., ["id-2", "id-3"])',
required: false
}),
confirmDeleteAll: Property.Checkbox({
displayName: 'Confirm Delete All',
description:
'Check this box to confirm you want to delete ALL vectors in the namespace',
required: false,
defaultValue: false
}),
filter: Property.Json({
displayName: 'Metadata Filter',
description: 'Metadata filter expression to select vectors to delete. Examples:\n• {"genre": {"$eq": "documentary"}}\n• {"year": {"$gt": 2019}}\n• {"$and": [{"genre": "comedy"}, {"year": {"$gte": 2020}}]}',
required: false
})
},
async run(context) {
const {
indexName,
indexHost,
namespace,
deleteMode,
id,
ids,
confirmDeleteAll,
filter
} = context.propsValue;
if (!indexName) {
throw new Error('You must provide an index name to delete vectors.');
}
const pc = createPineconeClientFromAuth(context.auth);
try {
const index = indexHost
? pc.index(indexName, indexHost)
: pc.index(indexName);
const ns = namespace ? index.namespace(namespace) : index;
let deleteResult: any;
let deletedCount = 0;
let operation = '';
if (deleteMode === 'one') {
if (!id) {
throw new Error(
'You must provide a vector ID for single vector deletion.'
);
}
await ns.deleteOne(id);
deletedCount = 1;
operation = `Deleted vector with ID: ${id}`;
} else if (deleteMode === 'many') {
if (!ids || !Array.isArray(ids) || ids.length === 0) {
throw new Error(
'You must provide an array of vector IDs for multiple vector deletion.'
);
}
await ns.deleteMany(ids as string[]);
deletedCount = ids.length;
operation = `Deleted ${ids.length} vectors with IDs: ${(
ids as string[]
).join(', ')}`;
} else if (deleteMode === 'all') {
if (!confirmDeleteAll) {
throw new Error(
'You must confirm deletion by checking "Confirm Delete All" to delete all vectors in the namespace.'
);
}
deleteResult = await ns.deleteAll();
operation = `Deleted ALL vectors in namespace: ${
namespace || 'default'
}`;
} else if (deleteMode === 'filter') {
if (!filter) {
throw new Error(
'You must provide a metadata filter for filter-based deletion.'
);
}
await ns.deleteMany(filter);
operation = `Deleted vectors matching filter: ${JSON.stringify(filter)}`;
}
return {
success: true,
indexName: indexName,
namespace: namespace || 'default',
deleteMode: deleteMode,
operation: operation,
...(deletedCount > 0 && { deletedCount }),
...(deleteResult && { deleteResult }),
message: `Successfully completed delete operation: ${operation}`
};
} catch (caught) {
console.log('Failed to delete vector(s).', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message: error.message || 'The request body included invalid request parameters.',
details: error.details || [],
indexName: indexName,
namespace: namespace || 'default',
};
}
if (error.status >= 400 && error.status < 500) {
return {
success: false,
error: 'Client Error',
code: error.status || error.code,
message: error.message || 'An unexpected client error occurred.',
details: error.details || [],
indexName: indexName,
namespace: namespace || 'default',
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || [],
indexName: indexName,
namespace: namespace || 'default',
};
}
}
return {
success: false,
error: 'Unknown Error',
message: caught instanceof Error ? caught.message : 'An unexpected error occurred while deleting vectors.',
indexName: indexName,
namespace: namespace || 'default',
};
}
}
});

View File

@@ -0,0 +1,141 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const getVector = createAction({
auth: pineconeAuth,
name: 'get_vector',
displayName: 'Get a Vector',
description: 'Look up and return vectors by ID from a namespace.',
props: {
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the index to fetch vectors from',
required: true,
}),
indexHost: Property.ShortText({
displayName: 'Index Host',
description: 'The unique host for the index (optional, see Pinecone docs for targeting an index)',
required: false,
}),
ids: Property.Array({
displayName: 'Vector IDs',
description: 'The vector IDs to fetch. Does not accept values containing spaces (e.g., ["id-1", "id-2"])',
required: true,
}),
namespace: Property.ShortText({
displayName: 'Namespace',
description: 'The namespace containing the vectors to fetch (e.g., "example-namespace")',
required: false,
defaultValue: 'example-namespace',
}),
},
async run(context) {
const {
indexName,
indexHost,
ids,
namespace,
} = context.propsValue;
if (!indexName) {
throw new Error('You must provide an index name to fetch vectors.');
}
if (!ids || !Array.isArray(ids) || ids.length === 0) {
throw new Error('You must provide at least one vector ID to fetch.');
}
const invalidIds = ids.filter((id: any) => typeof id === 'string' && id.includes(' '));
if (invalidIds.length > 0) {
throw new Error(`Vector IDs cannot contain spaces. Invalid IDs: ${invalidIds.join(', ')}`);
}
const pc = createPineconeClientFromAuth(context.auth);
try {
const index = indexHost ? pc.index(indexName, indexHost) : pc.index(indexName);
const fetchResult = namespace
? await index.namespace(namespace).fetch(ids as string[])
: await index.fetch(ids as string[]);
const records = fetchResult.records || {};
const vectorCount = Object.keys(records).length;
const foundIds = Object.keys(records);
const notFoundIds = (ids as string[]).filter(id => !foundIds.includes(id));
return {
success: true,
indexName: indexName,
namespace: fetchResult.namespace,
usage: fetchResult.usage,
vectors: records,
summary: {
requested: ids.length,
found: vectorCount,
foundIds: foundIds,
...(notFoundIds.length > 0 && { notFoundIds: notFoundIds }),
},
message: `Successfully fetched ${vectorCount} out of ${ids.length} requested vector(s)`,
};
} catch (caught) {
console.log('Failed to fetch vector(s).', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message: error.message || 'The request body included invalid request parameters.',
details: error.details || [],
requestedIds: ids,
indexName: indexName,
namespace: namespace || 'default',
};
}
if (error.status >= 400 && error.status < 500) {
return {
success: false,
error: 'Client Error',
code: error.status || error.code,
message: error.message || 'An unexpected client error occurred.',
details: error.details || [],
requestedIds: ids,
indexName: indexName,
namespace: namespace || 'default',
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || [],
requestedIds: ids,
indexName: indexName,
namespace: namespace || 'default',
};
}
}
return {
success: false,
error: 'Unknown Error',
message: caught instanceof Error ? caught.message : 'An unexpected error occurred while fetching vectors.',
requestedIds: ids,
indexName: indexName,
namespace: namespace || 'default',
};
}
},
});

View File

@@ -0,0 +1,144 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const searchIndex = createAction({
auth: pineconeAuth,
name: 'search_index',
displayName: 'Search Index',
description: 'Search indexes by name or list all indexes in your project.',
props: {
searchMode: Property.StaticDropdown({
displayName: 'Search Mode',
description: 'Choose how to search for indexes',
required: true,
options: {
options: [
{ label: 'List All Indexes', value: 'list' },
{ label: 'Find Specific Index', value: 'find' }
]
},
defaultValue: 'list'
}),
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the specific index to search for (when using Find Specific Index mode)',
required: false
}),
nameFilter: Property.ShortText({
displayName: 'Name Filter',
description: 'Filter indexes by name (partial match, case-insensitive)',
required: false
})
},
async run(context) {
const { searchMode, indexName, nameFilter } = context.propsValue;
if (searchMode === 'find' && !indexName) {
throw new Error('You must provide an index name when using Find Specific Index mode.');
}
const pc = createPineconeClientFromAuth(context.auth);
try {
if (searchMode === 'find') {
const indexDetails = await pc.describeIndex(indexName!);
return {
success: true,
mode: 'find',
searchTerm: indexName,
found: true,
index: indexDetails,
message: `Successfully found index: ${indexName}`
};
} else {
const indexList = await pc.listIndexes();
const allIndexes = indexList.indexes || [];
let filteredIndexes = allIndexes;
if (nameFilter) {
const filter = nameFilter.toLowerCase();
filteredIndexes = allIndexes.filter(index =>
index.name.toLowerCase().includes(filter)
);
}
return {
success: true,
mode: 'list',
...(nameFilter && { filter: nameFilter }),
indexes: filteredIndexes,
totalCount: allIndexes.length,
filteredCount: filteredIndexes.length,
summary: {
total: allIndexes.length,
returned: filteredIndexes.length,
...(nameFilter && { filtered: true })
},
message: nameFilter
? `Found ${filteredIndexes.length} indexes matching "${nameFilter}" out of ${allIndexes.length} total`
: `Successfully listed ${allIndexes.length} indexes`
};
}
} catch (caught) {
console.log('Failed to search indexes.', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 404 || error.code === 404) {
if (searchMode === 'find') {
return {
success: false,
mode: 'find',
searchTerm: indexName,
found: false,
error: 'Index Not Found',
code: 404,
message: `Index "${indexName}" does not exist in your project.`
};
}
}
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message: error.message || 'The request included invalid parameters.',
details: error.details || []
};
}
if (error.status === 403 || error.code === 403) {
return {
success: false,
error: 'Permission Denied',
code: 403,
message: 'You do not have permission to access indexes in this project.',
details: error.details || []
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || []
};
}
}
return {
success: false,
error: 'Unknown Error',
message: caught instanceof Error
? caught.message
: 'An unexpected error occurred while searching indexes.'
};
}
}
});

View File

@@ -0,0 +1,260 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const searchVector = createAction({
auth: pineconeAuth,
name: 'search_vector',
displayName: 'Search Vectors',
description: 'Search a namespace using a query vector to find similar records.',
props: {
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the index to search in',
required: true
}),
indexHost: Property.ShortText({
displayName: 'Index Host',
description:
'The unique host for the index (optional, see Pinecone docs for targeting an index)',
required: false
}),
topK: Property.Number({
displayName: 'Top K',
description:
'The number of results to return for each query (range: 1-10000)',
required: true,
defaultValue: 10
}),
namespace: Property.ShortText({
displayName: 'Namespace',
description: 'The namespace to query (e.g., "example-namespace")',
required: false,
defaultValue: 'example-namespace'
}),
queryMethod: Property.StaticDropdown({
displayName: 'Query Method',
description: 'Choose how to provide the query',
required: true,
options: {
options: [
{ label: 'Query Vector', value: 'vector' },
{ label: 'Query by ID', value: 'id' }
]
},
defaultValue: 'vector'
}),
vector: Property.Array({
displayName: 'Query Vector',
description:
'The query vector. This should be the same length as the dimension of the index (e.g., [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8])',
required: false
}),
id: Property.ShortText({
displayName: 'Query Vector ID',
description:
'The unique ID of the vector to be used as a query vector (max length: 512)',
required: false
}),
sparseIndices: Property.Array({
displayName: 'Sparse Indices',
description: 'Array of indices for sparse vector data (optional)',
required: false
}),
sparseValues: Property.Array({
displayName: 'Sparse Values',
description:
'Array of sparse values corresponding to indices (must be same length as indices)',
required: false
}),
filter: Property.Json({
displayName: 'Metadata Filter',
description: 'Filter to apply using vector metadata. Examples:\n• {"genre": {"$eq": "documentary"}}\n• {"year": {"$gt": 2019}}\n• {"$and": [{"genre": {"$in": ["comedy", "drama"]}}, {"year": {"$gte": 2020}}]}',
required: false
}),
includeValues: Property.Checkbox({
displayName: 'Include Values',
description: 'Whether vector values are included in the response',
required: false,
defaultValue: false
}),
includeMetadata: Property.Checkbox({
displayName: 'Include Metadata',
description: 'Whether metadata is included in the response',
required: false,
defaultValue: false
})
},
async run(context) {
const {
indexName,
indexHost,
topK,
namespace,
queryMethod,
vector,
id,
sparseIndices,
sparseValues,
filter,
includeValues,
includeMetadata
} = context.propsValue;
if (!indexName) {
throw new Error('You must provide an index name to search vectors.');
}
if (!topK || topK < 1 || topK > 10000) {
throw new Error('topK must be between 1 and 10000.');
}
if (queryMethod === 'vector') {
if (!vector || !Array.isArray(vector) || vector.length === 0) {
throw new Error(
'You must provide a query vector when using vector query method.'
);
}
} else if (queryMethod === 'id') {
if (!id || typeof id !== 'string' || id.length === 0 || id.length > 512) {
throw new Error(
'You must provide a valid vector ID (1-512 characters) when using ID query method.'
);
}
}
const pc = createPineconeClientFromAuth(context.auth);
let queryRequest: any = {};
try {
const index = indexHost
? pc.index(indexName, indexHost)
: pc.index(indexName);
queryRequest = {
topK: topK,
includeValues: includeValues || false,
includeMetadata: includeMetadata || false
};
if (queryMethod === 'vector') {
if (vector && Array.isArray(vector)) {
queryRequest.vector = vector.map((v) => Number(v));
}
} else if (queryMethod === 'id') {
if (id) {
queryRequest.id = id;
}
}
if (sparseIndices && sparseValues) {
if (!Array.isArray(sparseIndices) || !Array.isArray(sparseValues)) {
throw new Error('Sparse indices and values must be arrays.');
}
if (sparseIndices.length !== sparseValues.length) {
throw new Error(
'Sparse indices and values arrays must have the same length.'
);
}
queryRequest.sparseVector = {
indices: sparseIndices.map((i) => Number(i)),
values: sparseValues.map((v) => Number(v))
};
}
if (filter) {
queryRequest.filter = filter;
}
const queryResponse = namespace
? await index.namespace(namespace).query(queryRequest)
: await index.query(queryRequest);
const matches = queryResponse?.matches || [];
const usage = queryResponse?.usage || { readUnits: 0 };
return {
success: true,
indexName: indexName,
namespace: queryResponse?.namespace || namespace || 'default',
matches: matches,
usage: usage,
query: {
topK: topK,
method: queryMethod,
...(queryMethod === 'vector' && { vectorDimension: vector?.length }),
...(queryMethod === 'id' && { queryId: id }),
...(filter && { filter: filter })
},
summary: {
matchCount: matches.length,
topScore: matches.length > 0 ? matches[0]?.score : null,
readUnits: usage.readUnits || usage.readUnits || 0
},
message: `Successfully found ${matches.length} matches`
};
} catch (caught) {
console.log('Failed to search vectors.', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message:
error.message ||
'The request body included invalid request parameters.',
details: error.details || [],
query: queryRequest,
indexName: indexName,
namespace: namespace || 'default'
};
}
if (error.status >= 400 && error.status < 500) {
return {
success: false,
error: 'Client Error',
code: error.status || error.code,
message: error.message || 'An unexpected client error occurred.',
details: error.details || [],
query: queryRequest,
indexName: indexName,
namespace: namespace || 'default'
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || [],
query: queryRequest,
indexName: indexName,
namespace: namespace || 'default'
};
}
}
return {
success: false,
error: 'Unknown Error',
message:
caught instanceof Error
? caught.message
: 'An unexpected error occurred while searching vectors.',
query: queryRequest,
indexName: indexName,
namespace: namespace || 'default'
};
}
}
});

View File

@@ -0,0 +1,215 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const updateVector = createAction({
auth: pineconeAuth,
name: 'update_vector',
displayName: 'Update a Vector',
description: 'Updates a vector in a namespace. Overwrites existing values and metadata.',
props: {
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the index containing the vector to update',
required: true
}),
indexHost: Property.ShortText({
displayName: 'Index Host',
description:
'The unique host for the index (optional, see Pinecone docs for targeting an index)',
required: false
}),
id: Property.ShortText({
displayName: 'Vector ID',
description: "Vector's unique id (required, string length: 1 - 512)",
required: true
}),
namespace: Property.ShortText({
displayName: 'Namespace',
description:
'The namespace containing the vector to update (e.g., "example-namespace")',
required: false,
defaultValue: 'example-namespace'
}),
values: Property.Array({
displayName: 'Vector Values',
description:
'Vector data to update (e.g., [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8])',
required: false
}),
sparseIndices: Property.Array({
displayName: 'Sparse Indices',
description: 'Array of indices for sparse values (optional)',
required: false
}),
sparseValues: Property.Array({
displayName: 'Sparse Values',
description:
'Array of sparse values corresponding to indices (must be same length as indices)',
required: false
}),
metadata: Property.Array({
displayName: 'Metadata',
description: 'Key-value pairs to set for the vector',
required: false,
properties: {
key: Property.ShortText({
displayName: 'Key',
description: 'Metadata field name',
required: true
}),
value: Property.ShortText({
displayName: 'Value',
description: 'Metadata field value',
required: true
})
}
})
},
async run(context) {
const {
indexName,
indexHost,
id,
namespace,
values,
sparseIndices,
sparseValues,
metadata
} = context.propsValue;
if (!indexName) {
throw new Error('You must provide an index name to update a vector.');
}
if (!id || typeof id !== 'string' || id.length === 0 || id.length > 512) {
throw new Error(
'Vector ID is required and must be a string with length 1-512 characters.'
);
}
const pc = createPineconeClientFromAuth(context.auth);
try {
const index = indexHost
? pc.index(indexName, indexHost)
: pc.index(indexName);
const updateRequest: any = {
id: id
};
if (values && Array.isArray(values) && values.length > 0) {
updateRequest.values = values.map((v) => Number(v));
}
if (sparseIndices && sparseValues) {
if (!Array.isArray(sparseIndices) || !Array.isArray(sparseValues)) {
throw new Error('Sparse indices and values must be arrays.');
}
if (sparseIndices.length !== sparseValues.length) {
throw new Error(
'Sparse indices and values arrays must have the same length.'
);
}
updateRequest.sparseValues = {
indices: sparseIndices.map((i) => Number(i)),
values: sparseValues.map((v) => Number(v))
};
}
if (metadata && Array.isArray(metadata) && metadata.length > 0) {
const metadataObj: any = {};
for (const item of metadata) {
const metaItem = item as any;
const key = String(metaItem.key);
const value = metaItem.value;
const numValue = Number(value);
metadataObj[key] = isNaN(numValue) ? value : numValue;
}
updateRequest.metadata = metadataObj;
}
namespace
? await index.namespace(namespace).update(updateRequest)
: await index.update(updateRequest);
return {
success: true,
indexName: indexName,
namespace: namespace || 'default',
vectorId: id,
updatedFields: {
...(updateRequest.values && {
values: `${updateRequest.values.length} values`
}),
...(updateRequest.sparseValues && {
sparseValues: `${updateRequest.sparseValues.indices.length} sparse values`
}),
...(updateRequest.metadata && {
metadata:
Object.keys(updateRequest.metadata).length + ' metadata fields'
})
},
message: `Successfully updated vector '${id}'`
};
} catch (caught) {
console.log('Failed to update vector.', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message:
error.message ||
'The request body included invalid request parameters.',
details: error.details || [],
vectorId: id,
indexName: indexName
};
}
if (error.status >= 400 && error.status < 500) {
return {
success: false,
error: 'Client Error',
code: error.status || error.code,
message: error.message || 'An unexpected client error occurred.',
details: error.details || [],
vectorId: id,
indexName: indexName
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || [],
vectorId: id,
indexName: indexName
};
}
}
return {
success: false,
error: 'Unknown Error',
message:
caught instanceof Error
? caught.message
: 'An unexpected error occurred while updating the vector.',
vectorId: id,
indexName: indexName
};
}
}
});

View File

@@ -0,0 +1,301 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { createPineconeClientFromAuth } from '../common/pinecone-client';
import { pineconeAuth } from '../../index';
export const upsertVector = createAction({
auth: pineconeAuth,
name: 'upsert_vector',
displayName: 'Upsert Vector',
description: 'Upsert vectors into a namespace. Overwrites existing vectors with the same ID.',
props: {
indexName: Property.ShortText({
displayName: 'Index Name',
description: 'The name of the index to upsert vectors into',
required: true
}),
indexHost: Property.ShortText({
displayName: 'Index Host',
description:
'The unique host for the index (optional, see Pinecone docs for targeting an index)',
required: false
}),
namespace: Property.ShortText({
displayName: 'Namespace',
description:
'The namespace where you upsert vectors (e.g., "example-namespace")',
required: false,
defaultValue: 'example-namespace'
}),
vectorsInput: Property.StaticDropdown({
displayName: 'Input Method',
description: 'Choose how to provide vector data',
required: true,
options: {
options: [
{ label: 'Single Vector', value: 'single' },
{ label: 'Multiple Vectors (JSON)', value: 'multiple' }
]
},
defaultValue: 'single'
}),
id: Property.ShortText({
displayName: 'Vector ID',
description: 'The unique identifier for the vector (e.g., "vec1")',
required: false
}),
values: Property.Array({
displayName: 'Vector Values',
description:
'Array of numbers representing the vector (e.g., [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1])',
required: false
}),
vectors: Property.Array({
displayName: 'Vectors',
description: 'Array of vectors to upsert (for multiple vectors input)',
required: false,
properties: {
id: Property.ShortText({
displayName: 'Vector ID',
description: 'Unique identifier for this vector',
required: true
}),
values: Property.LongText({
displayName: 'Vector Values',
description: 'Comma-separated numbers (e.g., 0.1,0.2,0.3,0.4)',
required: true
}),
metadataKeys: Property.LongText({
displayName: 'Metadata Keys',
description: 'Comma-separated metadata field names (e.g., genre,year,rating)',
required: false
}),
metadataValues: Property.LongText({
displayName: 'Metadata Values',
description: 'Comma-separated metadata values (e.g., comedy,2020,8.5)',
required: false
}),
sparseIndices: Property.LongText({
displayName: 'Sparse Indices',
description: 'Comma-separated indices for sparse vector (e.g., 1,312,822)',
required: false
}),
sparseValues: Property.LongText({
displayName: 'Sparse Values',
description: 'Comma-separated values for sparse vector (e.g., 0.1,0.2,0.3)',
required: false
})
}
})
},
async run(context) {
const {
indexName,
indexHost,
namespace,
vectorsInput,
id,
values,
vectors
} = context.propsValue;
if (!indexName) {
throw new Error('You must provide an index name to upsert vectors.');
}
const pc = createPineconeClientFromAuth(context.auth);
let vectorsToUpsert: any[] = [];
try {
const index = indexHost
? pc.index(indexName, indexHost)
: pc.index(indexName);
if (vectorsInput === 'single') {
if (!id) {
throw new Error(
'You must provide a vector ID for single vector input.'
);
}
if (!values || !Array.isArray(values) || values.length === 0) {
throw new Error(
'You must provide vector values as a non-empty array of numbers.'
);
}
const record: any = {
id: id,
values: values.map((v) => Number(v))
};
vectorsToUpsert = [record];
} else if (vectorsInput === 'multiple') {
if (!vectors || !Array.isArray(vectors) || vectors.length === 0) {
throw new Error(
'You must provide vectors array when using multiple vectors input.'
);
}
if (vectors.length > 1000) {
throw new Error(
'Recommended batch limit is up to 1000 vectors. Please reduce the number of vectors.'
);
}
vectorsToUpsert = vectors.map((vector: any, index: number) => {
if (!vector.id) {
throw new Error(
`Vector at index ${index} must have an ID.`
);
}
if (!vector.values) {
throw new Error(
`Vector at index ${index} must have values.`
);
}
const values = vector.values.split(',').map((v: string) => Number(v.trim()));
if (values.some(isNaN) || values.length === 0) {
throw new Error(
`Vector at index ${index} has invalid values. Use comma-separated numbers.`
);
}
const record: any = {
id: String(vector.id),
values: values
};
if (vector.sparseIndices && vector.sparseValues) {
const sparseIndices = vector.sparseIndices.split(',').map((v: string) => Number(v.trim()));
const sparseValues = vector.sparseValues.split(',').map((v: string) => Number(v.trim()));
if (sparseIndices.some(isNaN) || sparseValues.some(isNaN)) {
throw new Error(
`Vector at index ${index} has invalid sparse values. Use comma-separated numbers.`
);
}
if (sparseIndices.length !== sparseValues.length) {
throw new Error(
`Vector at index ${index}: sparse indices and values must have the same length.`
);
}
record.sparseValues = {
indices: sparseIndices,
values: sparseValues
};
}
if (vector.metadataKeys && vector.metadataValues) {
const keys = vector.metadataKeys.split(',').map((k: string) => k.trim());
const vals = vector.metadataValues.split(',').map((v: string) => v.trim());
if (keys.length !== vals.length) {
throw new Error(
`Vector at index ${index}: metadata keys and values must have the same length.`
);
}
const metadata: any = {};
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = vals[i];
const numValue = Number(value);
metadata[key] = isNaN(numValue) ? value : numValue;
}
record.metadata = metadata;
}
return record;
});
}
const response: any = namespace
? await index.namespace(namespace).upsert(vectorsToUpsert)
: await index.upsert(vectorsToUpsert);
const upsertedCount =
response && typeof response === 'object' && response.upsertedCount
? response.upsertedCount
: vectorsToUpsert.length;
return {
success: true,
indexName: indexName,
namespace: namespace || 'default',
upsertedCount: upsertedCount,
vectorCount: vectorsToUpsert.length,
vectors: vectorsToUpsert.map((v) => ({
id: v.id,
dimension: v.values.length
})),
message: `Successfully upserted ${upsertedCount} vector(s)`
};
} catch (caught) {
console.log('Failed to upsert vector(s).', caught);
if (caught instanceof Error) {
const error = caught as any;
if (error.status === 400 || error.code === 400) {
return {
success: false,
error: 'Bad Request',
code: 400,
message:
error.message ||
'The request body included invalid request parameters.',
details: error.details || [],
vectorCount: vectorsToUpsert?.length || 0,
indexName: indexName,
namespace: namespace || 'default'
};
}
if (error.status >= 400 && error.status < 500) {
return {
success: false,
error: 'Client Error',
code: error.status || error.code,
message: error.message || 'An unexpected client error occurred.',
details: error.details || [],
vectorCount: vectorsToUpsert?.length || 0,
indexName: indexName,
namespace: namespace || 'default'
};
}
if (error.status >= 500 || error.code >= 500) {
return {
success: false,
error: 'Server Error',
code: error.status || error.code,
message: error.message || 'An unexpected server error occurred.',
details: error.details || [],
vectorCount: vectorsToUpsert?.length || 0,
indexName: indexName,
namespace: namespace || 'default'
};
}
}
return {
success: false,
error: 'Unknown Error',
message:
caught instanceof Error
? caught.message
: 'An unexpected error occurred while upserting vectors.',
vectorCount: vectorsToUpsert?.length || 0,
indexName: indexName,
namespace: namespace || 'default'
};
}
}
});

View File

@@ -0,0 +1,48 @@
import { AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { Pinecone, type PineconeConfiguration } from '@pinecone-database/pinecone';
import { pineconeAuth } from '../..';
export interface PineconeAuthConfig {
apiKey: string;
}
export function createPineconeClient(authConfig: PineconeAuthConfig): Pinecone {
if (!authConfig.apiKey) {
throw new Error('Pinecone API key is required. Please provide a valid API key from your Pinecone console.');
}
const config: PineconeConfiguration = {
apiKey: authConfig.apiKey,
};
return new Pinecone(config);
}
export function validateApiKey(apiKey: string): void {
if (!apiKey || typeof apiKey !== 'string') {
throw new Error('Invalid API key: API key must be a non-empty string');
}
if (apiKey.length < 10) {
throw new Error('Invalid API key: API key appears to be too short');
}
}
export function createPineconeClientFromAuth(auth: AppConnectionValueForAuthProperty<typeof pineconeAuth>): Pinecone {
if (typeof auth === 'string') {
return createPineconeClient({ apiKey: auth });
}
const actualAuth = auth.props;
if (!actualAuth || !actualAuth.apiKey) {
throw new Error('Invalid authentication: API key is required');
}
return createPineconeClient({
apiKey: actualAuth.apiKey,
});
}