diff --git a/src/__tests__/cli-argv.test.ts b/src/__tests__/cli-argv.test.ts index 8b93a05631..e795a09282 100644 --- a/src/__tests__/cli-argv.test.ts +++ b/src/__tests__/cli-argv.test.ts @@ -31,6 +31,7 @@ describe('CLI argv parsing', () => { expect(result.stdout).toContain('Usage: firecrawl developer'); expect(result.stdout).toContain('--limit'); expect(result.stdout).toContain('--skills-only'); + expect(result.stdout).not.toContain('--passage-budget'); expect(result.stderr).not.toContain('unknown command'); }); diff --git a/src/__tests__/commands/developer.test.ts b/src/__tests__/commands/developer.test.ts index 248246d51b..c4ba98b864 100644 --- a/src/__tests__/commands/developer.test.ts +++ b/src/__tests__/commands/developer.test.ts @@ -25,8 +25,17 @@ describe('handleDeveloperSearchCommand', () => { // Wrap a payload in the axios envelope returned by `client.http.get`. // Mirrors the `/v2/search/developer` response shape: // { success, results: [{ id, type, url, title, passages: [{ text }] }] } - const mockDeveloperResponse = (results: any[]) => ({ - data: { success: true, results }, + const mockDeveloperResponse = ( + results: any[], + passageBudgetApplied?: number + ) => ({ + data: { + success: true, + results, + ...(passageBudgetApplied == null + ? {} + : { passage_budget_applied: passageBudgetApplied }), + }, }); const sampleResult = { @@ -56,7 +65,7 @@ describe('handleDeveloperSearchCommand', () => { }); describe('API call generation', () => { - it('calls /v2/search/developer with the query and integration tag', async () => { + it('calls /v2/search/developer without a client passage budget', async () => { mockHttpGet.mockResolvedValue(mockDeveloperResponse([sampleResult])); await handleDeveloperSearchCommand({ query: 'tokio spawn_blocking' }); @@ -125,7 +134,7 @@ describe('handleDeveloperSearchCommand', () => { expect(content).toContain('It will panic if this limit is too low.'); }); - it('joins multiple passages and clips long content', async () => { + it('keeps the legacy local cut when the server omits budget metadata', async () => { mockHttpGet.mockResolvedValue( mockDeveloperResponse([ { @@ -143,6 +152,21 @@ describe('handleDeveloperSearchCommand', () => { expect(body.length).toBeLessThanOrEqual(1200); }); + it('does not cut content after the server applies the passage budget', async () => { + const passage = 'x'.repeat(5000); + mockHttpGet.mockResolvedValue( + mockDeveloperResponse( + [{ ...sampleResult, passages: [{ text: passage }] }], + 4096 + ) + ); + + await handleDeveloperSearchCommand({ query: 'tokio spawn_blocking' }); + + const [content] = vi.mocked(writeOutput).mock.calls[0] as [string]; + expect(content).toContain(passage); + }); + it('prints a placeholder when there are no results', async () => { mockHttpGet.mockResolvedValue(mockDeveloperResponse([])); diff --git a/src/commands/developer.ts b/src/commands/developer.ts index 701d22e242..146d11e139 100644 --- a/src/commands/developer.ts +++ b/src/commands/developer.ts @@ -5,7 +5,7 @@ import type { DeveloperItem, DeveloperSearchOptions } from '../types/developer'; // The other mount, /v2/developer/search, rejects keyless callers and may be // withdrawn. const BASE = '/v2/search/developer'; -const MAX_PASSAGE_CHARS = 1200; +const LEGACY_MAX_PASSAGE_CHARS = 1200; async function getDeveloper( path: string, @@ -22,7 +22,10 @@ async function getDeveloper( return (response?.data ?? {}) as T; } -function fmtDeveloper(results?: DeveloperItem[]): string { +function fmtDeveloper( + results?: DeveloperItem[], + passageBudgetApplied?: number +): string { if (!results || results.length === 0) return '(no results)'; return results @@ -36,7 +39,13 @@ function fmtDeveloper(results?: DeveloperItem[]): string { .map((passage) => passage.text ?? '') .join('\n---\n') .trim(); - lines.push(body ? body.slice(0, MAX_PASSAGE_CHARS) : '(no content)'); + // TODO(search#843): Remove this fallback after server passage budgeting + // is fully enabled. + const renderedBody = + passageBudgetApplied == null + ? body.slice(0, LEGACY_MAX_PASSAGE_CHARS) + : body; + lines.push(renderedBody || '(no content)'); return lines.join('\n'); }) .join('\n\n'); @@ -72,11 +81,15 @@ export async function handleDeveloperSearchCommand( params.append('query', options.query); if (options.k != null) params.append('k', String(options.k)); if (options.skillsOnly) params.append('skills', 'only'); - const data = await getDeveloper<{ results?: DeveloperItem[] }>( - `${BASE}?${params.toString()}`, + const data = await getDeveloper<{ + results?: DeveloperItem[]; + passage_budget_applied?: number; + }>(`${BASE}?${params.toString()}`, options); + writeDeveloperOutput( + data, + fmtDeveloper(data.results, data.passage_budget_applied), options ); - writeDeveloperOutput(data, fmtDeveloper(data.results), options); } catch (error) { handleError(error); }