diff --git a/components/dify/actions/get-app-parameters/get-app-parameters.mjs b/components/dify/actions/get-app-parameters/get-app-parameters.mjs new file mode 100644 index 0000000000000..3d482b8f7f803 --- /dev/null +++ b/components/dify/actions/get-app-parameters/get-app-parameters.mjs @@ -0,0 +1,27 @@ +import dify from "../../dify.app.mjs"; + +export default { + key: "dify-get-app-parameters", + name: "Get App Parameters", + description: "Return the connected Dify app's configuration: its `user_input_form` (the exact input variable names, types, and which are required), file-upload limits, opening statement, and suggested questions. Call this before **Run Workflow** to know what to pass in its `Inputs` parameter, instead of guessing variable names. [See the documentation](https://docs.dify.ai/en/api-reference/applications/get-app-parameters)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + }, + async run({ $ }) { + const response = await this.dify.getAppParameters({ + $, + }); + + const inputCount = response.user_input_form?.length ?? 0; + $.export("$summary", `Retrieved app parameters (${inputCount} input variable(s))`); + return response; + }, +}; diff --git a/components/dify/actions/list-conversations/list-conversations.mjs b/components/dify/actions/list-conversations/list-conversations.mjs new file mode 100644 index 0000000000000..e4b5ad3604d22 --- /dev/null +++ b/components/dify/actions/list-conversations/list-conversations.mjs @@ -0,0 +1,65 @@ +import dify from "../../dify.app.mjs"; + +export default { + key: "dify-list-conversations", + name: "List Conversations", + description: "List a Dify Chatflow, Chatbot, Agent, or Legacy Agent app's conversations, most recently active first. Conversations are scoped by `User`, so pass the same value used to create those conversations to see that end user's threads. [See the documentation](https://docs.dify.ai/en/api-reference/conversations/list-conversations)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + user: { + propDefinition: [ + dify, + "user", + ], + description: "A unique identifier for the end user whose conversations to list. This must match the `User` value used when those conversations were created — Dify silently returns an empty page instead of an error when `User` is omitted.", + }, + lastId: { + type: "string", + label: "Last Conversation ID", + description: "Pagination cursor: the `id` of the last conversation from the previous page's results. Omit to fetch the first page.", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "Number of conversations to return, between `1` and `100`. Defaults to `20`.", + min: 1, + max: 100, + optional: true, + }, + sortBy: { + type: "string", + label: "Sort By", + description: "Field to sort results by. Defaults to `-updated_at` (most recently updated first).", + options: [ + "created_at", + "-created_at", + "updated_at", + "-updated_at", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.dify.listConversations({ + $, + params: { + user: this.user, + last_id: this.lastId, + limit: this.limit, + sort_by: this.sortBy, + }, + }); + + $.export("$summary", `Found ${response.data.length} conversation(s)`); + return response; + }, +}; diff --git a/components/dify/actions/list-knowledge-bases/list-knowledge-bases.mjs b/components/dify/actions/list-knowledge-bases/list-knowledge-bases.mjs new file mode 100644 index 0000000000000..d13b441dba140 --- /dev/null +++ b/components/dify/actions/list-knowledge-bases/list-knowledge-bases.mjs @@ -0,0 +1,49 @@ +import dify from "../../dify.app.mjs"; + +export default { + key: "dify-list-knowledge-bases", + name: "List Knowledge Bases", + description: "List the knowledge bases (datasets) visible to your Dify account, optionally filtered by name. Use this to find the `Knowledge Base ID` needed by **Query Knowledge Base**. This requires a Dify connection authenticated with a knowledge base API key (issued from a knowledge base's own **API Access** page), not an app API key — those authenticate **Run Workflow** instead. A `401 unauthorized` error here usually means the connected account is using an app key; reconnect with a knowledge base key instead. [See the documentation](https://docs.dify.ai/en/api-reference/knowledge-bases/list-knowledge-bases)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + keyword: { + type: "string", + label: "Keyword", + description: "Filter knowledge bases by name, e.g. `Product Documentation`.", + optional: true, + }, + page: { + type: "integer", + label: "Page", + description: "Page number of results to return. Defaults to `1`.", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "Number of knowledge bases to return per page. Defaults to `20`.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.dify.listDatasets({ + $, + params: { + keyword: this.keyword, + page: this.page, + limit: this.limit, + }, + }); + + $.export("$summary", `Found ${response.data.length} knowledge base(s)`); + return response; + }, +}; diff --git a/components/dify/actions/list-messages/list-messages.mjs b/components/dify/actions/list-messages/list-messages.mjs new file mode 100644 index 0000000000000..d2f9880a9e439 --- /dev/null +++ b/components/dify/actions/list-messages/list-messages.mjs @@ -0,0 +1,58 @@ +import dify from "../../dify.app.mjs"; + +export default { + key: "dify-list-messages", + name: "List Messages", + description: "Return a Dify Chatflow, Chatbot, Agent, or Legacy Agent conversation's message history, newest first. Use **List Conversations** to find a `Conversation ID`. Each message includes the `query`/`answer` pair, so this is how an agent reconstructs prior turns of a conversation instead of relying on its own memory. [See the documentation](https://docs.dify.ai/en/api-reference/conversations/list-conversation-messages)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + conversationId: { + type: "string", + label: "Conversation ID", + description: "The ID of the conversation to read. Use **List Conversations** to find valid IDs.", + }, + user: { + propDefinition: [ + dify, + "user", + ], + description: "A unique identifier for the end user who owns this conversation. This must match the `User` value used when the conversation was created — Dify silently returns an empty page instead of an error when `User` is omitted.", + }, + firstId: { + type: "string", + label: "First Message ID", + description: "Pagination cursor: the `id` of the first (oldest) message on the current page. Pass it to fetch the previous, older page. Omit to fetch the most recent messages.", + optional: true, + }, + limit: { + type: "integer", + label: "Limit", + description: "Number of messages to return, between `1` and `100`. Defaults to `20`.", + min: 1, + max: 100, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.dify.listMessages({ + $, + params: { + conversation_id: this.conversationId, + user: this.user, + first_id: this.firstId, + limit: this.limit, + }, + }); + + $.export("$summary", `Found ${response.data.length} message(s) in conversation ${this.conversationId}`); + return response; + }, +}; diff --git a/components/dify/actions/query-knowledge-base/query-knowledge-base.mjs b/components/dify/actions/query-knowledge-base/query-knowledge-base.mjs new file mode 100644 index 0000000000000..f0335a1403cdb --- /dev/null +++ b/components/dify/actions/query-knowledge-base/query-knowledge-base.mjs @@ -0,0 +1,91 @@ +import { ConfigurationError } from "@pipedream/platform"; +import dify from "../../dify.app.mjs"; + +const SEARCH_METHODS = [ + "keyword_search", + "semantic_search", + "full_text_search", + "hybrid_search", +]; + +export default { + key: "dify-query-knowledge-base", + name: "Query Knowledge Base", + description: "Search a Dify knowledge base (dataset) and return the chunks most relevant to a query. Use **List Knowledge Bases** to find the `Knowledge Base ID`. This requires a Dify connection authenticated with a knowledge base API key (issued from a knowledge base's own **API Access** page), not an app API key — those authenticate **Run Workflow** instead. A `401 unauthorized` error here usually means the connected account is using an app key; reconnect with a knowledge base key instead. [See the documentation](https://docs.dify.ai/en/api-reference/knowledge-bases/retrieve-chunks-from-a-knowledge-base-test-retrieval)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + datasetId: { + type: "string", + label: "Knowledge Base ID", + description: "The UUID of the knowledge base to search, e.g. `c42e2a6e-40b3-4330-96f8-f1e4d768e8c9`. Use **List Knowledge Bases** to find valid IDs.", + }, + query: { + type: "string", + label: "Query", + description: "The search text, up to 250 characters, e.g. `What is Dify?`", + }, + searchMethod: { + type: "string", + label: "Search Method", + description: "The retrieval method to use. Setting this, `Top K`, or `Score Threshold` builds a full retrieval configuration for this request (with reranking disabled) instead of using the knowledge base's own configured defaults. Leave all three unset to use the knowledge base's defaults.", + options: SEARCH_METHODS, + optional: true, + }, + topK: { + type: "integer", + label: "Top K", + description: "The maximum number of matching chunks to return. Defaults to `3` if any of `Search Method`, `Top K`, or `Score Threshold` is set.", + optional: true, + }, + scoreThreshold: { + type: "string", + label: "Score Threshold", + description: "The minimum similarity score (between `0` and `1`) a chunk must have to be included in the results, e.g. `0.5`. Setting this enables score threshold filtering for this request.", + optional: true, + }, + }, + async run({ $ }) { + if (this.query.length > 250) { + throw new ConfigurationError("Query must be 250 characters or fewer."); + } + + let scoreThreshold; + if (this.scoreThreshold !== undefined) { + scoreThreshold = Number(this.scoreThreshold); + if (!Number.isFinite(scoreThreshold) || scoreThreshold < 0 || scoreThreshold > 1) { + throw new ConfigurationError("Score Threshold must be a number between 0 and 1."); + } + } + + const useCustomRetrieval = this.searchMethod + || this.topK !== undefined + || scoreThreshold !== undefined; + const retrievalModel = useCustomRetrieval && { + search_method: this.searchMethod || "hybrid_search", + reranking_enable: false, + top_k: this.topK ?? 3, + score_threshold_enabled: scoreThreshold !== undefined, + score_threshold: scoreThreshold, + }; + + const response = await this.dify.retrieveFromDataset({ + $, + datasetId: this.datasetId, + data: { + query: this.query, + retrieval_model: retrievalModel || undefined, + }, + }); + + $.export("$summary", `Found ${response.records.length} matching chunk(s) for "${this.query}"`); + return response; + }, +}; diff --git a/components/dify/actions/run-workflow/run-workflow.mjs b/components/dify/actions/run-workflow/run-workflow.mjs new file mode 100644 index 0000000000000..157406c5c95b9 --- /dev/null +++ b/components/dify/actions/run-workflow/run-workflow.mjs @@ -0,0 +1,44 @@ +import dify from "../../dify.app.mjs"; + +export default { + key: "dify-run-workflow", + name: "Run Workflow", + description: "Run a Dify Workflow app's published workflow and return its outputs. Requires the workflow to be published — draft-only workflows return a `bad_request` error. This action uses `blocking` response mode, which waits for the run to finish before returning; long-running workflows on Dify Cloud risk being cut off by the platform's 100-second edge proxy timeout, in which case the run may still complete server-side but this action will not see the result. [See the documentation](https://docs.dify.ai/en/api-reference/workflow-runs/run-workflow)", + version: "0.0.1", + ai: "optimized", + type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + }, + props: { + dify, + inputs: { + propDefinition: [ + dify, + "inputs", + ], + }, + user: { + propDefinition: [ + dify, + "user", + ], + }, + }, + async run({ $ }) { + const response = await this.dify.runWorkflow({ + $, + data: { + inputs: this.inputs ?? {}, + user: this.user, + response_mode: "blocking", + }, + }); + + const { data } = response; + $.export("$summary", `Workflow run ${data.id} finished with status "${data.status}"`); + return response; + }, +}; diff --git a/components/dify/dify.app.mjs b/components/dify/dify.app.mjs index ca3a408959c92..f92da3fc35a2c 100644 --- a/components/dify/dify.app.mjs +++ b/components/dify/dify.app.mjs @@ -1,11 +1,80 @@ +// x-pd-ai: optimized +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "dify", - propDefinitions: {}, + propDefinitions: { + user: { + type: "string", + label: "User", + description: "A unique identifier for the end user, defined by you (e.g. an internal user ID or session ID) — not a Dify account. Dify scopes conversations, messages, and files to this value, so reuse the same `User` across requests for the same end user. [See the documentation](https://docs.dify.ai/en/api-reference/guides/end-user-identity)", + }, + inputs: { + type: "object", + label: "Inputs", + description: "Values for the app's input variables, keyed by variable name, e.g. `{ \"city\": \"San Francisco\" }`. Leave empty (`{}`) if the app defines no input variables — most chat apps don't. Use **Get App Parameters** to discover this app's actual variable names and whether each is required.", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.dify.ai/v1"; + }, + _headers(headers) { + return { + Authorization: `Bearer ${this.$auth.api_key}`, + ...headers, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: this._headers(headers), + ...args, + }); + }, + runWorkflow(args = {}) { + return this._makeRequest({ + method: "POST", + path: "/workflows/run", + ...args, + }); + }, + listConversations(args = {}) { + return this._makeRequest({ + path: "/conversations", + ...args, + }); + }, + listMessages(args = {}) { + return this._makeRequest({ + path: "/messages", + ...args, + }); + }, + getAppParameters(args = {}) { + return this._makeRequest({ + path: "/parameters", + ...args, + }); + }, + listDatasets(args = {}) { + return this._makeRequest({ + path: "/datasets", + ...args, + }); + }, + retrieveFromDataset({ + datasetId, ...args + }) { + return this._makeRequest({ + method: "POST", + path: `/datasets/${datasetId}/retrieve`, + ...args, + }); }, }, }; diff --git a/components/dify/package.json b/components/dify/package.json index 2721d6eb94956..0fc543fea57e5 100644 --- a/components/dify/package.json +++ b/components/dify/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/dify", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Dify Components", "main": "dify.app.mjs", "keywords": [ @@ -9,7 +9,10 @@ ], "homepage": "https://pipedream.com/apps/dify", "author": "Pipedream (https://pipedream.com/)", + "dependencies": { + "@pipedream/platform": "^3.1.1" + }, "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 531e72db22d1f..4cde79a52c034 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4378,7 +4378,11 @@ importers: specifier: ^1.6.8 version: 1.6.8 - components/dify: {} + components/dify: + dependencies: + '@pipedream/platform': + specifier: ^3.1.1 + version: 3.4.0 components/digicert: {}