diff --git a/api/models/schema/assistant.js b/api/models/schema/assistant.js index a4ec36e199..67eb8e8e72 100644 --- a/api/models/schema/assistant.js +++ b/api/models/schema/assistant.js @@ -9,7 +9,6 @@ const assistantSchema = mongoose.Schema( }, assistant_id: { type: String, - unique: true, index: true, required: true, }, diff --git a/api/server/routes/assistants/actions.js b/api/server/routes/assistants/actions.js index eb55498603..55de9d944b 100644 --- a/api/server/routes/assistants/actions.js +++ b/api/server/routes/assistants/actions.js @@ -17,7 +17,7 @@ const router = express.Router(); */ router.get('/', async (req, res) => { try { - res.json(await getActions({ user: req.user.id })); + res.json(await getActions()); } catch (error) { res.status(500).json({ error: error.message }); } @@ -55,9 +55,9 @@ router.post('/:assistant_id', async (req, res) => { /** @type {{ openai: OpenAI }} */ const { openai } = await initializeClient({ req, res }); - initialPromises.push(getAssistant({ assistant_id, user: req.user.id })); + initialPromises.push(getAssistant({ assistant_id })); initialPromises.push(openai.beta.assistants.retrieve(assistant_id)); - !!_action_id && initialPromises.push(getActions({ user: req.user.id, action_id }, true)); + !!_action_id && initialPromises.push(getActions({ action_id }, true)); /** @type {[AssistantDocument, Assistant, [Action|undefined]]} */ const [assistant_data, assistant, actions_result] = await Promise.all(initialPromises); @@ -115,14 +115,15 @@ router.post('/:assistant_id', async (req, res) => { const promises = []; promises.push( updateAssistant( - { assistant_id, user: req.user.id }, + { assistant_id }, { actions, + user: req.user.id, }, ), ); promises.push(openai.beta.assistants.update(assistant_id, { tools })); - promises.push(updateAction({ action_id, user: req.user.id }, { metadata, assistant_id })); + promises.push(updateAction({ action_id }, { metadata, assistant_id, user: req.user.id })); /** @type {[AssistantDocument, Assistant, Action]} */ const resolved = await Promise.all(promises); @@ -155,7 +156,7 @@ router.delete('/:assistant_id/:action_id', async (req, res) => { const { openai } = await initializeClient({ req, res }); const initialPromises = []; - initialPromises.push(getAssistant({ assistant_id, user: req.user.id })); + initialPromises.push(getAssistant({ assistant_id })); initialPromises.push(openai.beta.assistants.retrieve(assistant_id)); /** @type {[AssistantDocument, Assistant]} */ @@ -180,14 +181,15 @@ router.delete('/:assistant_id/:action_id', async (req, res) => { const promises = []; promises.push( updateAssistant( - { assistant_id, user: req.user.id }, + { assistant_id }, { actions: updatedActions, + user: req.user.id, }, ), ); promises.push(openai.beta.assistants.update(assistant_id, { tools: updatedTools })); - promises.push(deleteAction({ action_id, user: req.user.id })); + promises.push(deleteAction({ action_id })); await Promise.all(promises); res.status(200).json({ message: 'Action deleted successfully' }); diff --git a/api/server/routes/assistants/assistants.js b/api/server/routes/assistants/assistants.js index 861521eaf5..d9c58e6600 100644 --- a/api/server/routes/assistants/assistants.js +++ b/api/server/routes/assistants/assistants.js @@ -241,12 +241,13 @@ router.post('/avatar/:assistant_id', upload.single('file'), async (req, res) => const promises = []; promises.push( updateAssistant( - { assistant_id, user: req.user.id }, + { assistant_id }, { avatar: { filepath: image.filepath, source: req.app.locals.fileStrategy, }, + user: req.user.id, }, ), ); diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 76e846af8c..7b3466239f 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -6,13 +6,13 @@ const { logger } = require('~/config'); /** * Loads action sets based on the user and assistant ID. * - * @param {Object} params - The parameters for loading action sets. - * @param {string} params.user - The user identifier. - * @param {string} params.assistant_id - The assistant identifier. + * @param {Object} searchParams - The parameters for loading action sets. + * @param {string} searchParams.user - The user identifier. + * @param {string} searchParams.assistant_id - The assistant identifier. * @returns {Promise} A promise that resolves to an array of actions or `null` if no match. */ -async function loadActionSets({ user, assistant_id }) { - return await getActions({ user, assistant_id }, true); +async function loadActionSets(searchParams) { + return await getActions(searchParams, true); } /** @@ -40,7 +40,9 @@ function createActionTool({ action, requestBuilder }) { logger.error(`API call to ${action.metadata.domain} failed`, error); if (error.response) { const { status, data } = error.response; - return `API call to ${action.metadata.domain} failed with status ${status}: ${data}`; + return `API call to ${ + action.metadata.domain + } failed with status ${status}: ${JSON.stringify(data)}`; } return `API call to ${action.metadata.domain} failed.`; diff --git a/api/server/utils/crypto.js b/api/server/utils/crypto.js index 911819634b..8989084e5a 100644 --- a/api/server/utils/crypto.js +++ b/api/server/utils/crypto.js @@ -30,6 +30,10 @@ function encryptV2(value) { function decryptV2(encryptedValue) { const parts = encryptedValue.split(':'); + // Already decrypted from an earlier invocation + if (parts.length === 1) { + return parts[0]; + } const gen_iv = Buffer.from(parts.shift(), 'hex'); const encrypted = parts.join(':'); const decipher = crypto.createDecipheriv(algorithm, key, gen_iv); diff --git a/packages/data-provider/specs/actions.spec.ts b/packages/data-provider/specs/actions.spec.ts index f2ca0aedf3..09ddf857b1 100644 --- a/packages/data-provider/specs/actions.spec.ts +++ b/packages/data-provider/specs/actions.spec.ts @@ -164,6 +164,39 @@ describe('ActionRequest', () => { ); await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: INVALID'); }); + + it('replaces path parameters with values from toolInput', async () => { + const actionRequest = new ActionRequest( + 'https://example.com', + '/stocks/{stocksTicker}/bars/{multiplier}', + 'GET', + 'getAggregateBars', + false, + 'application/json', + ); + + await actionRequest.setParams({ + stocksTicker: 'AAPL', + multiplier: 5, + startDate: '2023-01-01', + endDate: '2023-12-31', + }); + + expect(actionRequest.path).toBe('/stocks/AAPL/bars/5'); + expect(actionRequest.params).toEqual({ + startDate: '2023-01-01', + endDate: '2023-12-31', + }); + + await actionRequest.execute(); + expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/stocks/AAPL/bars/5', { + headers: expect.anything(), + params: { + startDate: '2023-01-01', + endDate: '2023-12-31', + }, + }); + }); }); it('throws an error for unsupported HTTP method', async () => { diff --git a/packages/data-provider/src/actions.ts b/packages/data-provider/src/actions.ts index 56113ba132..a13a9790aa 100644 --- a/packages/data-provider/src/actions.ts +++ b/packages/data-provider/src/actions.ts @@ -95,6 +95,14 @@ export class ActionRequest { async setParams(params: object) { this.operationHash = sha1(JSON.stringify(params)); this.params = params; + + for (const [key, value] of Object.entries(params)) { + const paramPattern = `{${key}}`; + if (this.path.includes(paramPattern)) { + this.path = this.path.replace(paramPattern, encodeURIComponent(value as string)); + delete (this.params as Record)[key]; + } + } } async setAuth(metadata: ActionMetadata) {