Skip to content

Conversation

@TheBestMoshe
Copy link
Collaborator

@TheBestMoshe TheBestMoshe commented May 16, 2025

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

    • Introduced the @pipedream/ai package, providing tools for integrating with OpenAI and Pipedream services.
    • Added classes for managing and executing tools, including OpenAI chat completion integration and dynamic tool execution.
    • Enabled validation and configuration of environment variables for secure API access.
  • Documentation

    • Added a README with usage instructions and examples for the new package.
    • Provided an environment variable example file for setup guidance.
  • Chores

    • Added configuration files for TypeScript, package management, and Git ignore rules.

@vercel
Copy link

vercel bot commented May 16, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Ignored (Inspect) Visit Preview May 16, 2025 7:58pm
pipedream-docs ⬜️ Ignored (Inspect) May 16, 2025 7:58pm
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) May 16, 2025 7:58pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 16, 2025

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 55fe65d and 08756d2.

📒 Files selected for processing (3)
  • packages/ai/README.md (1 hunks)
  • packages/ai/src/configurablePropsToZod.ts (1 hunks)
  • packages/ai/src/tool-sets/core.ts (1 hunks)

Walkthrough

A new package, @pipedream/ai, has been introduced with supporting configuration, documentation, TypeScript setup, and utility modules. It provides classes and utilities for managing and executing tools, including integration with OpenAI and Pipedream APIs. The package includes schema validation, authentication handling, and helper functions for tool execution and configuration.

Changes

File(s) Change Summary
packages/ai/.env.example, packages/ai/.gitignore, packages/ai/README.md Added environment variable example, gitignore, and README documentation files.
packages/ai/package.json, packages/ai/tsconfig.json Added package metadata, dependencies, publishing configuration, and TypeScript compiler options.
packages/ai/src/index.ts Added export for OpenAiTools from the OpenAI tool set module.
packages/ai/src/configurablePropsToZod.ts Added utility to convert component configurable properties to Zod schemas.
packages/ai/src/lib/componentAppKey.ts Added function to extract the app key from configurable properties.
packages/ai/src/lib/config.ts Added environment variable schema validation and config export using Zod.
packages/ai/src/lib/helpers.ts Added helper functions for tuple conversion and enum value extraction.
packages/ai/src/lib/pd-client.ts Added and exported a configured Pipedream backend client instance.
packages/ai/src/tool-sets/core.ts Introduced CoreTools class for managing, validating, and executing tools, including authentication provisioning.
packages/ai/src/tool-sets/openai.ts Introduced OpenAiTools class for OpenAI tool integration, including tool formatting and completion handling.

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
Loading

Poem

🐇
In the warren, code anew,
Tools and helpers hop into view.
With schemas checked and configs tight,
Each function leaps to work just right.
Now OpenAI and Pipedream play,
In fields of code, they work all day.
Hooray for features, clean and spry!

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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.example lists all required OpenAI and Pipedream credentials with a useful upstream link. Consider adding a top‐line instruction (e.g., “Copy to .env and 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 its app field or undefined. 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 compatibility

The 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 moduleResolution option 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 workflow

The 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 environment

The 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 any in 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

📥 Commits

Reviewing files that changed from the base of the PR and between ae1b6f8 and 55fe65d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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 of OpenAiTools from the tool-sets/openai module 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, and dist aligns 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
Instantiating pd via createBackendClient with values from your validated config is appropriate. Ensure that the config module 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.

Comment on lines +29 to +31
"devDependencies": {
"bun": "^1.2.13",
"openai": "^4.77.0"
Copy link
Contributor

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.

Suggested change
"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.

Comment on lines +1 to +33
{
"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"
}
}
Copy link
Contributor

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.

Suggested change
{
"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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +3 to +17
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",
]),
});
Copy link
Contributor

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.

Suggested change
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.

Comment on lines 1 to 35
# @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
Copy link
Contributor

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 openai

Environment 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=development

Basic 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 OpenAI
  • handleCompletion(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 -->

Comment on lines 14 to 15
const userId = <add user id here>
const openAiTools = new OpenAiTools(userId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +29 to +50
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;
}
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +72 to +98
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;
}
Copy link
Contributor

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 js

Length 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.

Comment on lines +22 to +38
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;
}
Copy link
Contributor

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.

Suggested change
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.

Comment on lines 44 to 70
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,
});
}
Copy link
Contributor

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.

Suggested change
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.

@TheBestMoshe TheBestMoshe merged commit 852838a into master May 16, 2025
8 checks passed
@TheBestMoshe TheBestMoshe deleted the moshe/pipedream_ai_sdk branch May 16, 2025 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants