-
Notifications
You must be signed in to change notification settings - Fork 5.5k
feat(ai): pipedream ai sdk #16699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ai): pipedream ai sdk #16699
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
|
|
Warning Rate limit exceeded@TheBestMoshe has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 24 minutes and 28 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
WalkthroughA new package, Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant OpenAiTools
participant CoreTools
participant PipedreamClient as pd
participant OpenAI
User->>OpenAiTools: getTools({ app, query })
OpenAiTools->>CoreTools: getTools({ app, query })
CoreTools->>PipedreamClient: getComponents({ app, query })
PipedreamClient-->>CoreTools: Components list
CoreTools-->>OpenAiTools: Tools (with schemas)
OpenAiTools-->>User: Tools formatted for OpenAI
User->>OpenAiTools: handleCompletion(completion)
OpenAiTools->>CoreTools: getTool(toolName)
CoreTools-->>OpenAiTools: Tool
OpenAiTools->>CoreTools: executeTool(component, args)
CoreTools->>CoreTools: getAuthProvision({ app, uuid })
CoreTools->>PipedreamClient: runAction({ args, auth })
PipedreamClient-->>CoreTools: Action result
CoreTools-->>OpenAiTools: Tool execution result
OpenAiTools-->>User: Results array
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (10)
packages/ai/.env.example (1)
1-7: Provide environment variable template
The.env.examplelists all required OpenAI and Pipedream credentials with a useful upstream link. Consider adding a top‐line instruction (e.g., “Copy to.envand fill in values”) to guide users.packages/ai/src/lib/componentAppKey.ts (1)
1-5: Extract app key from configurable props
The helper correctly finds the first prop of type"app"and returns itsappfield orundefined. For clarity, you may add an explicit return type (: string | undefined).packages/ai/tsconfig.json (1)
1-17: Good TypeScript configuration but consider future module resolution compatibilityThe TypeScript configuration is well-structured for a modern ESM package with appropriate settings for strict type checking, declaration generation, and proper file inclusion/exclusion patterns.
Consider updating the
moduleResolutionoption to "NodeNext" or "Bundler" instead of "node" for better compatibility with modern ESM packages:{ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "NodeNext", "declaration": true, "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist"] }packages/ai/package.json (1)
21-23: Add standard NPM scripts for better development workflowThe package only includes a build script, which makes the development workflow limited.
Add standard NPM scripts for a better development experience:
"scripts": { - "build": "tsc" + "build": "tsc", + "dev": "tsc --watch", + "prepublishOnly": "npm run build", + "lint": "eslint src --ext .ts", + "clean": "rm -rf dist" },packages/ai/src/lib/config.ts (1)
13-16: Consider adding a default value for project environmentThe PIPEDREAM_PROJECT_ENVIRONMENT is required but could have a sensible default.
Add a default value for the project environment:
PIPEDREAM_PROJECT_ID: z.string().min(1, { message: "PIPEDREAM_PROJECT_ID is required", }), - PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([ - "development", - "production", - ]), + PIPEDREAM_PROJECT_ENVIRONMENT: z + .enum(["development", "production"]) + .default("development"),packages/ai/src/configurablePropsToZod.ts (3)
11-39: Improve type safety and schema initialization.The function has good type handling for string types, but the type casting and initial setup could be improved.
export const configurablePropsToZod = ( component: V1Component, options?: { asyncOptionsDescription?: string; configureComponentDescription?: string; configurableProps?: ConfigurableProps; }, ) => { const schema: ZodRawShape = {}; - for (const cp of options?.configurableProps || - (component.configurable_props as ConfigurableProps)) { + const props = options?.configurableProps || + (Array.isArray(component.configurable_props) + ? component.configurable_props + : []); + + for (const cp of props) {
84-102: Simplify conditional logic for descriptions.The conditional logic for handling remote options and descriptions is unnecessarily complex and potentially confusing.
if (cp.remoteOptions) { - if (options?.asyncOptionsDescription) { - if (options.configureComponentDescription) { - description += `\n\n${options.asyncOptionsDescription}`; - } - } else { - if (options?.configureComponentDescription) { - description += `\n\n${options.configureComponentDescription}`; - } - } + // Add async options description if available, otherwise add component description + if (options?.asyncOptionsDescription) { + description += `\n\n${options.asyncOptionsDescription}`; + } else if (options?.configureComponentDescription) { + description += `\n\n${options.configureComponentDescription}`; + } // if (cp.name.includes("id")) { // description += `\n\nIMPORTANT: An ID is required for this property. If you don't have the id and only have the name, use the "${CONFIGURE_COMPONENT_TOOL_NAME}" tool to get the values.`; // } }
52-72: Consider more robust type mapping strategy.The current if-else chain for type mapping works but could be refactored to a more maintainable approach using a mapping object or function.
You could consider refactoring the type mapping to use a strategy pattern or a lookup object for better maintainability:
+ // Define type mapping strategies + const typeToZodSchema = { + "object": () => z.object({}).passthrough(), + "any": () => z.any(), + "number": () => z.number(), + "number[]": () => z.array(z.number()), + "integer": () => z.number().int(), + "integer[]": () => z.array(z.number().int()), + "boolean": () => z.boolean(), + "boolean[]": () => z.array(z.boolean()), + "$.discord.channel": () => z.string(), + "$.discord.channel[]": () => z.array(z.string()), + }; - else if (cp.type === "$.discord.channel") { - schema[cp.name] = z.string(); - } else if (cp.type === "$.discord.channel[]") { - schema[cp.name] = z.array(z.string()); - } else if (cp.type === "object") { - schema[cp.name] = z.object({}).passthrough(); - } else if (cp.type === "any") { - schema[cp.name] = z.any(); - } else if (cp.type === "number") { - schema[cp.name] = z.number(); - } else if (cp.type === "number[]") { - schema[cp.name] = z.array(z.number()); - } else if (cp.type === "integer") { - schema[cp.name] = z.number().int(); - } else if (cp.type === "integer[]") { - schema[cp.name] = z.array(z.number().int()); - } else if (cp.type === "boolean") { - schema[cp.name] = z.boolean(); - } else if (cp.type === "boolean[]") { - schema[cp.name] = z.array(z.boolean()); + else if (typeToZodSchema[cp.type]) { + schema[cp.name] = typeToZodSchema[cp.type]();packages/ai/src/tool-sets/core.ts (2)
7-12: Consider stronger typing for the Tool interface.The current use of
anyin the execute function signature reduces type safety. Consider using generics to make this more type-safe.- type Tool = { + type Tool<TArgs = any, TResult = any> = { name: string; description?: string; schema: z.ZodObject<z.ZodRawShape>; - execute: (args: any) => Promise<any>; + execute: (args: TArgs) => Promise<TResult>; };
40-42: Remove unnecessary async keyword.The getTool method doesn't contain any asynchronous operations, so the async keyword is unnecessary.
- async getTool(name: string) { + getTool(name: string) { return this.tools.find((tool) => tool.name === name); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (13)
packages/ai/.env.example(1 hunks)packages/ai/.gitignore(1 hunks)packages/ai/README.md(1 hunks)packages/ai/package.json(1 hunks)packages/ai/src/configurablePropsToZod.ts(1 hunks)packages/ai/src/index.ts(1 hunks)packages/ai/src/lib/componentAppKey.ts(1 hunks)packages/ai/src/lib/config.ts(1 hunks)packages/ai/src/lib/helpers.ts(1 hunks)packages/ai/src/lib/pd-client.ts(1 hunks)packages/ai/src/tool-sets/core.ts(1 hunks)packages/ai/src/tool-sets/openai.ts(1 hunks)packages/ai/tsconfig.json(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/ai/src/lib/componentAppKey.ts (1)
packages/sdk/src/shared/component.ts (1)
ConfigurableProps(106-106)
packages/ai/src/configurablePropsToZod.ts (2)
packages/sdk/src/shared/component.ts (2)
V1Component(131-138)ConfigurableProps(106-106)packages/ai/src/lib/helpers.ts (2)
toNonEmptyTuple(1-7)extractEnumValues(11-15)
packages/ai/src/tool-sets/core.ts (5)
packages/sdk/src/shared/index.ts (3)
component(1267-1271)Account(285-333)app(1198-1200)packages/ai/src/lib/pd-client.ts (1)
pd(4-11)packages/ai/src/configurablePropsToZod.ts (1)
configurablePropsToZod(11-111)packages/sdk/src/shared/component.ts (1)
V1Component(131-138)packages/ai/src/lib/componentAppKey.ts (1)
componentAppKey(3-5)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: pnpm publish
- GitHub Check: Lint Code Base
🔇 Additional comments (7)
packages/ai/src/index.ts (1)
1-3: Export OpenAiTools for public API
The export ofOpenAiToolsfrom thetool-sets/openaimodule correctly surfaces your new SDK’s core functionality.packages/ai/.gitignore (1)
1-3: Ignore build artifacts, deps, and sensitive files
Excluding.env,node_modules, anddistaligns with best practices to keep secrets and generated files out of version control.packages/ai/src/lib/pd-client.ts (1)
1-11: Initialize Pipedream backend client
InstantiatingpdviacreateBackendClientwith values from your validatedconfigis appropriate. Ensure that theconfigmodule covers missing or invalid environment variables to avoid runtime failures.packages/ai/src/lib/helpers.ts (2)
1-7: Well-designed type-safe tuple conversion function.The function elegantly handles the conversion of an array to a non-empty tuple type, which is useful for enforcing type constraints in TypeScript, particularly when working with enum values.
9-15: Good utility for enum value extraction.This function provides a clean way to extract string values from different enum representations, making it flexible to work with both plain string enums and object-based enums.
packages/ai/src/tool-sets/openai.ts (2)
1-10: Good foundation for OpenAI tools integration.The class properly leverages the CoreTools implementation by wrapping it with OpenAI-specific functionality.
12-27: Well-implemented tool conversion to OpenAI format.The getTools method correctly transforms internal tool representations to match OpenAI's expected format for function calling, using the zodToJsonSchema utility to convert validation schemas appropriately.
| "devDependencies": { | ||
| "bun": "^1.2.13", | ||
| "openai": "^4.77.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider making OpenAI a peer dependency
OpenAI is currently listed as a dev dependency, but the example in the README shows it being imported by the user.
Move the OpenAI package to peerDependencies to indicate to users that they need to install it themselves:
"dependencies": {
"@pipedream/sdk": "workspace:^",
"zod": "^3.24.4",
"zod-to-json-schema": "^3.24.5"
},
+ "peerDependencies": {
+ "openai": "^4.77.0"
+ },
"devDependencies": {
"bun": "^1.2.13",
- "openai": "^4.77.0"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "devDependencies": { | |
| "bun": "^1.2.13", | |
| "openai": "^4.77.0" | |
| "dependencies": { | |
| "@pipedream/sdk": "workspace:^", | |
| "zod": "^3.24.4", | |
| "zod-to-json-schema": "^3.24.5" | |
| }, | |
| "peerDependencies": { | |
| "openai": "^4.77.0" | |
| }, | |
| "devDependencies": { | |
| "bun": "^1.2.13" | |
| } |
🤖 Prompt for AI Agents
In packages/ai/package.json around lines 29 to 31, the OpenAI package is listed
under devDependencies but should be a peer dependency since users import it
directly. Move "openai" from devDependencies to peerDependencies to indicate
that users must install it themselves. Update the package.json accordingly by
removing "openai" from devDependencies and adding it under peerDependencies with
the appropriate version.
| { | ||
| "name": "@pipedream/ai", | ||
| "type": "module", | ||
| "version": "0.0.1", | ||
| "description": "Pipedream AI", | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "package.json" | ||
| ], | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc" | ||
| }, | ||
| "dependencies": { | ||
| "@pipedream/sdk": "workspace:^", | ||
| "zod": "^3.24.4", | ||
| "zod-to-json-schema": "^3.24.5" | ||
| }, | ||
| "devDependencies": { | ||
| "bun": "^1.2.13", | ||
| "openai": "^4.77.0" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Package.json needs additional metadata fields
The package structure is well-defined with appropriate ESM configuration, entry points, and dependencies. However, several standard metadata fields are missing that would improve package discoverability and usability.
Add the following missing fields to the package.json file:
{
"name": "@pipedream/ai",
"type": "module",
"version": "0.0.1",
"description": "Pipedream AI",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/PipedreamHQ/pipedream.git",
+ "directory": "packages/ai"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist",
"package.json"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc"
},
"dependencies": {
"@pipedream/sdk": "workspace:^",
"zod": "^3.24.4",
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"bun": "^1.2.13",
"openai": "^4.77.0"
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| { | |
| "name": "@pipedream/ai", | |
| "type": "module", | |
| "version": "0.0.1", | |
| "description": "Pipedream AI", | |
| "main": "./dist/index.js", | |
| "types": "./dist/index.d.ts", | |
| "exports": { | |
| ".": { | |
| "import": "./dist/index.js", | |
| "types": "./dist/index.d.ts" | |
| } | |
| }, | |
| "files": [ | |
| "dist", | |
| "package.json" | |
| ], | |
| "publishConfig": { | |
| "access": "public" | |
| }, | |
| "scripts": { | |
| "build": "tsc" | |
| }, | |
| "dependencies": { | |
| "@pipedream/sdk": "workspace:^", | |
| "zod": "^3.24.4", | |
| "zod-to-json-schema": "^3.24.5" | |
| }, | |
| "devDependencies": { | |
| "bun": "^1.2.13", | |
| "openai": "^4.77.0" | |
| } | |
| } | |
| { | |
| "name": "@pipedream/ai", | |
| "type": "module", | |
| "version": "0.0.1", | |
| "description": "Pipedream AI", | |
| "license": "MIT", | |
| "repository": { | |
| "type": "git", | |
| "url": "https://github.com/PipedreamHQ/pipedream.git", | |
| "directory": "packages/ai" | |
| }, | |
| "engines": { | |
| "node": ">=18.0.0" | |
| }, | |
| "main": "./dist/index.js", | |
| "types": "./dist/index.d.ts", | |
| "exports": { | |
| ".": { | |
| "import": "./dist/index.js", | |
| "types": "./dist/index.d.ts" | |
| } | |
| }, | |
| "files": [ | |
| "dist", | |
| "package.json" | |
| ], | |
| "publishConfig": { | |
| "access": "public" | |
| }, | |
| "scripts": { | |
| "build": "tsc" | |
| }, | |
| "dependencies": { | |
| "@pipedream/sdk": "workspace:^", | |
| "zod": "^3.24.4", | |
| "zod-to-json-schema": "^3.24.5" | |
| }, | |
| "devDependencies": { | |
| "bun": "^1.2.13", | |
| "openai": "^4.77.0" | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/ai/package.json lines 1 to 33, the package.json is missing standard
metadata fields that improve package discoverability and usability. Add fields
such as "author", "license", "repository", "keywords", and "homepage" with
appropriate values to provide more context about the package, its ownership,
licensing, source code location, relevant tags, and project homepage.
| ]), | ||
| }); | ||
|
|
||
| export const config = configSchema.parse(process?.env); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config validation should be more robust
The current implementation immediately parses environment variables on import, which will throw an error if any required variables are missing.
Make the config validation more robust by providing a function that validates the config when called, with better error handling:
- export const config = configSchema.parse(process?.env);
+ export function getConfig() {
+ try {
+ return configSchema.parse(process.env);
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const missingVars = error.issues.map(issue => issue.path[0]).join(", ");
+ throw new Error(`Missing or invalid environment variables: ${missingVars}. Please check your .env file.`);
+ }
+ throw error;
+ }
+ }
+
+ // For backward compatibility
+ export const config = getConfig();🤖 Prompt for AI Agents
In packages/ai/src/lib/config.ts at line 19, the config is currently parsed
immediately on import, which causes the application to throw errors if required
environment variables are missing. To fix this, refactor the code to export a
function that performs the configSchema.parse call when invoked, allowing for
controlled validation and error handling. This function should catch validation
errors and handle them gracefully, rather than throwing on import.
| const configSchema = z.object({ | ||
| PIPEDREAM_CLIENT_ID: z.string().min(1, { | ||
| message: "PIPEDREAM_CLIENT_ID is required", | ||
| }), | ||
| PIPEDREAM_CLIENT_SECRET: z.string().min(1, { | ||
| message: "PIPEDREAM_CLIENT_SECRET is required", | ||
| }), | ||
| PIPEDREAM_PROJECT_ID: z.string().min(1, { | ||
| message: "PIPEDREAM_PROJECT_ID is required", | ||
| }), | ||
| PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([ | ||
| "development", | ||
| "production", | ||
| ]), | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add JSDoc comments for better documentation
The schema definition lacks documentation that explains what each environment variable is used for.
Add JSDoc comments to document the purpose of each environment variable:
+ /**
+ * Schema for validating required Pipedream environment variables
+ */
const configSchema = z.object({
+ /**
+ * The client ID for authenticating with Pipedream API
+ */
PIPEDREAM_CLIENT_ID: z.string().min(1, {
message: "PIPEDREAM_CLIENT_ID is required",
}),
+ /**
+ * The client secret for authenticating with Pipedream API
+ */
PIPEDREAM_CLIENT_SECRET: z.string().min(1, {
message: "PIPEDREAM_CLIENT_SECRET is required",
}),
+ /**
+ * The project ID where Pipedream components will be executed
+ */
PIPEDREAM_PROJECT_ID: z.string().min(1, {
message: "PIPEDREAM_PROJECT_ID is required",
}),
+ /**
+ * The environment (development or production) where Pipedream components will be executed
+ */
PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([
"development",
"production",
]),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const configSchema = z.object({ | |
| PIPEDREAM_CLIENT_ID: z.string().min(1, { | |
| message: "PIPEDREAM_CLIENT_ID is required", | |
| }), | |
| PIPEDREAM_CLIENT_SECRET: z.string().min(1, { | |
| message: "PIPEDREAM_CLIENT_SECRET is required", | |
| }), | |
| PIPEDREAM_PROJECT_ID: z.string().min(1, { | |
| message: "PIPEDREAM_PROJECT_ID is required", | |
| }), | |
| PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([ | |
| "development", | |
| "production", | |
| ]), | |
| }); | |
| /** | |
| * Schema for validating required Pipedream environment variables | |
| */ | |
| const configSchema = z.object({ | |
| /** | |
| * The client ID for authenticating with Pipedream API | |
| */ | |
| PIPEDREAM_CLIENT_ID: z.string().min(1, { | |
| message: "PIPEDREAM_CLIENT_ID is required", | |
| }), | |
| /** | |
| * The client secret for authenticating with Pipedream API | |
| */ | |
| PIPEDREAM_CLIENT_SECRET: z.string().min(1, { | |
| message: "PIPEDREAM_CLIENT_SECRET is required", | |
| }), | |
| /** | |
| * The project ID where Pipedream components will be executed | |
| */ | |
| PIPEDREAM_PROJECT_ID: z.string().min(1, { | |
| message: "PIPEDREAM_PROJECT_ID is required", | |
| }), | |
| /** | |
| * The environment (development or production) where Pipedream components will be executed | |
| */ | |
| PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([ | |
| "development", | |
| "production", | |
| ]), | |
| }); |
🤖 Prompt for AI Agents
In packages/ai/src/lib/config.ts around lines 3 to 17, the configSchema object
defining environment variables lacks JSDoc comments. Add JSDoc comments above
the configSchema declaration to describe the purpose of each environment
variable, explaining what PIPEDREAM_CLIENT_ID, PIPEDREAM_CLIENT_SECRET,
PIPEDREAM_PROJECT_ID, and PIPEDREAM_PROJECT_ENVIRONMENT represent and how they
are used in the application.
| # @pipedream/ai | ||
|
|
||
| > This library is in alpha status. The API is subject to breaking changes. | ||
|
|
||
| Create a .env file based on the .env.example file. | ||
|
|
||
| Basic example: | ||
| ```ts | ||
| import { OpenAiTools } from "@pipedream/ai" | ||
| import { OpenAI } from "openai" | ||
|
|
||
| const openai = new OpenAI() | ||
|
|
||
| const userId = <add user id here> | ||
| const openAiTools = new OpenAiTools(userId) | ||
| const tools = await openAiTools.getTools({ | ||
| app: "slack", | ||
| }) | ||
|
|
||
| const completion = await openai.chat.completions.create({ | ||
| messages: [ | ||
| { | ||
| role: "user", | ||
| content: "Send a joke to #random channel", | ||
| }, | ||
| ], | ||
| model: "gpt-4o", | ||
| tools, | ||
| }) | ||
|
|
||
| const results = await openAiTools.handleCompletion(completion) | ||
|
|
||
| console.log(JSON.stringify(results, null, 2)) | ||
|
|
||
| ``` No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
README needs significant improvements for documentation and usability
The current README lacks essential information about what the package does, how to install it, what environment variables are needed, and provides an incomplete example.
Enhance the README with more comprehensive documentation:
# @pipedream/ai
> This library is in alpha status. The API is subject to breaking changes.
A Node.js SDK for simplifying the integration of Pipedream Connect with Large Language Models (LLMs). This package makes it easier to use Pipedream components as tools with OpenAI and other LLM providers.
## Installation
```bash
npm install @pipedream/ai openai
# or
yarn add @pipedream/ai openai
# or
pnpm add @pipedream/ai openaiEnvironment Setup
Create a .env file based on the .env.example file:
# OpenAI API Key
OPENAI_API_KEY=sk-...
# Pipedream Credentials
# These can be obtained from the Pipedream dashboard at https://pipedream.com/settings/account
PIPEDREAM_CLIENT_ID=
PIPEDREAM_CLIENT_SECRET=
PIPEDREAM_PROJECT_ID=
PIPEDREAM_PROJECT_ENVIRONMENT=developmentBasic Example
import { OpenAiTools } from "@pipedream/ai"
import { OpenAI } from "openai"
// Initialize OpenAI with your API key
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
// Replace with a unique identifier for your user
const userId = "user-123"
// Initialize the OpenAI Tools wrapper with the user ID
const openAiTools = new OpenAiTools(userId)
// Retrieve tools for a specific app (e.g., "slack")
// Supported apps include: "slack", "gmail", etc.
const tools = await openAiTools.getTools({
app: "slack",
})
// Create a chat completion with the tools
const completion = await openai.chat.completions.create({
messages: [
{
role: "user",
content: "Send a joke to #random channel",
},
],
model: "gpt-4o",
tools,
})
// Process any tool calls from the completion
const results = await openAiTools.handleCompletion(completion)
console.log(JSON.stringify(results, null, 2))API Reference
OpenAiTools
The main class for working with OpenAI tools integration.
Constructor
new OpenAiTools(userId: string)userId- A unique identifier for the user making the request
Methods
getTools(options: { app?: string })- Retrieves tools configurations for OpenAIhandleCompletion(completion: ChatCompletion)- Processes tool calls from an OpenAI completion
Supported Apps
slack- Slack messaging and channel operations- (Add other supported apps here)
Error Handling
try {
const results = await openAiTools.handleCompletion(completion)
console.log(results)
} catch (error) {
console.error("Error executing tools:", error)
}Contributing
(Add contribution guidelines here)
License
MIT
<details>
<summary>🤖 Prompt for AI Agents</summary>
In packages/ai/README.md lines 1 to 35, the README lacks essential details about
the package purpose, installation, environment variables, and a complete usage
example. Enhance it by adding sections for installation instructions with
package manager commands, environment variable setup with descriptions, a
detailed and runnable example including API key initialization and user ID
placeholder, an API reference for the main class and its methods, supported apps
list, error handling example, and placeholders for contributing and license
information to improve documentation clarity and usability.
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
| const userId = <add user id here> | ||
| const openAiTools = new OpenAiTools(userId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placeholder in example code should be replaced with proper guidance
The line const userId = <add user id here> contains a placeholder that should be replaced with clearer guidance.
Replace the placeholder with a proper example and explanation:
- const userId = <add user id here>
+ // Replace with a unique identifier for your user
+ const userId = "user-123"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const userId = <add user id here> | |
| const openAiTools = new OpenAiTools(userId) | |
| // Replace with a unique identifier for your user | |
| const userId = "user-123" | |
| const openAiTools = new OpenAiTools(userId) |
🤖 Prompt for AI Agents
In packages/ai/README.md around lines 14 to 15, replace the placeholder `const
userId = <add user id here>` with a clear example and explanation, such as
assigning a string representing a user ID (e.g., `const userId = "user-123"`),
and add a brief comment explaining that this should be replaced with the actual
user ID in real usage.
| async handleCompletion(completion: OpenAI.Chat.Completions.ChatCompletion) { | ||
| const toolCalls = completion.choices[0].message.tool_calls; | ||
| if (!toolCalls) { | ||
| return; | ||
| } | ||
| const results = await Promise.all( | ||
| toolCalls.map(async (toolCall) => { | ||
| const toolName = toolCall.function.name; | ||
| const tool = await this.core.getTool(toolName); | ||
| if (!tool) { | ||
| throw new Error(`Tool ${toolName} not found`); | ||
| } | ||
|
|
||
| const args = JSON.parse(toolCall.function.arguments); | ||
| const parsedArgs = tool.schema.parse(args); | ||
|
|
||
| const result = await tool.execute(parsedArgs); | ||
| return result; | ||
| }), | ||
| ); | ||
| return results; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and explicit return type.
The handleCompletion method needs improved error handling for JSON parsing and schema validation. Also, the method return type should be explicitly defined.
- async handleCompletion(completion: OpenAI.Chat.Completions.ChatCompletion) {
+ async handleCompletion(completion: OpenAI.Chat.Completions.ChatCompletion): Promise<any[] | undefined> {
const toolCalls = completion.choices[0].message.tool_calls;
if (!toolCalls) {
return;
}
const results = await Promise.all(
toolCalls.map(async (toolCall) => {
const toolName = toolCall.function.name;
const tool = await this.core.getTool(toolName);
if (!tool) {
throw new Error(`Tool ${toolName} not found`);
}
- const args = JSON.parse(toolCall.function.arguments);
- const parsedArgs = tool.schema.parse(args);
+ let args;
+ try {
+ args = JSON.parse(toolCall.function.arguments);
+ } catch (error) {
+ throw new Error(`Invalid JSON in arguments for tool ${toolName}: ${error.message}`);
+ }
+
+ let parsedArgs;
+ try {
+ parsedArgs = tool.schema.parse(args);
+ } catch (error) {
+ throw new Error(`Invalid arguments for tool ${toolName}: ${error.message}`);
+ }
const result = await tool.execute(parsedArgs);
return result;
}),
);
return results;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async handleCompletion(completion: OpenAI.Chat.Completions.ChatCompletion) { | |
| const toolCalls = completion.choices[0].message.tool_calls; | |
| if (!toolCalls) { | |
| return; | |
| } | |
| const results = await Promise.all( | |
| toolCalls.map(async (toolCall) => { | |
| const toolName = toolCall.function.name; | |
| const tool = await this.core.getTool(toolName); | |
| if (!tool) { | |
| throw new Error(`Tool ${toolName} not found`); | |
| } | |
| const args = JSON.parse(toolCall.function.arguments); | |
| const parsedArgs = tool.schema.parse(args); | |
| const result = await tool.execute(parsedArgs); | |
| return result; | |
| }), | |
| ); | |
| return results; | |
| } | |
| async handleCompletion(completion: OpenAI.Chat.Completions.ChatCompletion): Promise<any[] | undefined> { | |
| const toolCalls = completion.choices[0].message.tool_calls; | |
| if (!toolCalls) { | |
| return; | |
| } | |
| const results = await Promise.all( | |
| toolCalls.map(async (toolCall) => { | |
| const toolName = toolCall.function.name; | |
| const tool = await this.core.getTool(toolName); | |
| if (!tool) { | |
| throw new Error(`Tool ${toolName} not found`); | |
| } | |
| let args; | |
| try { | |
| args = JSON.parse(toolCall.function.arguments); | |
| } catch (error) { | |
| throw new Error(`Invalid JSON in arguments for tool ${toolName}: ${error.message}`); | |
| } | |
| let parsedArgs; | |
| try { | |
| parsedArgs = tool.schema.parse(args); | |
| } catch (error) { | |
| throw new Error(`Invalid arguments for tool ${toolName}: ${error.message}`); | |
| } | |
| const result = await tool.execute(parsedArgs); | |
| return result; | |
| }), | |
| ); | |
| return results; | |
| } |
🤖 Prompt for AI Agents
In packages/ai/src/tool-sets/openai.ts around lines 29 to 50, the
handleCompletion method lacks error handling for JSON parsing and schema
validation, and it does not have an explicit return type. To fix this, add
try-catch blocks around the JSON.parse and schema.parse calls to catch and
handle errors gracefully. Also, explicitly define the method's return type to
improve type safety and clarity.
| async getAuthProvision({ | ||
| app, | ||
| uuid, | ||
| }: { | ||
| app: string; | ||
| uuid: string; | ||
| }): Promise<Account | string> { | ||
| const authProvisions = await pd.getAccounts({ | ||
| external_user_id: uuid, | ||
| include_credentials: false, | ||
| app, | ||
| }); | ||
|
|
||
| const authProvision = authProvisions.data.find((ap) => ap.healthy); | ||
|
|
||
| if (!authProvision) { | ||
| const token = await pd.createConnectToken({ | ||
| external_user_id: uuid, | ||
| webhook_uri: "https://eokyfjps7uqmmrk.m.pipedream.net", // https://pipedream.com/@pd/p_G6Ck6Mk/ | ||
| }); | ||
| return ` | ||
| The user MUST be shown the following URL so they can click on it to connect their account and you MUST NOT modify the URL or it will break: https://pipedream.com/_static/connect.html?token=${token.token}&connectLink=true&app=${encodeURIComponent(app)} | ||
| `.trim(); | ||
| } | ||
|
|
||
| return authProvision; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Hardcoded webhook URI might be an issue.
The webhook URI is hardcoded, which could be problematic if this code is deployed across different environments.
🏁 Script executed:
#!/bin/bash
# Check if the webhook URI is referenced elsewhere or configurable
rg "eokyfjps7uqmmrk.m.pipedream.net" --type ts --type jsLength of output: 343
Extract the webhook URI into configuration
Hardcoding the Pipedream webhook URL in multiple places makes it brittle across environments. Move this value into a shared configuration (e.g. an environment variable or central config service) and reference it where needed.
Files to update:
- packages/ai/src/tool-sets/core.ts
- modelcontextprotocol/src/lib/authProvisions.ts
Suggested change example:
// config.ts
export const PIPEDREAM_WEBHOOK_URI = process.env.PIPEDREAM_WEBHOOK_URI!
// usage
const token = await pd.createConnectToken({
external_user_id: uuid,
webhook_uri: PIPEDREAM_WEBHOOK_URI,
});🤖 Prompt for AI Agents
In packages/ai/src/tool-sets/core.ts between lines 72 and 98, the webhook URI is
hardcoded in the call to pd.createConnectToken, which reduces flexibility across
environments. To fix this, extract the webhook URI into a shared configuration,
such as an environment variable or a central config file, and replace the
hardcoded string with a reference to this configuration value. This will allow
the webhook URI to be easily changed without modifying the code.
| async getTools(options?: { app?: string; query?: string }) { | ||
| const { data: components } = await pd.getComponents({ | ||
| app: options?.app, | ||
| q: options?.query, | ||
| }); | ||
|
|
||
| for (const component of components) { | ||
| this.tools.push({ | ||
| name: component.name.replace(/[^a-zA-Z0-9_-]/g, "_"), | ||
| description: component.description, | ||
| schema: z.object(configurablePropsToZod(component)), | ||
| execute: (args) => this.executeTool(component, args), | ||
| }); | ||
| } | ||
|
|
||
| return this.tools; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Prevent duplicate tools when fetching multiple times.
The current implementation adds tools to the array without checking for duplicates, which could lead to duplicates if getTools is called multiple times.
async getTools(options?: { app?: string; query?: string }) {
const { data: components } = await pd.getComponents({
app: options?.app,
q: options?.query,
});
+ // Clear existing tools or use a Set to prevent duplicates
+ this.tools = [];
for (const component of components) {
this.tools.push({
name: component.name.replace(/[^a-zA-Z0-9_-]/g, "_"),
description: component.description,
schema: z.object(configurablePropsToZod(component)),
execute: (args) => this.executeTool(component, args),
});
}
return this.tools;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async getTools(options?: { app?: string; query?: string }) { | |
| const { data: components } = await pd.getComponents({ | |
| app: options?.app, | |
| q: options?.query, | |
| }); | |
| for (const component of components) { | |
| this.tools.push({ | |
| name: component.name.replace(/[^a-zA-Z0-9_-]/g, "_"), | |
| description: component.description, | |
| schema: z.object(configurablePropsToZod(component)), | |
| execute: (args) => this.executeTool(component, args), | |
| }); | |
| } | |
| return this.tools; | |
| } | |
| async getTools(options?: { app?: string; query?: string }) { | |
| const { data: components } = await pd.getComponents({ | |
| app: options?.app, | |
| q: options?.query, | |
| }); | |
| // Clear existing tools or use a Set to prevent duplicates | |
| this.tools = []; | |
| for (const component of components) { | |
| this.tools.push({ | |
| name: component.name.replace(/[^a-zA-Z0-9_-]/g, "_"), | |
| description: component.description, | |
| schema: z.object(configurablePropsToZod(component)), | |
| execute: (args) => this.executeTool(component, args), | |
| }); | |
| } | |
| return this.tools; | |
| } |
🤖 Prompt for AI Agents
In packages/ai/src/tool-sets/core.ts around lines 22 to 38, the getTools method
appends tools to this.tools without checking for duplicates, causing repeated
entries if called multiple times. Modify the method to check if a tool with the
same name already exists in this.tools before pushing a new one, ensuring each
tool is unique and preventing duplicates on multiple calls.
packages/ai/src/tool-sets/core.ts
Outdated
| async executeTool(component: V1Component, args: any) { | ||
| const appKey = componentAppKey(component.configurable_props); | ||
|
|
||
| if (!appKey) { | ||
| throw new Error("App name not found"); | ||
| } | ||
|
|
||
| const authProvision = await this.getAuthProvision({ | ||
| app: appKey, | ||
| uuid: this.userId, | ||
| }); | ||
|
|
||
| if (typeof authProvision === "string") { | ||
| return authProvision; | ||
| } | ||
|
|
||
| return pd.runAction({ | ||
| actionId: component.key, | ||
| configuredProps: { | ||
| ...args, | ||
| [appKey]: { | ||
| authProvisionId: authProvision.id, | ||
| }, | ||
| }, | ||
| externalUserId: this.userId, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for API calls.
The executeTool method lacks error handling for the API calls, which could lead to unhandled promise rejections.
async executeTool(component: V1Component, args: any) {
const appKey = componentAppKey(component.configurable_props);
if (!appKey) {
throw new Error("App name not found");
}
- const authProvision = await this.getAuthProvision({
- app: appKey,
- uuid: this.userId,
- });
+ try {
+ const authProvision = await this.getAuthProvision({
+ app: appKey,
+ uuid: this.userId,
+ });
if (typeof authProvision === "string") {
return authProvision;
}
- return pd.runAction({
- actionId: component.key,
- configuredProps: {
- ...args,
- [appKey]: {
- authProvisionId: authProvision.id,
- },
- },
- externalUserId: this.userId,
- });
+ try {
+ return await pd.runAction({
+ actionId: component.key,
+ configuredProps: {
+ ...args,
+ [appKey]: {
+ authProvisionId: authProvision.id,
+ },
+ },
+ externalUserId: this.userId,
+ });
+ } catch (error) {
+ throw new Error(`Failed to run action: ${error.message}`);
+ }
+ } catch (error) {
+ throw new Error(`Failed to get authentication: ${error.message}`);
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async executeTool(component: V1Component, args: any) { | |
| const appKey = componentAppKey(component.configurable_props); | |
| if (!appKey) { | |
| throw new Error("App name not found"); | |
| } | |
| const authProvision = await this.getAuthProvision({ | |
| app: appKey, | |
| uuid: this.userId, | |
| }); | |
| if (typeof authProvision === "string") { | |
| return authProvision; | |
| } | |
| return pd.runAction({ | |
| actionId: component.key, | |
| configuredProps: { | |
| ...args, | |
| [appKey]: { | |
| authProvisionId: authProvision.id, | |
| }, | |
| }, | |
| externalUserId: this.userId, | |
| }); | |
| } | |
| async executeTool(component: V1Component, args: any) { | |
| const appKey = componentAppKey(component.configurable_props); | |
| if (!appKey) { | |
| throw new Error("App name not found"); | |
| } | |
| try { | |
| const authProvision = await this.getAuthProvision({ | |
| app: appKey, | |
| uuid: this.userId, | |
| }); | |
| if (typeof authProvision === "string") { | |
| return authProvision; | |
| } | |
| try { | |
| return await pd.runAction({ | |
| actionId: component.key, | |
| configuredProps: { | |
| ...args, | |
| [appKey]: { | |
| authProvisionId: authProvision.id, | |
| }, | |
| }, | |
| externalUserId: this.userId, | |
| }); | |
| } catch (error) { | |
| throw new Error(`Failed to run action: ${error.message}`); | |
| } | |
| } catch (error) { | |
| throw new Error(`Failed to get authentication: ${error.message}`); | |
| } | |
| } |
🤖 Prompt for AI Agents
In packages/ai/src/tool-sets/core.ts between lines 44 and 70, the executeTool
method makes asynchronous API calls without error handling, risking unhandled
promise rejections. Wrap the asynchronous calls, especially getAuthProvision and
pd.runAction, in try-catch blocks to catch and handle any errors. In the catch
block, log or rethrow the error appropriately to ensure failures are managed
gracefully.
WHY
Using Pipedream Connect with LLMs is more complex then it needs to be.
This new sdk drastically simplifies it. See the Readme for an example.
Summary by CodeRabbit
New Features
@pipedream/aipackage, providing tools for integrating with OpenAI and Pipedream services.Documentation
Chores