diff --git a/components/devin/actions/create-knowledge/create-knowledge.mjs b/components/devin/actions/create-knowledge/create-knowledge.mjs new file mode 100644 index 0000000000000..c68676c8441fc --- /dev/null +++ b/components/devin/actions/create-knowledge/create-knowledge.mjs @@ -0,0 +1,61 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-create-knowledge", + name: "Create Knowledge", + description: "Create a new knowledge object to share information with Devin. [See the documentation](https://docs.devin.ai/api-reference/knowledge/create-knowledge)", + version: "0.0.1", + type: "action", + props: { + devin, + body: { + propDefinition: [ + devin, + "body", + ], + }, + name: { + propDefinition: [ + devin, + "name", + ], + }, + triggerDescription: { + propDefinition: [ + devin, + "triggerDescription", + ], + }, + parentFolderId: { + propDefinition: [ + devin, + "folderId", + ], + label: "Parent Folder ID", + description: "The ID of the folder to create the knowledge in", + optional: true, + }, + pinnedRepo: { + propDefinition: [ + devin, + "pinnedRepo", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.devin.createKnowledge({ + $, + data: { + body: this.body, + name: this.name, + parent_folder_id: this.parentFolderId, + trigger_description: this.triggerDescription, + pinned_repo: this.pinnedRepo, + }, + }); + + $.export("$summary", `Successfully created knowledge object: ${this.name}`); + return response; + }, +}; diff --git a/components/devin/actions/create-session/create-session.mjs b/components/devin/actions/create-session/create-session.mjs new file mode 100644 index 0000000000000..92dfe6eb02e5e --- /dev/null +++ b/components/devin/actions/create-session/create-session.mjs @@ -0,0 +1,89 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-create-session", + name: "Create Session", + description: "Create a new session with Devin. [See the documentation](https://docs.devin.ai/api-reference/sessions/create-a-new-devin-session)", + version: "0.0.1", + type: "action", + props: { + devin, + prompt: { + type: "string", + label: "Prompt", + description: "The task description for Devin", + }, + snapshotId: { + type: "string", + label: "Snapshot ID", + description: "ID of a machine snapshot to use", + optional: true, + }, + unlisted: { + type: "boolean", + label: "Unlisted", + description: "Whether the session should be unlisted", + optional: true, + }, + idempotent: { + type: "boolean", + label: "Idempotent", + description: "Enable idempotent session creation", + optional: true, + }, + maxAcuLimit: { + type: "integer", + label: "Max ACU Limit", + description: "Maximum ACU limit for the session", + optional: true, + }, + secretIds: { + propDefinition: [ + devin, + "secretIds", + ], + optional: true, + }, + knowledgeIds: { + propDefinition: [ + devin, + "knowledgeId", + ], + type: "string[]", + label: "Knowledge IDs", + description: "The IDs of the knowledge objects to use", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "List of tags to add to the session", + optional: true, + }, + title: { + type: "string", + label: "Title", + description: "Custom title for the session. If None, a title will be generated automatically", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.devin.createSession({ + $, + data: { + prompt: this.prompt, + snapshot_id: this.snapshotId, + unlisted: this.unlisted, + idempotent: this.idempotent, + max_acu_limit: this.maxAcuLimit, + secret_ids: this.secretIds, + knowledge_ids: this.knowledgeIds, + tags: this.tags, + title: this.title, + }, + }); + + $.export("$summary", `Successfully created session with ID: ${response.session_id}`); + return response; + }, +}; diff --git a/components/devin/actions/delete-knowledge/delete-knowledge.mjs b/components/devin/actions/delete-knowledge/delete-knowledge.mjs new file mode 100644 index 0000000000000..268cb1533a6f1 --- /dev/null +++ b/components/devin/actions/delete-knowledge/delete-knowledge.mjs @@ -0,0 +1,27 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-delete-knowledge", + name: "Delete Knowledge", + description: "Delete an existing knowledge object. [See the documentation](https://docs.devin.ai/api-reference/knowledge/delete-knowledge)", + version: "0.0.1", + type: "action", + props: { + devin, + knowledgeId: { + propDefinition: [ + devin, + "knowledgeId", + ], + }, + }, + async run({ $ }) { + const response = await this.devin.deleteKnowledge({ + $, + knowledgeId: this.knowledgeId, + }); + + $.export("$summary", `Successfully deleted knowledge object: ${this.knowledgeId}`); + return response; + }, +}; diff --git a/components/devin/actions/get-session/get-session.mjs b/components/devin/actions/get-session/get-session.mjs new file mode 100644 index 0000000000000..e7e13f98044a4 --- /dev/null +++ b/components/devin/actions/get-session/get-session.mjs @@ -0,0 +1,27 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-get-session", + name: "Get Session", + description: "Retrieve details about an existing session. [See the documentation](https://docs.devin.ai/api-reference/sessions/retrieve-details-about-an-existing-session)", + version: "0.0.1", + type: "action", + props: { + devin, + sessionId: { + propDefinition: [ + devin, + "sessionId", + ], + }, + }, + async run({ $ }) { + const response = await this.devin.getSession({ + $, + sessionId: this.sessionId, + }); + + $.export("$summary", `Successfully retrieved session: ${response.title || this.sessionId}`); + return response; + }, +}; diff --git a/components/devin/actions/list-knowledge/list-knowledge.mjs b/components/devin/actions/list-knowledge/list-knowledge.mjs new file mode 100644 index 0000000000000..ee6f59a4f8a2a --- /dev/null +++ b/components/devin/actions/list-knowledge/list-knowledge.mjs @@ -0,0 +1,20 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-list-knowledge", + name: "List Knowledge", + description: "Retrieve a list of all knowledge objects. [See the documentation](https://docs.devin.ai/api-reference/knowledge/list-knowledge)", + version: "0.0.1", + type: "action", + props: { + devin, + }, + async run({ $ }) { + const response = await this.devin.listKnowledge({ + $, + }); + + $.export("$summary", `Successfully retrieved ${response.knowledge?.length || 0} knowledge objects`); + return response; + }, +}; diff --git a/components/devin/actions/list-sessions/list-sessions.mjs b/components/devin/actions/list-sessions/list-sessions.mjs new file mode 100644 index 0000000000000..62910cf595c54 --- /dev/null +++ b/components/devin/actions/list-sessions/list-sessions.mjs @@ -0,0 +1,54 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-list-sessions", + name: "List Sessions", + description: "Retrieve a list of all sessions. [See the documentation](https://docs.devin.ai/api-reference/sessions/list-sessions)", + version: "0.0.1", + type: "action", + props: { + devin, + tags: { + type: "string[]", + label: "Tags", + description: "Filter sessions by specific tags", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "Maximum number of sessions to retrieve", + optional: true, + default: 100, + }, + offset: { + type: "integer", + label: "Offset", + description: "Number of sessions to skip", + optional: true, + default: 0, + }, + }, + async run({ $ }) { + const response = await this.devin.listSessions({ + $, + params: { + limit: this.limit, + offset: this.offset, + }, + }); + + let sessions = response.sessions; + if (this.tags) { + sessions = sessions.filter((session) => session.tags.some((tag) => this.tags.includes(tag))); + } + + let summary = `Retrieved ${response.sessions.length} sessions`; + if (this.tags) { + summary += `, ${sessions.length} matching tags`; + } + + $.export("$summary", `${summary}`); + return sessions; + }, +}; diff --git a/components/devin/actions/send-message-to-session/send-message-to-session.mjs b/components/devin/actions/send-message-to-session/send-message-to-session.mjs new file mode 100644 index 0000000000000..f6ee2793f51cc --- /dev/null +++ b/components/devin/actions/send-message-to-session/send-message-to-session.mjs @@ -0,0 +1,35 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-send-message-to-session", + name: "Send Message to Session", + description: "Send a message to an existing Devin session. [See the documentation](https://docs.devin.ai/api-reference/sessions/send-a-message-to-an-existing-devin-session)", + version: "0.0.1", + type: "action", + props: { + devin, + sessionId: { + propDefinition: [ + devin, + "sessionId", + ], + }, + message: { + type: "string", + label: "Message", + description: "The message to send to Devin", + }, + }, + async run({ $ }) { + const response = await this.devin.sendMessageToSession({ + $, + sessionId: this.sessionId, + data: { + message: this.message, + }, + }); + + $.export("$summary", `Successfully sent message to session ${this.sessionId}`); + return response; + }, +}; diff --git a/components/devin/actions/update-knowledge/update-knowledge.mjs b/components/devin/actions/update-knowledge/update-knowledge.mjs new file mode 100644 index 0000000000000..5012e3ff4d074 --- /dev/null +++ b/components/devin/actions/update-knowledge/update-knowledge.mjs @@ -0,0 +1,77 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-update-knowledge", + name: "Update Knowledge", + description: "Update an existing knowledge object. [See the documentation](https://docs.devin.ai/api-reference/knowledge/update-knowledge)", + version: "0.0.1", + type: "action", + props: { + devin, + knowledgeId: { + propDefinition: [ + devin, + "knowledgeId", + ], + }, + body: { + propDefinition: [ + devin, + "body", + ], + optional: true, + }, + name: { + propDefinition: [ + devin, + "name", + ], + optional: true, + }, + triggerDescription: { + propDefinition: [ + devin, + "triggerDescription", + ], + optional: true, + }, + parentFolderId: { + propDefinition: [ + devin, + "folderId", + ], + label: "Parent Folder ID", + description: "The ID of the folder to create the knowledge in", + optional: true, + }, + pinnedRepo: { + propDefinition: [ + devin, + "pinnedRepo", + ], + optional: true, + }, + }, + async run({ $ }) { + const { knowledge } = await this.devin.listKnowledge({ + $, + }); + + const current = knowledge.find(({ id }) => id === this.knowledgeId); + + const response = await this.devin.updateKnowledge({ + $, + knowledgeId: this.knowledgeId, + data: { + body: this.body || current.body, + name: this.name || current.name, + trigger_description: this.triggerDescription || current.trigger_description, + parent_folder_id: this.parentFolderId || current.parent_folder_id, + pinned_repo: this.pinnedRepo || current.pinned_repo, + }, + }); + + $.export("$summary", `Successfully updated knowledge object: ${this.knowledgeId}`); + return response; + }, +}; diff --git a/components/devin/actions/update-session-tags/update-session-tags.mjs b/components/devin/actions/update-session-tags/update-session-tags.mjs new file mode 100644 index 0000000000000..7fd2050f3e649 --- /dev/null +++ b/components/devin/actions/update-session-tags/update-session-tags.mjs @@ -0,0 +1,35 @@ +import devin from "../../devin.app.mjs"; + +export default { + key: "devin-update-session-tags", + name: "Update Session Tags", + description: "Update the tags for an existing session. [See the documentation](https://docs.devin.ai/api-reference/sessions/update-session-tags)", + version: "0.0.1", + type: "action", + props: { + devin, + sessionId: { + propDefinition: [ + devin, + "sessionId", + ], + }, + tags: { + type: "string[]", + label: "Tags", + description: "New tags to set for the session (this will replace existing tags)", + }, + }, + async run({ $ }) { + const response = await this.devin.updateSessionTags({ + $, + sessionId: this.sessionId, + data: { + tags: this.tags, + }, + }); + + $.export("$summary", `Successfully updated tags for session ${this.sessionId}`); + return response; + }, +}; diff --git a/components/devin/actions/upload-file/upload-file.mjs b/components/devin/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..b3f87e1d3c056 --- /dev/null +++ b/components/devin/actions/upload-file/upload-file.mjs @@ -0,0 +1,39 @@ +import devin from "../../devin.app.mjs"; +import { getFileStreamAndMetadata } from "@pipedream/platform"; +import FormData from "form-data"; + +export default { + key: "devin-upload-file", + name: "Upload File", + description: "Upload files for Devin to use in sessions. [See the documentation](https://docs.devin.ai/api-reference/attachments/upload-files-for-devin-to-work-with)", + version: "0.0.1", + type: "action", + props: { + devin, + filePathOrUrl: { + type: "string", + label: "File Path or URL", + description: "The file to upload. Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/myFile.txt`)", + }, + }, + async run({ $ }) { + const { + stream, metadata, + } = await getFileStreamAndMetadata(this.filePathOrUrl); + const form = new FormData(); + form.append("file", stream, { + contentType: metadata.contentType, + knownLength: metadata.size, + filename: metadata.name, + }); + + const response = await this.devin.uploadFile({ + $, + headers: form.getHeaders(), + data: form, + }); + + $.export("$summary", `Successfully uploaded file: ${metadata.name}`); + return response; + }, +}; diff --git a/components/devin/devin.app.mjs b/components/devin/devin.app.mjs index 0d89f667ea23d..189d7b186ed50 100644 --- a/components/devin/devin.app.mjs +++ b/components/devin/devin.app.mjs @@ -1,11 +1,188 @@ +import { axios } from "@pipedream/platform"; +const DEFAULT_LIMIT = 100; + export default { type: "app", app: "devin", - propDefinitions: {}, + propDefinitions: { + sessionId: { + type: "string", + label: "Session ID", + description: "The ID of a session", + async options({ page }) { + const { sessions } = await this.listSessions({ + params: { + limit: DEFAULT_LIMIT, + offset: page * DEFAULT_LIMIT, + }, + }); + return sessions?.map(({ + session_id: value, title: label, + }) => ({ + label, + value, + })) || []; + }, + }, + knowledgeId: { + type: "string", + label: "Knowledge ID", + description: "The ID of a knowledge object", + async options() { + const { knowledge } = await this.listKnowledge(); + return knowledge?.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) || []; + }, + }, + folderId: { + type: "string", + label: "Folder ID", + description: "The ID of a folder", + async options() { + const { folders } = await this.listKnowledge(); + return folders?.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) || []; + }, + }, + secretIds: { + type: "string[]", + label: "Secret IDs", + description: "The IDs of the secrets to use", + async options() { + const secrets = await this.listSecrets(); + return secrets?.map(({ + id: value, key: label, + }) => ({ + label, + value, + })) || []; + }, + }, + body: { + type: "string", + label: "Body", + description: "The content of the knowledge. Devin will read this when the knowledge gets triggered", + }, + name: { + type: "string", + label: "Name", + description: "The name of the knowledge, used only for displaying the knowledge on the knowledge page", + }, + triggerDescription: { + type: "string", + label: "Trigger Description", + description: "The description of when this knowledge should be used by Devin", + }, + pinnedRepo: { + type: "string", + label: "Pinned Repo", + description: "Pin knowledge to specific repositories. Valid values: `null`: No pinning (default), `all`: Pin to all repositories, `owner/repo`: Pin to specific repository, `owner/repo`: Pin to specific repository", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.devin.ai/v1"; + }, + _makeRequest({ + $ = this, path, headers, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + ...opts, + }); + }, + getSession({ + sessionId, ...opts + }) { + return this._makeRequest({ + path: `/sessions/${sessionId}`, + ...opts, + }); + }, + listSessions(opts = {}) { + return this._makeRequest({ + path: "/sessions", + ...opts, + }); + }, + listKnowledge(opts = {}) { + return this._makeRequest({ + path: "/knowledge", + ...opts, + }); + }, + listSecrets(opts = {}) { + return this._makeRequest({ + path: "/secrets", + ...opts, + }); + }, + createSession(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/sessions", + ...opts, + }); + }, + sendMessageToSession({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/sessions/${sessionId}/message`, + ...opts, + }); + }, + uploadFile(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/attachments", + ...opts, + }); + }, + updateSessionTags({ + sessionId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/sessions/${sessionId}/tags`, + ...opts, + }); + }, + createKnowledge(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/knowledge", + ...opts, + }); + }, + updateKnowledge({ + knowledgeId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/knowledge/${knowledgeId}`, + ...opts, + }); + }, + deleteKnowledge({ knowledgeId }) { + return this._makeRequest({ + method: "DELETE", + path: `/knowledge/${knowledgeId}`, + }); }, }, }; diff --git a/components/devin/package.json b/components/devin/package.json index 3e8737002c3bb..f72a3da8fcaf3 100644 --- a/components/devin/package.json +++ b/components/devin/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/devin", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Devin Components", "main": "devin.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0", + "form-data": "^4.0.4" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a798a9636550..30bee2c563021 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3728,7 +3728,14 @@ importers: components/device_magic: {} - components/devin: {} + components/devin: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 + form-data: + specifier: ^4.0.4 + version: 4.0.4 components/devrev: dependencies: @@ -36917,7 +36924,7 @@ snapshots: axios: 1.10.0 body-parser: 1.20.3 file-type: 16.5.4 - form-data: 4.0.3 + form-data: 4.0.4 transitivePeerDependencies: - debug - supports-color @@ -37889,14 +37896,14 @@ snapshots: dependencies: '@pipedream/platform': 1.6.6 axios: 1.10.0 - form-data: 4.0.3 + form-data: 4.0.4 transitivePeerDependencies: - debug '@pipedream/monday@0.7.0': dependencies: '@pipedream/platform': 3.1.0 - form-data: 4.0.3 + form-data: 4.0.4 lodash.flatmap: 4.5.0 lodash.map: 4.6.0 lodash.uniqby: 4.7.0 @@ -38092,7 +38099,7 @@ snapshots: '@pipedream/platform': 3.1.0 async-retry: 1.3.3 bottleneck: 2.19.5 - form-data: 4.0.3 + form-data: 4.0.4 lodash.get: 4.4.2 lodash.topath: 4.5.2 shopify-api-node: 3.14.2 @@ -39283,7 +39290,7 @@ snapshots: '@scrapeless-ai/sdk@1.6.0(typescript@5.9.2)': dependencies: dotenv: 16.6.0 - form-data: 4.0.3 + form-data: 4.0.4 node-fetch: 2.7.0 playwright-core: 1.53.1 puppeteer-core: 24.10.2 @@ -39447,7 +39454,7 @@ snapshots: '@types/retry': 0.12.0 axios: 1.10.0 eventemitter3: 5.0.1 - form-data: 4.0.3 + form-data: 4.0.4 is-electron: 2.2.2 is-stream: 2.0.1 p-queue: 6.6.2 @@ -40536,7 +40543,7 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: '@types/node': 20.17.6 - form-data: 4.0.2 + form-data: 4.0.4 '@types/node@14.18.63': {} @@ -41217,7 +41224,7 @@ snapshots: datauri: 4.1.0 fetch-har: 5.0.5 find-cache-dir: 3.3.2 - form-data: 4.0.3 + form-data: 4.0.4 get-stream: 6.0.1 js-yaml: 4.1.0 make-dir: 3.1.0 @@ -41497,7 +41504,7 @@ snapshots: axios@0.28.1: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -41505,7 +41512,7 @@ snapshots: axios@1.10.0: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -41521,7 +41528,7 @@ snapshots: axios@1.6.0: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -41529,7 +41536,7 @@ snapshots: axios@1.6.7: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -41537,7 +41544,7 @@ snapshots: axios@1.6.8: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -41545,7 +41552,7 @@ snapshots: axios@1.7.4: dependencies: follow-redirects: 1.15.9(debug@3.2.7) - form-data: 4.0.3 + form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -47071,7 +47078,7 @@ snapshots: klaviyo-api@18.0.0: dependencies: axios: 1.10.0 - form-data: 4.0.3 + form-data: 4.0.4 transitivePeerDependencies: - debug @@ -49481,7 +49488,7 @@ snapshots: axios: 0.28.1 base-64: 0.1.0 build-url: 1.3.3 - form-data: 4.0.3 + form-data: 4.0.4 https-proxy-agent: 5.0.1 joi: 17.13.3 jsonwebtoken: 9.0.2 @@ -51759,7 +51766,7 @@ snapshots: cookiejar: 2.1.4 debug: 4.4.1(supports-color@10.0.0) fast-safe-stringify: 2.1.1 - form-data: 4.0.3 + form-data: 4.0.4 formidable: 2.1.2 methods: 1.1.2 mime: 2.6.0 @@ -53020,7 +53027,7 @@ snapshots: webflow-api@2.4.2: dependencies: - form-data: 4.0.3 + form-data: 4.0.4 formdata-node: 6.0.3 js-base64: 3.7.2 node-fetch: 2.7.0