@bbradar/mcp 0.1.5 → 0.1.7
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 +337 -68
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -92,10 +92,9 @@ const filterValueSchema = z
|
|
|
92
92
|
message: "Filter values cannot contain commas or control characters."
|
|
93
93
|
});
|
|
94
94
|
const stringListSchema = z
|
|
95
|
-
.array(filterValueSchema)
|
|
96
|
-
.max(50)
|
|
97
|
-
.default([])
|
|
95
|
+
.preprocess(normalizeStringListInput, z.array(filterValueSchema).max(50).default([]))
|
|
98
96
|
.describe("Filter values.");
|
|
97
|
+
const acceptedTargetLimitSchema = z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM);
|
|
99
98
|
const searchTextSchema = z
|
|
100
99
|
.string()
|
|
101
100
|
.trim()
|
|
@@ -329,7 +328,7 @@ export function createBbradarServer(client, config) {
|
|
|
329
328
|
include_out_of_scope: z.boolean().default(false),
|
|
330
329
|
include_ineligible: z.boolean().default(false),
|
|
331
330
|
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
332
|
-
max_targets:
|
|
331
|
+
max_targets: acceptedTargetLimitSchema.default(25),
|
|
333
332
|
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
334
333
|
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
335
334
|
max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
@@ -486,7 +485,7 @@ export function createBbradarServer(client, config) {
|
|
|
486
485
|
include_out_of_scope: z.boolean().default(false),
|
|
487
486
|
include_ineligible: z.boolean().default(false),
|
|
488
487
|
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
489
|
-
max_targets:
|
|
488
|
+
max_targets: acceptedTargetLimitSchema.default(50),
|
|
490
489
|
target_list_mode: targetListModeSchema.default("identifiers")
|
|
491
490
|
},
|
|
492
491
|
outputSchema: {
|
|
@@ -498,7 +497,7 @@ export function createBbradarServer(client, config) {
|
|
|
498
497
|
meta: jsonRecordSchema.optional()
|
|
499
498
|
},
|
|
500
499
|
annotations: readOnlyAnnotations
|
|
501
|
-
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }))));
|
|
500
|
+
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeDeltaFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
|
|
502
501
|
server.registerTool("get_recent_target_activity", {
|
|
503
502
|
title: "Get Recent Target Activity",
|
|
504
503
|
description: "Recent target changes grouped by program.",
|
|
@@ -534,7 +533,7 @@ export function createBbradarServer(client, config) {
|
|
|
534
533
|
target_type: filterValueSchema.optional(),
|
|
535
534
|
include_ineligible: z.boolean().default(false).describe("Include ineligible."),
|
|
536
535
|
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
537
|
-
max_targets:
|
|
536
|
+
max_targets: acceptedTargetLimitSchema.default(25),
|
|
538
537
|
target_list_mode: targetListModeSchema.default("identifiers")
|
|
539
538
|
},
|
|
540
539
|
outputSchema: {
|
|
@@ -1366,24 +1365,33 @@ export function createBbradarServer(client, config) {
|
|
|
1366
1365
|
},
|
|
1367
1366
|
outputSchema: {
|
|
1368
1367
|
...apiEnvelopeOutputShape,
|
|
1369
|
-
export: z.unknown().optional()
|
|
1368
|
+
export: z.unknown().optional(),
|
|
1369
|
+
meta: jsonRecordSchema.optional()
|
|
1370
1370
|
},
|
|
1371
1371
|
annotations: readOnlyAnnotations
|
|
1372
1372
|
}, (args) => runTool("export_targets", config, rateLimiter, async () => {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1373
|
+
try {
|
|
1374
|
+
const api = await client.exportTargets(compactRecord(args));
|
|
1375
|
+
const exportId = randomUUID();
|
|
1376
|
+
const sanitizedExport = sanitizeTargetsForExport(api.data, args.limit);
|
|
1377
|
+
const resourceUri = exportResourceUri(exportId);
|
|
1378
|
+
rememberExport(exportStore, exportId, sanitizedExport);
|
|
1379
|
+
return withApiMetadata(api, {
|
|
1380
|
+
export: {
|
|
1381
|
+
export_id: exportId,
|
|
1382
|
+
resource_uri: resourceUri,
|
|
1383
|
+
preview: previewExportPayload(sanitizedExport),
|
|
1384
|
+
returned_preview_count: previewExportCount(sanitizedExport),
|
|
1385
|
+
limit: args.limit
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
catch (error) {
|
|
1390
|
+
if (!(error instanceof BBRadarApiError) || (error.status !== 404 && error.status !== 405)) {
|
|
1391
|
+
throw error;
|
|
1385
1392
|
}
|
|
1386
|
-
|
|
1393
|
+
return exportTargetsFromProgramTargetsFallback(client, config, exportStore, args, error);
|
|
1394
|
+
}
|
|
1387
1395
|
}));
|
|
1388
1396
|
registerPrompts(server);
|
|
1389
1397
|
registerResources(server, client, config, exportStore);
|
|
@@ -1526,8 +1534,27 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
|
|
|
1526
1534
|
const payload = await indexFallbackCallback(indexFallback);
|
|
1527
1535
|
return withProgramIndexFallbackMetadata(payload, indexFallback);
|
|
1528
1536
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1537
|
+
try {
|
|
1538
|
+
const payload = await callback(fallback.resolvedProgramId);
|
|
1539
|
+
return withProgramIdFallbackMetadata(payload, fallback);
|
|
1540
|
+
}
|
|
1541
|
+
catch (fallbackError) {
|
|
1542
|
+
if (!isProgramNotFoundError(fallbackError) || !indexFallbackCallback) {
|
|
1543
|
+
throw fallbackError;
|
|
1544
|
+
}
|
|
1545
|
+
const indexFallback = await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError);
|
|
1546
|
+
if (!indexFallback) {
|
|
1547
|
+
throw fallbackError;
|
|
1548
|
+
}
|
|
1549
|
+
const combinedFallback = {
|
|
1550
|
+
...indexFallback,
|
|
1551
|
+
requestedProgramId,
|
|
1552
|
+
sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
|
|
1553
|
+
warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
|
|
1554
|
+
};
|
|
1555
|
+
const payload = await indexFallbackCallback(combinedFallback);
|
|
1556
|
+
return withProgramIndexFallbackMetadata(payload, combinedFallback);
|
|
1557
|
+
}
|
|
1531
1558
|
}
|
|
1532
1559
|
}
|
|
1533
1560
|
async function resolveIndexedProgramId(client, config, requestedProgramId, staleError) {
|
|
@@ -1602,6 +1629,7 @@ async function resolveStaleProgramId(client, config, requestedProgramId, staleEr
|
|
|
1602
1629
|
function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
|
|
1603
1630
|
const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
|
|
1604
1631
|
const requestedSearchText = normalizeTag(programSearchText(requestedProgramId));
|
|
1632
|
+
const requestedPlatform = normalizeTag(programPlatformText(requestedProgramId));
|
|
1605
1633
|
const candidates = readArray(resolution.matches)
|
|
1606
1634
|
.map((entry) => readObject(entry))
|
|
1607
1635
|
.filter((entry) => entry !== undefined)
|
|
@@ -1610,17 +1638,29 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
|
|
|
1610
1638
|
const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
|
|
1611
1639
|
const candidateHandle = normalizeTag(stringField(program, "handle") ?? "");
|
|
1612
1640
|
const candidateSearchText = resolvedProgramId ? normalizeTag(programSearchText(resolvedProgramId)) : "";
|
|
1641
|
+
const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
|
|
1613
1642
|
const score = readNumber(entry.score) ?? 0;
|
|
1614
1643
|
const exactHandleMatch = candidateHandle === requestedHandle ||
|
|
1615
1644
|
candidateHandle === requestedSearchText ||
|
|
1616
1645
|
candidateSearchText === requestedSearchText;
|
|
1646
|
+
const partialHandleMatch = requestedHandle.length >= 4 &&
|
|
1647
|
+
requestedPlatform.length > 0 &&
|
|
1648
|
+
candidatePlatform === requestedPlatform &&
|
|
1649
|
+
(candidateHandle.startsWith(`${requestedHandle}_`) ||
|
|
1650
|
+
candidateHandle.startsWith(`${requestedHandle}-`) ||
|
|
1651
|
+
candidateSearchText.startsWith(`${requestedSearchText}_`) ||
|
|
1652
|
+
candidateSearchText.startsWith(`${requestedSearchText}-`));
|
|
1617
1653
|
return {
|
|
1618
1654
|
resolvedProgramId,
|
|
1619
1655
|
score,
|
|
1620
|
-
exactHandleMatch
|
|
1656
|
+
exactHandleMatch,
|
|
1657
|
+
partialHandleMatch
|
|
1621
1658
|
};
|
|
1622
1659
|
})
|
|
1623
|
-
.filter((entry) => entry.resolvedProgramId !== undefined &&
|
|
1660
|
+
.filter((entry) => entry.resolvedProgramId !== undefined &&
|
|
1661
|
+
normalizeTag(entry.resolvedProgramId) !== normalizeTag(requestedProgramId) &&
|
|
1662
|
+
(entry.exactHandleMatch || entry.partialHandleMatch) &&
|
|
1663
|
+
entry.score >= 80)
|
|
1624
1664
|
.sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
|
|
1625
1665
|
if (candidates.length === 0) {
|
|
1626
1666
|
return undefined;
|
|
@@ -1650,11 +1690,15 @@ function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
|
|
|
1650
1690
|
const platformHandleMatch = requestedPlatform.length > 0 &&
|
|
1651
1691
|
candidatePlatform === requestedPlatform &&
|
|
1652
1692
|
candidateHandle === requestedHandle;
|
|
1693
|
+
const partialPlatformHandleMatch = requestedHandle.length >= 4 &&
|
|
1694
|
+
requestedPlatform.length > 0 &&
|
|
1695
|
+
candidatePlatform === requestedPlatform &&
|
|
1696
|
+
(candidateHandle.startsWith(`${requestedHandle}_`) || candidateHandle.startsWith(`${requestedHandle}-`));
|
|
1653
1697
|
return {
|
|
1654
1698
|
resolvedProgramId,
|
|
1655
1699
|
program,
|
|
1656
1700
|
score,
|
|
1657
|
-
exactMatch: idMatch || platformHandleMatch
|
|
1701
|
+
exactMatch: idMatch || platformHandleMatch || partialPlatformHandleMatch
|
|
1658
1702
|
};
|
|
1659
1703
|
})
|
|
1660
1704
|
.filter((entry) => entry.resolvedProgramId !== undefined && entry.program !== undefined && entry.exactMatch && entry.score >= 80)
|
|
@@ -2791,7 +2835,7 @@ async function getProgramTargetsPayload(client, input, programId) {
|
|
|
2791
2835
|
});
|
|
2792
2836
|
}
|
|
2793
2837
|
async function getProgramTargetsExportFallbackPayload(client, input, programId) {
|
|
2794
|
-
const exportLookup = await
|
|
2838
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, programId);
|
|
2795
2839
|
const sanitizedTargets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, input));
|
|
2796
2840
|
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2797
2841
|
return stripUndefined({
|
|
@@ -2817,6 +2861,42 @@ async function getProgramTargetsExportFallbackPayload(client, input, programId)
|
|
|
2817
2861
|
})
|
|
2818
2862
|
});
|
|
2819
2863
|
}
|
|
2864
|
+
async function fetchProgramTargetsWithEndpointFallback(client, programId) {
|
|
2865
|
+
try {
|
|
2866
|
+
const api = await client.getProgramTargets(programId);
|
|
2867
|
+
const data = readObject(api.data);
|
|
2868
|
+
const rawTargets = readArray(data?.targets).map(sanitizeTarget);
|
|
2869
|
+
return {
|
|
2870
|
+
requestId: api.requestId,
|
|
2871
|
+
upstreamRequestId: api.upstreamRequestId,
|
|
2872
|
+
fetchedAt: api.fetchedAt,
|
|
2873
|
+
cache: readObject(stripUndefined({
|
|
2874
|
+
hit: api.cached,
|
|
2875
|
+
coalesced_live_request: api.coalesced,
|
|
2876
|
+
expires_at: api.cacheExpiresAt
|
|
2877
|
+
})),
|
|
2878
|
+
source: "program_targets",
|
|
2879
|
+
rawTargets,
|
|
2880
|
+
sourceRequests: [
|
|
2881
|
+
{
|
|
2882
|
+
source: "program_targets",
|
|
2883
|
+
program_id: programId,
|
|
2884
|
+
request_id: api.requestId,
|
|
2885
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2886
|
+
...apiSourceMetadata(api)
|
|
2887
|
+
}
|
|
2888
|
+
],
|
|
2889
|
+
warnings: [],
|
|
2890
|
+
unavailable: false
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
catch (error) {
|
|
2894
|
+
if (!isProgramNotFoundError(error)) {
|
|
2895
|
+
throw error;
|
|
2896
|
+
}
|
|
2897
|
+
return fetchProgramTargetsFromExportFallback(client, programId);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2820
2900
|
async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
2821
2901
|
try {
|
|
2822
2902
|
const api = await client.exportTargets({
|
|
@@ -2872,6 +2952,116 @@ async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
|
2872
2952
|
};
|
|
2873
2953
|
}
|
|
2874
2954
|
}
|
|
2955
|
+
async function exportTargetsFromProgramTargetsFallback(client, config, exportStore, input, exportError) {
|
|
2956
|
+
const warnings = ["Target export endpoint is unavailable; assembled a fallback export from per-program target endpoints."];
|
|
2957
|
+
const sourceRequests = [
|
|
2958
|
+
{
|
|
2959
|
+
source: "target_export",
|
|
2960
|
+
failed: true,
|
|
2961
|
+
request_id: exportError.requestId,
|
|
2962
|
+
upstream_request_id: exportError.upstreamRequestId,
|
|
2963
|
+
status: exportError.status
|
|
2964
|
+
}
|
|
2965
|
+
];
|
|
2966
|
+
const programIds = input.program_ids.length > 0 ? input.program_ids : await discoverExportProgramIds(client, input, sourceRequests, warnings);
|
|
2967
|
+
const targets = [];
|
|
2968
|
+
const errors = [];
|
|
2969
|
+
for (const programId of programIds) {
|
|
2970
|
+
if (targets.length >= input.limit) {
|
|
2971
|
+
break;
|
|
2972
|
+
}
|
|
2973
|
+
try {
|
|
2974
|
+
const payload = await runProgramIdTool(client, config, programId, (resolvedProgramId) => getProgramTargetsPayload(client, {
|
|
2975
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2976
|
+
include_ineligible: input.include_ineligible,
|
|
2977
|
+
strict_scope_filter: false,
|
|
2978
|
+
offset: 0,
|
|
2979
|
+
limit: input.limit - targets.length,
|
|
2980
|
+
output_mode: "full"
|
|
2981
|
+
}, resolvedProgramId), (indexFallback) => getProgramTargetsExportFallbackPayload(client, {
|
|
2982
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2983
|
+
include_ineligible: input.include_ineligible,
|
|
2984
|
+
strict_scope_filter: false,
|
|
2985
|
+
offset: 0,
|
|
2986
|
+
limit: input.limit - targets.length,
|
|
2987
|
+
output_mode: "full"
|
|
2988
|
+
}, indexFallback.resolvedProgramId));
|
|
2989
|
+
const resolvedProgramId = stringField(payload, "program_id") ?? programId;
|
|
2990
|
+
sourceRequests.push(...readArray(payload.source_requests).filter((request) => readObject(request) !== undefined));
|
|
2991
|
+
warnings.push(...readArray(payload.warnings).filter((warning) => typeof warning === "string"));
|
|
2992
|
+
for (const target of readArray(payload.targets)) {
|
|
2993
|
+
const object = readObject(target);
|
|
2994
|
+
if (!object) {
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2997
|
+
targets.push({ program_id: resolvedProgramId, ...object });
|
|
2998
|
+
if (targets.length >= input.limit) {
|
|
2999
|
+
break;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
catch (error) {
|
|
3004
|
+
errors.push(apiOperationError(programId, error));
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (input.format === "csv") {
|
|
3008
|
+
warnings.push("Fallback target export returns JSON even when CSV was requested.");
|
|
3009
|
+
}
|
|
3010
|
+
const exportPayload = stripUndefined({
|
|
3011
|
+
targets,
|
|
3012
|
+
meta: {
|
|
3013
|
+
export_source: "program_targets_fallback",
|
|
3014
|
+
requested_program_count: programIds.length,
|
|
3015
|
+
returned: targets.length,
|
|
3016
|
+
limit: input.limit,
|
|
3017
|
+
format: "json"
|
|
3018
|
+
},
|
|
3019
|
+
generated_at: new Date().toISOString()
|
|
3020
|
+
});
|
|
3021
|
+
const exportId = randomUUID();
|
|
3022
|
+
const resourceUri = exportResourceUri(exportId);
|
|
3023
|
+
rememberExport(exportStore, exportId, exportPayload);
|
|
3024
|
+
return stripUndefined({
|
|
3025
|
+
request_id: randomUUID(),
|
|
3026
|
+
source_requests: sourceRequests,
|
|
3027
|
+
warnings: uniqueStrings(warnings),
|
|
3028
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
3029
|
+
export: {
|
|
3030
|
+
export_id: exportId,
|
|
3031
|
+
resource_uri: resourceUri,
|
|
3032
|
+
preview: previewExportPayload(exportPayload),
|
|
3033
|
+
returned_preview_count: previewExportCount(exportPayload),
|
|
3034
|
+
limit: input.limit
|
|
3035
|
+
},
|
|
3036
|
+
meta: stripUndefined({
|
|
3037
|
+
export_source: "program_targets_fallback",
|
|
3038
|
+
requested_program_count: programIds.length,
|
|
3039
|
+
returned: targets.length,
|
|
3040
|
+
failed_program_count: errors.length,
|
|
3041
|
+
fallback_error: apiOperationError("target_export", exportError)
|
|
3042
|
+
})
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
async function discoverExportProgramIds(client, input, sourceRequests, warnings) {
|
|
3046
|
+
const budget = {
|
|
3047
|
+
initial: 1,
|
|
3048
|
+
remaining: 1
|
|
3049
|
+
};
|
|
3050
|
+
const collected = await collectPrograms(client, {
|
|
3051
|
+
platforms: input.platforms,
|
|
3052
|
+
tags: input.tags,
|
|
3053
|
+
updated_since: undefined,
|
|
3054
|
+
opportunity_levels: input.opportunity_levels,
|
|
3055
|
+
max_pages: 1,
|
|
3056
|
+
budget,
|
|
3057
|
+
warnings
|
|
3058
|
+
});
|
|
3059
|
+
sourceRequests.push(...collected.sourceRequests);
|
|
3060
|
+
return uniqueStrings(collected.programs
|
|
3061
|
+
.map((program) => stringField(sanitizeProgram(program, ""), "id"))
|
|
3062
|
+
.filter((programId) => programId !== undefined)
|
|
3063
|
+
.slice(0, input.limit));
|
|
3064
|
+
}
|
|
2875
3065
|
function readExportTargetRows(data) {
|
|
2876
3066
|
if (Array.isArray(data)) {
|
|
2877
3067
|
return data;
|
|
@@ -2922,7 +3112,7 @@ async function getProgramScopeSummary(client, config, input) {
|
|
|
2922
3112
|
};
|
|
2923
3113
|
}
|
|
2924
3114
|
async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, input) {
|
|
2925
|
-
const exportLookup = await
|
|
3115
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
2926
3116
|
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
2927
3117
|
include_out_of_scope: input.include_out_of_scope,
|
|
2928
3118
|
include_ineligible: input.include_ineligible,
|
|
@@ -2995,7 +3185,7 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
2995
3185
|
};
|
|
2996
3186
|
}
|
|
2997
3187
|
async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback, input) {
|
|
2998
|
-
const exportLookup = await
|
|
3188
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
2999
3189
|
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
3000
3190
|
include_out_of_scope: input.include_out_of_scope,
|
|
3001
3191
|
include_ineligible: input.include_ineligible,
|
|
@@ -3033,19 +3223,8 @@ async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback,
|
|
|
3033
3223
|
async function getProgramScopeDelta(client, config, input) {
|
|
3034
3224
|
const programApi = await client.getProgram(input.program_id);
|
|
3035
3225
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
3036
|
-
const changesPayload = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope);
|
|
3037
|
-
const
|
|
3038
|
-
const filteredChanges = changesPayload.changes
|
|
3039
|
-
.filter((change) => !input.change_type || stringField(change, "change_type") === input.change_type)
|
|
3040
|
-
.filter((change) => sinceTimestamp === undefined || timestampField(change, "changed_at") >= sinceTimestamp)
|
|
3041
|
-
.filter((change) => {
|
|
3042
|
-
const target = readObject(change.target);
|
|
3043
|
-
return !input.target_type || (target !== undefined && targetMatchesRequestedType(target, input.target_type));
|
|
3044
|
-
})
|
|
3045
|
-
.filter((change) => {
|
|
3046
|
-
const target = readObject(change.target);
|
|
3047
|
-
return input.language_tags.length === 0 || (target !== undefined && targetMatchesAnySignal(target, input.language_tags));
|
|
3048
|
-
});
|
|
3226
|
+
const changesPayload = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope, input.max_targets);
|
|
3227
|
+
const filteredChanges = filterProgramScopeDeltaChanges(changesPayload.changes, input);
|
|
3049
3228
|
const limitedChanges = filteredChanges.slice(0, input.max_targets);
|
|
3050
3229
|
return stripUndefined({
|
|
3051
3230
|
request_id: randomUUID(),
|
|
@@ -3070,10 +3249,55 @@ async function getProgramScopeDelta(client, config, input) {
|
|
|
3070
3249
|
change_type: input.change_type,
|
|
3071
3250
|
target_type: input.target_type,
|
|
3072
3251
|
language_tags: input.language_tags.length > 0 ? input.language_tags : undefined,
|
|
3073
|
-
target_list_mode: input.target_list_mode
|
|
3252
|
+
target_list_mode: input.target_list_mode,
|
|
3253
|
+
upstream_total_pages: readNumber(changesPayload.meta?.total_pages),
|
|
3254
|
+
pages_fetched: readNumber(changesPayload.meta?.pages_fetched),
|
|
3255
|
+
scan_may_be_incomplete: (readNumber(changesPayload.meta?.total_pages) ?? 1) > (readNumber(changesPayload.meta?.pages_fetched) ?? 1)
|
|
3074
3256
|
})
|
|
3075
3257
|
});
|
|
3076
3258
|
}
|
|
3259
|
+
async function getProgramScopeDeltaFromIndexedProgram(client, config, input, indexedProgram) {
|
|
3260
|
+
const program = addProgramResourceLinks(indexedProgram);
|
|
3261
|
+
const changesPayload = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope, input.max_targets);
|
|
3262
|
+
const filteredChanges = filterProgramScopeDeltaChanges(changesPayload.changes, input);
|
|
3263
|
+
const limitedChanges = filteredChanges.slice(0, input.max_targets);
|
|
3264
|
+
return stripUndefined({
|
|
3265
|
+
request_id: randomUUID(),
|
|
3266
|
+
source_requests: changesPayload.sourceRequests,
|
|
3267
|
+
program: formatProgram(program, "compact"),
|
|
3268
|
+
delta: buildScopeDelta(limitedChanges, input.target_list_mode),
|
|
3269
|
+
changes: limitedChanges.map((change) => formatChange(change, "compact")),
|
|
3270
|
+
meta: stripUndefined({
|
|
3271
|
+
recent_changes_scanned: changesPayload.changes.length,
|
|
3272
|
+
changes_after_filters: filteredChanges.length,
|
|
3273
|
+
returned: limitedChanges.length,
|
|
3274
|
+
has_more: filteredChanges.length > limitedChanges.length,
|
|
3275
|
+
since: input.since,
|
|
3276
|
+
change_type: input.change_type,
|
|
3277
|
+
target_type: input.target_type,
|
|
3278
|
+
language_tags: input.language_tags.length > 0 ? input.language_tags : undefined,
|
|
3279
|
+
target_list_mode: input.target_list_mode,
|
|
3280
|
+
program_detail_source: "program_index",
|
|
3281
|
+
upstream_total_pages: readNumber(changesPayload.meta?.total_pages),
|
|
3282
|
+
pages_fetched: readNumber(changesPayload.meta?.pages_fetched),
|
|
3283
|
+
scan_may_be_incomplete: (readNumber(changesPayload.meta?.total_pages) ?? 1) > (readNumber(changesPayload.meta?.pages_fetched) ?? 1)
|
|
3284
|
+
})
|
|
3285
|
+
});
|
|
3286
|
+
}
|
|
3287
|
+
function filterProgramScopeDeltaChanges(changes, input) {
|
|
3288
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
3289
|
+
return changes
|
|
3290
|
+
.filter((change) => !input.change_type || stringField(change, "change_type") === input.change_type)
|
|
3291
|
+
.filter((change) => sinceTimestamp === undefined || timestampField(change, "changed_at") >= sinceTimestamp)
|
|
3292
|
+
.filter((change) => {
|
|
3293
|
+
const target = readObject(change.target);
|
|
3294
|
+
return !input.target_type || (target !== undefined && targetMatchesRequestedType(target, input.target_type));
|
|
3295
|
+
})
|
|
3296
|
+
.filter((change) => {
|
|
3297
|
+
const target = readObject(change.target);
|
|
3298
|
+
return input.language_tags.length === 0 || (target !== undefined && targetMatchesAnySignal(target, input.language_tags));
|
|
3299
|
+
});
|
|
3300
|
+
}
|
|
3077
3301
|
async function getRecentTargetActivity(client, config, input) {
|
|
3078
3302
|
const query = toQuery({
|
|
3079
3303
|
change_type: input.change_type,
|
|
@@ -3481,32 +3705,45 @@ async function getProgramDelta(client, config, input) {
|
|
|
3481
3705
|
meta: changes.meta
|
|
3482
3706
|
};
|
|
3483
3707
|
}
|
|
3484
|
-
async function fetchProgramChanges(client, config, programId, pageSize, includeRemoved, includeIneligible, includeOutOfScope) {
|
|
3708
|
+
async function fetchProgramChanges(client, config, programId, pageSize, includeRemoved, includeIneligible, includeOutOfScope, maxChanges = pageSize) {
|
|
3485
3709
|
const handle = programSearchText(programId);
|
|
3486
|
-
const
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3710
|
+
const changes = [];
|
|
3711
|
+
const sourceRequests = [];
|
|
3712
|
+
let meta;
|
|
3713
|
+
const maxPages = Math.max(1, Math.ceil(maxChanges / pageSize));
|
|
3714
|
+
for (let page = 1; page <= maxPages && changes.length < maxChanges; page += 1) {
|
|
3715
|
+
const api = await client.getRecentChanges({
|
|
3716
|
+
search: handle.length >= 2 ? handle : programId,
|
|
3717
|
+
include_removed: includeRemoved,
|
|
3718
|
+
include_ineligible: includeIneligible,
|
|
3719
|
+
include_out_of_scope: includeOutOfScope,
|
|
3720
|
+
page,
|
|
3721
|
+
page_size: pageSize
|
|
3722
|
+
});
|
|
3723
|
+
const data = readObject(api.data);
|
|
3724
|
+
const pageMeta = readObject(sanitizeJson(data?.meta));
|
|
3725
|
+
const rows = readArray(data?.results);
|
|
3726
|
+
meta = {
|
|
3727
|
+
...(meta ?? {}),
|
|
3728
|
+
...(pageMeta ?? {}),
|
|
3729
|
+
pages_fetched: page
|
|
3730
|
+
};
|
|
3731
|
+
sourceRequests.push({
|
|
3732
|
+
source: "recent_changes",
|
|
3733
|
+
request_id: api.requestId,
|
|
3734
|
+
upstream_request_id: api.upstreamRequestId,
|
|
3735
|
+
page,
|
|
3736
|
+
...apiSourceMetadata(api)
|
|
3737
|
+
});
|
|
3738
|
+
changes.push(...rows
|
|
3739
|
+
.map((change) => sanitizeChange(change, config.webBaseUrl))
|
|
3740
|
+
.filter((change) => stringField(readObject(change.program), "id") === programId));
|
|
3741
|
+
const totalPages = readNumber(pageMeta?.total_pages);
|
|
3742
|
+
if (rows.length === 0 || (totalPages !== undefined && page >= totalPages)) {
|
|
3743
|
+
break;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
return { changes: changes.slice(0, maxChanges), meta, sourceRequests };
|
|
3510
3747
|
}
|
|
3511
3748
|
function addProgramResourceLinks(program) {
|
|
3512
3749
|
const id = stringField(program, "id");
|
|
@@ -4100,6 +4337,23 @@ function programFetchError(programId, error) {
|
|
|
4100
4337
|
suggested_fix: "Retry the program lookup or verify the BBRadar program id."
|
|
4101
4338
|
};
|
|
4102
4339
|
}
|
|
4340
|
+
function apiOperationError(operation, error) {
|
|
4341
|
+
if (error instanceof BBRadarApiError) {
|
|
4342
|
+
return stripUndefined({
|
|
4343
|
+
operation,
|
|
4344
|
+
request_id: error.requestId,
|
|
4345
|
+
upstream_request_id: error.upstreamRequestId,
|
|
4346
|
+
status: error.status,
|
|
4347
|
+
message: error.message,
|
|
4348
|
+
detail: sanitizeJson(error.detail),
|
|
4349
|
+
errors: sanitizeJson(error.errors)
|
|
4350
|
+
});
|
|
4351
|
+
}
|
|
4352
|
+
return {
|
|
4353
|
+
operation,
|
|
4354
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4103
4357
|
function programSearchText(programId) {
|
|
4104
4358
|
const colonIndex = programId.indexOf(":");
|
|
4105
4359
|
const handle = colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
@@ -4963,6 +5217,21 @@ function toRateLimitPayload(decision) {
|
|
|
4963
5217
|
function rateLimitForTool(toolName, config) {
|
|
4964
5218
|
return toolName === "export_targets" ? config.exportRateLimitPerMinute : config.defaultRateLimitPerMinute;
|
|
4965
5219
|
}
|
|
5220
|
+
function normalizeStringListInput(value) {
|
|
5221
|
+
if (typeof value === "string") {
|
|
5222
|
+
return splitFilterList(value);
|
|
5223
|
+
}
|
|
5224
|
+
if (Array.isArray(value)) {
|
|
5225
|
+
return value.flatMap((entry) => (typeof entry === "string" ? splitFilterList(entry) : [entry]));
|
|
5226
|
+
}
|
|
5227
|
+
return value;
|
|
5228
|
+
}
|
|
5229
|
+
function splitFilterList(value) {
|
|
5230
|
+
return value
|
|
5231
|
+
.split(",")
|
|
5232
|
+
.map((entry) => entry.trim())
|
|
5233
|
+
.filter((entry) => entry.length > 0);
|
|
5234
|
+
}
|
|
4966
5235
|
function toQuery(values) {
|
|
4967
5236
|
return compactRecord(values);
|
|
4968
5237
|
}
|