Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/__tests__/cli-argv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down
32 changes: 28 additions & 4 deletions src/__tests__/commands/developer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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' });
Expand Down Expand Up @@ -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([
{
Expand All @@ -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([]));

Expand Down
25 changes: 19 additions & 6 deletions src/commands/developer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(
path: string,
Expand All @@ -22,7 +22,10 @@ async function getDeveloper<T>(
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
Expand All @@ -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');
Expand Down Expand Up @@ -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);
}
Expand Down
Loading