@bbradar/mcp 0.1.3 → 0.1.4
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 +227 -56
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -29,6 +29,8 @@ 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;
|
|
32
34
|
const SDK_VERSION = "1.29.0";
|
|
33
35
|
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
34
36
|
const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
|
|
@@ -124,8 +126,11 @@ const apiEnvelopeOutputShape = {
|
|
|
124
126
|
upstream_request_id: z.string().optional(),
|
|
125
127
|
fetched_at: z.string().optional(),
|
|
126
128
|
cache: cacheOutputSchema.optional(),
|
|
129
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
127
130
|
mcp_rate_limit: rateLimitOutputSchema.optional(),
|
|
128
131
|
mcp_timing: toolTimingOutputSchema.optional(),
|
|
132
|
+
warnings: z.array(z.string()).optional(),
|
|
133
|
+
program_id_resolution: jsonRecordSchema.optional(),
|
|
129
134
|
error: errorOutputSchema.optional()
|
|
130
135
|
};
|
|
131
136
|
const programListOutputShape = {
|
|
@@ -229,9 +234,11 @@ export function createBbradarServer(client, config) {
|
|
|
229
234
|
},
|
|
230
235
|
annotations: readOnlyAnnotations
|
|
231
236
|
}, (args) => runTool("get_program", config, rateLimiter, async () => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
return runProgramIdTool(client, config, args.program_id, async (programId) => {
|
|
238
|
+
const api = await client.getProgram(programId);
|
|
239
|
+
return withApiMetadata(api, {
|
|
240
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
241
|
+
});
|
|
235
242
|
});
|
|
236
243
|
}));
|
|
237
244
|
server.registerTool("resolve_program", {
|
|
@@ -312,23 +319,25 @@ export function createBbradarServer(client, config) {
|
|
|
312
319
|
},
|
|
313
320
|
annotations: readOnlyAnnotations
|
|
314
321
|
}, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
322
|
+
return runProgramIdTool(client, config, args.program_id, async (programId) => {
|
|
323
|
+
const api = await client.getProgramTargets(programId);
|
|
324
|
+
const data = readObject(api.data);
|
|
325
|
+
const rawTargets = readArray(data?.targets);
|
|
326
|
+
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, args));
|
|
327
|
+
const targets = sanitizedTargets.slice(args.offset, args.offset + args.limit);
|
|
328
|
+
return withApiMetadata(api, {
|
|
329
|
+
program_id: programId,
|
|
330
|
+
targets: formatTargetList(targets, args.output_mode),
|
|
331
|
+
meta: {
|
|
332
|
+
offset: args.offset,
|
|
333
|
+
limit: args.limit,
|
|
334
|
+
output_mode: args.output_mode,
|
|
335
|
+
total_active_targets: rawTargets.length,
|
|
336
|
+
total_after_filters: sanitizedTargets.length,
|
|
337
|
+
returned: targets.length,
|
|
338
|
+
has_more: args.offset + args.limit < sanitizedTargets.length
|
|
339
|
+
}
|
|
340
|
+
});
|
|
332
341
|
});
|
|
333
342
|
}));
|
|
334
343
|
server.registerTool("get_recent_changes", {
|
|
@@ -414,7 +423,7 @@ export function createBbradarServer(client, config) {
|
|
|
414
423
|
meta: jsonRecordSchema.optional()
|
|
415
424
|
},
|
|
416
425
|
annotations: readOnlyAnnotations
|
|
417
|
-
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => getProgramScopeSummary(client, config, args)));
|
|
426
|
+
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }))));
|
|
418
427
|
server.registerTool("get_program_target_breakdown", {
|
|
419
428
|
title: "Get Program Target Breakdown",
|
|
420
429
|
description: "Target type, scope tag, language tag, and bucket counts for one program.",
|
|
@@ -433,7 +442,7 @@ export function createBbradarServer(client, config) {
|
|
|
433
442
|
meta: jsonRecordSchema.optional()
|
|
434
443
|
},
|
|
435
444
|
annotations: readOnlyAnnotations
|
|
436
|
-
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => getProgramTargetBreakdown(client, config, args)));
|
|
445
|
+
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }))));
|
|
437
446
|
server.registerTool("get_program_scope_delta", {
|
|
438
447
|
title: "Get Program Scope Delta",
|
|
439
448
|
description: "Compact recent scope changes for one program.",
|
|
@@ -459,7 +468,7 @@ export function createBbradarServer(client, config) {
|
|
|
459
468
|
meta: jsonRecordSchema.optional()
|
|
460
469
|
},
|
|
461
470
|
annotations: readOnlyAnnotations
|
|
462
|
-
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => getProgramScopeDelta(client, config, args)));
|
|
471
|
+
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }))));
|
|
463
472
|
server.registerTool("get_recent_target_activity", {
|
|
464
473
|
title: "Get Recent Target Activity",
|
|
465
474
|
description: "Recent target changes grouped by program.",
|
|
@@ -1155,7 +1164,7 @@ export function createBbradarServer(client, config) {
|
|
|
1155
1164
|
changes: z.array(jsonRecordSchema).optional()
|
|
1156
1165
|
},
|
|
1157
1166
|
annotations: readOnlyAnnotations
|
|
1158
|
-
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () =>
|
|
1167
|
+
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
|
|
1159
1168
|
server.registerTool("get_program_brief", {
|
|
1160
1169
|
title: "Get Program Brief",
|
|
1161
1170
|
description: "Compact worth-hunting brief for one program.",
|
|
@@ -1178,7 +1187,7 @@ export function createBbradarServer(client, config) {
|
|
|
1178
1187
|
meta: jsonRecordSchema.optional()
|
|
1179
1188
|
},
|
|
1180
1189
|
annotations: readOnlyAnnotations
|
|
1181
|
-
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => getProgramBrief(client, config, args)));
|
|
1190
|
+
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
|
|
1182
1191
|
server.registerTool("find_recently_added_wildcards", {
|
|
1183
1192
|
title: "Find Recently Added Wildcards",
|
|
1184
1193
|
description: "Recently added wildcard programs.",
|
|
@@ -1307,7 +1316,7 @@ export function createBbradarServer(client, config) {
|
|
|
1307
1316
|
meta: jsonRecordSchema.optional()
|
|
1308
1317
|
},
|
|
1309
1318
|
annotations: readOnlyAnnotations
|
|
1310
|
-
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => getProgramDelta(client, config, args)));
|
|
1319
|
+
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
|
|
1311
1320
|
server.registerTool("export_targets", {
|
|
1312
1321
|
title: "Export BBRadar Targets",
|
|
1313
1322
|
description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
|
|
@@ -1374,9 +1383,11 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1374
1383
|
mimeType: "application/json"
|
|
1375
1384
|
}, async (uri) => {
|
|
1376
1385
|
const programId = parseProgramIdFromResourceUri(uri);
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1386
|
+
const payload = await runProgramIdTool(client, config, programId, async (resolvedProgramId) => {
|
|
1387
|
+
const api = await client.getProgram(resolvedProgramId);
|
|
1388
|
+
return withApiMetadata(api, {
|
|
1389
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
1390
|
+
});
|
|
1380
1391
|
});
|
|
1381
1392
|
return jsonResource(uri.toString(), payload);
|
|
1382
1393
|
});
|
|
@@ -1386,15 +1397,17 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1386
1397
|
mimeType: "application/json"
|
|
1387
1398
|
}, async (uri) => {
|
|
1388
1399
|
const programId = parseProgramIdFromResourceUri(uri);
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1400
|
+
const payload = await runProgramIdTool(client, config, programId, async (resolvedProgramId) => {
|
|
1401
|
+
const api = await client.getProgramTargets(resolvedProgramId);
|
|
1402
|
+
const data = readObject(api.data);
|
|
1403
|
+
const targets = readArray(data?.targets).map(sanitizeTarget);
|
|
1404
|
+
return withApiMetadata(api, {
|
|
1405
|
+
program_id: resolvedProgramId,
|
|
1406
|
+
targets,
|
|
1407
|
+
meta: {
|
|
1408
|
+
total_active_targets: targets.length
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1398
1411
|
});
|
|
1399
1412
|
return jsonResource(uri.toString(), payload);
|
|
1400
1413
|
});
|
|
@@ -1433,6 +1446,136 @@ function registerResources(server, client, config, exportStore) {
|
|
|
1433
1446
|
}));
|
|
1434
1447
|
});
|
|
1435
1448
|
}
|
|
1449
|
+
async function getProgramWithIdFallback(client, config, requestedProgramId) {
|
|
1450
|
+
try {
|
|
1451
|
+
return {
|
|
1452
|
+
api: await client.getProgram(requestedProgramId),
|
|
1453
|
+
programId: requestedProgramId,
|
|
1454
|
+
sourceRequests: [],
|
|
1455
|
+
warnings: []
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
catch (error) {
|
|
1459
|
+
if (!isProgramNotFoundError(error)) {
|
|
1460
|
+
throw error;
|
|
1461
|
+
}
|
|
1462
|
+
const fallback = await resolveStaleProgramId(client, config, requestedProgramId, error);
|
|
1463
|
+
if (!fallback) {
|
|
1464
|
+
throw error;
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
api: await client.getProgram(fallback.resolvedProgramId),
|
|
1468
|
+
programId: fallback.resolvedProgramId,
|
|
1469
|
+
sourceRequests: fallback.sourceRequests,
|
|
1470
|
+
warnings: fallback.warnings,
|
|
1471
|
+
fallback
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
async function runProgramIdTool(client, config, requestedProgramId, callback) {
|
|
1476
|
+
try {
|
|
1477
|
+
return await callback(requestedProgramId);
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
if (!isProgramNotFoundError(error)) {
|
|
1481
|
+
throw error;
|
|
1482
|
+
}
|
|
1483
|
+
const fallback = await resolveStaleProgramId(client, config, requestedProgramId, error);
|
|
1484
|
+
if (!fallback) {
|
|
1485
|
+
throw error;
|
|
1486
|
+
}
|
|
1487
|
+
const payload = await callback(fallback.resolvedProgramId);
|
|
1488
|
+
return withProgramIdFallbackMetadata(payload, fallback);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async function resolveStaleProgramId(client, config, requestedProgramId, staleError) {
|
|
1492
|
+
const query = programSearchText(requestedProgramId);
|
|
1493
|
+
if (query.length < 2) {
|
|
1494
|
+
return undefined;
|
|
1495
|
+
}
|
|
1496
|
+
const resolution = await resolveProgram(client, config, {
|
|
1497
|
+
query,
|
|
1498
|
+
platforms: [],
|
|
1499
|
+
tags: [],
|
|
1500
|
+
opportunity_levels: [],
|
|
1501
|
+
max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
|
|
1502
|
+
max_results: 5,
|
|
1503
|
+
output_mode: "compact",
|
|
1504
|
+
upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
|
|
1505
|
+
});
|
|
1506
|
+
const match = selectProgramIdFallbackMatch(requestedProgramId, resolution);
|
|
1507
|
+
if (!match) {
|
|
1508
|
+
return undefined;
|
|
1509
|
+
}
|
|
1510
|
+
const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1511
|
+
const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
1512
|
+
const staleApiError = staleError instanceof BBRadarApiError ? staleError : undefined;
|
|
1513
|
+
const fallbackWarning = `program_id ${requestedProgramId} was stale; automatically retried with resolved program_id ${match.resolvedProgramId}.`;
|
|
1514
|
+
return {
|
|
1515
|
+
requestedProgramId,
|
|
1516
|
+
resolvedProgramId: match.resolvedProgramId,
|
|
1517
|
+
query,
|
|
1518
|
+
staleRequestId: staleApiError?.requestId,
|
|
1519
|
+
staleUpstreamRequestId: staleApiError?.upstreamRequestId,
|
|
1520
|
+
sourceRequests,
|
|
1521
|
+
warnings: uniqueStrings([...warnings, fallbackWarning]),
|
|
1522
|
+
resolution
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
|
|
1526
|
+
const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
|
|
1527
|
+
const requestedSearchText = normalizeTag(programSearchText(requestedProgramId));
|
|
1528
|
+
const candidates = readArray(resolution.matches)
|
|
1529
|
+
.map((entry) => readObject(entry))
|
|
1530
|
+
.filter((entry) => entry !== undefined)
|
|
1531
|
+
.map((entry) => {
|
|
1532
|
+
const program = readObject(entry.program);
|
|
1533
|
+
const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
|
|
1534
|
+
const candidateHandle = normalizeTag(stringField(program, "handle") ?? "");
|
|
1535
|
+
const candidateSearchText = resolvedProgramId ? normalizeTag(programSearchText(resolvedProgramId)) : "";
|
|
1536
|
+
const score = readNumber(entry.score) ?? 0;
|
|
1537
|
+
const exactHandleMatch = candidateHandle === requestedHandle ||
|
|
1538
|
+
candidateHandle === requestedSearchText ||
|
|
1539
|
+
candidateSearchText === requestedSearchText;
|
|
1540
|
+
return {
|
|
1541
|
+
resolvedProgramId,
|
|
1542
|
+
score,
|
|
1543
|
+
exactHandleMatch
|
|
1544
|
+
};
|
|
1545
|
+
})
|
|
1546
|
+
.filter((entry) => entry.resolvedProgramId !== undefined && entry.resolvedProgramId !== requestedProgramId && entry.exactHandleMatch && entry.score >= 80)
|
|
1547
|
+
.sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
|
|
1548
|
+
if (candidates.length === 0) {
|
|
1549
|
+
return undefined;
|
|
1550
|
+
}
|
|
1551
|
+
const [best, next] = candidates;
|
|
1552
|
+
if (!best || (next && best.score === next.score)) {
|
|
1553
|
+
return undefined;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
resolvedProgramId: best.resolvedProgramId
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function withProgramIdFallbackMetadata(payload, fallback) {
|
|
1560
|
+
const sourceRequests = readArray(payload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1561
|
+
const warnings = readArray(payload.warnings).filter((warning) => typeof warning === "string");
|
|
1562
|
+
const resolution = stripUndefined({
|
|
1563
|
+
requested_program_id: fallback.requestedProgramId,
|
|
1564
|
+
resolved_program_id: fallback.resolvedProgramId,
|
|
1565
|
+
query: fallback.query,
|
|
1566
|
+
reason: "stale_program_id",
|
|
1567
|
+
stale_status: 404,
|
|
1568
|
+
stale_request_id: fallback.staleRequestId,
|
|
1569
|
+
stale_upstream_request_id: fallback.staleUpstreamRequestId,
|
|
1570
|
+
resolution_request_id: stringField(fallback.resolution, "request_id")
|
|
1571
|
+
});
|
|
1572
|
+
return stripUndefined({
|
|
1573
|
+
...payload,
|
|
1574
|
+
source_requests: [...fallback.sourceRequests, ...sourceRequests],
|
|
1575
|
+
warnings: uniqueStrings([...fallback.warnings, ...warnings]),
|
|
1576
|
+
program_id_resolution: resolution
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1436
1579
|
async function resolveProgram(client, config, input) {
|
|
1437
1580
|
const warnings = [];
|
|
1438
1581
|
const budget = {
|
|
@@ -2268,17 +2411,22 @@ async function comparePrograms(client, config, input) {
|
|
|
2268
2411
|
const candidates = [];
|
|
2269
2412
|
const sourceRequests = [];
|
|
2270
2413
|
const errors = [];
|
|
2414
|
+
const warnings = [];
|
|
2271
2415
|
await mapWithConcurrency(input.program_ids, TARGET_SAMPLE_CONCURRENCY, async (programId) => {
|
|
2272
2416
|
try {
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2417
|
+
const lookup = await getProgramWithIdFallback(client, config, programId);
|
|
2418
|
+
const api = lookup.api;
|
|
2419
|
+
const requestSource = stripUndefined({
|
|
2276
2420
|
source: "program",
|
|
2277
|
-
program_id: programId,
|
|
2421
|
+
program_id: lookup.programId,
|
|
2422
|
+
requested_program_id: lookup.programId === programId ? undefined : programId,
|
|
2278
2423
|
request_id: api.requestId,
|
|
2279
2424
|
upstream_request_id: api.upstreamRequestId,
|
|
2280
2425
|
...apiSourceMetadata(api)
|
|
2281
2426
|
});
|
|
2427
|
+
candidates.push(toProgramCandidate(api.data, config.webBaseUrl));
|
|
2428
|
+
sourceRequests.push(...lookup.sourceRequests, requestSource);
|
|
2429
|
+
warnings.push(...lookup.warnings);
|
|
2282
2430
|
}
|
|
2283
2431
|
catch (error) {
|
|
2284
2432
|
errors.push(programFetchError(programId, error));
|
|
@@ -2333,6 +2481,7 @@ async function comparePrograms(client, config, input) {
|
|
|
2333
2481
|
partial_success: errors.length > 0,
|
|
2334
2482
|
program_ids_requested: input.program_ids,
|
|
2335
2483
|
failed_program_ids: errors.map((error) => error.program_id).filter((id) => typeof id === "string"),
|
|
2484
|
+
warnings: warnings.length > 0 ? uniqueStrings(warnings) : undefined,
|
|
2336
2485
|
errors: errors.length > 0 ? errors : undefined,
|
|
2337
2486
|
source_requests: sourceRequests,
|
|
2338
2487
|
compared_programs: candidates.map((candidate, index) => candidateOutput(candidate, index, targetSamples, input.output_mode ?? "compact")),
|
|
@@ -2673,13 +2822,15 @@ async function getRecentTargetActivity(client, config, input) {
|
|
|
2673
2822
|
};
|
|
2674
2823
|
}
|
|
2675
2824
|
async function checkProgramNewTargets(client, config, input) {
|
|
2676
|
-
const
|
|
2825
|
+
const programLookup = await getProgramWithIdFallback(client, config, input.program_id);
|
|
2826
|
+
const programId = programLookup.programId;
|
|
2827
|
+
const handle = programSearchText(programId);
|
|
2677
2828
|
const api = await client.getRecentChanges({
|
|
2678
2829
|
change_type: "added",
|
|
2679
2830
|
include_removed: false,
|
|
2680
2831
|
include_ineligible: input.include_ineligible,
|
|
2681
2832
|
include_out_of_scope: false,
|
|
2682
|
-
search: handle.length >= 2 ? handle :
|
|
2833
|
+
search: handle.length >= 2 ? handle : programId,
|
|
2683
2834
|
tags: input.target_type ? [input.target_type] : [],
|
|
2684
2835
|
page: 1,
|
|
2685
2836
|
page_size: input.page_size
|
|
@@ -2693,7 +2844,7 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2693
2844
|
const target = readObject(change.target);
|
|
2694
2845
|
const changedAt = timestampField(change, "changed_at");
|
|
2695
2846
|
return (stringField(change, "change_type") === "added" &&
|
|
2696
|
-
stringField(readObject(change.program), "id") ===
|
|
2847
|
+
stringField(readObject(change.program), "id") === programId &&
|
|
2697
2848
|
target !== undefined &&
|
|
2698
2849
|
(sinceTimestamp === undefined || changedAt >= sinceTimestamp) &&
|
|
2699
2850
|
(!input.target_type || targetMatchesRequestedType(target, input.target_type)) &&
|
|
@@ -2709,18 +2860,30 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2709
2860
|
.map((change) => readObject(change.target))
|
|
2710
2861
|
.filter((target) => target !== undefined);
|
|
2711
2862
|
const latest = matchingChanges[0];
|
|
2712
|
-
|
|
2863
|
+
const sourceRequests = [
|
|
2864
|
+
{
|
|
2865
|
+
source: "program",
|
|
2866
|
+
program_id: programId,
|
|
2867
|
+
requested_program_id: programId === input.program_id ? undefined : input.program_id,
|
|
2868
|
+
request_id: programLookup.api.requestId,
|
|
2869
|
+
upstream_request_id: programLookup.api.upstreamRequestId,
|
|
2870
|
+
...apiSourceMetadata(programLookup.api)
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
source: "recent_changes",
|
|
2874
|
+
request_id: api.requestId,
|
|
2875
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2876
|
+
...apiSourceMetadata(api)
|
|
2877
|
+
}
|
|
2878
|
+
];
|
|
2879
|
+
const payload = stripUndefined({
|
|
2713
2880
|
request_id: randomUUID(),
|
|
2714
|
-
source_requests:
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
}
|
|
2721
|
-
],
|
|
2722
|
-
program_id: input.program_id,
|
|
2723
|
-
program: latest ? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact") : undefined,
|
|
2881
|
+
source_requests: sourceRequests,
|
|
2882
|
+
warnings: !programLookup.fallback && programLookup.warnings.length > 0 ? uniqueStrings(programLookup.warnings) : undefined,
|
|
2883
|
+
program_id: programId,
|
|
2884
|
+
program: latest
|
|
2885
|
+
? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact")
|
|
2886
|
+
: formatProgram(addProgramResourceLinks(sanitizeProgram(programLookup.api.data, config.webBaseUrl)), "compact"),
|
|
2724
2887
|
has_new_targets: matchingChanges.length > 0,
|
|
2725
2888
|
new_target_count: matchingChanges.length,
|
|
2726
2889
|
latest_added_at: latest ? stringField(latest, "changed_at") : undefined,
|
|
@@ -2738,6 +2901,7 @@ async function checkProgramNewTargets(client, config, input) {
|
|
|
2738
2901
|
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
2739
2902
|
})
|
|
2740
2903
|
});
|
|
2904
|
+
return programLookup.fallback ? withProgramIdFallbackMetadata(payload, programLookup.fallback) : payload;
|
|
2741
2905
|
}
|
|
2742
2906
|
async function checkWatchlistNewTargets(client, config, input) {
|
|
2743
2907
|
const watchlist = new Set(input.program_ids);
|
|
@@ -3504,6 +3668,10 @@ function programSearchText(programId) {
|
|
|
3504
3668
|
const lastPathSegment = handle.split("/").filter(Boolean).at(-1) ?? handle;
|
|
3505
3669
|
return lastPathSegment.length >= 2 ? lastPathSegment : handle;
|
|
3506
3670
|
}
|
|
3671
|
+
function programHandleText(programId) {
|
|
3672
|
+
const colonIndex = programId.indexOf(":");
|
|
3673
|
+
return colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
3674
|
+
}
|
|
3507
3675
|
function normalizeFindProgramsInput(input) {
|
|
3508
3676
|
const warnings = [];
|
|
3509
3677
|
const normalized = { ...input };
|
|
@@ -3984,6 +4152,9 @@ function addUniqueWarning(warnings, warning) {
|
|
|
3984
4152
|
warnings.push(warning);
|
|
3985
4153
|
}
|
|
3986
4154
|
}
|
|
4155
|
+
function isProgramNotFoundError(error) {
|
|
4156
|
+
return error instanceof BBRadarApiError && error.status === 404;
|
|
4157
|
+
}
|
|
3987
4158
|
function runImmediately(callback) {
|
|
3988
4159
|
return callback();
|
|
3989
4160
|
}
|