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,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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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.'
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user