@ixo/oracle-runtime-workers 0.2.0
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.
- package/README.md +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `call_editor_agent`: the document surface. One tool for every case — it
|
|
3
|
+
* targets the document the user has open by default, and any `room_id` the
|
|
4
|
+
* main agent names, so a page created mid-turn can be written into while
|
|
5
|
+
* another document is open. Each invocation spins up a short-lived inner agent
|
|
6
|
+
* over that one document and forwards its tool calls into the parent graph.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { tool as lcTool } from '@langchain/core/tools';
|
|
10
|
+
import {
|
|
11
|
+
HumanMessage,
|
|
12
|
+
ToolMessage,
|
|
13
|
+
type BaseMessage,
|
|
14
|
+
} from '@langchain/core/messages';
|
|
15
|
+
import { Command } from '@langchain/langgraph';
|
|
16
|
+
import { createAgent, type StructuredTool } from 'langchain';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
import { filterForwardedMessages } from '../../core/subagent-as-tool';
|
|
20
|
+
import { tool as pluginTool } from '../../plugin-api/tool-helper';
|
|
21
|
+
import type { PluginTool, RuntimeContext } from '../../plugin-api/types';
|
|
22
|
+
import { createContentTools } from './content-tools';
|
|
23
|
+
import { buildAppConfig, EDITOR_AGENT_TOOL_NAME } from './editor-agent';
|
|
24
|
+
import type { BlocknoteToolsConfig } from './editor-config';
|
|
25
|
+
import { resolveEditorMatrixClient } from './editor-mx';
|
|
26
|
+
import { noDocument, notAMember } from './failures';
|
|
27
|
+
import { editorAgentPrompt } from './prompts';
|
|
28
|
+
import { isUserInRoom } from './room-membership';
|
|
29
|
+
|
|
30
|
+
const standaloneEditorSchema = z.object({
|
|
31
|
+
room_id: z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(
|
|
34
|
+
/^![A-Za-z0-9._=-]+:[A-Za-z0-9.-]+(?::\d+)?$/,
|
|
35
|
+
'Not a Matrix room id. A real id looks like "!oeGkcJIKNpeSiaGHVE:devmx.ixo.earth" ' +
|
|
36
|
+
'and only ever comes from a tool result (list_workspace_pages, ' +
|
|
37
|
+
'create_page_room). Never invent or placeholder one — omit room_id ' +
|
|
38
|
+
'to target the document the user has open.',
|
|
39
|
+
)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(
|
|
42
|
+
'Matrix room ID of the document to read or edit (e.g. ' +
|
|
43
|
+
'"!oeGkcJIKNpeSiaGHVE:devmx.ixo.earth"). Omit it to target the ' +
|
|
44
|
+
'document the user currently has open. Pass it to target any other ' +
|
|
45
|
+
'document — the id returned by create_page_room, or one found with ' +
|
|
46
|
+
'list_workspace_pages. Never guess it.',
|
|
47
|
+
),
|
|
48
|
+
task: z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.describe(
|
|
52
|
+
'A detailed, self-contained instruction. The document assistant has NO ' +
|
|
53
|
+
'conversation context — this string is all it receives. Include the ' +
|
|
54
|
+
'objective, block ids, property names, and exact values. Do not put ' +
|
|
55
|
+
'the room id here.',
|
|
56
|
+
),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const STANDALONE_DESCRIPTION =
|
|
60
|
+
'Content assistant for one document. Targets the document the user has ' +
|
|
61
|
+
'open by default; pass `room_id` to target any other document, including ' +
|
|
62
|
+
'one just created with create_page_room. Give it a self-contained `task`: ' +
|
|
63
|
+
'it reads the document and edits its content (insert, rewrite, reorder, ' +
|
|
64
|
+
'delete, replace text). It does not create documents, build flows, or run ' +
|
|
65
|
+
'blocks.';
|
|
66
|
+
|
|
67
|
+
function lastMessageContent(messages: BaseMessage[]): string {
|
|
68
|
+
const last = messages.at(-1);
|
|
69
|
+
if (!last?.content) return '';
|
|
70
|
+
if (typeof last.content === 'string') return last.content;
|
|
71
|
+
if (Array.isArray(last.content)) {
|
|
72
|
+
for (const part of last.content) {
|
|
73
|
+
if (typeof part !== 'object' || part === null) continue;
|
|
74
|
+
const record: Record<string, unknown> = part;
|
|
75
|
+
if (record.type === 'text' && typeof record.text === 'string') {
|
|
76
|
+
return record.text;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
return JSON.stringify(last.content);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface CreateStandaloneEditorToolOptions {
|
|
85
|
+
/** Editor config built when the plugin's request tools are resolved. */
|
|
86
|
+
toolsConfig: BlocknoteToolsConfig;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Bridge the content `PluginTool`s into LangChain tools for the inner agent,
|
|
91
|
+
* reusing the outer request's `RuntimeContext` rather than rebuilding one.
|
|
92
|
+
*/
|
|
93
|
+
function toStructuredTools(
|
|
94
|
+
tools: PluginTool[],
|
|
95
|
+
ctx: RuntimeContext,
|
|
96
|
+
): StructuredTool[] {
|
|
97
|
+
return tools.map((t) =>
|
|
98
|
+
lcTool(async (args) => t.handler(args, ctx), {
|
|
99
|
+
name: t.name,
|
|
100
|
+
description: t.description,
|
|
101
|
+
schema: t.schema,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The document the client reports as open, when there is one. */
|
|
107
|
+
function readOpenDocument(ctx: RuntimeContext): string | undefined {
|
|
108
|
+
const value = ctx.history.state.editorRoomId;
|
|
109
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createStandaloneEditorTool(
|
|
113
|
+
opts: CreateStandaloneEditorToolOptions,
|
|
114
|
+
): PluginTool {
|
|
115
|
+
return pluginTool(
|
|
116
|
+
async (rawArgs, ctx: RuntimeContext) => {
|
|
117
|
+
const { room_id: explicitRoomId, task } =
|
|
118
|
+
standaloneEditorSchema.parse(rawArgs);
|
|
119
|
+
const roomId = explicitRoomId ?? readOpenDocument(ctx);
|
|
120
|
+
if (!roomId) return JSON.stringify(noDocument());
|
|
121
|
+
|
|
122
|
+
// The assistant acts with the oracle's admin identity, so "the user's
|
|
123
|
+
// documents" must be computed from the *user's* membership — otherwise an
|
|
124
|
+
// agent could be steered into any room id it is handed. On Workers the
|
|
125
|
+
// check runs through the gateway Durable Object (ctx.matrix).
|
|
126
|
+
if (!(await isUserInRoom(ctx, roomId, ctx.user.matrixUserId))) {
|
|
127
|
+
ctx.logger.warn(
|
|
128
|
+
`[editor] user ${ctx.user.did} is not a member of ${roomId} — refusing document access`,
|
|
129
|
+
);
|
|
130
|
+
return JSON.stringify(notAMember(roomId));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const matrixClient = await resolveEditorMatrixClient({
|
|
135
|
+
baseUrl: opts.toolsConfig.matrix.baseUrl,
|
|
136
|
+
userId: opts.toolsConfig.matrix.userId,
|
|
137
|
+
accessToken: opts.toolsConfig.matrix.accessToken,
|
|
138
|
+
matrixClient: opts.toolsConfig.matrixClient,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const contentTools = createContentTools({
|
|
142
|
+
matrixClient,
|
|
143
|
+
appConfig: buildAppConfig(opts.toolsConfig, {
|
|
144
|
+
type: 'id',
|
|
145
|
+
value: roomId,
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
const boundTools = toStructuredTools(contentTools, ctx);
|
|
149
|
+
|
|
150
|
+
const agent = createAgent({
|
|
151
|
+
model: ctx.llm.get('subagent'),
|
|
152
|
+
tools: boundTools,
|
|
153
|
+
systemPrompt: editorAgentPrompt,
|
|
154
|
+
middleware: [],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await agent.invoke({
|
|
158
|
+
messages: [new HumanMessage(task)],
|
|
159
|
+
});
|
|
160
|
+
const messages = result.messages as BaseMessage[];
|
|
161
|
+
const text = lastMessageContent(messages);
|
|
162
|
+
|
|
163
|
+
// Forward the inner tool calls + results into the parent graph — the
|
|
164
|
+
// same mechanism `createSubagentAsTool` uses with `forwardTools` — so
|
|
165
|
+
// the FE renders document activity inline whichever path handled the
|
|
166
|
+
// turn. Without a parent tool_call_id there is nothing to attach to.
|
|
167
|
+
const toolCallId = ctx.toolCallId;
|
|
168
|
+
if (!toolCallId) return text;
|
|
169
|
+
|
|
170
|
+
const forwardSet = new Set(boundTools.map((t) => t.name));
|
|
171
|
+
const forwarded = filterForwardedMessages(
|
|
172
|
+
messages,
|
|
173
|
+
forwardSet,
|
|
174
|
+
toolCallId,
|
|
175
|
+
);
|
|
176
|
+
if (forwarded.length === 0) return text;
|
|
177
|
+
|
|
178
|
+
return new Command({
|
|
179
|
+
update: {
|
|
180
|
+
messages: [
|
|
181
|
+
...forwarded,
|
|
182
|
+
new ToolMessage({ content: text, tool_call_id: toolCallId }),
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
+
ctx.logger.error(
|
|
189
|
+
`[editor] standalone failed for ${roomId}: ${message}`,
|
|
190
|
+
);
|
|
191
|
+
return `Error opening the document ${roomId}: ${message}`;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: EDITOR_AGENT_TOOL_NAME,
|
|
196
|
+
description: STANDALONE_DESCRIPTION,
|
|
197
|
+
schema: standaloneEditorSchema,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { PluginSubAgent, PluginTool } from '../../plugin-api/types';
|
|
2
|
+
|
|
3
|
+
const SHARED_EXPECTATIONS = `
|
|
4
|
+
You are the Firecrawl Agent for this workspace. Your entire job is to perform
|
|
5
|
+
web search and scraping tasks through the Firecrawl MCP tools on behalf of the
|
|
6
|
+
user.
|
|
7
|
+
|
|
8
|
+
🚨 **API REJECTION RULE**: You are ONLY for scraping human-readable web pages and web search. If the task asks you to fetch data from an API endpoint (any URL containing /api/, /v1/, /v2/, /v3/, returning JSON/XML data, or any REST/GraphQL endpoint), you MUST refuse and reply: "This is an API call — use the Sandbox instead (write a script with fetch/curl/requests). I only handle web pages and web search." Do NOT attempt to scrape API endpoints.
|
|
9
|
+
|
|
10
|
+
Core expectations:
|
|
11
|
+
- Treat published web content as potentially unreliable—cross-check when you can.
|
|
12
|
+
- Never make HTTP requests directly; always operate through the exposed Firecrawl
|
|
13
|
+
tools and respect their rate limits.
|
|
14
|
+
- Narrate what you're about to fetch or search, then summarize the findings with
|
|
15
|
+
citations (URLs) when possible.
|
|
16
|
+
- Call out stale, conflicting, or missing information before acting on it.
|
|
17
|
+
|
|
18
|
+
Efficiency rules (CRITICAL — you run under a strict time budget):
|
|
19
|
+
- **Search once, search smart.** Write a single, well-crafted search query that
|
|
20
|
+
targets exactly what you need. Do NOT issue multiple redundant searches hoping
|
|
21
|
+
for better results.
|
|
22
|
+
- **One source is enough when the data is clear.** For factual lookups (prices,
|
|
23
|
+
weather, scores, exchange rates), use ONE authoritative result. Do not scrape
|
|
24
|
+
multiple sites to cross-verify commodity prices or similar public data.
|
|
25
|
+
- **Prefer search over scrape.** \`firecrawl_search\` returns snippets directly —
|
|
26
|
+
use it first. Only fall back to \`firecrawl_scrape\` when you need full-page
|
|
27
|
+
content that search snippets can't provide (e.g., full articles, tables).
|
|
28
|
+
- **Never crawl entire sites.** If a search gives you the answer, stop. Do not
|
|
29
|
+
follow links to "learn more" or scrape additional pages for context.
|
|
30
|
+
- **Fail fast.** If a search returns no useful results on the first try, report
|
|
31
|
+
what you found (or didn't) and stop. Do NOT rephrase and retry endlessly.
|
|
32
|
+
- **Total tool calls budget: max 3.** You should almost always finish in 1-2 tool
|
|
33
|
+
calls. If you've made 3 calls, wrap up with whatever you have.
|
|
34
|
+
|
|
35
|
+
Task discipline:
|
|
36
|
+
- You are a sub-agent invoked by the main agent. You receive a single task message — that is ALL the context you have.
|
|
37
|
+
- If the task is unclear, ambiguous, or missing critical details (IDs, names, scope, what to do), do NOT guess. Instead, STOP immediately and return a clear message explaining what information you need. The main agent will ask the user and re-invoke you with a complete task.
|
|
38
|
+
- Never loop or retry the same failing approach. If something fails twice, return the error and stop.
|
|
39
|
+
- Complete the requested task and STOP. Do not do additional unrequested work.
|
|
40
|
+
`.trim();
|
|
41
|
+
|
|
42
|
+
const WORKFLOW_GUIDELINES = `
|
|
43
|
+
### Workflow
|
|
44
|
+
1. Identify the single most important piece of information the task needs.
|
|
45
|
+
2. Write ONE precise search query (e.g., "gold spot price USD today" — not
|
|
46
|
+
"gold price" then "gold market" then "gold value per ounce").
|
|
47
|
+
3. If the search result contains the answer, extract it and STOP.
|
|
48
|
+
4. Only scrape a URL if the search snippet was incomplete and you need the full page.
|
|
49
|
+
5. Return findings with citations. If data is unavailable, say so — don't keep searching.
|
|
50
|
+
`.trim();
|
|
51
|
+
|
|
52
|
+
const formatToolDocs = (tools: PluginTool[]): string => {
|
|
53
|
+
if (!tools.length) {
|
|
54
|
+
return '- No Firecrawl tools are currently configured.';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return tools
|
|
58
|
+
.map((t) => {
|
|
59
|
+
const description = t.description?.trim() ?? 'No description provided.';
|
|
60
|
+
return `Firecrawl Agent: \`${t.name}\`: ${description}`;
|
|
61
|
+
})
|
|
62
|
+
.join('\n');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const buildFirecrawlPrompt = (tools: PluginTool[]): string =>
|
|
66
|
+
`
|
|
67
|
+
${SHARED_EXPECTATIONS}
|
|
68
|
+
|
|
69
|
+
### Available Firecrawl Tools
|
|
70
|
+
${formatToolDocs(tools)}
|
|
71
|
+
|
|
72
|
+
${WORKFLOW_GUIDELINES}
|
|
73
|
+
`.trim();
|
|
74
|
+
|
|
75
|
+
const buildFirecrawlDescription = (tools: PluginTool[]): string => {
|
|
76
|
+
const names = tools.map((t) => t.name).join(', ') || 'no tools configured';
|
|
77
|
+
return `Firecrawl Agent for web search & scraping human-readable web pages via (${names}). NOT for API calls — use the Sandbox for APIs (fetch/curl/requests).`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build the Firecrawl sub-agent definition. Tools are supplied by the
|
|
82
|
+
* plugin and close over a `MultiServerMCPClient` keyed to `FIRECRAWL_MCP_URL`.
|
|
83
|
+
*/
|
|
84
|
+
export function createFirecrawlSubAgent(tools: PluginTool[]): PluginSubAgent {
|
|
85
|
+
return {
|
|
86
|
+
name: 'Firecrawl Agent',
|
|
87
|
+
description: buildFirecrawlDescription(tools),
|
|
88
|
+
systemPrompt: buildFirecrawlPrompt(tools),
|
|
89
|
+
tools,
|
|
90
|
+
model: 'subagent',
|
|
91
|
+
middlewares: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
|
|
2
|
+
import { withCallTimeout } from '../mcp-call-timeout';
|
|
3
|
+
|
|
4
|
+
/** Our own per-call limit (never given to the SDK — see `mcp-call-timeout.ts`). */
|
|
5
|
+
const FIRECRAWL_TOOL_TIMEOUT_MS = 120_000;
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { tool } from '../../plugin-api/tool-helper';
|
|
8
|
+
import type { PluginTool } from '../../plugin-api/types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal surface a Firecrawl MCP tool needs to expose to be proxied: an
|
|
12
|
+
* identifier and an `invoke` method. `MultiServerMCPClient.getTools()`
|
|
13
|
+
* returns `DynamicStructuredTool[]` which trivially satisfies this shape,
|
|
14
|
+
* and unit tests can satisfy it with a plain object — no SDK ceremony.
|
|
15
|
+
*/
|
|
16
|
+
export interface FirecrawlMcpProxyTool {
|
|
17
|
+
name: string;
|
|
18
|
+
invoke: (input: unknown) => Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tool names that the upstream Firecrawl MCP server exposes (prefixed with
|
|
23
|
+
* the server name configured in {@link MultiServerMCPClient}). Filtering by
|
|
24
|
+
* these names keeps the agent's surface area tight even if the server adds
|
|
25
|
+
* extra capabilities later.
|
|
26
|
+
*/
|
|
27
|
+
export const FIRECRAWL_SCRAPE_MCP_NAME = 'firecrawl__firecrawl_scrape';
|
|
28
|
+
export const FIRECRAWL_SEARCH_MCP_NAME = 'firecrawl__firecrawl_search';
|
|
29
|
+
|
|
30
|
+
/** Public tool names shown to the agent (server prefix dropped). */
|
|
31
|
+
export const FIRECRAWL_SCRAPE_TOOL = 'firecrawl_scrape';
|
|
32
|
+
export const FIRECRAWL_SEARCH_TOOL = 'firecrawl_search';
|
|
33
|
+
|
|
34
|
+
const SEARCH_DESCRIPTION = `Web search via Firecrawl. Returns a ranked list of result snippets with titles and URLs.
|
|
35
|
+
|
|
36
|
+
Use this BEFORE \`firecrawl_scrape\` whenever a search snippet is enough — it's faster and cheaper. Only escalate to scraping when the snippet is incomplete and you need the full page.
|
|
37
|
+
|
|
38
|
+
🚨 Never use this for API endpoints (any URL with /api/, /v1/, /v2/, /v3/, or returning JSON/XML). Use the Sandbox for those.`;
|
|
39
|
+
|
|
40
|
+
const SCRAPE_DESCRIPTION = `Scrape a single human-readable web page via Firecrawl. Returns the page as Markdown plus structured metadata.
|
|
41
|
+
|
|
42
|
+
Use this only when you've already tried \`firecrawl_search\` and need the full page content (long article, table, structured data on a static page).
|
|
43
|
+
|
|
44
|
+
🚨 Never use this on API endpoints (any URL with /api/, /v1/, /v2/, /v3/, or returning JSON/XML). Use the Sandbox for those.`;
|
|
45
|
+
|
|
46
|
+
const searchSchema = z.object({
|
|
47
|
+
query: z
|
|
48
|
+
.string()
|
|
49
|
+
.min(1, 'Query is required and cannot be empty.')
|
|
50
|
+
.describe('Search query — write ONE precise, well-targeted query.'),
|
|
51
|
+
limit: z
|
|
52
|
+
.number()
|
|
53
|
+
.int()
|
|
54
|
+
.min(1)
|
|
55
|
+
.max(10)
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Max number of results (1-10). Default upstream.'),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const scrapeSchema = z.object({
|
|
61
|
+
url: z
|
|
62
|
+
.string()
|
|
63
|
+
.url()
|
|
64
|
+
.describe('Fully-qualified URL of the page to scrape (https://...).'),
|
|
65
|
+
formats: z
|
|
66
|
+
.array(z.enum(['markdown', 'html', 'rawHtml', 'links', 'screenshot']))
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Output formats. Defaults to ['markdown']. Use 'links' to enumerate outbound URLs.",
|
|
70
|
+
),
|
|
71
|
+
onlyMainContent: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe(
|
|
75
|
+
'When true (default), strips navigation, ads, and boilerplate — returns just the main content block.',
|
|
76
|
+
),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Factory for the underlying MCP-tool list. Injectable so unit tests can
|
|
81
|
+
* substitute a stub without spinning up the real MCP transport.
|
|
82
|
+
*/
|
|
83
|
+
export type FirecrawlMcpFactory = () => Promise<FirecrawlMcpProxyTool[]>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build the default factory keyed to `FIRECRAWL_MCP_URL`.
|
|
87
|
+
*
|
|
88
|
+
* No client outlives a call: the streamable-HTTP transport keeps a
|
|
89
|
+
* server→client stream open for as long as the client exists, and a client
|
|
90
|
+
* cached for the object's lifetime (the previous shape, with `reconnect`)
|
|
91
|
+
* left the user's Durable Object permanently resident — never hibernating,
|
|
92
|
+
* billed around the clock. Each invocation connects, calls, and closes; the
|
|
93
|
+
* upstream tool names are fixed, so nothing needs discovering up front.
|
|
94
|
+
*/
|
|
95
|
+
export function createDefaultFirecrawlMcpFactory(
|
|
96
|
+
firecrawlUrl: string,
|
|
97
|
+
): FirecrawlMcpFactory {
|
|
98
|
+
const withClient = async <T>(
|
|
99
|
+
run: (tools: FirecrawlMcpProxyTool[]) => Promise<T>,
|
|
100
|
+
): Promise<T> => {
|
|
101
|
+
const client = new MultiServerMCPClient({
|
|
102
|
+
useStandardContentBlocks: true,
|
|
103
|
+
prefixToolNameWithServerName: true,
|
|
104
|
+
mcpServers: {
|
|
105
|
+
firecrawl: {
|
|
106
|
+
type: 'http',
|
|
107
|
+
transport: 'http',
|
|
108
|
+
url: firecrawlUrl,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
const all = await client.getTools();
|
|
114
|
+
return await run(
|
|
115
|
+
all.filter(
|
|
116
|
+
(t) =>
|
|
117
|
+
t.name === FIRECRAWL_SCRAPE_MCP_NAME ||
|
|
118
|
+
t.name === FIRECRAWL_SEARCH_MCP_NAME,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
} finally {
|
|
122
|
+
await client.close().catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const proxy = (name: string): FirecrawlMcpProxyTool => ({
|
|
126
|
+
name,
|
|
127
|
+
invoke: (input) =>
|
|
128
|
+
withClient(async (tools) => {
|
|
129
|
+
const target = tools.find((t) => t.name === name);
|
|
130
|
+
if (!target) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`upstream MCP tool "${name}" is not exposed by the configured Firecrawl server.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return withCallTimeout(
|
|
136
|
+
() => target.invoke(input),
|
|
137
|
+
FIRECRAWL_TOOL_TIMEOUT_MS,
|
|
138
|
+
name,
|
|
139
|
+
);
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
const tools = [
|
|
143
|
+
proxy(FIRECRAWL_SCRAPE_MCP_NAME),
|
|
144
|
+
proxy(FIRECRAWL_SEARCH_MCP_NAME),
|
|
145
|
+
];
|
|
146
|
+
return async () => tools;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ProxyHandlerArgs {
|
|
150
|
+
factory: FirecrawlMcpFactory;
|
|
151
|
+
mcpToolName: string;
|
|
152
|
+
publicToolName: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Build a handler that proxies a validated input through the MCP client.
|
|
157
|
+
* The underlying tool is resolved lazily on the first call and cached.
|
|
158
|
+
*/
|
|
159
|
+
function buildProxyHandler({
|
|
160
|
+
factory,
|
|
161
|
+
mcpToolName,
|
|
162
|
+
publicToolName,
|
|
163
|
+
}: ProxyHandlerArgs): (args: unknown) => Promise<unknown> {
|
|
164
|
+
return async (args) => {
|
|
165
|
+
const mcpTools = await factory();
|
|
166
|
+
const target = mcpTools.find((t) => t.name === mcpToolName);
|
|
167
|
+
if (!target) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`${publicToolName}: upstream MCP tool "${mcpToolName}" is not exposed by the configured Firecrawl server.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return target.invoke(args);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Construct the two Firecrawl plugin tools. Each tool's handler proxies
|
|
178
|
+
* the validated input into the upstream MCP server using the supplied
|
|
179
|
+
* factory. Schemas are declared statically so the agent sees a stable
|
|
180
|
+
* tool surface even before the first MCP fetch.
|
|
181
|
+
*/
|
|
182
|
+
export function createFirecrawlTools(
|
|
183
|
+
factory: FirecrawlMcpFactory,
|
|
184
|
+
): PluginTool[] {
|
|
185
|
+
const search = tool(
|
|
186
|
+
async (rawArgs) => {
|
|
187
|
+
const parsed = searchSchema.parse(rawArgs);
|
|
188
|
+
const handler = buildProxyHandler({
|
|
189
|
+
factory,
|
|
190
|
+
mcpToolName: FIRECRAWL_SEARCH_MCP_NAME,
|
|
191
|
+
publicToolName: FIRECRAWL_SEARCH_TOOL,
|
|
192
|
+
});
|
|
193
|
+
return handler(parsed);
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: FIRECRAWL_SEARCH_TOOL,
|
|
197
|
+
description: SEARCH_DESCRIPTION,
|
|
198
|
+
schema: searchSchema,
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const scrape = tool(
|
|
203
|
+
async (rawArgs) => {
|
|
204
|
+
const parsed = scrapeSchema.parse(rawArgs);
|
|
205
|
+
const handler = buildProxyHandler({
|
|
206
|
+
factory,
|
|
207
|
+
mcpToolName: FIRECRAWL_SCRAPE_MCP_NAME,
|
|
208
|
+
publicToolName: FIRECRAWL_SCRAPE_TOOL,
|
|
209
|
+
});
|
|
210
|
+
return handler(parsed);
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: FIRECRAWL_SCRAPE_TOOL,
|
|
214
|
+
description: SCRAPE_DESCRIPTION,
|
|
215
|
+
schema: scrapeSchema,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return [search, scrape];
|
|
220
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OraclePlugin, type PluginEnv } from '../../plugin-api/oracle-plugin';
|
|
3
|
+
import type {
|
|
4
|
+
MergedConfig,
|
|
5
|
+
PluginContext,
|
|
6
|
+
PluginManifest,
|
|
7
|
+
PluginSubAgent,
|
|
8
|
+
} from '../../plugin-api/types';
|
|
9
|
+
import { createFirecrawlSubAgent } from './firecrawl-agent';
|
|
10
|
+
import {
|
|
11
|
+
createDefaultFirecrawlMcpFactory,
|
|
12
|
+
createFirecrawlTools,
|
|
13
|
+
type FirecrawlMcpFactory,
|
|
14
|
+
} from './firecrawl-tools';
|
|
15
|
+
|
|
16
|
+
const configSchema = z.object({
|
|
17
|
+
FIRECRAWL_MCP_URL: z
|
|
18
|
+
.string()
|
|
19
|
+
.url('FIRECRAWL_MCP_URL must be a valid HTTP(S) URL.'),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const manifest: PluginManifest = {
|
|
23
|
+
title: 'Firecrawl',
|
|
24
|
+
summary: 'Web search and scraping of human-readable pages via Firecrawl.',
|
|
25
|
+
whenToUse: [
|
|
26
|
+
'User asks the agent to search the web for current information.',
|
|
27
|
+
'User wants the contents of a specific page summarized or extracted.',
|
|
28
|
+
'A question can only be answered by recent public web content.',
|
|
29
|
+
],
|
|
30
|
+
whenNotToUse: [
|
|
31
|
+
'Fetching from an API endpoint (URLs containing /api/, /v1/, /v2/, /v3/, or that return JSON/XML) — use the Sandbox instead.',
|
|
32
|
+
'IXO entity lookups (use the Domain Indexer).',
|
|
33
|
+
'Personal memory or past-conversation recall (use Memory).',
|
|
34
|
+
],
|
|
35
|
+
examples: [
|
|
36
|
+
{
|
|
37
|
+
user: 'What did OpenAI announce at their last event?',
|
|
38
|
+
thought:
|
|
39
|
+
'Recent public web content — delegate to call_firecrawl_agent for a fresh search.',
|
|
40
|
+
tool: 'call_firecrawl_agent',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
user: 'Pull the key points from this page: https://example.com/blog/post',
|
|
44
|
+
thought: 'Specific human-readable page — scrape it, summarize.',
|
|
45
|
+
tool: 'call_firecrawl_agent',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
tags: ['web', 'search', 'scrape', 'firecrawl'],
|
|
49
|
+
category: 'data',
|
|
50
|
+
visibility: 'on-demand',
|
|
51
|
+
stability: 'stable',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function resolveFirecrawlUrl(config: MergedConfig): string {
|
|
55
|
+
const parsed = configSchema.parse(config);
|
|
56
|
+
return parsed.FIRECRAWL_MCP_URL;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Optional dependency injection — primarily for tests. */
|
|
60
|
+
export interface FirecrawlPluginOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Override the MCP-tools factory. Defaults to a Firecrawl-MCP HTTP client
|
|
63
|
+
* built from `FIRECRAWL_MCP_URL`. Tests pass a stub so the plugin never
|
|
64
|
+
* touches the network.
|
|
65
|
+
*/
|
|
66
|
+
mcpFactory?: (firecrawlUrl: string) => FirecrawlMcpFactory;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Firecrawl plugin. Exposes a sub-agent (`call_firecrawl_agent`) that wraps
|
|
71
|
+
* the upstream Firecrawl MCP server's `firecrawl_search` and
|
|
72
|
+
* `firecrawl_scrape` tools.
|
|
73
|
+
*/
|
|
74
|
+
export class FirecrawlPlugin extends OraclePlugin {
|
|
75
|
+
readonly name = 'firecrawl';
|
|
76
|
+
|
|
77
|
+
readonly version = '1.0.0';
|
|
78
|
+
|
|
79
|
+
readonly manifest = manifest;
|
|
80
|
+
|
|
81
|
+
override readonly configSchema = configSchema;
|
|
82
|
+
|
|
83
|
+
override readonly autoDetectHint = 'FIRECRAWL_MCP_URL';
|
|
84
|
+
|
|
85
|
+
private readonly mcpFactory: (firecrawlUrl: string) => FirecrawlMcpFactory;
|
|
86
|
+
|
|
87
|
+
constructor(options: FirecrawlPluginOptions = {}) {
|
|
88
|
+
super();
|
|
89
|
+
this.mcpFactory = options.mcpFactory ?? createDefaultFirecrawlMcpFactory;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override autoDetect(env: PluginEnv): boolean {
|
|
93
|
+
return (
|
|
94
|
+
typeof env.FIRECRAWL_MCP_URL === 'string' &&
|
|
95
|
+
env.FIRECRAWL_MCP_URL.length > 0
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override getSubAgents(ctx: PluginContext): PluginSubAgent[] {
|
|
100
|
+
const firecrawlUrl = resolveFirecrawlUrl(ctx.config);
|
|
101
|
+
const factory = this.mcpFactory(firecrawlUrl);
|
|
102
|
+
const tools = createFirecrawlTools(factory);
|
|
103
|
+
return [createFirecrawlSubAgent(tools)];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createFirecrawlSubAgent } from './firecrawl-agent';
|
|
2
|
+
export {
|
|
3
|
+
createDefaultFirecrawlMcpFactory,
|
|
4
|
+
createFirecrawlTools,
|
|
5
|
+
FIRECRAWL_SCRAPE_MCP_NAME,
|
|
6
|
+
FIRECRAWL_SCRAPE_TOOL,
|
|
7
|
+
FIRECRAWL_SEARCH_MCP_NAME,
|
|
8
|
+
FIRECRAWL_SEARCH_TOOL,
|
|
9
|
+
type FirecrawlMcpFactory,
|
|
10
|
+
type FirecrawlMcpProxyTool,
|
|
11
|
+
} from './firecrawl-tools';
|
|
12
|
+
export {
|
|
13
|
+
FirecrawlPlugin,
|
|
14
|
+
type FirecrawlPluginOptions,
|
|
15
|
+
} from './firecrawl.plugin';
|