Skip to content

Commit 852838a

Browse files
authored
feat(ai): pipedream ai sdk (#16699)
1 parent ae1b6f8 commit 852838a

File tree

14 files changed

+545
-10
lines changed

14 files changed

+545
-10
lines changed

packages/ai/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OPENAI_API_KEY=
2+
3+
# Get these by following https://pipedream.com/docs/connect/managed-auth/quickstart/#getting-started
4+
PIPEDREAM_CLIENT_ID=
5+
PIPEDREAM_CLIENT_SECRET=
6+
PIPEDREAM_PROJECT_ID=
7+
PIPEDREAM_PROJECT_ENVIRONMENT=

packages/ai/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.env
2+
node_modules
3+
dist

packages/ai/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# @pipedream/ai
2+
3+
> This library is in alpha status. The API is subject to breaking changes.
4+
5+
Create a .env file based on the .env.example file.
6+
7+
Basic example:
8+
```ts
9+
import { OpenAiTools } from "@pipedream/ai"
10+
import { OpenAI } from "openai"
11+
12+
const openai = new OpenAI()
13+
14+
// Replace with a unique identifier for your user
15+
const userId = <add user id here>
16+
17+
const openAiTools = new OpenAiTools(userId)
18+
const tools = await openAiTools.getTools({
19+
app: "slack",
20+
})
21+
22+
const completion = await openai.chat.completions.create({
23+
messages: [
24+
{
25+
role: "user",
26+
content: "Send a joke to #random channel",
27+
},
28+
],
29+
model: "gpt-4o",
30+
tools,
31+
})
32+
33+
const results = await openAiTools.handleCompletion(completion)
34+
35+
console.log(JSON.stringify(results, null, 2))
36+
37+
```

packages/ai/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@pipedream/ai",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"description": "Pipedream AI",
6+
"main": "./dist/index.js",
7+
"types": "./dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"import": "./dist/index.js",
11+
"types": "./dist/index.d.ts"
12+
}
13+
},
14+
"files": [
15+
"dist",
16+
"package.json"
17+
],
18+
"publishConfig": {
19+
"access": "public"
20+
},
21+
"scripts": {
22+
"build": "tsc"
23+
},
24+
"dependencies": {
25+
"@pipedream/sdk": "workspace:^",
26+
"zod": "^3.24.4",
27+
"zod-to-json-schema": "^3.24.5"
28+
},
29+
"devDependencies": {
30+
"bun": "^1.2.13",
31+
"openai": "^4.77.0"
32+
}
33+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {
2+
ConfigurableProps, V1Component,
3+
} from "@pipedream/sdk";
4+
import {
5+
z, ZodRawShape,
6+
} from "zod";
7+
import {
8+
extractEnumValues, toNonEmptyTuple,
9+
} from "./lib/helpers";
10+
11+
export const configurablePropsToZod = (
12+
component: V1Component,
13+
options?: {
14+
asyncOptionsDescription?: string;
15+
configureComponentDescription?: string;
16+
configurableProps?: ConfigurableProps;
17+
},
18+
) => {
19+
const schema: ZodRawShape = {};
20+
21+
for (const cp of options?.configurableProps ||
22+
(component.configurable_props as ConfigurableProps)) {
23+
if (cp.hidden) {
24+
continue;
25+
}
26+
27+
if (cp.type === "app") {
28+
// XXX handle directly in implementation
29+
continue;
30+
} else if (cp.type === "string") {
31+
if (cp.options && Array.isArray(cp.options) && cp.options.length > 0) {
32+
const enumValues = toNonEmptyTuple(extractEnumValues(cp.options));
33+
if (enumValues) {
34+
schema[cp.name] = z.enum(enumValues);
35+
} else {
36+
schema[cp.name] = z.string();
37+
}
38+
} else {
39+
schema[cp.name] = z.string();
40+
}
41+
} else if (cp.type === "string[]") {
42+
if (cp.options && Array.isArray(cp.options) && cp.options.length > 0) {
43+
const enumValues = toNonEmptyTuple(extractEnumValues(cp.options));
44+
if (enumValues) {
45+
schema[cp.name] = z.array(z.enum(enumValues));
46+
} else {
47+
schema[cp.name] = z.array(z.string());
48+
}
49+
} else {
50+
schema[cp.name] = z.array(z.string());
51+
}
52+
} else if (cp.type === "$.discord.channel") {
53+
schema[cp.name] = z.string();
54+
} else if (cp.type === "$.discord.channel[]") {
55+
schema[cp.name] = z.array(z.string());
56+
} else if (cp.type === "object") {
57+
schema[cp.name] = z.object({}).passthrough();
58+
} else if (cp.type === "any") {
59+
schema[cp.name] = z.any();
60+
} else if (cp.type === "integer") {
61+
schema[cp.name] = z.number().int();
62+
} else if (cp.type === "integer[]") {
63+
schema[cp.name] = z.array(z.number().int());
64+
} else if (cp.type === "boolean") {
65+
schema[cp.name] = z.boolean();
66+
} else if (cp.type === "boolean[]") {
67+
schema[cp.name] = z.array(z.boolean());
68+
// ignore alerts, as no user input required
69+
} else {
70+
console.error("unhandled type. Skipping", cp.name, cp.type);
71+
}
72+
73+
if (schema[cp.name]) {
74+
if (cp.optional) {
75+
schema[cp.name] = schema[cp.name].optional().nullable();
76+
}
77+
78+
let description: string = cp.description || "";
79+
80+
if (cp.hidden) {
81+
description +=
82+
"\n\nIMPORTANT: This property is hidden. Do not configure it and leave it blank.\n";
83+
}
84+
85+
if (cp.remoteOptions) {
86+
if (options?.asyncOptionsDescription) {
87+
if (options.configureComponentDescription) {
88+
description += `\n\n${options.asyncOptionsDescription}`;
89+
}
90+
} else {
91+
if (options?.configureComponentDescription) {
92+
description += `\n\n${options.configureComponentDescription}`;
93+
}
94+
}
95+
// if (cp.name.includes("id")) {
96+
// 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.`;
97+
// }
98+
}
99+
100+
if (description.trim()) {
101+
schema[cp.name] = schema[cp.name].describe(description.trim());
102+
}
103+
}
104+
}
105+
106+
return schema;
107+
};

packages/ai/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export {
2+
OpenAiTools,
3+
} from "./tool-sets/openai"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ConfigurableProps } from "@pipedream/sdk";
2+
3+
export const componentAppKey = (configuredProps: ConfigurableProps) => {
4+
return configuredProps.find((prop) => prop.type === "app")?.app;
5+
};

packages/ai/src/lib/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { z } from "zod";
2+
3+
const configSchema = z.object({
4+
PIPEDREAM_CLIENT_ID: z.string().min(1, {
5+
message: "PIPEDREAM_CLIENT_ID is required",
6+
}),
7+
PIPEDREAM_CLIENT_SECRET: z.string().min(1, {
8+
message: "PIPEDREAM_CLIENT_SECRET is required",
9+
}),
10+
PIPEDREAM_PROJECT_ID: z.string().min(1, {
11+
message: "PIPEDREAM_PROJECT_ID is required",
12+
}),
13+
PIPEDREAM_PROJECT_ENVIRONMENT: z.enum([
14+
"development",
15+
"production",
16+
]),
17+
});
18+
19+
export const config = configSchema.parse(process?.env);
20+
21+
export type Config = z.infer<typeof configSchema>;

packages/ai/src/lib/helpers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function toNonEmptyTuple<T extends string>(
2+
arr: T[],
3+
): [T, ...T[]] | undefined {
4+
return arr.length > 0
5+
? (arr as [T, ...T[]])
6+
: undefined;
7+
}
8+
9+
type EnumLike = string | { value: string };
10+
11+
export function extractEnumValues(values: EnumLike[]): string[] {
12+
return values.map((v) => (typeof v === "string"
13+
? v
14+
: v.value));
15+
}

packages/ai/src/lib/pd-client.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createBackendClient } from "@pipedream/sdk";
2+
import { config } from "./config";
3+
4+
export const pd = createBackendClient({
5+
credentials: {
6+
clientId: config.PIPEDREAM_CLIENT_ID,
7+
clientSecret: config.PIPEDREAM_CLIENT_SECRET,
8+
},
9+
projectId: config.PIPEDREAM_PROJECT_ID,
10+
environment: config.PIPEDREAM_PROJECT_ENVIRONMENT,
11+
});

0 commit comments

Comments
 (0)