@bbradar/mcp 0.1.7 → 0.1.10
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 +597 -85
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BBRadarApiError } from "./bbradarClient.js";
|
|
5
|
-
import { stripUndefined } from "./json.js";
|
|
5
|
+
import { stableStringify, stripUndefined } from "./json.js";
|
|
6
6
|
import { FixedWindowRateLimiter } from "./rateLimit.js";
|
|
7
7
|
import { readArray, readBoolean, readNumber, readObject, readString, sanitizeJson, sanitizeChange, sanitizeProgram, sanitizeTarget, sanitizeTargetsForExport } from "./sanitize.js";
|
|
8
8
|
const SERVER_VERSION = "0.1.0";
|
|
@@ -29,8 +29,9 @@ 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 =
|
|
33
|
-
const STALE_PROGRAM_ID_RESOLVE_BUDGET =
|
|
32
|
+
const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
|
|
33
|
+
const STALE_PROGRAM_ID_RESOLVE_BUDGET = 1;
|
|
34
|
+
const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
|
|
34
35
|
const MAX_RESOLVE_SEARCH_QUERIES = 4;
|
|
35
36
|
const SDK_VERSION = "1.29.0";
|
|
36
37
|
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
@@ -275,10 +276,18 @@ export function createBbradarServer(client, config) {
|
|
|
275
276
|
annotations: readOnlyAnnotations
|
|
276
277
|
}, (args) => runTool("get_program", config, rateLimiter, async () => {
|
|
277
278
|
return runProgramIdTool(client, config, args.program_id, async (programId) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
try {
|
|
280
|
+
const api = await client.getProgram(programId);
|
|
281
|
+
return withApiMetadata(api, {
|
|
282
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (!isProgramNotFoundError(error)) {
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
return getProgramFromTargetsOnlyFallback(client, programId, error);
|
|
290
|
+
}
|
|
282
291
|
}, async (indexFallback) => ({
|
|
283
292
|
request_id: randomUUID(),
|
|
284
293
|
warnings: [`Returned program metadata from the BBRadar program index because the per-program detail endpoint returned 404 for ${indexFallback.resolvedProgramId}.`],
|
|
@@ -347,9 +356,40 @@ export function createBbradarServer(client, config) {
|
|
|
347
356
|
},
|
|
348
357
|
annotations: readOnlyAnnotations
|
|
349
358
|
}, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
|
|
359
|
+
server.registerTool("get_program_eligible_targets_by_name", {
|
|
360
|
+
title: "Get Eligible Targets By Program Name",
|
|
361
|
+
description: "Fast one-call path for user asks like 'find in-scope eligible targets for Aztec': resolve the program name, fetch targets, and return filtered in-scope bounty-eligible targets. Prefer this over separate resolve_program + get_program_targets calls when the user asks for a target list by name.",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
query: searchTextSchema,
|
|
364
|
+
platforms: stringListSchema,
|
|
365
|
+
tags: stringListSchema,
|
|
366
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
|
|
367
|
+
max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
368
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET),
|
|
369
|
+
include_out_of_scope: z.boolean().default(false),
|
|
370
|
+
include_ineligible: z.boolean().default(false),
|
|
371
|
+
strict_scope_filter: z.boolean().default(true),
|
|
372
|
+
offset: z.number().int().min(0).default(0),
|
|
373
|
+
limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
374
|
+
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
375
|
+
output_mode: programListModeSchema.default("compact")
|
|
376
|
+
},
|
|
377
|
+
outputSchema: {
|
|
378
|
+
...apiEnvelopeOutputShape,
|
|
379
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
380
|
+
resolved_program: jsonRecordSchema.optional(),
|
|
381
|
+
program_id: z.string().optional(),
|
|
382
|
+
targets: z.array(z.unknown()).optional(),
|
|
383
|
+
warnings: z.array(z.string()).optional(),
|
|
384
|
+
upstream_budget: jsonRecordSchema.optional(),
|
|
385
|
+
ranking_scope: jsonRecordSchema.optional(),
|
|
386
|
+
meta: jsonRecordSchema.optional()
|
|
387
|
+
},
|
|
388
|
+
annotations: readOnlyAnnotations
|
|
389
|
+
}, (args) => runTool("get_program_eligible_targets_by_name", config, rateLimiter, async () => getProgramEligibleTargetsByName(client, config, args)));
|
|
350
390
|
server.registerTool("get_program_targets", {
|
|
351
391
|
title: "Get Program Targets",
|
|
352
|
-
description: `Get active targets for one program. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
|
|
392
|
+
description: `Get active targets for one known program_id. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, prefer get_program_eligible_targets_by_name. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
|
|
353
393
|
inputSchema: {
|
|
354
394
|
program_id: programIdSchema,
|
|
355
395
|
include_out_of_scope: z.boolean().default(false),
|
|
@@ -1488,7 +1528,20 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1488
1528
|
}));
|
|
1489
1529
|
});
|
|
1490
1530
|
}
|
|
1531
|
+
const programFallbackCache = new Map();
|
|
1532
|
+
const programTargetsFallbackCaches = new WeakMap();
|
|
1533
|
+
const resolveProgramCache = new Map();
|
|
1491
1534
|
async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
1535
|
+
const cachedFallback = getCachedProgramFallback(config, requestedProgramId);
|
|
1536
|
+
if (cachedFallback && !isProgramIndexFallback(cachedFallback)) {
|
|
1537
|
+
return {
|
|
1538
|
+
api: await client.getProgram(cachedFallback.resolvedProgramId),
|
|
1539
|
+
programId: cachedFallback.resolvedProgramId,
|
|
1540
|
+
sourceRequests: cachedFallback.sourceRequests,
|
|
1541
|
+
warnings: cachedFallback.warnings,
|
|
1542
|
+
fallback: cachedFallback
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1492
1545
|
try {
|
|
1493
1546
|
return {
|
|
1494
1547
|
api: await client.getProgram(requestedProgramId),
|
|
@@ -1505,6 +1558,7 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
|
1505
1558
|
if (!fallback) {
|
|
1506
1559
|
throw error;
|
|
1507
1560
|
}
|
|
1561
|
+
rememberProgramFallback(config, fallback);
|
|
1508
1562
|
return {
|
|
1509
1563
|
api: await client.getProgram(fallback.resolvedProgramId),
|
|
1510
1564
|
programId: fallback.resolvedProgramId,
|
|
@@ -1515,6 +1569,30 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
|
1515
1569
|
}
|
|
1516
1570
|
}
|
|
1517
1571
|
async function runProgramIdTool(client, config, requestedProgramId, callback, indexFallbackCallback) {
|
|
1572
|
+
const cachedFallback = getCachedProgramFallback(config, requestedProgramId);
|
|
1573
|
+
if (cachedFallback) {
|
|
1574
|
+
if (isProgramIndexFallback(cachedFallback) && indexFallbackCallback) {
|
|
1575
|
+
const payload = await indexFallbackCallback(cachedFallback);
|
|
1576
|
+
return withProgramIndexFallbackMetadata(payload, cachedFallback);
|
|
1577
|
+
}
|
|
1578
|
+
try {
|
|
1579
|
+
const payload = await callback(cachedFallback.resolvedProgramId);
|
|
1580
|
+
return withProgramIdFallbackMetadata(payload, cachedFallback);
|
|
1581
|
+
}
|
|
1582
|
+
catch (cachedFallbackError) {
|
|
1583
|
+
if (!isProgramNotFoundError(cachedFallbackError) || !indexFallbackCallback) {
|
|
1584
|
+
throw cachedFallbackError;
|
|
1585
|
+
}
|
|
1586
|
+
const indexFallback = createProgramIndexFallbackFromStaleResolution(cachedFallback, requestedProgramId, cachedFallbackError) ??
|
|
1587
|
+
(await resolveIndexedProgramId(client, config, cachedFallback.resolvedProgramId, cachedFallbackError));
|
|
1588
|
+
if (!indexFallback) {
|
|
1589
|
+
throw cachedFallbackError;
|
|
1590
|
+
}
|
|
1591
|
+
rememberProgramFallback(config, indexFallback);
|
|
1592
|
+
const payload = await indexFallbackCallback(indexFallback);
|
|
1593
|
+
return withProgramIndexFallbackMetadata(payload, indexFallback);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1518
1596
|
try {
|
|
1519
1597
|
return await callback(requestedProgramId);
|
|
1520
1598
|
}
|
|
@@ -1531,10 +1609,12 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
|
|
|
1531
1609
|
if (!indexFallback) {
|
|
1532
1610
|
throw error;
|
|
1533
1611
|
}
|
|
1612
|
+
rememberProgramFallback(config, indexFallback);
|
|
1534
1613
|
const payload = await indexFallbackCallback(indexFallback);
|
|
1535
1614
|
return withProgramIndexFallbackMetadata(payload, indexFallback);
|
|
1536
1615
|
}
|
|
1537
1616
|
try {
|
|
1617
|
+
rememberProgramFallback(config, fallback);
|
|
1538
1618
|
const payload = await callback(fallback.resolvedProgramId);
|
|
1539
1619
|
return withProgramIdFallbackMetadata(payload, fallback);
|
|
1540
1620
|
}
|
|
@@ -1542,21 +1622,107 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
|
|
|
1542
1622
|
if (!isProgramNotFoundError(fallbackError) || !indexFallbackCallback) {
|
|
1543
1623
|
throw fallbackError;
|
|
1544
1624
|
}
|
|
1545
|
-
const indexFallback =
|
|
1625
|
+
const indexFallback = createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, fallbackError) ??
|
|
1626
|
+
(await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError));
|
|
1546
1627
|
if (!indexFallback) {
|
|
1547
1628
|
throw fallbackError;
|
|
1548
1629
|
}
|
|
1549
|
-
const combinedFallback =
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1630
|
+
const combinedFallback = indexFallback.requestedProgramId === requestedProgramId
|
|
1631
|
+
? indexFallback
|
|
1632
|
+
: {
|
|
1633
|
+
...indexFallback,
|
|
1634
|
+
requestedProgramId,
|
|
1635
|
+
sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
|
|
1636
|
+
warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
|
|
1637
|
+
};
|
|
1638
|
+
rememberProgramFallback(config, combinedFallback);
|
|
1555
1639
|
const payload = await indexFallbackCallback(combinedFallback);
|
|
1556
1640
|
return withProgramIndexFallbackMetadata(payload, combinedFallback);
|
|
1557
1641
|
}
|
|
1558
1642
|
}
|
|
1559
1643
|
}
|
|
1644
|
+
function getCachedProgramFallback(config, requestedProgramId) {
|
|
1645
|
+
if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
|
|
1646
|
+
return undefined;
|
|
1647
|
+
}
|
|
1648
|
+
const key = programFallbackCacheKey(config, requestedProgramId);
|
|
1649
|
+
const cached = programFallbackCache.get(key);
|
|
1650
|
+
if (!cached) {
|
|
1651
|
+
return undefined;
|
|
1652
|
+
}
|
|
1653
|
+
if (cached.expiresAt <= Date.now()) {
|
|
1654
|
+
programFallbackCache.delete(key);
|
|
1655
|
+
return undefined;
|
|
1656
|
+
}
|
|
1657
|
+
return cloneProgramFallbackForRequest(cached.fallback, requestedProgramId, true);
|
|
1658
|
+
}
|
|
1659
|
+
function rememberProgramFallback(config, fallback) {
|
|
1660
|
+
if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
pruneProgramFallbackCache();
|
|
1664
|
+
const key = programFallbackCacheKey(config, fallback.requestedProgramId);
|
|
1665
|
+
programFallbackCache.set(key, {
|
|
1666
|
+
expiresAt: Date.now() + config.cacheTtlMs,
|
|
1667
|
+
fallback: cloneProgramFallbackForRequest(fallback, fallback.requestedProgramId, false)
|
|
1668
|
+
});
|
|
1669
|
+
if (programFallbackCache.size > PROGRAM_FALLBACK_CACHE_MAX_ENTRIES) {
|
|
1670
|
+
const oldestKey = programFallbackCache.keys().next().value;
|
|
1671
|
+
if (oldestKey) {
|
|
1672
|
+
programFallbackCache.delete(oldestKey);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function pruneProgramFallbackCache() {
|
|
1677
|
+
const now = Date.now();
|
|
1678
|
+
for (const [key, cached] of programFallbackCache) {
|
|
1679
|
+
if (cached.expiresAt <= now) {
|
|
1680
|
+
programFallbackCache.delete(key);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function programFallbackCacheKey(config, requestedProgramId) {
|
|
1685
|
+
return `${config.apiBaseUrl}\n${normalizeTag(requestedProgramId)}`;
|
|
1686
|
+
}
|
|
1687
|
+
function cloneProgramFallbackForRequest(fallback, requestedProgramId, fromCache) {
|
|
1688
|
+
const warnings = fromCache
|
|
1689
|
+
? uniqueStrings([...fallback.warnings, `Reused cached program_id resolution for ${requestedProgramId}.`])
|
|
1690
|
+
: [...fallback.warnings];
|
|
1691
|
+
const cloned = {
|
|
1692
|
+
...fallback,
|
|
1693
|
+
requestedProgramId,
|
|
1694
|
+
warnings,
|
|
1695
|
+
sourceRequests: [...fallback.sourceRequests],
|
|
1696
|
+
resolution: { ...fallback.resolution }
|
|
1697
|
+
};
|
|
1698
|
+
if (isProgramIndexFallback(fallback)) {
|
|
1699
|
+
const indexClone = {
|
|
1700
|
+
...cloned,
|
|
1701
|
+
program: { ...fallback.program }
|
|
1702
|
+
};
|
|
1703
|
+
return indexClone;
|
|
1704
|
+
}
|
|
1705
|
+
return cloned;
|
|
1706
|
+
}
|
|
1707
|
+
function isProgramIndexFallback(fallback) {
|
|
1708
|
+
return readObject(fallback.program) !== undefined;
|
|
1709
|
+
}
|
|
1710
|
+
function createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, detailError) {
|
|
1711
|
+
const match = selectProgramIndexFallbackMatch(fallback.resolvedProgramId, fallback.resolution);
|
|
1712
|
+
if (!match) {
|
|
1713
|
+
return undefined;
|
|
1714
|
+
}
|
|
1715
|
+
const detailApiError = detailError instanceof BBRadarApiError ? detailError : undefined;
|
|
1716
|
+
const indexWarning = `program_id ${fallback.resolvedProgramId} exists in the BBRadar program index, but the per-program detail endpoint returned 404.`;
|
|
1717
|
+
return {
|
|
1718
|
+
...fallback,
|
|
1719
|
+
requestedProgramId,
|
|
1720
|
+
staleRequestId: fallback.staleRequestId ?? detailApiError?.requestId,
|
|
1721
|
+
staleUpstreamRequestId: fallback.staleUpstreamRequestId ?? detailApiError?.upstreamRequestId,
|
|
1722
|
+
warnings: uniqueStrings([...fallback.warnings, indexWarning]),
|
|
1723
|
+
program: match.program
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1560
1726
|
async function resolveIndexedProgramId(client, config, requestedProgramId, staleError) {
|
|
1561
1727
|
const query = programSearchText(requestedProgramId);
|
|
1562
1728
|
if (query.length < 2) {
|
|
@@ -1747,6 +1913,10 @@ function withProgramIndexFallbackMetadata(payload, fallback) {
|
|
|
1747
1913
|
};
|
|
1748
1914
|
}
|
|
1749
1915
|
async function resolveProgram(client, config, input) {
|
|
1916
|
+
const cached = getCachedResolveProgram(config, input);
|
|
1917
|
+
if (cached) {
|
|
1918
|
+
return cached;
|
|
1919
|
+
}
|
|
1750
1920
|
const warnings = [];
|
|
1751
1921
|
const budget = {
|
|
1752
1922
|
initial: input.upstream_request_budget,
|
|
@@ -1756,6 +1926,8 @@ async function resolveProgram(client, config, input) {
|
|
|
1756
1926
|
const searchQueries = programResolutionSearchQueries(input.query);
|
|
1757
1927
|
const collected = await collectProgramsForResolution(client, {
|
|
1758
1928
|
searchQueries,
|
|
1929
|
+
isHighConfidenceMatch: (programs) => hasHighConfidenceProgramResolutionMatch(programs, config.webBaseUrl, input.query),
|
|
1930
|
+
scorePrograms: (programs) => bestProgramResolutionScore(programs, config.webBaseUrl, input.query),
|
|
1759
1931
|
platforms: input.platforms,
|
|
1760
1932
|
tags: input.tags,
|
|
1761
1933
|
opportunity_levels: input.opportunity_levels,
|
|
@@ -1773,7 +1945,7 @@ async function resolveProgram(client, config, input) {
|
|
|
1773
1945
|
.sort((left, right) => right.match.score - left.match.score || compareProgramCandidates(left.candidate, right.candidate, "best_hunt_value"))
|
|
1774
1946
|
.slice(0, input.max_results);
|
|
1775
1947
|
const programs = matches.map((entry) => formatProgram(entry.candidate.program, input.output_mode));
|
|
1776
|
-
|
|
1948
|
+
const payload = stripUndefined({
|
|
1777
1949
|
request_id: randomUUID(),
|
|
1778
1950
|
source_requests: collected.sourceRequests,
|
|
1779
1951
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
@@ -1807,6 +1979,75 @@ async function resolveProgram(client, config, input) {
|
|
|
1807
1979
|
upstream_requests_scanned: collected.requestsScanned
|
|
1808
1980
|
}
|
|
1809
1981
|
});
|
|
1982
|
+
rememberResolveProgram(config, input, payload);
|
|
1983
|
+
return payload;
|
|
1984
|
+
}
|
|
1985
|
+
function getCachedResolveProgram(config, input) {
|
|
1986
|
+
if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
|
|
1987
|
+
return undefined;
|
|
1988
|
+
}
|
|
1989
|
+
const key = resolveProgramCacheKey(config, input);
|
|
1990
|
+
const cached = resolveProgramCache.get(key);
|
|
1991
|
+
if (!cached) {
|
|
1992
|
+
return undefined;
|
|
1993
|
+
}
|
|
1994
|
+
if (cached.expiresAt <= Date.now()) {
|
|
1995
|
+
resolveProgramCache.delete(key);
|
|
1996
|
+
return undefined;
|
|
1997
|
+
}
|
|
1998
|
+
const sourceRequests = readArray(cached.payload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1999
|
+
const warnings = readArray(cached.payload.warnings).filter((warning) => typeof warning === "string");
|
|
2000
|
+
return stripUndefined({
|
|
2001
|
+
...cached.payload,
|
|
2002
|
+
request_id: randomUUID(),
|
|
2003
|
+
cache: {
|
|
2004
|
+
hit: true,
|
|
2005
|
+
expires_at: new Date(cached.expiresAt).toISOString()
|
|
2006
|
+
},
|
|
2007
|
+
source_requests: sourceRequests.map((request) => ({
|
|
2008
|
+
...request,
|
|
2009
|
+
cache_hit: true,
|
|
2010
|
+
cache_expires_at: new Date(cached.expiresAt).toISOString()
|
|
2011
|
+
})),
|
|
2012
|
+
warnings: uniqueStrings([...warnings, `Reused cached program resolution for ${input.query}.`])
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
function rememberResolveProgram(config, input, payload) {
|
|
2016
|
+
if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
pruneResolveProgramCache();
|
|
2020
|
+
resolveProgramCache.set(resolveProgramCacheKey(config, input), {
|
|
2021
|
+
expiresAt: Date.now() + config.cacheTtlMs,
|
|
2022
|
+
payload: { ...payload }
|
|
2023
|
+
});
|
|
2024
|
+
if (resolveProgramCache.size > PROGRAM_FALLBACK_CACHE_MAX_ENTRIES) {
|
|
2025
|
+
const oldestKey = resolveProgramCache.keys().next().value;
|
|
2026
|
+
if (oldestKey) {
|
|
2027
|
+
resolveProgramCache.delete(oldestKey);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
function pruneResolveProgramCache() {
|
|
2032
|
+
const now = Date.now();
|
|
2033
|
+
for (const [key, cached] of resolveProgramCache) {
|
|
2034
|
+
if (cached.expiresAt <= now) {
|
|
2035
|
+
resolveProgramCache.delete(key);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
function resolveProgramCacheKey(config, input) {
|
|
2040
|
+
return stableStringify({
|
|
2041
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
2042
|
+
query: normalizeTag(input.query),
|
|
2043
|
+
platforms: input.platforms.map(normalizeTag).sort(),
|
|
2044
|
+
tags: input.tags.map(normalizeTag).sort(),
|
|
2045
|
+
opportunity_levels: [...input.opportunity_levels].sort(),
|
|
2046
|
+
max_pages: input.max_pages,
|
|
2047
|
+
max_results: input.max_results,
|
|
2048
|
+
output_mode: input.output_mode,
|
|
2049
|
+
upstream_request_budget: input.upstream_request_budget
|
|
2050
|
+
});
|
|
1810
2051
|
}
|
|
1811
2052
|
async function runProgramNameAction(client, config, input) {
|
|
1812
2053
|
const resolution = await resolveProgram(client, config, {
|
|
@@ -1851,6 +2092,76 @@ async function runProgramNameAction(client, config, input) {
|
|
|
1851
2092
|
ranking_scope: readObject(resolution.ranking_scope)
|
|
1852
2093
|
});
|
|
1853
2094
|
}
|
|
2095
|
+
async function getProgramEligibleTargetsByName(client, config, input) {
|
|
2096
|
+
const resolution = await resolveProgram(client, config, {
|
|
2097
|
+
query: input.query,
|
|
2098
|
+
platforms: input.platforms,
|
|
2099
|
+
tags: input.tags,
|
|
2100
|
+
opportunity_levels: input.opportunity_levels,
|
|
2101
|
+
max_pages: input.max_resolve_pages,
|
|
2102
|
+
max_results: 1,
|
|
2103
|
+
output_mode: input.output_mode,
|
|
2104
|
+
upstream_request_budget: input.upstream_request_budget
|
|
2105
|
+
});
|
|
2106
|
+
const bestMatch = readObject(readArray(resolution.matches)[0]);
|
|
2107
|
+
const programId = stringField(bestMatch, "program_id");
|
|
2108
|
+
const resolvedProgram = readObject(bestMatch?.program);
|
|
2109
|
+
const resolutionSourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
2110
|
+
const resolutionWarnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
2111
|
+
if (!programId) {
|
|
2112
|
+
return stripUndefined({
|
|
2113
|
+
request_id: randomUUID(),
|
|
2114
|
+
source_requests: resolutionSourceRequests,
|
|
2115
|
+
warnings: uniqueStrings([...resolutionWarnings, "No matching program_id was resolved for the requested program name."]),
|
|
2116
|
+
resolved_program: undefined,
|
|
2117
|
+
targets: [],
|
|
2118
|
+
upstream_budget: readObject(resolution.upstream_budget),
|
|
2119
|
+
ranking_scope: readObject(resolution.ranking_scope),
|
|
2120
|
+
meta: {
|
|
2121
|
+
found: false,
|
|
2122
|
+
returned: 0,
|
|
2123
|
+
query: input.query
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
const targetsPayload = await runProgramIdTool(client, config, programId, (resolvedProgramId) => getProgramTargetsPayload(client, {
|
|
2128
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2129
|
+
include_ineligible: input.include_ineligible,
|
|
2130
|
+
strict_scope_filter: input.strict_scope_filter,
|
|
2131
|
+
offset: input.offset,
|
|
2132
|
+
limit: input.limit,
|
|
2133
|
+
output_mode: input.target_list_mode
|
|
2134
|
+
}, resolvedProgramId), (indexFallback) => getProgramTargetsExportFallbackPayload(client, {
|
|
2135
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2136
|
+
include_ineligible: input.include_ineligible,
|
|
2137
|
+
strict_scope_filter: input.strict_scope_filter,
|
|
2138
|
+
offset: input.offset,
|
|
2139
|
+
limit: input.limit,
|
|
2140
|
+
output_mode: input.target_list_mode
|
|
2141
|
+
}, indexFallback.resolvedProgramId));
|
|
2142
|
+
const targetSourceRequests = readArray(targetsPayload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
2143
|
+
const targetWarnings = readArray(targetsPayload.warnings).filter((warning) => typeof warning === "string");
|
|
2144
|
+
const targetMeta = readObject(targetsPayload.meta);
|
|
2145
|
+
return stripUndefined({
|
|
2146
|
+
request_id: randomUUID(),
|
|
2147
|
+
source_requests: [...resolutionSourceRequests, ...targetSourceRequests],
|
|
2148
|
+
warnings: uniqueStrings([...resolutionWarnings, ...targetWarnings]),
|
|
2149
|
+
resolved_program: resolvedProgram,
|
|
2150
|
+
program_id: stringField(targetsPayload, "program_id") ?? programId,
|
|
2151
|
+
targets: readArray(targetsPayload.targets),
|
|
2152
|
+
upstream_budget: readObject(resolution.upstream_budget),
|
|
2153
|
+
ranking_scope: readObject(resolution.ranking_scope),
|
|
2154
|
+
program_id_resolution: readObject(targetsPayload.program_id_resolution),
|
|
2155
|
+
meta: stripUndefined({
|
|
2156
|
+
...(targetMeta ?? {}),
|
|
2157
|
+
found: true,
|
|
2158
|
+
query: input.query,
|
|
2159
|
+
target_filter: "in_scope_and_eligible_by_default",
|
|
2160
|
+
resolve_request_id: stringField(resolution, "request_id"),
|
|
2161
|
+
targets_request_id: stringField(targetsPayload, "request_id")
|
|
2162
|
+
})
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
1854
2165
|
async function runResolvedProgramAction(client, config, programId, input) {
|
|
1855
2166
|
if (input.action === "scope_delta") {
|
|
1856
2167
|
return getProgramScopeDelta(client, config, {
|
|
@@ -2862,6 +3173,10 @@ async function getProgramTargetsExportFallbackPayload(client, input, programId)
|
|
|
2862
3173
|
});
|
|
2863
3174
|
}
|
|
2864
3175
|
async function fetchProgramTargetsWithEndpointFallback(client, programId) {
|
|
3176
|
+
const cachedLookup = getCachedProgramTargetsFallback(client, programId);
|
|
3177
|
+
if (cachedLookup) {
|
|
3178
|
+
return cachedLookup;
|
|
3179
|
+
}
|
|
2865
3180
|
try {
|
|
2866
3181
|
const api = await client.getProgramTargets(programId);
|
|
2867
3182
|
const data = readObject(api.data);
|
|
@@ -2894,9 +3209,86 @@ async function fetchProgramTargetsWithEndpointFallback(client, programId) {
|
|
|
2894
3209
|
if (!isProgramNotFoundError(error)) {
|
|
2895
3210
|
throw error;
|
|
2896
3211
|
}
|
|
2897
|
-
|
|
3212
|
+
const lookup = await fetchProgramTargetsFromExportFallback(client, programId);
|
|
3213
|
+
rememberProgramTargetsFallback(client, programId, lookup);
|
|
3214
|
+
return lookup;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
function getCachedProgramTargetsFallback(client, programId) {
|
|
3218
|
+
const diagnostics = client.diagnostics();
|
|
3219
|
+
if (!diagnostics.response_cache.enabled) {
|
|
3220
|
+
return undefined;
|
|
3221
|
+
}
|
|
3222
|
+
const cache = programTargetsFallbackCaches.get(client);
|
|
3223
|
+
const key = programTargetsFallbackCacheKey(programId);
|
|
3224
|
+
const cached = cache?.get(key);
|
|
3225
|
+
if (!cached) {
|
|
3226
|
+
return undefined;
|
|
3227
|
+
}
|
|
3228
|
+
if (cached.expiresAt <= Date.now()) {
|
|
3229
|
+
cache?.delete(key);
|
|
3230
|
+
return undefined;
|
|
3231
|
+
}
|
|
3232
|
+
return {
|
|
3233
|
+
...cached.lookup,
|
|
3234
|
+
cache: readObject(stripUndefined({
|
|
3235
|
+
...(cached.lookup.cache ?? {}),
|
|
3236
|
+
hit: true,
|
|
3237
|
+
expires_at: new Date(cached.expiresAt).toISOString()
|
|
3238
|
+
})),
|
|
3239
|
+
sourceRequests: cached.lookup.sourceRequests.map((request) => ({
|
|
3240
|
+
...request,
|
|
3241
|
+
cache_hit: true,
|
|
3242
|
+
cache_expires_at: new Date(cached.expiresAt).toISOString()
|
|
3243
|
+
})),
|
|
3244
|
+
warnings: uniqueStrings([...cached.lookup.warnings, `Reused cached target fallback for ${programId}.`])
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
function rememberProgramTargetsFallback(client, programId, lookup) {
|
|
3248
|
+
const diagnostics = client.diagnostics();
|
|
3249
|
+
if (!diagnostics.response_cache.enabled || lookup.unavailable) {
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
const cache = programTargetsFallbackCacheForClient(client);
|
|
3253
|
+
pruneProgramTargetsFallbackCache(cache);
|
|
3254
|
+
const ttlMs = cacheTtlMsFromDiagnostics(diagnostics.response_cache);
|
|
3255
|
+
if (ttlMs <= 0) {
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
cache.set(programTargetsFallbackCacheKey(programId), {
|
|
3259
|
+
expiresAt: Date.now() + ttlMs,
|
|
3260
|
+
lookup
|
|
3261
|
+
});
|
|
3262
|
+
if (cache.size > PROGRAM_FALLBACK_CACHE_MAX_ENTRIES) {
|
|
3263
|
+
const oldestKey = cache.keys().next().value;
|
|
3264
|
+
if (oldestKey) {
|
|
3265
|
+
cache.delete(oldestKey);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
function programTargetsFallbackCacheForClient(client) {
|
|
3270
|
+
const existing = programTargetsFallbackCaches.get(client);
|
|
3271
|
+
if (existing) {
|
|
3272
|
+
return existing;
|
|
3273
|
+
}
|
|
3274
|
+
const cache = new Map();
|
|
3275
|
+
programTargetsFallbackCaches.set(client, cache);
|
|
3276
|
+
return cache;
|
|
3277
|
+
}
|
|
3278
|
+
function pruneProgramTargetsFallbackCache(cache) {
|
|
3279
|
+
const now = Date.now();
|
|
3280
|
+
for (const [key, cached] of cache) {
|
|
3281
|
+
if (cached.expiresAt <= now) {
|
|
3282
|
+
cache.delete(key);
|
|
3283
|
+
}
|
|
2898
3284
|
}
|
|
2899
3285
|
}
|
|
3286
|
+
function programTargetsFallbackCacheKey(programId) {
|
|
3287
|
+
return normalizeTag(programId);
|
|
3288
|
+
}
|
|
3289
|
+
function cacheTtlMsFromDiagnostics(cache) {
|
|
3290
|
+
return cache.enabled ? cache.ttl_ms : 0;
|
|
3291
|
+
}
|
|
2900
3292
|
async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
2901
3293
|
try {
|
|
2902
3294
|
const api = await client.exportTargets({
|
|
@@ -2952,6 +3344,93 @@ async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
|
2952
3344
|
};
|
|
2953
3345
|
}
|
|
2954
3346
|
}
|
|
3347
|
+
async function getProgramFromTargetsOnlyFallback(client, programId, programError) {
|
|
3348
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
3349
|
+
const programApiError = programError instanceof BBRadarApiError ? programError : undefined;
|
|
3350
|
+
return stripUndefined({
|
|
3351
|
+
request_id: targetsApi.requestId,
|
|
3352
|
+
fetched_at: targetsApi.fetchedAt,
|
|
3353
|
+
cache: readObject(stripUndefined({
|
|
3354
|
+
hit: targetsApi.cached,
|
|
3355
|
+
coalesced_live_request: targetsApi.coalesced,
|
|
3356
|
+
expires_at: targetsApi.cacheExpiresAt
|
|
3357
|
+
})),
|
|
3358
|
+
source_requests: [
|
|
3359
|
+
stripUndefined({
|
|
3360
|
+
source: "program",
|
|
3361
|
+
program_id: programId,
|
|
3362
|
+
failed: true,
|
|
3363
|
+
request_id: programApiError?.requestId,
|
|
3364
|
+
upstream_request_id: programApiError?.upstreamRequestId,
|
|
3365
|
+
status: programApiError?.status
|
|
3366
|
+
}),
|
|
3367
|
+
{
|
|
3368
|
+
source: "program_targets",
|
|
3369
|
+
program_id: programId,
|
|
3370
|
+
request_id: targetsApi.requestId,
|
|
3371
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3372
|
+
...apiSourceMetadata(targetsApi)
|
|
3373
|
+
}
|
|
3374
|
+
],
|
|
3375
|
+
warnings: [`Returned minimal program metadata because the per-program detail endpoint returned 404 for ${programId}, but the targets endpoint is available.`],
|
|
3376
|
+
program: addProgramResourceLinks({ id: programId }),
|
|
3377
|
+
meta: {
|
|
3378
|
+
detail_source: "program_targets",
|
|
3379
|
+
detail_endpoint_unavailable: true
|
|
3380
|
+
}
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
async function fetchProgramDetailsAndTargets(client, config, programId) {
|
|
3384
|
+
const [programResult, targetsResult] = await Promise.allSettled([client.getProgram(programId), client.getProgramTargets(programId)]);
|
|
3385
|
+
if (targetsResult.status === "rejected") {
|
|
3386
|
+
throw targetsResult.reason;
|
|
3387
|
+
}
|
|
3388
|
+
const targetsApi = targetsResult.value;
|
|
3389
|
+
const targetSourceRequest = {
|
|
3390
|
+
source: "program_targets",
|
|
3391
|
+
request_id: targetsApi.requestId,
|
|
3392
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3393
|
+
...apiSourceMetadata(targetsApi)
|
|
3394
|
+
};
|
|
3395
|
+
if (programResult.status === "fulfilled") {
|
|
3396
|
+
return {
|
|
3397
|
+
program: addProgramResourceLinks(sanitizeProgram(programResult.value.data, config.webBaseUrl)),
|
|
3398
|
+
targetsApi,
|
|
3399
|
+
sourceRequests: [
|
|
3400
|
+
{
|
|
3401
|
+
source: "program",
|
|
3402
|
+
request_id: programResult.value.requestId,
|
|
3403
|
+
upstream_request_id: programResult.value.upstreamRequestId,
|
|
3404
|
+
...apiSourceMetadata(programResult.value)
|
|
3405
|
+
},
|
|
3406
|
+
targetSourceRequest
|
|
3407
|
+
],
|
|
3408
|
+
warnings: [],
|
|
3409
|
+
programDetailUnavailable: false
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
if (!isProgramNotFoundError(programResult.reason)) {
|
|
3413
|
+
throw programResult.reason;
|
|
3414
|
+
}
|
|
3415
|
+
const programApiError = programResult.reason instanceof BBRadarApiError ? programResult.reason : undefined;
|
|
3416
|
+
return {
|
|
3417
|
+
program: addProgramResourceLinks({ id: programId }),
|
|
3418
|
+
targetsApi,
|
|
3419
|
+
sourceRequests: [
|
|
3420
|
+
stripUndefined({
|
|
3421
|
+
source: "program",
|
|
3422
|
+
program_id: programId,
|
|
3423
|
+
failed: true,
|
|
3424
|
+
request_id: programApiError?.requestId,
|
|
3425
|
+
upstream_request_id: programApiError?.upstreamRequestId,
|
|
3426
|
+
status: programApiError?.status
|
|
3427
|
+
}),
|
|
3428
|
+
targetSourceRequest
|
|
3429
|
+
],
|
|
3430
|
+
warnings: [`Used targets endpoint with minimal program metadata because the per-program detail endpoint returned 404 for ${programId}.`],
|
|
3431
|
+
programDetailUnavailable: true
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
2955
3434
|
async function exportTargetsFromProgramTargetsFallback(client, config, exportStore, input, exportError) {
|
|
2956
3435
|
const warnings = ["Target export endpoint is unavailable; assembled a fallback export from per-program target endpoints."];
|
|
2957
3436
|
const sourceRequests = [
|
|
@@ -3075,8 +3554,8 @@ function readExportTargetRows(data) {
|
|
|
3075
3554
|
.find((rows) => rows.length > 0) ?? [];
|
|
3076
3555
|
}
|
|
3077
3556
|
async function getProgramScopeSummary(client, config, input) {
|
|
3078
|
-
const
|
|
3079
|
-
const targetsData = readObject(targetsApi.data);
|
|
3557
|
+
const lookup = await fetchProgramDetailsAndTargets(client, config, input.program_id);
|
|
3558
|
+
const targetsData = readObject(lookup.targetsApi.data);
|
|
3080
3559
|
const rawTargets = readArray(targetsData?.targets);
|
|
3081
3560
|
const targets = rawTargets
|
|
3082
3561
|
.map(sanitizeTarget)
|
|
@@ -3085,31 +3564,21 @@ async function getProgramScopeSummary(client, config, input) {
|
|
|
3085
3564
|
include_ineligible: input.include_ineligible,
|
|
3086
3565
|
strict_scope_filter: false
|
|
3087
3566
|
}));
|
|
3088
|
-
return {
|
|
3567
|
+
return stripUndefined({
|
|
3089
3568
|
request_id: randomUUID(),
|
|
3090
|
-
source_requests:
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
request_id: programApi.requestId,
|
|
3094
|
-
upstream_request_id: programApi.upstreamRequestId,
|
|
3095
|
-
...apiSourceMetadata(programApi)
|
|
3096
|
-
},
|
|
3097
|
-
{
|
|
3098
|
-
source: "program_targets",
|
|
3099
|
-
request_id: targetsApi.requestId,
|
|
3100
|
-
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3101
|
-
...apiSourceMetadata(targetsApi)
|
|
3102
|
-
}
|
|
3103
|
-
],
|
|
3104
|
-
program: formatProgram(addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl)), "compact"),
|
|
3569
|
+
source_requests: lookup.sourceRequests,
|
|
3570
|
+
warnings: lookup.warnings.length > 0 ? lookup.warnings : undefined,
|
|
3571
|
+
program: formatProgram(lookup.program, "compact"),
|
|
3105
3572
|
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
3106
3573
|
meta: {
|
|
3107
3574
|
total_active_targets: rawTargets.length,
|
|
3108
3575
|
total_after_filters: targets.length,
|
|
3109
3576
|
target_list_mode: input.target_list_mode,
|
|
3110
|
-
group_limit: input.group_limit
|
|
3577
|
+
group_limit: input.group_limit,
|
|
3578
|
+
target_source: "program_targets",
|
|
3579
|
+
detail_endpoint_unavailable: lookup.programDetailUnavailable || undefined
|
|
3111
3580
|
}
|
|
3112
|
-
};
|
|
3581
|
+
});
|
|
3113
3582
|
}
|
|
3114
3583
|
async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, input) {
|
|
3115
3584
|
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
@@ -3139,8 +3608,8 @@ async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, in
|
|
|
3139
3608
|
});
|
|
3140
3609
|
}
|
|
3141
3610
|
async function getProgramTargetBreakdown(client, config, input) {
|
|
3142
|
-
const
|
|
3143
|
-
const targetsData = readObject(targetsApi.data);
|
|
3611
|
+
const lookup = await fetchProgramDetailsAndTargets(client, config, input.program_id);
|
|
3612
|
+
const targetsData = readObject(lookup.targetsApi.data);
|
|
3144
3613
|
const rawTargets = readArray(targetsData?.targets);
|
|
3145
3614
|
const targets = rawTargets
|
|
3146
3615
|
.map(sanitizeTarget)
|
|
@@ -3150,23 +3619,11 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
3150
3619
|
strict_scope_filter: false
|
|
3151
3620
|
}));
|
|
3152
3621
|
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible);
|
|
3153
|
-
return {
|
|
3622
|
+
return stripUndefined({
|
|
3154
3623
|
request_id: randomUUID(),
|
|
3155
|
-
source_requests:
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
request_id: programApi.requestId,
|
|
3159
|
-
upstream_request_id: programApi.upstreamRequestId,
|
|
3160
|
-
...apiSourceMetadata(programApi)
|
|
3161
|
-
},
|
|
3162
|
-
{
|
|
3163
|
-
source: "program_targets",
|
|
3164
|
-
request_id: targetsApi.requestId,
|
|
3165
|
-
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3166
|
-
...apiSourceMetadata(targetsApi)
|
|
3167
|
-
}
|
|
3168
|
-
],
|
|
3169
|
-
program: formatProgram(addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl)), "compact"),
|
|
3624
|
+
source_requests: lookup.sourceRequests,
|
|
3625
|
+
warnings: lookup.warnings.length > 0 ? lookup.warnings : undefined,
|
|
3626
|
+
program: formatProgram(lookup.program, "compact"),
|
|
3170
3627
|
breakdown: {
|
|
3171
3628
|
target_counts: readObject(scope.target_counts),
|
|
3172
3629
|
by_target_type: countTargetValues(targets, (target) => stringField(target, "target_type")),
|
|
@@ -3180,9 +3637,11 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
3180
3637
|
total_active_targets: rawTargets.length,
|
|
3181
3638
|
total_after_filters: targets.length,
|
|
3182
3639
|
target_list_mode: input.target_list_mode,
|
|
3183
|
-
group_limit: input.group_limit
|
|
3640
|
+
group_limit: input.group_limit,
|
|
3641
|
+
target_source: "program_targets",
|
|
3642
|
+
detail_endpoint_unavailable: lookup.programDetailUnavailable || undefined
|
|
3184
3643
|
}
|
|
3185
|
-
};
|
|
3644
|
+
});
|
|
3186
3645
|
}
|
|
3187
3646
|
async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback, input) {
|
|
3188
3647
|
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
@@ -3632,14 +4091,13 @@ async function checkWatchlistNewTargets(client, config, input) {
|
|
|
3632
4091
|
});
|
|
3633
4092
|
}
|
|
3634
4093
|
async function getProgramBrief(client, config, input) {
|
|
3635
|
-
const
|
|
3636
|
-
const targetsPromise = client.getProgramTargets(input.program_id);
|
|
4094
|
+
const detailsAndTargetsPromise = fetchProgramDetailsAndTargets(client, config, input.program_id);
|
|
3637
4095
|
const changesPromise = input.include_recent_changes && input.recent_changes_limit > 0
|
|
3638
4096
|
? fetchProgramChanges(client, config, input.program_id, input.recent_changes_limit, true, input.include_ineligible, input.include_out_of_scope)
|
|
3639
4097
|
: Promise.resolve(undefined);
|
|
3640
|
-
const [
|
|
3641
|
-
const candidate = toProgramCandidate(
|
|
3642
|
-
const targetsData = readObject(targetsApi.data);
|
|
4098
|
+
const [lookup, changes] = await Promise.all([detailsAndTargetsPromise, changesPromise]);
|
|
4099
|
+
const candidate = toProgramCandidate(lookup.program, config.webBaseUrl);
|
|
4100
|
+
const targetsData = readObject(lookup.targetsApi.data);
|
|
3643
4101
|
const rawTargets = readArray(targetsData?.targets);
|
|
3644
4102
|
const targets = rawTargets
|
|
3645
4103
|
.map(sanitizeTarget)
|
|
@@ -3651,21 +4109,8 @@ async function getProgramBrief(client, config, input) {
|
|
|
3651
4109
|
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.target_sample_size, input.include_out_of_scope, input.include_ineligible);
|
|
3652
4110
|
return stripUndefined({
|
|
3653
4111
|
request_id: randomUUID(),
|
|
3654
|
-
source_requests: [
|
|
3655
|
-
|
|
3656
|
-
source: "program",
|
|
3657
|
-
request_id: programApi.requestId,
|
|
3658
|
-
upstream_request_id: programApi.upstreamRequestId,
|
|
3659
|
-
...apiSourceMetadata(programApi)
|
|
3660
|
-
},
|
|
3661
|
-
{
|
|
3662
|
-
source: "program_targets",
|
|
3663
|
-
request_id: targetsApi.requestId,
|
|
3664
|
-
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3665
|
-
...apiSourceMetadata(targetsApi)
|
|
3666
|
-
},
|
|
3667
|
-
...(changes?.sourceRequests ?? [])
|
|
3668
|
-
],
|
|
4112
|
+
source_requests: [...lookup.sourceRequests, ...(changes?.sourceRequests ?? [])],
|
|
4113
|
+
warnings: lookup.warnings.length > 0 ? lookup.warnings : undefined,
|
|
3669
4114
|
program: formatProgram(candidate.program, "compact"),
|
|
3670
4115
|
brief: {
|
|
3671
4116
|
rank_factors: rankFactors(candidate),
|
|
@@ -3681,7 +4126,9 @@ async function getProgramBrief(client, config, input) {
|
|
|
3681
4126
|
total_active_targets: rawTargets.length,
|
|
3682
4127
|
total_targets_after_filters: targets.length,
|
|
3683
4128
|
recent_changes_returned: changes?.changes.length ?? 0,
|
|
3684
|
-
target_list_mode: input.target_list_mode
|
|
4129
|
+
target_list_mode: input.target_list_mode,
|
|
4130
|
+
target_source: "program_targets",
|
|
4131
|
+
detail_endpoint_unavailable: lookup.programDetailUnavailable || undefined
|
|
3685
4132
|
}
|
|
3686
4133
|
});
|
|
3687
4134
|
}
|
|
@@ -4278,6 +4725,12 @@ function programResolutionMatch(candidate, query) {
|
|
|
4278
4725
|
reasons: [...reasons]
|
|
4279
4726
|
};
|
|
4280
4727
|
}
|
|
4728
|
+
function hasHighConfidenceProgramResolutionMatch(programs, webBaseUrl, query) {
|
|
4729
|
+
return programs.some((program) => programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score >= 85);
|
|
4730
|
+
}
|
|
4731
|
+
function bestProgramResolutionScore(programs, webBaseUrl, query) {
|
|
4732
|
+
return programs.reduce((best, program) => Math.max(best, programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score), 0);
|
|
4733
|
+
}
|
|
4281
4734
|
function programResolutionSearchQueries(query) {
|
|
4282
4735
|
return programResolutionQueryVariants(query).slice(0, MAX_RESOLVE_SEARCH_QUERIES);
|
|
4283
4736
|
}
|
|
@@ -4285,11 +4738,20 @@ function programResolutionQueryVariants(query) {
|
|
|
4285
4738
|
const normalized = normalizeTag(query);
|
|
4286
4739
|
const meaningfulTokens = [...meaningfulProgramQueryTokens(query)];
|
|
4287
4740
|
const meaningfulPhrase = meaningfulTokens.join(" ");
|
|
4741
|
+
const singleTokenNameVariants = meaningfulTokens.length === 1
|
|
4742
|
+
? [
|
|
4743
|
+
`${meaningfulTokens[0]} network`,
|
|
4744
|
+
`${meaningfulTokens[0]} protocol`,
|
|
4745
|
+
`${meaningfulTokens[0]} bug bounty`,
|
|
4746
|
+
`${meaningfulTokens[0]} bounty`
|
|
4747
|
+
]
|
|
4748
|
+
: [];
|
|
4288
4749
|
const values = [
|
|
4289
4750
|
normalized,
|
|
4290
4751
|
meaningfulPhrase,
|
|
4291
4752
|
meaningfulTokens.join("-"),
|
|
4292
|
-
...meaningfulTokens
|
|
4753
|
+
...meaningfulTokens,
|
|
4754
|
+
...singleTokenNameVariants
|
|
4293
4755
|
];
|
|
4294
4756
|
return uniqueStrings(values.filter((value) => value.length >= 2));
|
|
4295
4757
|
}
|
|
@@ -4504,22 +4966,48 @@ async function collectPrograms(client, options) {
|
|
|
4504
4966
|
}
|
|
4505
4967
|
async function collectProgramsForResolution(client, options) {
|
|
4506
4968
|
const collections = [];
|
|
4969
|
+
const queryCollections = [];
|
|
4507
4970
|
const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
|
|
4508
4971
|
for (const search of queries) {
|
|
4509
4972
|
if (options.budget.remaining <= 0) {
|
|
4510
4973
|
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all program name search variants could be checked.");
|
|
4511
4974
|
break;
|
|
4512
4975
|
}
|
|
4513
|
-
|
|
4976
|
+
const collection = await collectPrograms(client, {
|
|
4514
4977
|
search,
|
|
4515
4978
|
platforms: options.platforms,
|
|
4516
4979
|
tags: options.tags,
|
|
4517
4980
|
updated_since: undefined,
|
|
4518
4981
|
opportunity_levels: options.opportunity_levels,
|
|
4519
|
-
max_pages:
|
|
4982
|
+
max_pages: 1,
|
|
4520
4983
|
budget: options.budget,
|
|
4521
4984
|
warnings: options.warnings
|
|
4522
|
-
})
|
|
4985
|
+
});
|
|
4986
|
+
collections.push(collection);
|
|
4987
|
+
queryCollections.push({ search, collection });
|
|
4988
|
+
if (options.isHighConfidenceMatch?.(mergeProgramCollections(collections).programs)) {
|
|
4989
|
+
return mergeProgramCollections(collections);
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
if (options.max_pages > 1) {
|
|
4993
|
+
const search = bestResolutionDeepSearch(queryCollections, options.scorePrograms) ?? queries[0];
|
|
4994
|
+
if (options.budget.remaining <= 0) {
|
|
4995
|
+
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before more program name result pages could be checked.");
|
|
4996
|
+
}
|
|
4997
|
+
else if (search !== undefined || queries.length > 0) {
|
|
4998
|
+
const collection = await collectPrograms(client, {
|
|
4999
|
+
search,
|
|
5000
|
+
platforms: options.platforms,
|
|
5001
|
+
tags: options.tags,
|
|
5002
|
+
updated_since: undefined,
|
|
5003
|
+
opportunity_levels: options.opportunity_levels,
|
|
5004
|
+
max_pages: options.max_pages,
|
|
5005
|
+
start_page: 2,
|
|
5006
|
+
budget: options.budget,
|
|
5007
|
+
warnings: options.warnings
|
|
5008
|
+
});
|
|
5009
|
+
collections.push(collection);
|
|
5010
|
+
}
|
|
4523
5011
|
}
|
|
4524
5012
|
if (collections.length === 0) {
|
|
4525
5013
|
return {
|
|
@@ -4532,6 +5020,16 @@ async function collectProgramsForResolution(client, options) {
|
|
|
4532
5020
|
}
|
|
4533
5021
|
return mergeProgramCollections(collections);
|
|
4534
5022
|
}
|
|
5023
|
+
function bestResolutionDeepSearch(queryCollections, scorePrograms) {
|
|
5024
|
+
if (queryCollections.length === 0) {
|
|
5025
|
+
return undefined;
|
|
5026
|
+
}
|
|
5027
|
+
if (!scorePrograms) {
|
|
5028
|
+
return queryCollections[0]?.search;
|
|
5029
|
+
}
|
|
5030
|
+
return [...queryCollections]
|
|
5031
|
+
.sort((left, right) => scorePrograms(right.collection.programs) - scorePrograms(left.collection.programs))[0]?.search;
|
|
5032
|
+
}
|
|
4535
5033
|
function mergeProgramCollections(collections) {
|
|
4536
5034
|
const programs = new Map();
|
|
4537
5035
|
const totalPagesBySource = {};
|
|
@@ -4559,6 +5057,18 @@ async function collectProgramSources(client, sources, options) {
|
|
|
4559
5057
|
return pages;
|
|
4560
5058
|
}
|
|
4561
5059
|
async function collectProgramSource(client, source, sourceIndex, options) {
|
|
5060
|
+
const startPage = Math.max(1, options.start_page ?? 1);
|
|
5061
|
+
if (startPage > 1) {
|
|
5062
|
+
const pageNumbers = Array.from({ length: Math.max(0, options.max_pages - startPage + 1) }, (_, index) => startPage + index);
|
|
5063
|
+
const pages = [];
|
|
5064
|
+
await mapWithConcurrency(pageNumbers, PROGRAM_COLLECTION_CONCURRENCY, async (page) => {
|
|
5065
|
+
const result = await fetchProgramPage(client, source, sourceIndex, page, options);
|
|
5066
|
+
if (result) {
|
|
5067
|
+
pages.push(result);
|
|
5068
|
+
}
|
|
5069
|
+
});
|
|
5070
|
+
return pages;
|
|
5071
|
+
}
|
|
4562
5072
|
const firstPage = await fetchProgramPage(client, source, sourceIndex, 1, options);
|
|
4563
5073
|
if (!firstPage) {
|
|
4564
5074
|
return [];
|
|
@@ -5023,7 +5533,8 @@ Find the best BBRadar program candidates.
|
|
|
5023
5533
|
- Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
|
|
5024
5534
|
- Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
|
|
5025
5535
|
- Compare freshness, opportunity score, reward range, public report count, target counts, and scope tags.
|
|
5026
|
-
- For
|
|
5536
|
+
- For a user asking for an in-scope or bounty-eligible target list by program name, call get_program_eligible_targets_by_name once and treat its target list as final.
|
|
5537
|
+
- For an already known program_id, call get_program_targets directly for exact target lists. Use get_program_scope_summary or get_program_target_breakdown only when grouped counts are needed.
|
|
5027
5538
|
- Return a ranked list with concise rationale, likely target themes, and BBRadar URLs.
|
|
5028
5539
|
- Stay passive-only; do not recommend scanning, probing, exploit attempts, or direct contact with targets.`));
|
|
5029
5540
|
server.registerPrompt("summarize_program_scope", {
|
|
@@ -5032,7 +5543,7 @@ Find the best BBRadar program candidates.
|
|
|
5032
5543
|
argsSchema: {
|
|
5033
5544
|
program_id: programIdSchema
|
|
5034
5545
|
}
|
|
5035
|
-
}, (args) => promptResult(`Use
|
|
5546
|
+
}, (args) => promptResult(`Use get_program_targets for the program_id in this JSON string: ${promptJson(args.program_id)} with include_out_of_scope=false, include_ineligible=false, and limit=${MAX_TARGETS_PER_PROGRAM}. Do not call get_program first unless program metadata is explicitly needed. Only request out-of-scope or ineligible targets if the user explicitly asks for them.
|
|
5036
5547
|
|
|
5037
5548
|
Summarize the program scope:
|
|
5038
5549
|
- Program name, platform, reward range, public report count, first seen date, last updated date, and BBRadar URL.
|
|
@@ -5053,7 +5564,8 @@ Summarize the program scope:
|
|
|
5053
5564
|
Do not invoke local bug bounty skills, methodology files, worklogs, or non-BBRadar tools unless the user explicitly asks for external methodology.
|
|
5054
5565
|
|
|
5055
5566
|
Use BBRadar MCP data first:
|
|
5056
|
-
- If a program_id is provided, call get_program
|
|
5567
|
+
- If a program_id is provided, call get_program_targets first. Call get_program only if metadata is explicitly needed.
|
|
5568
|
+
- If the user provides a program name and asks for in-scope or bounty-eligible targets, call get_program_eligible_targets_by_name once.
|
|
5057
5569
|
- If no program_id is provided, call get_opportunities and select a small set of candidate programs before planning.
|
|
5058
5570
|
- Focus area JSON string: ${promptJson(args.focus ?? "highest-signal in-scope bounty-eligible targets")}.
|
|
5059
5571
|
|