@meetopenbot/firecrawl 1.0.2 → 1.0.4

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 +267 -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,233 @@ 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);
154
+ yield agentOutput({
155
+ agentId: context.agentId,
156
+ content: "API key required. Please provide it in the widget.",
157
+ threadId
158
+ });
88
159
  return;
89
160
  }
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
- };
161
+ try {
162
+ yield agentOutput({
163
+ agentId: context.agentId,
164
+ content: "Starting Firecrawl agent...",
165
+ threadId
166
+ });
167
+ const started = await firecrawlRequest(apiKey, apiUrl, "/v2/agent", {
168
+ method: "POST",
169
+ body: { prompt: content }
170
+ });
171
+ if (!started?.success || !started.id) {
172
+ const errMsg = started?.error || "Unknown error";
173
+ if (isAuthError(errMsg)) {
174
+ yield buildApiKeyWidget(context.agentId, threadId, errMsg);
175
+ yield agentOutput({
176
+ agentId: context.agentId,
177
+ content: `Authentication failed: ${errMsg}. Please update the API key in the widget.`,
178
+ threadId
179
+ });
180
+ } else {
181
+ yield agentOutput({
182
+ agentId: context.agentId,
183
+ content: `Error starting Firecrawl agent: ${errMsg}`,
184
+ threadId
185
+ });
186
+ }
102
187
  return;
103
188
  }
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
- };
189
+ yield agentOutput({
190
+ agentId: context.agentId,
191
+ content: `Task created (id: ${started.id}). Waiting for results...`,
192
+ threadId
193
+ });
194
+ const startTime = Date.now();
195
+ let lastStatus = "";
196
+ while (true) {
197
+ if (Date.now() - startTime > POLL_TIMEOUT_MS) {
198
+ yield agentOutput({
199
+ agentId: context.agentId,
200
+ content: `Timed out after ${Math.round(POLL_TIMEOUT_MS / 1e3)}s.`,
201
+ threadId
202
+ });
203
+ return;
204
+ }
205
+ const status = await firecrawlRequest(
206
+ apiKey,
207
+ apiUrl,
208
+ `/v2/agent/${started.id}`
209
+ );
210
+ if (status.status === "completed") {
211
+ yield agentOutput({
212
+ agentId: context.agentId,
213
+ content: "Completed successfully.",
214
+ threadId
215
+ });
216
+ yield agentOutput({
217
+ agentId: context.agentId,
218
+ content: "```json\n" + JSON.stringify(status.data ?? {}, null, 2) + "\n```",
219
+ threadId
220
+ });
221
+ return;
222
+ }
223
+ if (status.status === "failed" || status.status === "cancelled") {
224
+ yield agentOutput({
225
+ agentId: context.agentId,
226
+ content: `Agent ${status.status}: ${status.error || "no details provided"}`,
227
+ threadId
228
+ });
229
+ return;
230
+ }
231
+ if (status.status && status.status !== lastStatus) {
232
+ yield agentOutput({
233
+ agentId: context.agentId,
234
+ content: `Status: ${status.status}...`,
235
+ threadId
236
+ });
237
+ lastStatus = status.status;
238
+ }
239
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
240
+ }
241
+ } catch (error) {
242
+ const message = error instanceof Error ? error.message : "Firecrawl request failed.";
243
+ if (isAuthError(message)) {
244
+ yield buildApiKeyWidget(context.agentId, threadId, message);
245
+ yield agentOutput({
246
+ agentId: context.agentId,
247
+ content: `Authentication failed: ${message}. Please update the API key in the widget.`,
248
+ threadId
249
+ });
250
+ } else {
251
+ yield agentOutput({
252
+ agentId: context.agentId,
253
+ content: `Error: ${message}`,
254
+ threadId
255
+ });
256
+ }
257
+ }
258
+ });
259
+ builder.on(
260
+ "client:ui:widget:response",
261
+ async function* (event, handlerCtx) {
262
+ const { metadata, values, widgetId } = event.data;
263
+ if (!metadata || metadata.type !== "api_key_request" || metadata.source !== "firecrawl") {
115
264
  return;
116
265
  }
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
- };
266
+ const apiKey = values?.apiKey;
267
+ if (typeof apiKey !== "string" || !apiKey) {
124
268
  return;
125
269
  }
126
- if (status.status && status.status !== lastStatus) {
127
- yield { type: "agent:output", data: { content: `Status: ${status.status}...` } };
128
- lastStatus = status.status;
270
+ const envVar = typeof metadata.envVar === "string" && metadata.envVar.trim() ? metadata.envVar : API_KEY_ENV_VAR;
271
+ try {
272
+ await context.storage.createVariable({ key: envVar, value: apiKey, secret: true });
273
+ readEnv()[envVar] = apiKey;
274
+ yield uiWidget({
275
+ agentId: context.agentId,
276
+ widget: {
277
+ widgetId,
278
+ kind: "message",
279
+ title: "API Key Saved",
280
+ body: `Saved ${envVar} as a workspace variable. You can now continue the conversation.`,
281
+ state: "submitted",
282
+ actions: [{ id: "ok", label: "Got it", variant: "primary" }]
283
+ },
284
+ meta: { agentId: handlerCtx.state.agentId }
285
+ });
286
+ yield agentOutput({
287
+ agentId: context.agentId,
288
+ content: "API key saved to workspace variables. Re-send your last message to retry.",
289
+ meta: { agentId: handlerCtx.state.agentId }
290
+ });
291
+ } catch (error) {
292
+ const errorMessage = error instanceof Error ? error.message : String(error);
293
+ yield agentOutput({
294
+ agentId: context.agentId,
295
+ content: `Failed to save API key: ${errorMessage}`,
296
+ meta: { agentId: handlerCtx.state.agentId }
297
+ });
129
298
  }
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
299
  }
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
- };
300
+ );
301
+ }
302
+ });
303
+ var src_default = plugin;
194
304
  export {
195
- firecrawlPlugin,
196
- plugin
305
+ src_default as default
197
306
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/firecrawl",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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
  }