Skip to content

Commit 003ae96

Browse files
authored
default to fetch (#100)
1 parent 5159d5c commit 003ae96

File tree

17 files changed

+481
-135
lines changed

17 files changed

+481
-135
lines changed

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,8 @@ If any of the above commands fail or show errors:
7878
- `api.workflow.*` - Workflow CRUD and operations (create, update, delete, deploy, execute, etc.)
7979
- **No Barrel Files**: Do not create barrel/index files that re-export from other files
8080

81+
## Plugin Guidelines
82+
- **No SDK Dependencies**: Plugin step files must use `fetch` directly instead of SDK client libraries. Do not add npm package dependencies for API integrations.
83+
- **No dependencies field**: Do not use the `dependencies` field in plugin `index.ts` files. All API calls should use native `fetch`.
84+
- **Why**: Using `fetch` instead of SDKs reduces supply chain attack surface. SDKs have transitive dependencies that could be compromised.
85+

app/api/integrations/[integrationId]/test/route.ts

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import { LinearClient } from "@linear/sdk";
2-
import FirecrawlApp from "@mendable/firecrawl-js";
3-
import { WebClient } from "@slack/web-api";
41
import { createGateway } from "ai";
52
import { NextResponse } from "next/server";
63
import postgres from "postgres";
7-
import { Resend } from "resend";
84
import { auth } from "@/lib/auth";
95
import { getIntegration } from "@/lib/db/integrations";
106

@@ -104,8 +100,35 @@ async function testLinearConnection(
104100
};
105101
}
106102

107-
const client = new LinearClient({ apiKey });
108-
await client.viewer;
103+
const response = await fetch("https://api.linear.app/graphql", {
104+
method: "POST",
105+
headers: {
106+
"Content-Type": "application/json",
107+
Authorization: apiKey,
108+
},
109+
body: JSON.stringify({
110+
query: "query { viewer { id } }",
111+
}),
112+
});
113+
114+
if (!response.ok) {
115+
return {
116+
status: "error",
117+
message: "Connection failed",
118+
};
119+
}
120+
121+
const result = (await response.json()) as {
122+
data?: { viewer?: { id: string } };
123+
errors?: Array<{ message: string }>;
124+
};
125+
126+
if (result.errors?.length || !result.data?.viewer) {
127+
return {
128+
status: "error",
129+
message: "Connection failed",
130+
};
131+
}
109132

110133
return {
111134
status: "success",
@@ -130,13 +153,26 @@ async function testSlackConnection(
130153
};
131154
}
132155

133-
const client = new WebClient(apiKey);
134-
const slackAuth = await client.auth.test();
156+
const response = await fetch("https://slack.com/api/auth.test", {
157+
method: "POST",
158+
headers: {
159+
Authorization: `Bearer ${apiKey}`,
160+
},
161+
});
162+
163+
if (!response.ok) {
164+
return {
165+
status: "error",
166+
message: "Connection failed",
167+
};
168+
}
169+
170+
const result = (await response.json()) as { ok: boolean; error?: string };
135171

136-
if (!slackAuth.ok) {
172+
if (!result.ok) {
137173
return {
138174
status: "error",
139-
message: slackAuth.error || "Connection failed",
175+
message: result.error || "Connection failed",
140176
};
141177
}
142178

@@ -163,10 +199,14 @@ async function testResendConnection(
163199
};
164200
}
165201

166-
const resend = new Resend(apiKey);
167-
const domains = await resend.domains.list();
202+
const response = await fetch("https://api.resend.com/domains", {
203+
method: "GET",
204+
headers: {
205+
Authorization: `Bearer ${apiKey}`,
206+
},
207+
});
168208

169-
if (!domains.data) {
209+
if (!response.ok) {
170210
return {
171211
status: "error",
172212
message: "Connection failed",
@@ -263,12 +303,19 @@ async function testFirecrawlConnection(
263303
};
264304
}
265305

266-
const app = new FirecrawlApp({ apiKey });
267-
const result = await app.scrape("https://firecrawl.dev", {
268-
formats: ["markdown"],
306+
const response = await fetch("https://api.firecrawl.dev/v1/scrape", {
307+
method: "POST",
308+
headers: {
309+
"Content-Type": "application/json",
310+
Authorization: `Bearer ${apiKey}`,
311+
},
312+
body: JSON.stringify({
313+
url: "https://example.com",
314+
formats: ["markdown"],
315+
}),
269316
});
270317

271-
if (!result) {
318+
if (!response.ok) {
272319
return {
273320
status: "error",
274321
message: "Authentication or scrape failed",

lib/codegen-registry.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,7 @@ export async function findIssuesStep(
419419
}
420420
`,
421421

422-
"resend/send-email": `import { Resend } from "resend";
423-
import { fetchCredentials } from "./lib/credential-helper";
422+
"resend/send-email": `import { fetchCredentials } from "./lib/credential-helper";
424423
425424
function getErrorMessage(error: unknown): string {
426425
if (error instanceof Error) return error.message;
@@ -471,33 +470,41 @@ export async function sendEmailStep(
471470
}
472471
473472
try {
474-
const resend = new Resend(apiKey);
473+
const headers: Record<string, string> = {
474+
Authorization: \`Bearer \${apiKey}\`,
475+
"Content-Type": "application/json",
476+
};
477+
478+
if (input.idempotencyKey) {
479+
headers["Idempotency-Key"] = input.idempotencyKey;
480+
}
475481
476-
const result = await resend.emails.send(
477-
{
482+
const response = await fetch(\`\${RESEND_API_URL}/emails\`, {
483+
method: "POST",
484+
headers,
485+
body: JSON.stringify({
478486
from: senderEmail,
479487
to: input.emailTo,
480488
subject: input.emailSubject,
481489
text: input.emailBody,
482490
...(input.emailCc && { cc: input.emailCc }),
483491
...(input.emailBcc && { bcc: input.emailBcc }),
484-
...(input.emailReplyTo && { replyTo: input.emailReplyTo }),
485-
...(input.emailScheduledAt && { scheduledAt: input.emailScheduledAt }),
486-
...(input.emailTopicId && { topicId: input.emailTopicId }),
487-
},
488-
input.idempotencyKey
489-
? { idempotencyKey: input.idempotencyKey }
490-
: undefined,
491-
);
492+
...(input.emailReplyTo && { reply_to: input.emailReplyTo }),
493+
...(input.emailScheduledAt && { scheduled_at: input.emailScheduledAt }),
494+
}),
495+
});
492496
493-
if (result.error) {
497+
if (!response.ok) {
498+
const errorData = (await response.json()) as ResendErrorResponse;
494499
return {
495500
success: false,
496-
error: result.error.message || "Failed to send email",
501+
error:
502+
errorData.message || \`HTTP \${response.status}: Failed to send email\`,
497503
};
498504
}
499505
500-
return { success: true, id: result.data?.id || "" };
506+
const data = (await response.json()) as ResendEmailResponse;
507+
return { success: true, id: data.id };
501508
} catch (error) {
502509
const message = error instanceof Error ? error.message : String(error);
503510
return {

plugins/firecrawl/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ const firecrawlPlugin: IntegrationPlugin = {
3232
},
3333
},
3434

35-
dependencies: {
36-
"@mendable/firecrawl-js": "^4.6.2",
37-
},
38-
3935
actions: [
4036
{
4137
slug: "scrape",

plugins/firecrawl/steps/scrape.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import "server-only";
22

3-
import FirecrawlApp from "@mendable/firecrawl-js";
43
import { fetchCredentials } from "@/lib/credential-fetcher";
54
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
65
import { getErrorMessage } from "@/lib/utils";
76
import type { FirecrawlCredentials } from "../credentials";
87

8+
const FIRECRAWL_API_URL = "https://api.firecrawl.dev/v1";
9+
10+
type FirecrawlScrapeResponse = {
11+
success: boolean;
12+
data?: {
13+
markdown?: string;
14+
metadata?: Record<string, unknown>;
15+
};
16+
error?: string;
17+
};
18+
919
type ScrapeResult = {
1020
markdown?: string;
1121
metadata?: Record<string, unknown>;
@@ -35,14 +45,32 @@ async function stepHandler(
3545
}
3646

3747
try {
38-
const firecrawl = new FirecrawlApp({ apiKey });
39-
const result = await firecrawl.scrape(input.url, {
40-
formats: input.formats || ["markdown"],
48+
const response = await fetch(`${FIRECRAWL_API_URL}/scrape`, {
49+
method: "POST",
50+
headers: {
51+
"Content-Type": "application/json",
52+
Authorization: `Bearer ${apiKey}`,
53+
},
54+
body: JSON.stringify({
55+
url: input.url,
56+
formats: input.formats || ["markdown"],
57+
}),
4158
});
4259

60+
if (!response.ok) {
61+
const errorText = await response.text();
62+
throw new Error(`HTTP ${response.status}: ${errorText}`);
63+
}
64+
65+
const result = (await response.json()) as FirecrawlScrapeResponse;
66+
67+
if (!result.success) {
68+
throw new Error(result.error || "Scrape failed");
69+
}
70+
4371
return {
44-
markdown: result.markdown,
45-
metadata: result.metadata,
72+
markdown: result.data?.markdown,
73+
metadata: result.data?.metadata,
4674
};
4775
} catch (error) {
4876
throw new Error(`Failed to scrape: ${getErrorMessage(error)}`);

plugins/firecrawl/steps/search.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import "server-only";
22

3-
import FirecrawlApp from "@mendable/firecrawl-js";
43
import { fetchCredentials } from "@/lib/credential-fetcher";
54
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
65
import { getErrorMessage } from "@/lib/utils";
76
import type { FirecrawlCredentials } from "../credentials";
87

8+
const FIRECRAWL_API_URL = "https://api.firecrawl.dev/v1";
9+
10+
type FirecrawlSearchResponse = {
11+
success: boolean;
12+
data?: unknown[];
13+
error?: string;
14+
};
15+
916
type SearchResult = {
10-
web?: unknown[];
17+
data?: unknown[];
1118
};
1219

1320
export type FirecrawlSearchCoreInput = {
@@ -37,14 +44,32 @@ async function stepHandler(
3744
}
3845

3946
try {
40-
const firecrawl = new FirecrawlApp({ apiKey });
41-
const result = await firecrawl.search(input.query, {
42-
limit: input.limit ? Number(input.limit) : undefined,
43-
scrapeOptions: input.scrapeOptions,
47+
const response = await fetch(`${FIRECRAWL_API_URL}/search`, {
48+
method: "POST",
49+
headers: {
50+
"Content-Type": "application/json",
51+
Authorization: `Bearer ${apiKey}`,
52+
},
53+
body: JSON.stringify({
54+
query: input.query,
55+
limit: input.limit ? Number(input.limit) : undefined,
56+
scrapeOptions: input.scrapeOptions,
57+
}),
4458
});
4559

60+
if (!response.ok) {
61+
const errorText = await response.text();
62+
throw new Error(`HTTP ${response.status}: ${errorText}`);
63+
}
64+
65+
const result = (await response.json()) as FirecrawlSearchResponse;
66+
67+
if (!result.success) {
68+
throw new Error(result.error || "Search failed");
69+
}
70+
4671
return {
47-
web: result.web,
72+
data: result.data,
4873
};
4974
} catch (error) {
5075
throw new Error(`Failed to search: ${getErrorMessage(error)}`);

plugins/linear/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ const linearPlugin: IntegrationPlugin = {
4242
},
4343
},
4444

45-
dependencies: {
46-
"@linear/sdk": "^63.2.0",
47-
},
48-
4945
actions: [
5046
{
5147
slug: "create-ticket",

0 commit comments

Comments
 (0)