@bbradar/mcp 0.1.3 → 0.1.5
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/dist/server.js +776 -104
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -29,17 +29,53 @@ const DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS = 10;
|
|
|
29
29
|
const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
|
|
30
30
|
const MAX_LOCAL_EXPORT_RESOURCES = 25;
|
|
31
31
|
const EXPORT_PREVIEW_LIMIT = 25;
|
|
32
|
+
const STALE_PROGRAM_ID_RESOLVE_PAGES = 5;
|
|
33
|
+
const STALE_PROGRAM_ID_RESOLVE_BUDGET = 5;
|
|
34
|
+
const MAX_RESOLVE_SEARCH_QUERIES = 4;
|
|
32
35
|
const SDK_VERSION = "1.29.0";
|
|
33
36
|
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
34
37
|
const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
|
|
35
38
|
const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
|
|
39
|
+
const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
|
|
40
|
+
"program",
|
|
41
|
+
"programs",
|
|
42
|
+
"target",
|
|
43
|
+
"targets",
|
|
44
|
+
"scope",
|
|
45
|
+
"scopes",
|
|
46
|
+
"new",
|
|
47
|
+
"latest",
|
|
48
|
+
"recent",
|
|
49
|
+
"recently",
|
|
50
|
+
"added",
|
|
51
|
+
"additions",
|
|
52
|
+
"for",
|
|
53
|
+
"from",
|
|
54
|
+
"the",
|
|
55
|
+
"a",
|
|
56
|
+
"an",
|
|
57
|
+
"of",
|
|
58
|
+
"on",
|
|
59
|
+
"in",
|
|
60
|
+
"with",
|
|
61
|
+
"to",
|
|
62
|
+
"get",
|
|
63
|
+
"find",
|
|
64
|
+
"show",
|
|
65
|
+
"list",
|
|
66
|
+
"check",
|
|
67
|
+
"does",
|
|
68
|
+
"has",
|
|
69
|
+
"have",
|
|
70
|
+
"any"
|
|
71
|
+
]);
|
|
36
72
|
const targetListModeSchema = z.enum(["identifiers", "compact", "full"]);
|
|
37
73
|
const changeListModeSchema = z.enum(["compact", "full"]);
|
|
38
74
|
const programListModeSchema = z.enum(["compact", "full"]);
|
|
39
75
|
const rewardThresholdModeSchema = z.enum(["max_at_least", "min_at_least"]);
|
|
40
76
|
const vdpModeSchema = z.enum(["include", "exclude", "only_likely", "only_known_no_bounty"]);
|
|
41
77
|
const targetMatchModeSchema = z.enum(["contains", "exact", "suffix", "wildcard"]);
|
|
42
|
-
const programNameActionSchema = z.enum(["new_targets", "scope_delta", "brief", "target_breakdown"]);
|
|
78
|
+
const programNameActionSchema = z.enum(["new_targets", "targets", "scope_delta", "brief", "target_breakdown"]);
|
|
43
79
|
const readOnlyAnnotations = {
|
|
44
80
|
readOnlyHint: true,
|
|
45
81
|
destructiveHint: false,
|
|
@@ -124,8 +160,11 @@ const apiEnvelopeOutputShape = {
|
|
|
124
160
|
upstream_request_id: z.string().optional(),
|
|
125
161
|
fetched_at: z.string().optional(),
|
|
126
162
|
cache: cacheOutputSchema.optional(),
|
|
163
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
127
164
|
mcp_rate_limit: rateLimitOutputSchema.optional(),
|
|
128
165
|
mcp_timing: toolTimingOutputSchema.optional(),
|
|
166
|
+
warnings: z.array(z.string()).optional(),
|
|
167
|
+
program_id_resolution: jsonRecordSchema.optional(),
|
|
129
168
|
error: errorOutputSchema.optional()
|
|
130
169
|
};
|
|
131
170
|
const programListOutputShape = {
|
|
@@ -187,7 +226,8 @@ export function createBbradarServer(client, config) {
|
|
|
187
226
|
instructions: [
|
|
188
227
|
"Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
|
|
189
228
|
"For BBRadar asks, use these tools first and avoid external skills unless methodology is explicitly requested.",
|
|
190
|
-
"
|
|
229
|
+
"Partial program name + action: run_program_name_action. Partial program name only: resolve_program.",
|
|
230
|
+
"Default to bounty-eligible in-scope data. Include out-of-scope, ineligible, VDP, disclosure-only, or no-bounty data only when the user explicitly asks.",
|
|
191
231
|
"Freshness: get_latest_added_targets, check_program_new_targets, check_watchlist_new_targets, find_recent_by_type, get_program_scope_delta.",
|
|
192
232
|
"Discovery: find_program_candidates, find_stack_matches, find_language_programs, find_target_type_programs, find_reward_programs, find_paid_programs, find_vdp_programs, find_web3_contests, find_low_noise_programs.",
|
|
193
233
|
"Details: get_program_brief, get_program_scope_summary, get_program_target_breakdown, find_programs_by_target, compare_programs_compact.",
|
|
@@ -203,15 +243,20 @@ export function createBbradarServer(client, config) {
|
|
|
203
243
|
platforms: stringListSchema,
|
|
204
244
|
tags: stringListSchema,
|
|
205
245
|
updated_since: isoDateTimeSchema.optional().describe("ISO datetime."),
|
|
246
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
206
247
|
page: pageSchema,
|
|
207
248
|
page_size: programsPageSizeSchema
|
|
208
249
|
},
|
|
209
250
|
outputSchema: programListOutputShape,
|
|
210
251
|
annotations: readOnlyAnnotations
|
|
211
252
|
}, (args) => runTool("search_programs", config, rateLimiter, async () => {
|
|
212
|
-
const
|
|
253
|
+
const { vdp_mode, ...filters } = args;
|
|
254
|
+
const api = await client.listPrograms(toQuery(filters));
|
|
213
255
|
const data = readObject(api.data);
|
|
214
|
-
const programs = readArray(data?.programs)
|
|
256
|
+
const programs = readArray(data?.programs)
|
|
257
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
258
|
+
.filter((candidate) => candidateMatchesVdpMode(candidate, vdp_mode))
|
|
259
|
+
.map((candidate) => candidate.program);
|
|
215
260
|
return withApiMetadata(api, {
|
|
216
261
|
programs,
|
|
217
262
|
meta: sanitizeJson(data?.meta)
|
|
@@ -225,18 +270,29 @@ export function createBbradarServer(client, config) {
|
|
|
225
270
|
},
|
|
226
271
|
outputSchema: {
|
|
227
272
|
...apiEnvelopeOutputShape,
|
|
228
|
-
program: programOutputSchema.optional()
|
|
273
|
+
program: programOutputSchema.optional(),
|
|
274
|
+
meta: jsonRecordSchema.optional()
|
|
229
275
|
},
|
|
230
276
|
annotations: readOnlyAnnotations
|
|
231
277
|
}, (args) => runTool("get_program", config, rateLimiter, async () => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
278
|
+
return runProgramIdTool(client, config, args.program_id, async (programId) => {
|
|
279
|
+
const api = await client.getProgram(programId);
|
|
280
|
+
return withApiMetadata(api, {
|
|
281
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
282
|
+
});
|
|
283
|
+
}, async (indexFallback) => ({
|
|
284
|
+
request_id: randomUUID(),
|
|
285
|
+
warnings: [`Returned program metadata from the BBRadar program index because the per-program detail endpoint returned 404 for ${indexFallback.resolvedProgramId}.`],
|
|
286
|
+
program: indexFallback.program,
|
|
287
|
+
meta: {
|
|
288
|
+
detail_source: "program_index",
|
|
289
|
+
detail_endpoint_unavailable: true
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
236
292
|
}));
|
|
237
293
|
server.registerTool("resolve_program", {
|
|
238
294
|
title: "Resolve Program",
|
|
239
|
-
description: "Resolve name/handle text to likely program_ids.",
|
|
295
|
+
description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
|
|
240
296
|
inputSchema: {
|
|
241
297
|
query: searchTextSchema,
|
|
242
298
|
platforms: stringListSchema,
|
|
@@ -260,7 +316,7 @@ export function createBbradarServer(client, config) {
|
|
|
260
316
|
}, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
|
|
261
317
|
server.registerTool("run_program_name_action", {
|
|
262
318
|
title: "Run Program Name Action",
|
|
263
|
-
description: "Resolve a name, then run new_targets, scope_delta, brief, or target_breakdown.",
|
|
319
|
+
description: "Resolve a partial program name, then run targets, new_targets, scope_delta, brief, or target_breakdown.",
|
|
264
320
|
inputSchema: {
|
|
265
321
|
query: searchTextSchema,
|
|
266
322
|
action: programNameActionSchema.default("new_targets"),
|
|
@@ -312,24 +368,7 @@ export function createBbradarServer(client, config) {
|
|
|
312
368
|
},
|
|
313
369
|
annotations: readOnlyAnnotations
|
|
314
370
|
}, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
|
|
315
|
-
|
|
316
|
-
const data = readObject(api.data);
|
|
317
|
-
const rawTargets = readArray(data?.targets);
|
|
318
|
-
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, args));
|
|
319
|
-
const targets = sanitizedTargets.slice(args.offset, args.offset + args.limit);
|
|
320
|
-
return withApiMetadata(api, {
|
|
321
|
-
program_id: args.program_id,
|
|
322
|
-
targets: formatTargetList(targets, args.output_mode),
|
|
323
|
-
meta: {
|
|
324
|
-
offset: args.offset,
|
|
325
|
-
limit: args.limit,
|
|
326
|
-
output_mode: args.output_mode,
|
|
327
|
-
total_active_targets: rawTargets.length,
|
|
328
|
-
total_after_filters: sanitizedTargets.length,
|
|
329
|
-
returned: targets.length,
|
|
330
|
-
has_more: args.offset + args.limit < sanitizedTargets.length
|
|
331
|
-
}
|
|
332
|
-
});
|
|
371
|
+
return runProgramIdTool(client, config, args.program_id, async (programId) => getProgramTargetsPayload(client, args, programId), async (indexFallback) => getProgramTargetsExportFallbackPayload(client, args, indexFallback.resolvedProgramId));
|
|
333
372
|
}));
|
|
334
373
|
server.registerTool("get_recent_changes", {
|
|
335
374
|
title: "Get Recent Target Changes",
|
|
@@ -381,8 +420,8 @@ export function createBbradarServer(client, config) {
|
|
|
381
420
|
include_full_target_list: z.boolean().default(true),
|
|
382
421
|
full_target_list_mode: targetListModeSchema.default("identifiers"),
|
|
383
422
|
full_target_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
384
|
-
full_list_include_out_of_scope: z.boolean().default(
|
|
385
|
-
full_list_include_ineligible: z.boolean().default(
|
|
423
|
+
full_list_include_out_of_scope: z.boolean().default(false),
|
|
424
|
+
full_list_include_ineligible: z.boolean().default(false)
|
|
386
425
|
},
|
|
387
426
|
outputSchema: {
|
|
388
427
|
...apiEnvelopeOutputShape,
|
|
@@ -403,8 +442,8 @@ export function createBbradarServer(client, config) {
|
|
|
403
442
|
program_id: programIdSchema,
|
|
404
443
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
405
444
|
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
406
|
-
include_out_of_scope: z.boolean().default(
|
|
407
|
-
include_ineligible: z.boolean().default(
|
|
445
|
+
include_out_of_scope: z.boolean().default(false),
|
|
446
|
+
include_ineligible: z.boolean().default(false)
|
|
408
447
|
},
|
|
409
448
|
outputSchema: {
|
|
410
449
|
...apiEnvelopeOutputShape,
|
|
@@ -414,7 +453,7 @@ export function createBbradarServer(client, config) {
|
|
|
414
453
|
meta: jsonRecordSchema.optional()
|
|
415
454
|
},
|
|
416
455
|
annotations: readOnlyAnnotations
|
|
417
|
-
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => getProgramScopeSummary(client, config, args)));
|
|
456
|
+
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeSummaryFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
418
457
|
server.registerTool("get_program_target_breakdown", {
|
|
419
458
|
title: "Get Program Target Breakdown",
|
|
420
459
|
description: "Target type, scope tag, language tag, and bucket counts for one program.",
|
|
@@ -422,8 +461,8 @@ export function createBbradarServer(client, config) {
|
|
|
422
461
|
program_id: programIdSchema,
|
|
423
462
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
424
463
|
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
425
|
-
include_out_of_scope: z.boolean().default(
|
|
426
|
-
include_ineligible: z.boolean().default(
|
|
464
|
+
include_out_of_scope: z.boolean().default(false),
|
|
465
|
+
include_ineligible: z.boolean().default(false)
|
|
427
466
|
},
|
|
428
467
|
outputSchema: {
|
|
429
468
|
...apiEnvelopeOutputShape,
|
|
@@ -433,7 +472,7 @@ export function createBbradarServer(client, config) {
|
|
|
433
472
|
meta: jsonRecordSchema.optional()
|
|
434
473
|
},
|
|
435
474
|
annotations: readOnlyAnnotations
|
|
436
|
-
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => getProgramTargetBreakdown(client, config, args)));
|
|
475
|
+
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramTargetBreakdownFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
437
476
|
server.registerTool("get_program_scope_delta", {
|
|
438
477
|
title: "Get Program Scope Delta",
|
|
439
478
|
description: "Compact recent scope changes for one program.",
|
|
@@ -459,7 +498,7 @@ export function createBbradarServer(client, config) {
|
|
|
459
498
|
meta: jsonRecordSchema.optional()
|
|
460
499
|
},
|
|
461
500
|
annotations: readOnlyAnnotations
|
|
462
|
-
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => getProgramScopeDelta(client, config, args)));
|
|
501
|
+
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }))));
|
|
463
502
|
server.registerTool("get_recent_target_activity", {
|
|
464
503
|
title: "Get Recent Target Activity",
|
|
465
504
|
description: "Recent target changes grouped by program.",
|
|
@@ -510,7 +549,7 @@ export function createBbradarServer(client, config) {
|
|
|
510
549
|
meta: jsonRecordSchema.optional()
|
|
511
550
|
},
|
|
512
551
|
annotations: readOnlyAnnotations
|
|
513
|
-
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => checkProgramNewTargets(client, config, args)));
|
|
552
|
+
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => checkProgramNewTargets(client, config, { ...args, program_id: programId }), (indexFallback) => checkProgramNewTargetsFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
|
|
514
553
|
server.registerTool("check_watchlist_new_targets", {
|
|
515
554
|
title: "Check Watchlist New Targets",
|
|
516
555
|
description: "New in-scope targets across many known program_ids.",
|
|
@@ -540,6 +579,7 @@ export function createBbradarServer(client, config) {
|
|
|
540
579
|
platforms: stringListSchema,
|
|
541
580
|
tags: stringListSchema,
|
|
542
581
|
updated_since: isoDateTimeSchema.optional(),
|
|
582
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
543
583
|
page: pageSchema,
|
|
544
584
|
page_size: programsPageSizeSchema
|
|
545
585
|
},
|
|
@@ -549,10 +589,13 @@ export function createBbradarServer(client, config) {
|
|
|
549
589
|
},
|
|
550
590
|
annotations: readOnlyAnnotations
|
|
551
591
|
}, (args) => runTool("get_opportunities", config, rateLimiter, async () => {
|
|
552
|
-
const { level, ...filters } = args;
|
|
592
|
+
const { level, vdp_mode, ...filters } = args;
|
|
553
593
|
const api = await client.getOpportunities(level, toQuery(filters));
|
|
554
594
|
const data = readObject(api.data);
|
|
555
|
-
const programs = readArray(data?.programs)
|
|
595
|
+
const programs = readArray(data?.programs)
|
|
596
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
597
|
+
.filter((candidate) => candidateMatchesVdpMode(candidate, vdp_mode))
|
|
598
|
+
.map((candidate) => candidate.program);
|
|
556
599
|
return withApiMetadata(api, {
|
|
557
600
|
level,
|
|
558
601
|
programs,
|
|
@@ -583,7 +626,7 @@ export function createBbradarServer(client, config) {
|
|
|
583
626
|
exclude_non_web: z.boolean().default(false),
|
|
584
627
|
has_api_scope: z.boolean().default(false),
|
|
585
628
|
contest_like: z.boolean().default(false).describe("Require contest/audit signals."),
|
|
586
|
-
vdp_mode: vdpModeSchema.default("
|
|
629
|
+
vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
|
|
587
630
|
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
588
631
|
min_wildcards: z.number().int().min(0).max(1000).default(0),
|
|
589
632
|
min_eligible_targets: z.number().int().min(0).optional(),
|
|
@@ -791,7 +834,7 @@ export function createBbradarServer(client, config) {
|
|
|
791
834
|
min_reward: z.number().int().min(0).optional(),
|
|
792
835
|
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
793
836
|
require_bounty: z.boolean().default(false),
|
|
794
|
-
exclude_vdp: z.boolean().default(
|
|
837
|
+
exclude_vdp: z.boolean().default(true),
|
|
795
838
|
max_public_report_count: z.number().int().min(0).optional(),
|
|
796
839
|
require_known_report_count: z.boolean().default(false),
|
|
797
840
|
include_unknown_report_count: z.boolean().default(true),
|
|
@@ -821,7 +864,7 @@ export function createBbradarServer(client, config) {
|
|
|
821
864
|
require_bounty: z.boolean().default(false),
|
|
822
865
|
fresh_only: z.boolean().default(false),
|
|
823
866
|
min_added_7d: z.number().int().min(0).optional(),
|
|
824
|
-
exclude_vdp: z.boolean().default(
|
|
867
|
+
exclude_vdp: z.boolean().default(true),
|
|
825
868
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
826
869
|
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
827
870
|
include_target_samples: z.boolean().default(false),
|
|
@@ -890,7 +933,7 @@ export function createBbradarServer(client, config) {
|
|
|
890
933
|
web3: z.boolean().default(false),
|
|
891
934
|
contest_like: z.boolean().default(false),
|
|
892
935
|
require_bounty: z.boolean().default(false),
|
|
893
|
-
exclude_vdp: z.boolean().default(
|
|
936
|
+
exclude_vdp: z.boolean().default(true),
|
|
894
937
|
max_public_report_count: z.number().int().min(0).optional(),
|
|
895
938
|
min_eligible_targets: z.number().int().min(0).optional(),
|
|
896
939
|
fresh_only: z.boolean().default(false),
|
|
@@ -969,8 +1012,8 @@ export function createBbradarServer(client, config) {
|
|
|
969
1012
|
use_api_search: z.boolean().default(true),
|
|
970
1013
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(2),
|
|
971
1014
|
max_results: z.number().int().min(1).max(25).default(10),
|
|
972
|
-
include_out_of_scope: z.boolean().default(
|
|
973
|
-
include_ineligible: z.boolean().default(
|
|
1015
|
+
include_out_of_scope: z.boolean().default(false),
|
|
1016
|
+
include_ineligible: z.boolean().default(false),
|
|
974
1017
|
max_targets_per_program: z.number().int().min(1).max(50).default(10),
|
|
975
1018
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
976
1019
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
@@ -1155,7 +1198,7 @@ export function createBbradarServer(client, config) {
|
|
|
1155
1198
|
changes: z.array(jsonRecordSchema).optional()
|
|
1156
1199
|
},
|
|
1157
1200
|
annotations: readOnlyAnnotations
|
|
1158
|
-
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () =>
|
|
1201
|
+
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
|
|
1159
1202
|
server.registerTool("get_program_brief", {
|
|
1160
1203
|
title: "Get Program Brief",
|
|
1161
1204
|
description: "Compact worth-hunting brief for one program.",
|
|
@@ -1178,7 +1221,7 @@ export function createBbradarServer(client, config) {
|
|
|
1178
1221
|
meta: jsonRecordSchema.optional()
|
|
1179
1222
|
},
|
|
1180
1223
|
annotations: readOnlyAnnotations
|
|
1181
|
-
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => getProgramBrief(client, config, args)));
|
|
1224
|
+
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
|
|
1182
1225
|
server.registerTool("find_recently_added_wildcards", {
|
|
1183
1226
|
title: "Find Recently Added Wildcards",
|
|
1184
1227
|
description: "Recently added wildcard programs.",
|
|
@@ -1307,7 +1350,7 @@ export function createBbradarServer(client, config) {
|
|
|
1307
1350
|
meta: jsonRecordSchema.optional()
|
|
1308
1351
|
},
|
|
1309
1352
|
annotations: readOnlyAnnotations
|
|
1310
|
-
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => getProgramDelta(client, config, args)));
|
|
1353
|
+
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
|
|
1311
1354
|
server.registerTool("export_targets", {
|
|
1312
1355
|
title: "Export BBRadar Targets",
|
|
1313
1356
|
description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
|
|
@@ -1374,9 +1417,11 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1374
1417
|
mimeType: "application/json"
|
|
1375
1418
|
}, async (uri) => {
|
|
1376
1419
|
const programId = parseProgramIdFromResourceUri(uri);
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1420
|
+
const payload = await runProgramIdTool(client, config, programId, async (resolvedProgramId) => {
|
|
1421
|
+
const api = await client.getProgram(resolvedProgramId);
|
|
1422
|
+
return withApiMetadata(api, {
|
|
1423
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
1424
|
+
});
|
|
1380
1425
|
});
|
|
1381
1426
|
return jsonResource(uri.toString(), payload);
|
|
1382
1427
|
});
|
|
@@ -1386,15 +1431,17 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1386
1431
|
mimeType: "application/json"
|
|
1387
1432
|
}, async (uri) => {
|
|
1388
1433
|
const programId = parseProgramIdFromResourceUri(uri);
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1434
|
+
const payload = await runProgramIdTool(client, config, programId, async (resolvedProgramId) => {
|
|
1435
|
+
const api = await client.getProgramTargets(resolvedProgramId);
|
|
1436
|
+
const data = readObject(api.data);
|
|
1437
|
+
const targets = readArray(data?.targets).map(sanitizeTarget);
|
|
1438
|
+
return withApiMetadata(api, {
|
|
1439
|
+
program_id: resolvedProgramId,
|
|
1440
|
+
targets,
|
|
1441
|
+
meta: {
|
|
1442
|
+
total_active_targets: targets.length
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1398
1445
|
});
|
|
1399
1446
|
return jsonResource(uri.toString(), payload);
|
|
1400
1447
|
});
|
|
@@ -1433,6 +1480,228 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1433
1480
|
}));
|
|
1434
1481
|
});
|
|
1435
1482
|
}
|
|
1483
|
+
async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
1484
|
+
try {
|
|
1485
|
+
return {
|
|
1486
|
+
api: await client.getProgram(requestedProgramId),
|
|
1487
|
+
programId: requestedProgramId,
|
|
1488
|
+
sourceRequests: [],
|
|
1489
|
+
warnings: []
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
catch (error) {
|
|
1493
|
+
if (!isProgramNotFoundError(error)) {
|
|
1494
|
+
throw error;
|
|
1495
|
+
}
|
|
1496
|
+
const fallback = await resolveStaleProgramId(client, config, requestedProgramId, error);
|
|
1497
|
+
if (!fallback) {
|
|
1498
|
+
throw error;
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
api: await client.getProgram(fallback.resolvedProgramId),
|
|
1502
|
+
programId: fallback.resolvedProgramId,
|
|
1503
|
+
sourceRequests: fallback.sourceRequests,
|
|
1504
|
+
warnings: fallback.warnings,
|
|
1505
|
+
fallback
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
async function runProgramIdTool(client, config, requestedProgramId, callback, indexFallbackCallback) {
|
|
1510
|
+
try {
|
|
1511
|
+
return await callback(requestedProgramId);
|
|
1512
|
+
}
|
|
1513
|
+
catch (error) {
|
|
1514
|
+
if (!isProgramNotFoundError(error)) {
|
|
1515
|
+
throw error;
|
|
1516
|
+
}
|
|
1517
|
+
const fallback = await resolveStaleProgramId(client, config, requestedProgramId, error);
|
|
1518
|
+
if (!fallback) {
|
|
1519
|
+
if (!indexFallbackCallback) {
|
|
1520
|
+
throw error;
|
|
1521
|
+
}
|
|
1522
|
+
const indexFallback = await resolveIndexedProgramId(client, config, requestedProgramId, error);
|
|
1523
|
+
if (!indexFallback) {
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
const payload = await indexFallbackCallback(indexFallback);
|
|
1527
|
+
return withProgramIndexFallbackMetadata(payload, indexFallback);
|
|
1528
|
+
}
|
|
1529
|
+
const payload = await callback(fallback.resolvedProgramId);
|
|
1530
|
+
return withProgramIdFallbackMetadata(payload, fallback);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
async function resolveIndexedProgramId(client, config, requestedProgramId, staleError) {
|
|
1534
|
+
const query = programSearchText(requestedProgramId);
|
|
1535
|
+
if (query.length < 2) {
|
|
1536
|
+
return undefined;
|
|
1537
|
+
}
|
|
1538
|
+
const resolution = await resolveProgram(client, config, {
|
|
1539
|
+
query,
|
|
1540
|
+
platforms: [],
|
|
1541
|
+
tags: [],
|
|
1542
|
+
opportunity_levels: [],
|
|
1543
|
+
max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
|
|
1544
|
+
max_results: 5,
|
|
1545
|
+
output_mode: "full",
|
|
1546
|
+
upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
|
|
1547
|
+
});
|
|
1548
|
+
const match = selectProgramIndexFallbackMatch(requestedProgramId, resolution);
|
|
1549
|
+
if (!match) {
|
|
1550
|
+
return undefined;
|
|
1551
|
+
}
|
|
1552
|
+
const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1553
|
+
const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
1554
|
+
const staleApiError = staleError instanceof BBRadarApiError ? staleError : undefined;
|
|
1555
|
+
const indexWarning = `program_id ${requestedProgramId} exists in the BBRadar program index, but the per-program detail endpoint returned 404.`;
|
|
1556
|
+
return {
|
|
1557
|
+
requestedProgramId,
|
|
1558
|
+
resolvedProgramId: match.resolvedProgramId,
|
|
1559
|
+
query,
|
|
1560
|
+
staleRequestId: staleApiError?.requestId,
|
|
1561
|
+
staleUpstreamRequestId: staleApiError?.upstreamRequestId,
|
|
1562
|
+
sourceRequests,
|
|
1563
|
+
warnings: uniqueStrings([...warnings, indexWarning]),
|
|
1564
|
+
resolution,
|
|
1565
|
+
program: match.program
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
async function resolveStaleProgramId(client, config, requestedProgramId, staleError) {
|
|
1569
|
+
const query = programSearchText(requestedProgramId);
|
|
1570
|
+
if (query.length < 2) {
|
|
1571
|
+
return undefined;
|
|
1572
|
+
}
|
|
1573
|
+
const resolution = await resolveProgram(client, config, {
|
|
1574
|
+
query,
|
|
1575
|
+
platforms: [],
|
|
1576
|
+
tags: [],
|
|
1577
|
+
opportunity_levels: [],
|
|
1578
|
+
max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
|
|
1579
|
+
max_results: 5,
|
|
1580
|
+
output_mode: "compact",
|
|
1581
|
+
upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
|
|
1582
|
+
});
|
|
1583
|
+
const match = selectProgramIdFallbackMatch(requestedProgramId, resolution);
|
|
1584
|
+
if (!match) {
|
|
1585
|
+
return undefined;
|
|
1586
|
+
}
|
|
1587
|
+
const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1588
|
+
const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
1589
|
+
const staleApiError = staleError instanceof BBRadarApiError ? staleError : undefined;
|
|
1590
|
+
const fallbackWarning = `program_id ${requestedProgramId} was stale; automatically retried with resolved program_id ${match.resolvedProgramId}.`;
|
|
1591
|
+
return {
|
|
1592
|
+
requestedProgramId,
|
|
1593
|
+
resolvedProgramId: match.resolvedProgramId,
|
|
1594
|
+
query,
|
|
1595
|
+
staleRequestId: staleApiError?.requestId,
|
|
1596
|
+
staleUpstreamRequestId: staleApiError?.upstreamRequestId,
|
|
1597
|
+
sourceRequests,
|
|
1598
|
+
warnings: uniqueStrings([...warnings, fallbackWarning]),
|
|
1599
|
+
resolution
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
|
|
1603
|
+
const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
|
|
1604
|
+
const requestedSearchText = normalizeTag(programSearchText(requestedProgramId));
|
|
1605
|
+
const candidates = readArray(resolution.matches)
|
|
1606
|
+
.map((entry) => readObject(entry))
|
|
1607
|
+
.filter((entry) => entry !== undefined)
|
|
1608
|
+
.map((entry) => {
|
|
1609
|
+
const program = readObject(entry.program);
|
|
1610
|
+
const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
|
|
1611
|
+
const candidateHandle = normalizeTag(stringField(program, "handle") ?? "");
|
|
1612
|
+
const candidateSearchText = resolvedProgramId ? normalizeTag(programSearchText(resolvedProgramId)) : "";
|
|
1613
|
+
const score = readNumber(entry.score) ?? 0;
|
|
1614
|
+
const exactHandleMatch = candidateHandle === requestedHandle ||
|
|
1615
|
+
candidateHandle === requestedSearchText ||
|
|
1616
|
+
candidateSearchText === requestedSearchText;
|
|
1617
|
+
return {
|
|
1618
|
+
resolvedProgramId,
|
|
1619
|
+
score,
|
|
1620
|
+
exactHandleMatch
|
|
1621
|
+
};
|
|
1622
|
+
})
|
|
1623
|
+
.filter((entry) => entry.resolvedProgramId !== undefined && entry.resolvedProgramId !== requestedProgramId && entry.exactHandleMatch && entry.score >= 80)
|
|
1624
|
+
.sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
|
|
1625
|
+
if (candidates.length === 0) {
|
|
1626
|
+
return undefined;
|
|
1627
|
+
}
|
|
1628
|
+
const [best, next] = candidates;
|
|
1629
|
+
if (!best || (next && best.score === next.score)) {
|
|
1630
|
+
return undefined;
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
resolvedProgramId: best.resolvedProgramId
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
|
|
1637
|
+
const requestedId = normalizeTag(requestedProgramId);
|
|
1638
|
+
const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
|
|
1639
|
+
const requestedPlatform = normalizeTag(programPlatformText(requestedProgramId));
|
|
1640
|
+
const candidates = readArray(resolution.matches)
|
|
1641
|
+
.map((entry) => readObject(entry))
|
|
1642
|
+
.filter((entry) => entry !== undefined)
|
|
1643
|
+
.map((entry) => {
|
|
1644
|
+
const program = readObject(entry.program);
|
|
1645
|
+
const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
|
|
1646
|
+
const candidateHandle = normalizeTag(stringField(program, "handle") ?? (resolvedProgramId ? programHandleText(resolvedProgramId) : ""));
|
|
1647
|
+
const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
|
|
1648
|
+
const score = readNumber(entry.score) ?? 0;
|
|
1649
|
+
const idMatch = resolvedProgramId !== undefined && normalizeTag(resolvedProgramId) === requestedId;
|
|
1650
|
+
const platformHandleMatch = requestedPlatform.length > 0 &&
|
|
1651
|
+
candidatePlatform === requestedPlatform &&
|
|
1652
|
+
candidateHandle === requestedHandle;
|
|
1653
|
+
return {
|
|
1654
|
+
resolvedProgramId,
|
|
1655
|
+
program,
|
|
1656
|
+
score,
|
|
1657
|
+
exactMatch: idMatch || platformHandleMatch
|
|
1658
|
+
};
|
|
1659
|
+
})
|
|
1660
|
+
.filter((entry) => entry.resolvedProgramId !== undefined && entry.program !== undefined && entry.exactMatch && entry.score >= 80)
|
|
1661
|
+
.sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
|
|
1662
|
+
if (candidates.length === 0) {
|
|
1663
|
+
return undefined;
|
|
1664
|
+
}
|
|
1665
|
+
const [best, next] = candidates;
|
|
1666
|
+
if (!best || (next && best.score === next.score && normalizeTag(next.resolvedProgramId) !== normalizeTag(best.resolvedProgramId))) {
|
|
1667
|
+
return undefined;
|
|
1668
|
+
}
|
|
1669
|
+
return {
|
|
1670
|
+
resolvedProgramId: best.resolvedProgramId,
|
|
1671
|
+
program: addProgramResourceLinks(best.program)
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
function withProgramIdFallbackMetadata(payload, fallback) {
|
|
1675
|
+
const sourceRequests = readArray(payload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1676
|
+
const warnings = readArray(payload.warnings).filter((warning) => typeof warning === "string");
|
|
1677
|
+
const resolution = stripUndefined({
|
|
1678
|
+
requested_program_id: fallback.requestedProgramId,
|
|
1679
|
+
resolved_program_id: fallback.resolvedProgramId,
|
|
1680
|
+
query: fallback.query,
|
|
1681
|
+
reason: "stale_program_id",
|
|
1682
|
+
stale_status: 404,
|
|
1683
|
+
stale_request_id: fallback.staleRequestId,
|
|
1684
|
+
stale_upstream_request_id: fallback.staleUpstreamRequestId,
|
|
1685
|
+
resolution_request_id: stringField(fallback.resolution, "request_id")
|
|
1686
|
+
});
|
|
1687
|
+
return stripUndefined({
|
|
1688
|
+
...payload,
|
|
1689
|
+
source_requests: [...fallback.sourceRequests, ...sourceRequests],
|
|
1690
|
+
warnings: uniqueStrings([...fallback.warnings, ...warnings]),
|
|
1691
|
+
program_id_resolution: resolution
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
function withProgramIndexFallbackMetadata(payload, fallback) {
|
|
1695
|
+
const enriched = withProgramIdFallbackMetadata(payload, fallback);
|
|
1696
|
+
const resolution = readObject(enriched.program_id_resolution);
|
|
1697
|
+
return {
|
|
1698
|
+
...enriched,
|
|
1699
|
+
program_id_resolution: stripUndefined({
|
|
1700
|
+
...(resolution ?? {}),
|
|
1701
|
+
reason: "program_index_detail_404"
|
|
1702
|
+
})
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1436
1705
|
async function resolveProgram(client, config, input) {
|
|
1437
1706
|
const warnings = [];
|
|
1438
1707
|
const budget = {
|
|
@@ -1440,10 +1709,11 @@ async function resolveProgram(client, config, input) {
|
|
|
1440
1709
|
remaining: input.upstream_request_budget
|
|
1441
1710
|
};
|
|
1442
1711
|
const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
1443
|
-
const
|
|
1712
|
+
const searchQueries = programResolutionSearchQueries(input.query);
|
|
1713
|
+
const collected = await collectProgramsForResolution(client, {
|
|
1714
|
+
searchQueries,
|
|
1444
1715
|
platforms: input.platforms,
|
|
1445
1716
|
tags: input.tags,
|
|
1446
|
-
updated_since: undefined,
|
|
1447
1717
|
opportunity_levels: input.opportunity_levels,
|
|
1448
1718
|
max_pages: maxPages,
|
|
1449
1719
|
budget,
|
|
@@ -1465,6 +1735,7 @@ async function resolveProgram(client, config, input) {
|
|
|
1465
1735
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1466
1736
|
query: {
|
|
1467
1737
|
text: input.query,
|
|
1738
|
+
search_queries: searchQueries,
|
|
1468
1739
|
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
1469
1740
|
tags: input.tags.length > 0 ? input.tags : undefined
|
|
1470
1741
|
},
|
|
@@ -1572,6 +1843,31 @@ async function runResolvedProgramAction(client, config, programId, input) {
|
|
|
1572
1843
|
include_ineligible: input.include_ineligible
|
|
1573
1844
|
});
|
|
1574
1845
|
}
|
|
1846
|
+
if (input.action === "targets") {
|
|
1847
|
+
const api = await client.getProgramTargets(programId);
|
|
1848
|
+
const data = readObject(api.data);
|
|
1849
|
+
const rawTargets = readArray(data?.targets);
|
|
1850
|
+
const sanitizedTargets = rawTargets
|
|
1851
|
+
.map(sanitizeTarget)
|
|
1852
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
1853
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1854
|
+
include_ineligible: input.include_ineligible,
|
|
1855
|
+
strict_scope_filter: true
|
|
1856
|
+
}));
|
|
1857
|
+
const targets = sanitizedTargets.slice(0, input.max_targets);
|
|
1858
|
+
return withApiMetadata(api, {
|
|
1859
|
+
program_id: programId,
|
|
1860
|
+
targets: formatTargetList(targets, input.target_list_mode),
|
|
1861
|
+
meta: {
|
|
1862
|
+
limit: input.max_targets,
|
|
1863
|
+
output_mode: input.target_list_mode,
|
|
1864
|
+
total_active_targets: rawTargets.length,
|
|
1865
|
+
total_after_filters: sanitizedTargets.length,
|
|
1866
|
+
returned: targets.length,
|
|
1867
|
+
has_more: sanitizedTargets.length > targets.length
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1575
1871
|
return checkProgramNewTargets(client, config, {
|
|
1576
1872
|
program_id: programId,
|
|
1577
1873
|
since: input.since,
|
|
@@ -1937,7 +2233,7 @@ function findLowNoisePrograms(client, config, input) {
|
|
|
1937
2233
|
exclude_mobile_only: false,
|
|
1938
2234
|
exclude_non_web: false,
|
|
1939
2235
|
has_api_scope: false,
|
|
1940
|
-
vdp_mode:
|
|
2236
|
+
vdp_mode: "exclude",
|
|
1941
2237
|
fresh_launch_days: undefined,
|
|
1942
2238
|
min_wildcards: 0,
|
|
1943
2239
|
min_eligible_targets: input.min_eligible_targets,
|
|
@@ -2268,17 +2564,22 @@ async function comparePrograms(client, config, input) {
|
|
|
2268
2564
|
const candidates = [];
|
|
2269
2565
|
const sourceRequests = [];
|
|
2270
2566
|
const errors = [];
|
|
2567
|
+
const warnings = [];
|
|
2271
2568
|
await mapWithConcurrency(input.program_ids, TARGET_SAMPLE_CONCURRENCY, async (programId) => {
|
|
2272
2569
|
try {
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2570
|
+
const lookup = await getProgramWithIdFallback(client, config, programId);
|
|
2571
|
+
const api = lookup.api;
|
|
2572
|
+
const requestSource = stripUndefined({
|
|
2276
2573
|
source: "program",
|
|
2277
|
-
program_id: programId,
|
|
2574
|
+
program_id: lookup.programId,
|
|
2575
|
+
requested_program_id: lookup.programId === programId ? undefined : programId,
|
|
2278
2576
|
request_id: api.requestId,
|
|
2279
2577
|
upstream_request_id: api.upstreamRequestId,
|
|
2280
2578
|
...apiSourceMetadata(api)
|
|
2281
2579
|
});
|
|
2580
|
+
candidates.push(toProgramCandidate(api.data, config.webBaseUrl));
|
|
2581
|
+
sourceRequests.push(...lookup.sourceRequests, requestSource);
|
|
2582
|
+
warnings.push(...lookup.warnings);
|
|
2282
2583
|
}
|
|
2283
2584
|
catch (error) {
|
|
2284
2585
|
errors.push(programFetchError(programId, error));
|
|
@@ -2333,6 +2634,7 @@ async function comparePrograms(client, config, input) {
|
|
|
2333
2634
|
partial_success: errors.length > 0,
|
|
2334
2635
|
program_ids_requested: input.program_ids,
|
|
2335
2636
|
failed_program_ids: errors.map((error) => error.program_id).filter((id) => typeof id === "string"),
|
|
2637
|
+
warnings: warnings.length > 0 ? uniqueStrings(warnings) : undefined,
|
|
2336
2638
|
errors: errors.length > 0 ? errors : undefined,
|
|
2337
2639
|
source_requests: sourceRequests,
|
|
2338
2640
|
compared_programs: candidates.map((candidate, index) => candidateOutput(candidate, index, targetSamples, input.output_mode ?? "compact")),
|
|
@@ -2467,11 +2769,132 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
2467
2769
|
})
|
|
2468
2770
|
});
|
|
2469
2771
|
}
|
|
2772
|
+
async function getProgramTargetsPayload(client, input, programId) {
|
|
2773
|
+
const api = await client.getProgramTargets(programId);
|
|
2774
|
+
const data = readObject(api.data);
|
|
2775
|
+
const rawTargets = readArray(data?.targets);
|
|
2776
|
+
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, input));
|
|
2777
|
+
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2778
|
+
return withApiMetadata(api, {
|
|
2779
|
+
program_id: programId,
|
|
2780
|
+
targets: formatTargetList(targets, input.output_mode),
|
|
2781
|
+
meta: {
|
|
2782
|
+
offset: input.offset,
|
|
2783
|
+
limit: input.limit,
|
|
2784
|
+
output_mode: input.output_mode,
|
|
2785
|
+
total_active_targets: rawTargets.length,
|
|
2786
|
+
total_after_filters: sanitizedTargets.length,
|
|
2787
|
+
returned: targets.length,
|
|
2788
|
+
has_more: input.offset + input.limit < sanitizedTargets.length,
|
|
2789
|
+
target_source: "program_targets"
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
async function getProgramTargetsExportFallbackPayload(client, input, programId) {
|
|
2794
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, programId);
|
|
2795
|
+
const sanitizedTargets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, input));
|
|
2796
|
+
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2797
|
+
return stripUndefined({
|
|
2798
|
+
request_id: exportLookup.requestId,
|
|
2799
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
2800
|
+
fetched_at: exportLookup.fetchedAt,
|
|
2801
|
+
cache: exportLookup.cache,
|
|
2802
|
+
source_requests: exportLookup.sourceRequests,
|
|
2803
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
2804
|
+
program_id: programId,
|
|
2805
|
+
targets: formatTargetList(targets, input.output_mode),
|
|
2806
|
+
meta: stripUndefined({
|
|
2807
|
+
offset: input.offset,
|
|
2808
|
+
limit: input.limit,
|
|
2809
|
+
output_mode: input.output_mode,
|
|
2810
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
2811
|
+
total_after_filters: sanitizedTargets.length,
|
|
2812
|
+
returned: targets.length,
|
|
2813
|
+
has_more: input.offset + input.limit < sanitizedTargets.length,
|
|
2814
|
+
target_source: exportLookup.source,
|
|
2815
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
2816
|
+
fallback_error: exportLookup.error
|
|
2817
|
+
})
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
2821
|
+
try {
|
|
2822
|
+
const api = await client.exportTargets({
|
|
2823
|
+
program_ids: [programId],
|
|
2824
|
+
include_out_of_scope: true,
|
|
2825
|
+
include_ineligible: true,
|
|
2826
|
+
format: "json",
|
|
2827
|
+
limit: MAX_TARGET_EXPORT
|
|
2828
|
+
});
|
|
2829
|
+
const rawTargets = readExportTargetRows(api.data).map((row) => sanitizeTarget(readObject(row)?.target ?? row));
|
|
2830
|
+
return {
|
|
2831
|
+
requestId: api.requestId,
|
|
2832
|
+
upstreamRequestId: api.upstreamRequestId,
|
|
2833
|
+
fetchedAt: api.fetchedAt,
|
|
2834
|
+
cache: readObject(stripUndefined({
|
|
2835
|
+
hit: api.cached,
|
|
2836
|
+
coalesced_live_request: api.coalesced,
|
|
2837
|
+
expires_at: api.cacheExpiresAt
|
|
2838
|
+
})),
|
|
2839
|
+
source: "target_export",
|
|
2840
|
+
rawTargets,
|
|
2841
|
+
sourceRequests: [
|
|
2842
|
+
{
|
|
2843
|
+
source: "target_export",
|
|
2844
|
+
program_id: programId,
|
|
2845
|
+
request_id: api.requestId,
|
|
2846
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2847
|
+
...apiSourceMetadata(api)
|
|
2848
|
+
}
|
|
2849
|
+
],
|
|
2850
|
+
warnings: [`Used target export fallback because the per-program targets endpoint returned 404 for ${programId}.`],
|
|
2851
|
+
unavailable: false
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
catch (error) {
|
|
2855
|
+
return {
|
|
2856
|
+
requestId: randomUUID(),
|
|
2857
|
+
source: "unavailable",
|
|
2858
|
+
rawTargets: [],
|
|
2859
|
+
sourceRequests: [
|
|
2860
|
+
{
|
|
2861
|
+
source: "target_export",
|
|
2862
|
+
program_id: programId,
|
|
2863
|
+
failed: true,
|
|
2864
|
+
error: programFetchError(programId, error)
|
|
2865
|
+
}
|
|
2866
|
+
],
|
|
2867
|
+
warnings: [
|
|
2868
|
+
`The BBRadar program index contains ${programId}, but both the per-program targets endpoint and target export fallback were unavailable. Exact targets cannot be returned from the current API response.`
|
|
2869
|
+
],
|
|
2870
|
+
unavailable: true,
|
|
2871
|
+
error: programFetchError(programId, error)
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function readExportTargetRows(data) {
|
|
2876
|
+
if (Array.isArray(data)) {
|
|
2877
|
+
return data;
|
|
2878
|
+
}
|
|
2879
|
+
const object = readObject(data);
|
|
2880
|
+
if (!object) {
|
|
2881
|
+
return [];
|
|
2882
|
+
}
|
|
2883
|
+
return [object.targets, object.results, object.data]
|
|
2884
|
+
.map(readArray)
|
|
2885
|
+
.find((rows) => rows.length > 0) ?? [];
|
|
2886
|
+
}
|
|
2470
2887
|
async function getProgramScopeSummary(client, config, input) {
|
|
2471
2888
|
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2472
2889
|
const targetsData = readObject(targetsApi.data);
|
|
2473
2890
|
const rawTargets = readArray(targetsData?.targets);
|
|
2474
|
-
const targets = rawTargets
|
|
2891
|
+
const targets = rawTargets
|
|
2892
|
+
.map(sanitizeTarget)
|
|
2893
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2894
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2895
|
+
include_ineligible: input.include_ineligible,
|
|
2896
|
+
strict_scope_filter: false
|
|
2897
|
+
}));
|
|
2475
2898
|
return {
|
|
2476
2899
|
request_id: randomUUID(),
|
|
2477
2900
|
source_requests: [
|
|
@@ -2492,11 +2915,39 @@ async function getProgramScopeSummary(client, config, input) {
|
|
|
2492
2915
|
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
2493
2916
|
meta: {
|
|
2494
2917
|
total_active_targets: rawTargets.length,
|
|
2918
|
+
total_after_filters: targets.length,
|
|
2495
2919
|
target_list_mode: input.target_list_mode,
|
|
2496
2920
|
group_limit: input.group_limit
|
|
2497
2921
|
}
|
|
2498
2922
|
};
|
|
2499
2923
|
}
|
|
2924
|
+
async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, input) {
|
|
2925
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, input.program_id);
|
|
2926
|
+
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
2927
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2928
|
+
include_ineligible: input.include_ineligible,
|
|
2929
|
+
strict_scope_filter: false
|
|
2930
|
+
}));
|
|
2931
|
+
return stripUndefined({
|
|
2932
|
+
request_id: exportLookup.requestId,
|
|
2933
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
2934
|
+
fetched_at: exportLookup.fetchedAt,
|
|
2935
|
+
cache: exportLookup.cache,
|
|
2936
|
+
source_requests: exportLookup.sourceRequests,
|
|
2937
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
2938
|
+
program: formatProgram(indexFallback.program, "compact"),
|
|
2939
|
+
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
2940
|
+
meta: stripUndefined({
|
|
2941
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
2942
|
+
total_after_filters: targets.length,
|
|
2943
|
+
target_list_mode: input.target_list_mode,
|
|
2944
|
+
group_limit: input.group_limit,
|
|
2945
|
+
target_source: exportLookup.source,
|
|
2946
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
2947
|
+
fallback_error: exportLookup.error
|
|
2948
|
+
})
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2500
2951
|
async function getProgramTargetBreakdown(client, config, input) {
|
|
2501
2952
|
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2502
2953
|
const targetsData = readObject(targetsApi.data);
|
|
@@ -2543,6 +2994,42 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
2543
2994
|
}
|
|
2544
2995
|
};
|
|
2545
2996
|
}
|
|
2997
|
+
async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback, input) {
|
|
2998
|
+
const exportLookup = await fetchProgramTargetsFromExportFallback(client, input.program_id);
|
|
2999
|
+
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
3000
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
3001
|
+
include_ineligible: input.include_ineligible,
|
|
3002
|
+
strict_scope_filter: false
|
|
3003
|
+
}));
|
|
3004
|
+
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible);
|
|
3005
|
+
return stripUndefined({
|
|
3006
|
+
request_id: exportLookup.requestId,
|
|
3007
|
+
upstream_request_id: exportLookup.upstreamRequestId,
|
|
3008
|
+
fetched_at: exportLookup.fetchedAt,
|
|
3009
|
+
cache: exportLookup.cache,
|
|
3010
|
+
source_requests: exportLookup.sourceRequests,
|
|
3011
|
+
warnings: exportLookup.warnings.length > 0 ? uniqueStrings(exportLookup.warnings) : undefined,
|
|
3012
|
+
program: formatProgram(indexFallback.program, "compact"),
|
|
3013
|
+
breakdown: {
|
|
3014
|
+
target_counts: readObject(scope.target_counts),
|
|
3015
|
+
by_target_type: countTargetValues(targets, (target) => stringField(target, "target_type")),
|
|
3016
|
+
by_scope_tag: countTargetValues(targets, (target) => readStringArrayField(target, "scope_tags")),
|
|
3017
|
+
by_language_tag: countTargetValues(targets, (target) => readStringArrayField(target, "language_tags")),
|
|
3018
|
+
group_counts: readObject(scope.group_counts),
|
|
3019
|
+
group_has_more: readObject(scope.group_has_more),
|
|
3020
|
+
groups: readObject(scope.groups)
|
|
3021
|
+
},
|
|
3022
|
+
meta: stripUndefined({
|
|
3023
|
+
total_active_targets: exportLookup.rawTargets.length,
|
|
3024
|
+
total_after_filters: targets.length,
|
|
3025
|
+
target_list_mode: input.target_list_mode,
|
|
3026
|
+
group_limit: input.group_limit,
|
|
3027
|
+
target_source: exportLookup.source,
|
|
3028
|
+
target_list_unavailable: exportLookup.unavailable,
|
|
3029
|
+
fallback_error: exportLookup.error
|
|
3030
|
+
})
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
2546
3033
|
async function getProgramScopeDelta(client, config, input) {
|
|
2547
3034
|
const programApi = await client.getProgram(input.program_id);
|
|
2548
3035
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
@@ -2673,13 +3160,15 @@ async function getRecentTargetActivity(client, config, input) {
|
|
|
2673
3160
|
};
|
|
2674
3161
|
}
|
|
2675
3162
|
async function checkProgramNewTargets(client, config, input) {
|
|
2676
|
-
const
|
|
3163
|
+
const programLookup = await getProgramWithIdFallback(client, config, input.program_id);
|
|
3164
|
+
const programId = programLookup.programId;
|
|
3165
|
+
const handle = programSearchText(programId);
|
|
2677
3166
|
const api = await client.getRecentChanges({
|
|
2678
3167
|
change_type: "added",
|
|
2679
3168
|
include_removed: false,
|
|
2680
3169
|
include_ineligible: input.include_ineligible,
|
|
2681
3170
|
include_out_of_scope: false,
|
|
2682
|
-
search: handle.length >= 2 ? handle :
|
|
3171
|
+
search: handle.length >= 2 ? handle : programId,
|
|
2683
3172
|
tags: input.target_type ? [input.target_type] : [],
|
|
2684
3173
|
page: 1,
|
|
2685
3174
|
page_size: input.page_size
|
|
@@ -2693,7 +3182,88 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2693
3182
|
const target = readObject(change.target);
|
|
2694
3183
|
const changedAt = timestampField(change, "changed_at");
|
|
2695
3184
|
return (stringField(change, "change_type") === "added" &&
|
|
2696
|
-
stringField(readObject(change.program), "id") ===
|
|
3185
|
+
stringField(readObject(change.program), "id") === programId &&
|
|
3186
|
+
target !== undefined &&
|
|
3187
|
+
(sinceTimestamp === undefined || changedAt >= sinceTimestamp) &&
|
|
3188
|
+
(!input.target_type || targetMatchesRequestedType(target, input.target_type)) &&
|
|
3189
|
+
targetHasAllowedScope(target, {
|
|
3190
|
+
include_out_of_scope: false,
|
|
3191
|
+
include_ineligible: input.include_ineligible,
|
|
3192
|
+
strict_scope_filter: true
|
|
3193
|
+
}));
|
|
3194
|
+
})
|
|
3195
|
+
.sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
|
|
3196
|
+
const limitedChanges = matchingChanges.slice(0, input.max_targets);
|
|
3197
|
+
const targets = limitedChanges
|
|
3198
|
+
.map((change) => readObject(change.target))
|
|
3199
|
+
.filter((target) => target !== undefined);
|
|
3200
|
+
const latest = matchingChanges[0];
|
|
3201
|
+
const sourceRequests = [
|
|
3202
|
+
{
|
|
3203
|
+
source: "program",
|
|
3204
|
+
program_id: programId,
|
|
3205
|
+
requested_program_id: programId === input.program_id ? undefined : input.program_id,
|
|
3206
|
+
request_id: programLookup.api.requestId,
|
|
3207
|
+
upstream_request_id: programLookup.api.upstreamRequestId,
|
|
3208
|
+
...apiSourceMetadata(programLookup.api)
|
|
3209
|
+
},
|
|
3210
|
+
{
|
|
3211
|
+
source: "recent_changes",
|
|
3212
|
+
request_id: api.requestId,
|
|
3213
|
+
upstream_request_id: api.upstreamRequestId,
|
|
3214
|
+
...apiSourceMetadata(api)
|
|
3215
|
+
}
|
|
3216
|
+
];
|
|
3217
|
+
const payload = stripUndefined({
|
|
3218
|
+
request_id: randomUUID(),
|
|
3219
|
+
source_requests: sourceRequests,
|
|
3220
|
+
warnings: !programLookup.fallback && programLookup.warnings.length > 0 ? uniqueStrings(programLookup.warnings) : undefined,
|
|
3221
|
+
program_id: programId,
|
|
3222
|
+
program: latest
|
|
3223
|
+
? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact")
|
|
3224
|
+
: formatProgram(addProgramResourceLinks(sanitizeProgram(programLookup.api.data, config.webBaseUrl)), "compact"),
|
|
3225
|
+
has_new_targets: matchingChanges.length > 0,
|
|
3226
|
+
new_target_count: matchingChanges.length,
|
|
3227
|
+
latest_added_at: latest ? stringField(latest, "changed_at") : undefined,
|
|
3228
|
+
new_targets: formatTargetList(targets, input.target_list_mode),
|
|
3229
|
+
meta: stripUndefined({
|
|
3230
|
+
recent_changes_scanned: changes.length,
|
|
3231
|
+
returned: targets.length,
|
|
3232
|
+
has_more: matchingChanges.length > targets.length,
|
|
3233
|
+
since: input.since,
|
|
3234
|
+
target_type: input.target_type,
|
|
3235
|
+
in_scope_only: true,
|
|
3236
|
+
include_ineligible: input.include_ineligible,
|
|
3237
|
+
target_list_mode: input.target_list_mode,
|
|
3238
|
+
upstream_total_pages: readNumber(upstreamMeta?.total_pages),
|
|
3239
|
+
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
3240
|
+
})
|
|
3241
|
+
});
|
|
3242
|
+
return programLookup.fallback ? withProgramIdFallbackMetadata(payload, programLookup.fallback) : payload;
|
|
3243
|
+
}
|
|
3244
|
+
async function checkProgramNewTargetsFromIndexedProgram(client, config, input, indexedProgram) {
|
|
3245
|
+
const programId = input.program_id;
|
|
3246
|
+
const handle = programSearchText(programId);
|
|
3247
|
+
const api = await client.getRecentChanges({
|
|
3248
|
+
change_type: "added",
|
|
3249
|
+
include_removed: false,
|
|
3250
|
+
include_ineligible: input.include_ineligible,
|
|
3251
|
+
include_out_of_scope: false,
|
|
3252
|
+
search: handle.length >= 2 ? handle : programId,
|
|
3253
|
+
tags: input.target_type ? [input.target_type] : [],
|
|
3254
|
+
page: 1,
|
|
3255
|
+
page_size: input.page_size
|
|
3256
|
+
});
|
|
3257
|
+
const data = readObject(api.data);
|
|
3258
|
+
const upstreamMeta = readObject(sanitizeJson(data?.meta));
|
|
3259
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
3260
|
+
const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
3261
|
+
const matchingChanges = changes
|
|
3262
|
+
.filter((change) => {
|
|
3263
|
+
const target = readObject(change.target);
|
|
3264
|
+
const changedAt = timestampField(change, "changed_at");
|
|
3265
|
+
return (stringField(change, "change_type") === "added" &&
|
|
3266
|
+
stringField(readObject(change.program), "id") === programId &&
|
|
2697
3267
|
target !== undefined &&
|
|
2698
3268
|
(sinceTimestamp === undefined || changedAt >= sinceTimestamp) &&
|
|
2699
3269
|
(!input.target_type || targetMatchesRequestedType(target, input.target_type)) &&
|
|
@@ -2719,8 +3289,10 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2719
3289
|
...apiSourceMetadata(api)
|
|
2720
3290
|
}
|
|
2721
3291
|
],
|
|
2722
|
-
program_id:
|
|
2723
|
-
program: latest
|
|
3292
|
+
program_id: programId,
|
|
3293
|
+
program: latest
|
|
3294
|
+
? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact")
|
|
3295
|
+
: formatProgram(indexedProgram, "compact"),
|
|
2724
3296
|
has_new_targets: matchingChanges.length > 0,
|
|
2725
3297
|
new_target_count: matchingChanges.length,
|
|
2726
3298
|
latest_added_at: latest ? stringField(latest, "changed_at") : undefined,
|
|
@@ -2734,6 +3306,7 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2734
3306
|
in_scope_only: true,
|
|
2735
3307
|
include_ineligible: input.include_ineligible,
|
|
2736
3308
|
target_list_mode: input.target_list_mode,
|
|
3309
|
+
program_detail_source: "program_index",
|
|
2737
3310
|
upstream_total_pages: readNumber(upstreamMeta?.total_pages),
|
|
2738
3311
|
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
2739
3312
|
})
|
|
@@ -3409,8 +3982,9 @@ function candidateLooksLikeVdp(candidate) {
|
|
|
3409
3982
|
return candidateHasKnownNoBounty(candidate) || tagIncludesAnyNormalized(candidate.normalizedSearchSignals, VDP_SIGNALS);
|
|
3410
3983
|
}
|
|
3411
3984
|
function programResolutionMatch(candidate, query) {
|
|
3412
|
-
const
|
|
3413
|
-
const
|
|
3985
|
+
const queryVariants = programResolutionQueryVariants(query);
|
|
3986
|
+
const primaryQuery = queryVariants[0] ?? normalizeTag(query);
|
|
3987
|
+
const queryTokens = meaningfulProgramQueryTokens(query);
|
|
3414
3988
|
const fields = [
|
|
3415
3989
|
{ name: "id", value: stringField(candidate.program, "id") },
|
|
3416
3990
|
{ name: "handle", value: stringField(candidate.program, "handle") },
|
|
@@ -3428,16 +4002,23 @@ function programResolutionMatch(candidate, query) {
|
|
|
3428
4002
|
}
|
|
3429
4003
|
const normalizedValue = normalizeTag(field.value);
|
|
3430
4004
|
const fieldTokens = tokenSet(normalizedValue);
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
4005
|
+
for (const queryVariant of queryVariants) {
|
|
4006
|
+
if (normalizedValue === queryVariant) {
|
|
4007
|
+
score = Math.max(score, field.name === "id" || field.name === "handle" ? 100 : 95);
|
|
4008
|
+
matchedFields.add(field.name);
|
|
4009
|
+
reasons.add(queryVariant === primaryQuery ? `${field.name} exact match` : `${field.name} exact meaningful-query match`);
|
|
4010
|
+
continue;
|
|
4011
|
+
}
|
|
4012
|
+
if (normalizedValue.includes(queryVariant)) {
|
|
4013
|
+
const containsScore = field.name === "name" || field.name === "handle"
|
|
4014
|
+
? queryVariant.includes("-") || queryVariant.includes(" ")
|
|
4015
|
+
? 90
|
|
4016
|
+
: 85
|
|
4017
|
+
: 70;
|
|
4018
|
+
matchedFields.add(field.name);
|
|
4019
|
+
score = Math.max(score, containsScore);
|
|
4020
|
+
reasons.add(queryVariant === primaryQuery ? `${field.name} contains query` : `${field.name} contains meaningful query`);
|
|
4021
|
+
}
|
|
3441
4022
|
}
|
|
3442
4023
|
const tokenOverlap = [...queryTokens].filter((token) => fieldTokens.has(token)).length;
|
|
3443
4024
|
if (tokenOverlap > 0) {
|
|
@@ -3447,10 +4028,12 @@ function programResolutionMatch(candidate, query) {
|
|
|
3447
4028
|
reasons.add(`${field.name} token match`);
|
|
3448
4029
|
}
|
|
3449
4030
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
4031
|
+
for (const queryVariant of queryVariants) {
|
|
4032
|
+
if (candidate.normalizedSearchSignals.some((signal) => signal.includes(queryVariant))) {
|
|
4033
|
+
score = Math.max(score, queryVariant === primaryQuery ? 55 : 65);
|
|
4034
|
+
matchedFields.add("search_signals");
|
|
4035
|
+
reasons.add(queryVariant === primaryQuery ? "program search signals contain query" : "program search signals contain meaningful query");
|
|
4036
|
+
}
|
|
3454
4037
|
}
|
|
3455
4038
|
return {
|
|
3456
4039
|
score,
|
|
@@ -3458,6 +4041,25 @@ function programResolutionMatch(candidate, query) {
|
|
|
3458
4041
|
reasons: [...reasons]
|
|
3459
4042
|
};
|
|
3460
4043
|
}
|
|
4044
|
+
function programResolutionSearchQueries(query) {
|
|
4045
|
+
return programResolutionQueryVariants(query).slice(0, MAX_RESOLVE_SEARCH_QUERIES);
|
|
4046
|
+
}
|
|
4047
|
+
function programResolutionQueryVariants(query) {
|
|
4048
|
+
const normalized = normalizeTag(query);
|
|
4049
|
+
const meaningfulTokens = [...meaningfulProgramQueryTokens(query)];
|
|
4050
|
+
const meaningfulPhrase = meaningfulTokens.join(" ");
|
|
4051
|
+
const values = [
|
|
4052
|
+
normalized,
|
|
4053
|
+
meaningfulPhrase,
|
|
4054
|
+
meaningfulTokens.join("-"),
|
|
4055
|
+
...meaningfulTokens
|
|
4056
|
+
];
|
|
4057
|
+
return uniqueStrings(values.filter((value) => value.length >= 2));
|
|
4058
|
+
}
|
|
4059
|
+
function meaningfulProgramQueryTokens(query) {
|
|
4060
|
+
const tokens = [...tokenSet(normalizeTag(query))].filter((token) => !PROGRAM_RESOLUTION_STOP_WORDS.has(token));
|
|
4061
|
+
return new Set(tokens.length > 0 ? tokens : [...tokenSet(normalizeTag(query))]);
|
|
4062
|
+
}
|
|
3461
4063
|
function normalizedCandidateValues(program, normalizedScopeTags) {
|
|
3462
4064
|
return [
|
|
3463
4065
|
stringField(program, "id"),
|
|
@@ -3504,9 +4106,18 @@ function programSearchText(programId) {
|
|
|
3504
4106
|
const lastPathSegment = handle.split("/").filter(Boolean).at(-1) ?? handle;
|
|
3505
4107
|
return lastPathSegment.length >= 2 ? lastPathSegment : handle;
|
|
3506
4108
|
}
|
|
4109
|
+
function programHandleText(programId) {
|
|
4110
|
+
const colonIndex = programId.indexOf(":");
|
|
4111
|
+
return colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
4112
|
+
}
|
|
4113
|
+
function programPlatformText(programId) {
|
|
4114
|
+
const colonIndex = programId.indexOf(":");
|
|
4115
|
+
return colonIndex >= 0 ? programId.slice(0, colonIndex) : "";
|
|
4116
|
+
}
|
|
3507
4117
|
function normalizeFindProgramsInput(input) {
|
|
3508
4118
|
const warnings = [];
|
|
3509
4119
|
const normalized = { ...input };
|
|
4120
|
+
normalized.vdp_mode ??= "exclude";
|
|
3510
4121
|
normalized.max_pages = clampLimit("max_pages", normalized.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
3511
4122
|
normalized.max_results = clampLimit("max_results", normalized.max_results, MAX_FIND_PROGRAM_RESULTS, warnings);
|
|
3512
4123
|
normalized.target_sample_programs = clampLimit("target_sample_programs", normalized.target_sample_programs, MAX_FIND_TARGET_SAMPLE_PROGRAMS, warnings);
|
|
@@ -3637,6 +4248,55 @@ async function collectPrograms(client, options) {
|
|
|
3637
4248
|
totalPagesBySource
|
|
3638
4249
|
};
|
|
3639
4250
|
}
|
|
4251
|
+
async function collectProgramsForResolution(client, options) {
|
|
4252
|
+
const collections = [];
|
|
4253
|
+
const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
|
|
4254
|
+
for (const search of queries) {
|
|
4255
|
+
if (options.budget.remaining <= 0) {
|
|
4256
|
+
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all program name search variants could be checked.");
|
|
4257
|
+
break;
|
|
4258
|
+
}
|
|
4259
|
+
collections.push(await collectPrograms(client, {
|
|
4260
|
+
search,
|
|
4261
|
+
platforms: options.platforms,
|
|
4262
|
+
tags: options.tags,
|
|
4263
|
+
updated_since: undefined,
|
|
4264
|
+
opportunity_levels: options.opportunity_levels,
|
|
4265
|
+
max_pages: options.max_pages,
|
|
4266
|
+
budget: options.budget,
|
|
4267
|
+
warnings: options.warnings
|
|
4268
|
+
}));
|
|
4269
|
+
}
|
|
4270
|
+
if (collections.length === 0) {
|
|
4271
|
+
return {
|
|
4272
|
+
programs: [],
|
|
4273
|
+
sourceRequests: [],
|
|
4274
|
+
requestsScanned: 0,
|
|
4275
|
+
programsScanned: 0,
|
|
4276
|
+
totalPagesBySource: {}
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
return mergeProgramCollections(collections);
|
|
4280
|
+
}
|
|
4281
|
+
function mergeProgramCollections(collections) {
|
|
4282
|
+
const programs = new Map();
|
|
4283
|
+
const totalPagesBySource = {};
|
|
4284
|
+
for (const collection of collections) {
|
|
4285
|
+
for (const program of collection.programs) {
|
|
4286
|
+
programs.set(programKey(program, programs.size), program);
|
|
4287
|
+
}
|
|
4288
|
+
for (const [sourceName, totalPages] of Object.entries(collection.totalPagesBySource)) {
|
|
4289
|
+
totalPagesBySource[sourceName] = totalPages;
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
return {
|
|
4293
|
+
programs: [...programs.values()],
|
|
4294
|
+
sourceRequests: collections.flatMap((collection) => collection.sourceRequests),
|
|
4295
|
+
requestsScanned: collections.reduce((total, collection) => total + collection.requestsScanned, 0),
|
|
4296
|
+
programsScanned: programs.size,
|
|
4297
|
+
totalPagesBySource
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
3640
4300
|
async function collectProgramSources(client, sources, options) {
|
|
3641
4301
|
const pages = [];
|
|
3642
4302
|
await mapWithConcurrency(sources.map((source, sourceIndex) => ({ source, sourceIndex })), PROGRAM_COLLECTION_CONCURRENCY, async ({ source, sourceIndex }) => {
|
|
@@ -3681,6 +4341,7 @@ async function fetchProgramPage(client, source, sourceIndex, page, options) {
|
|
|
3681
4341
|
const runWithSlot = options.runWithProgramPageSlot ?? runImmediately;
|
|
3682
4342
|
return runWithSlot(async () => {
|
|
3683
4343
|
const query = toQuery({
|
|
4344
|
+
search: options.search,
|
|
3684
4345
|
platforms: options.platforms,
|
|
3685
4346
|
tags: options.tags,
|
|
3686
4347
|
updated_since: options.updated_since,
|
|
@@ -3696,14 +4357,16 @@ async function fetchProgramPage(client, source, sourceIndex, page, options) {
|
|
|
3696
4357
|
const data = readObject(api.data);
|
|
3697
4358
|
const meta = readObject(data?.meta);
|
|
3698
4359
|
const sourceName = source.kind === "opportunity" ? `opportunities:${source.level}` : "programs";
|
|
4360
|
+
const labeledSourceName = options.search ? `${sourceName}:search:${normalizeTag(options.search)}` : sourceName;
|
|
3699
4361
|
return {
|
|
3700
|
-
sourceName,
|
|
4362
|
+
sourceName: labeledSourceName,
|
|
3701
4363
|
sourceIndex,
|
|
3702
4364
|
page,
|
|
3703
4365
|
programs: readArray(data?.programs),
|
|
3704
4366
|
totalPages: readNumber(meta?.total_pages),
|
|
3705
4367
|
sourceRequest: stripUndefined({
|
|
3706
|
-
source:
|
|
4368
|
+
source: labeledSourceName,
|
|
4369
|
+
search: options.search,
|
|
3707
4370
|
request_id: api.requestId,
|
|
3708
4371
|
upstream_request_id: api.upstreamRequestId,
|
|
3709
4372
|
...apiSourceMetadata(api),
|
|
@@ -3826,13 +4489,7 @@ function programCandidateMatches(candidate, filters) {
|
|
|
3826
4489
|
if (filters.contest_like === true && !candidateLooksContestLike(candidate)) {
|
|
3827
4490
|
return false;
|
|
3828
4491
|
}
|
|
3829
|
-
if (filters.vdp_mode
|
|
3830
|
-
return false;
|
|
3831
|
-
}
|
|
3832
|
-
if (filters.vdp_mode === "only_likely" && !candidateLooksLikeVdp(candidate)) {
|
|
3833
|
-
return false;
|
|
3834
|
-
}
|
|
3835
|
-
if (filters.vdp_mode === "only_known_no_bounty" && !candidateHasKnownNoBounty(candidate)) {
|
|
4492
|
+
if (!candidateMatchesVdpMode(candidate, filters.vdp_mode ?? "exclude")) {
|
|
3836
4493
|
return false;
|
|
3837
4494
|
}
|
|
3838
4495
|
if (filters.fresh_launch_days !== undefined) {
|
|
@@ -3893,6 +4550,18 @@ function compareProgramCandidates(left, right, sortBy) {
|
|
|
3893
4550
|
right.firstSeenTime - left.firstSeenTime ||
|
|
3894
4551
|
right.lastUpdatedTime - left.lastUpdatedTime);
|
|
3895
4552
|
}
|
|
4553
|
+
function candidateMatchesVdpMode(candidate, vdpMode) {
|
|
4554
|
+
if (vdpMode === "exclude") {
|
|
4555
|
+
return !candidateLooksLikeVdp(candidate);
|
|
4556
|
+
}
|
|
4557
|
+
if (vdpMode === "only_likely") {
|
|
4558
|
+
return candidateLooksLikeVdp(candidate);
|
|
4559
|
+
}
|
|
4560
|
+
if (vdpMode === "only_known_no_bounty") {
|
|
4561
|
+
return candidateHasKnownNoBounty(candidate);
|
|
4562
|
+
}
|
|
4563
|
+
return true;
|
|
4564
|
+
}
|
|
3896
4565
|
async function collectTargetSamples(client, candidates, input, budget) {
|
|
3897
4566
|
const samples = {};
|
|
3898
4567
|
const sampleCandidates = candidates.slice(0, input.target_sample_programs);
|
|
@@ -3984,6 +4653,9 @@ function addUniqueWarning(warnings, warning) {
|
|
|
3984
4653
|
warnings.push(warning);
|
|
3985
4654
|
}
|
|
3986
4655
|
}
|
|
4656
|
+
function isProgramNotFoundError(error) {
|
|
4657
|
+
return error instanceof BBRadarApiError && error.status === 404;
|
|
4658
|
+
}
|
|
3987
4659
|
function runImmediately(callback) {
|
|
3988
4660
|
return callback();
|
|
3989
4661
|
}
|
|
@@ -4092,7 +4764,7 @@ function registerPrompts(server) {
|
|
|
4092
4764
|
|
|
4093
4765
|
Find the best BBRadar program candidates.
|
|
4094
4766
|
- Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from this JSON array unless the user asked for a broader search: ${promptJson([args.opportunity_level ?? "elite"])}.
|
|
4095
|
-
- Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs for VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
|
|
4767
|
+
- Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs only for explicit VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
|
|
4096
4768
|
- Apply platform filters from this JSON string if provided: ${promptJson(args.platforms ?? "")}.
|
|
4097
4769
|
- Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
|
|
4098
4770
|
- Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
|
|
@@ -4102,16 +4774,16 @@ Find the best BBRadar program candidates.
|
|
|
4102
4774
|
- Stay passive-only; do not recommend scanning, probing, exploit attempts, or direct contact with targets.`));
|
|
4103
4775
|
server.registerPrompt("summarize_program_scope", {
|
|
4104
4776
|
title: "Summarize Program Scope",
|
|
4105
|
-
description: "Summarize
|
|
4777
|
+
description: "Summarize BBRadar target data for one program.",
|
|
4106
4778
|
argsSchema: {
|
|
4107
4779
|
program_id: programIdSchema
|
|
4108
4780
|
}
|
|
4109
|
-
}, (args) => promptResult(`Use get_program for the program_id in this JSON string: ${promptJson(args.program_id)}. Then use get_program_targets with include_out_of_scope=
|
|
4781
|
+
}, (args) => promptResult(`Use get_program for the program_id in this JSON string: ${promptJson(args.program_id)}. Then use get_program_targets with include_out_of_scope=false, include_ineligible=false, and limit=${MAX_TARGETS_PER_PROGRAM}. Only request out-of-scope or ineligible targets if the user explicitly asks for them.
|
|
4110
4782
|
|
|
4111
4783
|
Summarize the program scope:
|
|
4112
4784
|
- Program name, platform, reward range, public report count, first seen date, last updated date, and BBRadar URL.
|
|
4113
4785
|
- In-scope bounty-eligible target categories.
|
|
4114
|
-
- Out-of-scope or ineligible target categories.
|
|
4786
|
+
- Out-of-scope or ineligible target categories only if explicitly requested.
|
|
4115
4787
|
- Wildcards, high-severity target labels, and notable language or scope tags.
|
|
4116
4788
|
- Recent target update signals if present.
|
|
4117
4789
|
- Do not contact, scan, probe, or validate any listed target.`));
|