Skip to content

Commit f5b5d0a

Browse files
committed
fix(release): leave Firecrawl crawl depth to #9864
1 parent cd0e043 commit f5b5d0a

2 files changed

Lines changed: 42 additions & 46 deletions

File tree

‎apps/sim/tools/firecrawl/crawl.test.ts‎

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { isPlainRecord } from '@sim/utils/object'
22
import { describe, expect, it } from 'vitest'
33
import { crawlTool } from '@/tools/firecrawl/crawl'
4-
import type { FirecrawlCrawlParams } from '@/tools/firecrawl/types'
54

65
function crawlBody(options: {
7-
url?: string
86
maxDepth: number
7+
url?: string
98
maxDiscoveryDepth?: number
109
includePaths?: string[]
1110
excludePaths?: string[]
@@ -68,40 +67,35 @@ describe('Firecrawl legacy crawl depth', () => {
6867
expect(patterns.some((pattern) => pattern.test(path))).toBe(excluded)
6968
})
7069

71-
it.each([-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY])(
72-
'rejects an invalid legacy depth: %s',
73-
(maxDepth) => {
74-
expect(() => crawlBody({ maxDepth })).toThrow(/maxDepth/)
75-
}
76-
)
77-
78-
it('measures depth below the start URL, as Firecrawl v1 did', () => {
79-
const body = crawlBody({ url: 'https://example.com/guide/intro', maxDepth: 1 })
70+
it.each<[string, string, boolean]>([
71+
['https://example.com/docs/guide', '/docs/guide', true],
72+
['https://example.com/docs/guide/', '/docs/guide/setup', true],
73+
['https://example.com/docs/guide/index.html', '/docs/guide/setup', true],
74+
['https://example.com/docs/guide', '/docs/guide/setup/advanced', false],
75+
['docs.example.com/guide', '/guide/setup', true],
76+
])('counts depth from the start URL %s for %s', (url, path, accepted) => {
77+
const body = crawlBody({ maxDepth: 1, url })
8078
const patterns = (body.includePaths as string[]).map((pattern) => new RegExp(pattern))
81-
const accepts = (path: string) => patterns.some((pattern) => pattern.test(path))
82-
83-
expect(accepts('/guide/intro')).toBe(true)
84-
expect(accepts('/guide/intro/setup')).toBe(true)
85-
expect(accepts('/guide/intro/setup/advanced')).toBe(false)
79+
expect(patterns.some((pattern) => pattern.test(path))).toBe(accepted)
8680
})
8781

88-
it('ignores an index file in the start URL when measuring its depth', () => {
89-
const body = crawlBody({ url: 'https://example.com/docs/index.html', maxDepth: 0 })
90-
const patterns = (body.includePaths as string[]).map((pattern) => new RegExp(pattern))
91-
expect(patterns.some((pattern) => pattern.test('/docs'))).toBe(true)
92-
expect(patterns.some((pattern) => pattern.test('/docs/guide'))).toBe(false)
82+
it('counts depth from the start URL alongside existing path filters', () => {
83+
const body = crawlBody({
84+
maxDepth: 1,
85+
url: 'https://example.com/docs/guide',
86+
includePaths: ['^/docs'],
87+
})
88+
const patterns = (body.excludePaths as string[]).map((pattern) => new RegExp(pattern))
89+
const excludes = (path: string) => patterns.some((pattern) => pattern.test(path))
90+
expect(excludes('/docs/guide')).toBe(false)
91+
expect(excludes('/docs/guide/setup')).toBe(false)
92+
expect(excludes('/docs/guide/setup/advanced')).toBe(true)
9393
})
9494

95-
it('treats a blank depth as unset instead of a zero-depth crawl', () => {
96-
if (typeof crawlTool.request.body !== 'function') throw new Error('Missing request formatter')
97-
const blankDepth: Record<string, unknown> = {
98-
apiKey: 'test',
99-
url: 'https://example.com',
100-
maxDepth: '',
95+
it.each([-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY])(
96+
'rejects an invalid legacy depth: %s',
97+
(maxDepth) => {
98+
expect(() => crawlBody({ maxDepth })).toThrow(/maxDepth/)
10199
}
102-
const body = crawlTool.request.body(blankDepth as FirecrawlCrawlParams)
103-
if (!isPlainRecord(body)) throw new Error('Expected a JSON request body')
104-
expect(body.includePaths).toBeUndefined()
105-
expect(body.regexOnFullURL).toBeUndefined()
106-
})
100+
)
107101
})

‎apps/sim/tools/firecrawl/crawl.ts‎

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ const logger = createLogger('FirecrawlCrawlTool')
1818
const POLL_INTERVAL_MS = 5000
1919
const MAX_POLL_TIME_MS = DEFAULT_EXECUTION_TIMEOUT_MS
2020

21-
/** Path segments in a URL, ignoring index files, as Firecrawl v1 measured crawl depth. */
22-
function startUrlPathDepth(url: string): number {
23-
try {
24-
return new URL(url).pathname
25-
.split('/')
26-
.filter((segment) => segment !== '' && segment !== 'index.html' && segment !== 'index.php')
27-
.length
28-
} catch {
29-
return 0
30-
}
21+
/**
22+
* Path segments of a URL as Firecrawl v1 counted them: index files add no depth. A URL without
23+
* a scheme is read as https, as Firecrawl reads it.
24+
*/
25+
function urlPathDepth(url: string): number {
26+
const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(url) ? url : `https://${url}`
27+
if (!URL.canParse(absolute)) return 0
28+
return new URL(absolute).pathname
29+
.split('/')
30+
.filter((segment) => segment !== '' && segment !== 'index.html' && segment !== 'index.php')
31+
.length
3132
}
3233

3334
export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse> = {
@@ -54,7 +55,7 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
5455
required: false,
5556
visibility: 'user-or-llm',
5657
description:
57-
'Maximum URL path depth (e.g., 1 allows /docs and /docs/index.html). Independent of link discovery depth',
58+
'Maximum URL path depth below the starting URL (e.g., from /docs, 1 allows /docs/guide). Independent of link discovery depth',
5859
},
5960
formats: {
6061
type: 'json',
@@ -167,14 +168,15 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
167168
if (params.maxConcurrency) body.maxConcurrency = Number(params.maxConcurrency)
168169
if (params.excludePaths) body.excludePaths = params.excludePaths
169170
if (params.includePaths) body.includePaths = params.includePaths
170-
if (params.maxDepth != null && String(params.maxDepth).trim() !== '') {
171+
if (params.maxDepth != null) {
171172
const requestedDepth = Number(params.maxDepth)
172173
if (!Number.isSafeInteger(requestedDepth) || requestedDepth < 0) {
173174
throw new Error('maxDepth must be a non-negative integer')
174175
}
175-
// v1 counted path segments below the start URL's own depth; v2 discovery depth counts link hops.
176-
const maxDepth = requestedDepth + startUrlPathDepth(params.url)
176+
// v1 counted depth from the start URL, so the start page itself always passes the filter.
177+
const maxDepth = requestedDepth + urlPathDepth(params.url)
177178

179+
// v1 depth counted URL path segments; v2 discovery depth counts link hops.
178180
body.regexOnFullURL = false
179181
if (params.includePaths?.length) {
180182
const indexFiles = ['index.html', 'index.php']

0 commit comments

Comments
 (0)