@meetopenbot/firecrawl 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +252 -158
  2. package/package.json +5 -2
package/dist/index.js CHANGED
@@ -1,34 +1,63 @@
1
- // index.ts
2
- var FIRECRAWL_API_URL = globalThis?.process?.env?.FIRECRAWL_API_URL || "https://api.firecrawl.dev";
1
+ // node_modules/@meetopenbot/plugin-sdk/dist/plugin.js
2
+ function definePlugin(definition) {
3
+ return definition;
4
+ }
5
+
6
+ // node_modules/@meetopenbot/plugin-sdk/dist/helpers.js
7
+ function shouldHandleInvoke(event, agentId) {
8
+ const routedTo = event.data?.agentId;
9
+ return !(typeof routedTo === "string" && routedTo && routedTo !== agentId);
10
+ }
11
+ function agentOutput(args) {
12
+ return {
13
+ type: "agent:output",
14
+ data: { content: args.content },
15
+ meta: {
16
+ ...args.meta ?? {},
17
+ agentId: args.agentId,
18
+ ...args.threadId ? { threadId: args.threadId } : {}
19
+ }
20
+ };
21
+ }
22
+ function uiWidget(args) {
23
+ return {
24
+ type: "client:ui:widget",
25
+ data: args.widget,
26
+ meta: {
27
+ ...args.meta ?? {},
28
+ agentId: args.agentId,
29
+ ...args.threadId ? { threadId: args.threadId } : {}
30
+ }
31
+ };
32
+ }
33
+
34
+ // src/constants.ts
35
+ var DEFAULT_API_URL = "https://api.firecrawl.dev";
3
36
  var POLL_INTERVAL_MS = 3e3;
4
37
  var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
5
- var buildApiKeyWidget = (agentId, threadId, reason) => ({
6
- type: "client:ui:widget",
7
- data: {
8
- kind: "form",
9
- widgetId: `firecrawl_api_key_request_${Date.now()}`,
10
- title: "Firecrawl API Key Required",
11
- description: `Firecrawl could not authenticate${reason ? ` (${reason})` : ""}. Provide a Firecrawl API key to continue. The key is stored as a workspace variable on your machine and never leaves your local runtime.`,
12
- fields: [
13
- {
14
- id: "apiKey",
15
- label: "API Key",
16
- type: "text",
17
- placeholder: "fc-...",
18
- required: true
19
- }
20
- ],
21
- submitLabel: "Save API Key",
22
- metadata: {
23
- type: "api_key_request",
24
- provider: "firecrawl",
25
- envVar: "FIRECRAWL_API_KEY",
26
- source: "firecrawl"
27
- }
28
- },
29
- meta: { agentId, threadId }
30
- });
38
+ var API_KEY_ENV_VAR = "FIRECRAWL_API_KEY";
39
+
40
+ // src/utils.ts
41
+ var readEnv = () => globalThis.process?.env ?? {};
42
+ var resolveApiUrl = (config) => {
43
+ const configured = config.apiUrl;
44
+ if (typeof configured === "string" && configured.trim()) {
45
+ return configured.trim();
46
+ }
47
+ const envUrl = readEnv().FIRECRAWL_API_URL;
48
+ return envUrl?.trim() || DEFAULT_API_URL;
49
+ };
50
+ var resolveApiKey = (config) => {
51
+ const configured = config.apiKey;
52
+ if (typeof configured === "string" && configured.trim()) {
53
+ return configured.trim();
54
+ }
55
+ const envKey = readEnv()[API_KEY_ENV_VAR];
56
+ return envKey?.trim() || void 0;
57
+ };
31
58
  var isAuthError = (message) => !!message && (/unauthor/i.test(message) || /api[\s-]?key/i.test(message) || /401|403/.test(message));
59
+
60
+ // src/client.ts
32
61
  async function firecrawlRequest(apiKey, apiUrl, path, init = { method: "GET" }) {
33
62
  const res = await fetch(`${apiUrl.replace(/\/$/, "")}${path}`, {
34
63
  method: init.method,
@@ -45,153 +74,218 @@ async function firecrawlRequest(apiKey, apiUrl, path, init = { method: "GET" })
45
74
  payload = null;
46
75
  }
47
76
  if (!res.ok) {
48
- const message = payload?.error || payload?.message || `Firecrawl request failed (${res.status} ${res.statusText})`;
77
+ const errorPayload = payload;
78
+ const message = errorPayload?.error || errorPayload?.message || `Firecrawl request failed (${res.status} ${res.statusText})`;
49
79
  const err = new Error(message);
50
80
  err.status = res.status;
51
81
  throw err;
52
82
  }
53
83
  return payload;
54
84
  }
55
- var firecrawlPlugin = (options = {}) => (builder) => {
56
- const { storage } = options;
57
- const env = globalThis?.process?.env || {};
58
- const apiUrl = options.apiUrl || env.FIRECRAWL_API_URL || FIRECRAWL_API_URL;
59
- const getApiKey = () => options.apiKey ?? env.FIRECRAWL_API_KEY;
60
- builder.on("agent:invoke", async function* (event, ctx) {
61
- const { content } = event.data ?? {};
62
- const threadId = event.meta?.threadId || ctx.state.threadId;
63
- if (!content) {
64
- yield { type: "agent:output", data: { content: "No prompt provided." } };
65
- return;
85
+
86
+ // src/ui.ts
87
+ var buildApiKeyWidget = (agentId, threadId, reason) => uiWidget({
88
+ agentId,
89
+ threadId,
90
+ widget: {
91
+ kind: "form",
92
+ widgetId: `firecrawl_api_key_request_${Date.now()}`,
93
+ title: "Firecrawl API Key Required",
94
+ description: `Firecrawl could not authenticate${reason ? ` (${reason})` : ""}. Provide a Firecrawl API key to continue. The key is stored as a workspace variable on your machine and never leaves your local runtime.`,
95
+ fields: [
96
+ {
97
+ id: "apiKey",
98
+ label: "API Key",
99
+ type: "text",
100
+ placeholder: "fc-...",
101
+ required: true
102
+ }
103
+ ],
104
+ submitLabel: "Save API Key",
105
+ metadata: {
106
+ type: "api_key_request",
107
+ provider: "firecrawl",
108
+ envVar: API_KEY_ENV_VAR,
109
+ source: "firecrawl"
66
110
  }
67
- const apiKey = getApiKey();
68
- if (!apiKey) {
69
- yield buildApiKeyWidget(ctx.state.agentId, threadId);
70
- return;
111
+ }
112
+ });
113
+
114
+ // src/index.ts
115
+ var plugin = definePlugin({
116
+ name: "Firecrawl",
117
+ description: "Firecrawl agent for web data gathering",
118
+ configSchema: {
119
+ type: "object",
120
+ properties: {
121
+ apiKey: {
122
+ type: "string",
123
+ description: "Firecrawl API key. Falls back to the FIRECRAWL_API_KEY workspace variable.",
124
+ format: "password"
125
+ },
126
+ apiUrl: {
127
+ type: "string",
128
+ description: "Firecrawl API base URL.",
129
+ format: "url",
130
+ default: DEFAULT_API_URL
131
+ }
71
132
  }
72
- try {
73
- yield { type: "agent:output", data: { content: "Starting Firecrawl agent..." } };
74
- const started = await firecrawlRequest(apiKey, apiUrl, "/v2/agent", {
75
- method: "POST",
76
- body: { prompt: content }
77
- });
78
- if (!started?.success || !started.id) {
79
- const errMsg = started?.error || "Unknown error";
80
- if (isAuthError(errMsg)) {
81
- yield buildApiKeyWidget(ctx.state.agentId, threadId, errMsg);
82
- } else {
83
- yield {
84
- type: "agent:output",
85
- data: { content: `Error starting Firecrawl agent: ${errMsg}` }
86
- };
87
- }
133
+ },
134
+ factory: (context) => (builder) => {
135
+ const apiUrl = resolveApiUrl(context.config);
136
+ const getApiKey = () => resolveApiKey(context.config);
137
+ builder.on("agent:invoke", async function* (event, ctx) {
138
+ if (!shouldHandleInvoke(event, context.agentId)) {
139
+ return;
140
+ }
141
+ const { content } = event.data;
142
+ const threadId = event.meta?.threadId ?? ctx.state.threadId;
143
+ if (!content) {
144
+ yield agentOutput({
145
+ agentId: context.agentId,
146
+ content: "No prompt provided.",
147
+ threadId
148
+ });
149
+ return;
150
+ }
151
+ const apiKey = getApiKey();
152
+ if (!apiKey) {
153
+ yield buildApiKeyWidget(context.agentId, threadId);
88
154
  return;
89
155
  }
90
- yield {
91
- type: "agent:output",
92
- data: { content: `Firecrawl agent task created (id: ${started.id}). Waiting for results...` }
93
- };
94
- const startTime = Date.now();
95
- let lastStatus = "";
96
- while (true) {
97
- if (Date.now() - startTime > POLL_TIMEOUT_MS) {
98
- yield {
99
- type: "agent:output",
100
- data: { content: `Firecrawl agent timed out after ${Math.round(POLL_TIMEOUT_MS / 1e3)}s.` }
101
- };
156
+ try {
157
+ yield agentOutput({
158
+ agentId: context.agentId,
159
+ content: "Starting Firecrawl agent...",
160
+ threadId
161
+ });
162
+ const started = await firecrawlRequest(apiKey, apiUrl, "/v2/agent", {
163
+ method: "POST",
164
+ body: { prompt: content }
165
+ });
166
+ if (!started?.success || !started.id) {
167
+ const errMsg = started?.error || "Unknown error";
168
+ if (isAuthError(errMsg)) {
169
+ yield buildApiKeyWidget(context.agentId, threadId, errMsg);
170
+ } else {
171
+ yield agentOutput({
172
+ agentId: context.agentId,
173
+ content: `Error starting Firecrawl agent: ${errMsg}`,
174
+ threadId
175
+ });
176
+ }
102
177
  return;
103
178
  }
104
- const status = await firecrawlRequest(
105
- apiKey,
106
- apiUrl,
107
- `/v2/agent/${started.id}`
108
- );
109
- if (status.status === "completed") {
110
- yield { type: "agent:output", data: { content: "Firecrawl agent completed successfully." } };
111
- yield {
112
- type: "agent:output",
113
- data: { content: "```json\n" + JSON.stringify(status.data ?? {}, null, 2) + "\n```" }
114
- };
179
+ yield agentOutput({
180
+ agentId: context.agentId,
181
+ content: `Firecrawl agent task created (id: ${started.id}). Waiting for results...`,
182
+ threadId
183
+ });
184
+ const startTime = Date.now();
185
+ let lastStatus = "";
186
+ while (true) {
187
+ if (Date.now() - startTime > POLL_TIMEOUT_MS) {
188
+ yield agentOutput({
189
+ agentId: context.agentId,
190
+ content: `Firecrawl agent timed out after ${Math.round(POLL_TIMEOUT_MS / 1e3)}s.`,
191
+ threadId
192
+ });
193
+ return;
194
+ }
195
+ const status = await firecrawlRequest(
196
+ apiKey,
197
+ apiUrl,
198
+ `/v2/agent/${started.id}`
199
+ );
200
+ if (status.status === "completed") {
201
+ yield agentOutput({
202
+ agentId: context.agentId,
203
+ content: "Firecrawl agent completed successfully.",
204
+ threadId
205
+ });
206
+ yield agentOutput({
207
+ agentId: context.agentId,
208
+ content: "```json\n" + JSON.stringify(status.data ?? {}, null, 2) + "\n```",
209
+ threadId
210
+ });
211
+ return;
212
+ }
213
+ if (status.status === "failed" || status.status === "cancelled") {
214
+ yield agentOutput({
215
+ agentId: context.agentId,
216
+ content: `Firecrawl agent ${status.status}: ${status.error || "no details provided"}`,
217
+ threadId
218
+ });
219
+ return;
220
+ }
221
+ if (status.status && status.status !== lastStatus) {
222
+ yield agentOutput({
223
+ agentId: context.agentId,
224
+ content: `Status: ${status.status}...`,
225
+ threadId
226
+ });
227
+ lastStatus = status.status;
228
+ }
229
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
230
+ }
231
+ } catch (error) {
232
+ const message = error instanceof Error ? error.message : "Firecrawl request failed.";
233
+ if (isAuthError(message)) {
234
+ yield buildApiKeyWidget(context.agentId, threadId, message);
235
+ } else {
236
+ yield agentOutput({
237
+ agentId: context.agentId,
238
+ content: `Error: ${message}`,
239
+ threadId
240
+ });
241
+ }
242
+ }
243
+ });
244
+ builder.on(
245
+ "plugin:ui:widget:response",
246
+ async function* (event, handlerCtx) {
247
+ const { metadata, values, widgetId } = event.data;
248
+ if (!metadata || metadata.type !== "api_key_request" || metadata.source !== "firecrawl") {
115
249
  return;
116
250
  }
117
- if (status.status === "failed" || status.status === "cancelled") {
118
- yield {
119
- type: "agent:output",
120
- data: {
121
- content: `Firecrawl agent ${status.status}: ${status.error || "no details provided"}`
122
- }
123
- };
251
+ const apiKey = values?.apiKey;
252
+ if (typeof apiKey !== "string" || !apiKey) {
124
253
  return;
125
254
  }
126
- if (status.status && status.status !== lastStatus) {
127
- yield { type: "agent:output", data: { content: `Status: ${status.status}...` } };
128
- lastStatus = status.status;
255
+ const envVar = typeof metadata.envVar === "string" && metadata.envVar.trim() ? metadata.envVar : API_KEY_ENV_VAR;
256
+ try {
257
+ await context.storage.createVariable({ key: envVar, value: apiKey, secret: true });
258
+ readEnv()[envVar] = apiKey;
259
+ yield uiWidget({
260
+ agentId: context.agentId,
261
+ widget: {
262
+ widgetId,
263
+ kind: "message",
264
+ title: "API Key Saved",
265
+ body: `Saved ${envVar} as a workspace variable. You can now continue the conversation.`,
266
+ state: "submitted",
267
+ actions: [{ id: "ok", label: "Got it", variant: "primary" }]
268
+ },
269
+ meta: { agentId: handlerCtx.state.agentId }
270
+ });
271
+ yield agentOutput({
272
+ agentId: context.agentId,
273
+ content: "Saved Firecrawl API key to workspace variables. Re-send your last message to retry.",
274
+ meta: { agentId: handlerCtx.state.agentId }
275
+ });
276
+ } catch (error) {
277
+ const errorMessage = error instanceof Error ? error.message : String(error);
278
+ yield agentOutput({
279
+ agentId: context.agentId,
280
+ content: `[firecrawl] failed to save API key: ${errorMessage}`,
281
+ meta: { agentId: handlerCtx.state.agentId }
282
+ });
129
283
  }
130
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
131
- }
132
- } catch (error) {
133
- const message = error?.message || "Firecrawl request failed.";
134
- if (isAuthError(message)) {
135
- yield buildApiKeyWidget(ctx.state.agentId, threadId, message);
136
- } else {
137
- yield { type: "agent:output", data: { content: `Error: ${message}` } };
138
284
  }
139
- }
140
- });
141
- builder.on("client:ui:widget:response", async function* (event, context) {
142
- const { metadata, values, widgetId } = event.data ?? {};
143
- if (!metadata || metadata.type !== "api_key_request" || metadata.source !== "firecrawl") return;
144
- const apiKey = values?.apiKey;
145
- if (typeof apiKey !== "string" || !apiKey) return;
146
- const envVar = typeof metadata.envVar === "string" ? metadata.envVar : "FIRECRAWL_API_KEY";
147
- if (!storage) {
148
- yield {
149
- type: "agent:output",
150
- data: { content: "[firecrawl] no storage available; cannot persist API key." },
151
- meta: { agentId: context.state.agentId }
152
- };
153
- return;
154
- }
155
- try {
156
- await storage.createVariable({ key: envVar, value: apiKey, secret: true });
157
- env[envVar] = apiKey;
158
- yield {
159
- type: "client:ui:widget",
160
- data: {
161
- widgetId,
162
- kind: "message",
163
- title: "API Key Saved",
164
- body: `Saved ${envVar} as a workspace variable. You can now continue the conversation.`,
165
- state: "submitted",
166
- actions: [{ id: "ok", label: "Got it", variant: "primary" }]
167
- },
168
- meta: { agentId: context.state.agentId }
169
- };
170
- yield {
171
- type: "agent:output",
172
- data: {
173
- content: "Saved Firecrawl API key to workspace variables. Re-send your last message to retry."
174
- },
175
- meta: { agentId: context.state.agentId }
176
- };
177
- } catch (error) {
178
- const errorMessage = error instanceof Error ? error.message : String(error);
179
- yield {
180
- type: "agent:output",
181
- data: { content: `[firecrawl] failed to save API key: ${errorMessage}` },
182
- meta: { agentId: context.state.agentId }
183
- };
184
- }
185
- });
186
- };
187
- var plugin = {
188
- id: "firecrawl",
189
- name: "Firecrawl",
190
- description: "Firecrawl agent for web data gathering",
191
- kind: "runtime",
192
- factory: (options) => firecrawlPlugin(options)
193
- };
285
+ );
286
+ }
287
+ });
288
+ var src_default = plugin;
194
289
  export {
195
- firecrawlPlugin,
196
- plugin
290
+ src_default as default
197
291
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/firecrawl",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "Firecrawl agent plugin for OpenBot",
6
6
  "main": "./dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "dist"
15
15
  ],
16
16
  "scripts": {
17
- "build": "esbuild index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js",
17
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js",
18
18
  "prepublishOnly": "npm run build"
19
19
  },
20
20
  "engines": {
@@ -23,5 +23,8 @@
23
23
  "devDependencies": {
24
24
  "@types/node": "^20.10.1",
25
25
  "esbuild": "^0.21.0"
26
+ },
27
+ "dependencies": {
28
+ "@meetopenbot/plugin-sdk": "^0.1.1"
26
29
  }
27
30
  }