@bbradar/mcp 0.1.6 → 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 +296 -63
- 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);
|
|
@@ -2827,7 +2835,7 @@ async function getProgramTargetsPayload(client, input, programId) {
|
|
|
2827
2835
|
});
|
|
2828
2836
|
}
|
|
2829
2837
|
async function getProgramTargetsExportFallbackPayload(client, input, programId) {
|
|
2830
|
-
const exportLookup = await
|
|
2838
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, programId);
|
|
2831
2839
|
const sanitizedTargets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, input));
|
|
2832
2840
|
const targets = sanitizedTargets.slice(input.offset, input.offset + input.limit);
|
|
2833
2841
|
return stripUndefined({
|
|
@@ -2853,6 +2861,42 @@ async function getProgramTargetsExportFallbackPayload(client, input, programId)
|
|
|
2853
2861
|
})
|
|
2854
2862
|
});
|
|
2855
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
|
+
}
|
|
2856
2900
|
async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
2857
2901
|
try {
|
|
2858
2902
|
const api = await client.exportTargets({
|
|
@@ -2908,6 +2952,116 @@ async function fetchProgramTargetsFromExportFallback(client, programId) {
|
|
|
2908
2952
|
};
|
|
2909
2953
|
}
|
|
2910
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
|
+
}
|
|
2911
3065
|
function readExportTargetRows(data) {
|
|
2912
3066
|
if (Array.isArray(data)) {
|
|
2913
3067
|
return data;
|
|
@@ -2958,7 +3112,7 @@ async function getProgramScopeSummary(client, config, input) {
|
|
|
2958
3112
|
};
|
|
2959
3113
|
}
|
|
2960
3114
|
async function getProgramScopeSummaryFromIndexFallback(client, indexFallback, input) {
|
|
2961
|
-
const exportLookup = await
|
|
3115
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
2962
3116
|
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
2963
3117
|
include_out_of_scope: input.include_out_of_scope,
|
|
2964
3118
|
include_ineligible: input.include_ineligible,
|
|
@@ -3031,7 +3185,7 @@ async function getProgramTargetBreakdown(client, config, input) {
|
|
|
3031
3185
|
};
|
|
3032
3186
|
}
|
|
3033
3187
|
async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback, input) {
|
|
3034
|
-
const exportLookup = await
|
|
3188
|
+
const exportLookup = await fetchProgramTargetsWithEndpointFallback(client, input.program_id);
|
|
3035
3189
|
const targets = exportLookup.rawTargets.filter((target) => targetHasAllowedScope(target, {
|
|
3036
3190
|
include_out_of_scope: input.include_out_of_scope,
|
|
3037
3191
|
include_ineligible: input.include_ineligible,
|
|
@@ -3069,19 +3223,8 @@ async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback,
|
|
|
3069
3223
|
async function getProgramScopeDelta(client, config, input) {
|
|
3070
3224
|
const programApi = await client.getProgram(input.program_id);
|
|
3071
3225
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
3072
|
-
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);
|
|
3073
|
-
const
|
|
3074
|
-
const filteredChanges = changesPayload.changes
|
|
3075
|
-
.filter((change) => !input.change_type || stringField(change, "change_type") === input.change_type)
|
|
3076
|
-
.filter((change) => sinceTimestamp === undefined || timestampField(change, "changed_at") >= sinceTimestamp)
|
|
3077
|
-
.filter((change) => {
|
|
3078
|
-
const target = readObject(change.target);
|
|
3079
|
-
return !input.target_type || (target !== undefined && targetMatchesRequestedType(target, input.target_type));
|
|
3080
|
-
})
|
|
3081
|
-
.filter((change) => {
|
|
3082
|
-
const target = readObject(change.target);
|
|
3083
|
-
return input.language_tags.length === 0 || (target !== undefined && targetMatchesAnySignal(target, input.language_tags));
|
|
3084
|
-
});
|
|
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);
|
|
3085
3228
|
const limitedChanges = filteredChanges.slice(0, input.max_targets);
|
|
3086
3229
|
return stripUndefined({
|
|
3087
3230
|
request_id: randomUUID(),
|
|
@@ -3106,10 +3249,55 @@ async function getProgramScopeDelta(client, config, input) {
|
|
|
3106
3249
|
change_type: input.change_type,
|
|
3107
3250
|
target_type: input.target_type,
|
|
3108
3251
|
language_tags: input.language_tags.length > 0 ? input.language_tags : undefined,
|
|
3109
|
-
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)
|
|
3256
|
+
})
|
|
3257
|
+
});
|
|
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)
|
|
3110
3284
|
})
|
|
3111
3285
|
});
|
|
3112
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
|
+
}
|
|
3113
3301
|
async function getRecentTargetActivity(client, config, input) {
|
|
3114
3302
|
const query = toQuery({
|
|
3115
3303
|
change_type: input.change_type,
|
|
@@ -3517,32 +3705,45 @@ async function getProgramDelta(client, config, input) {
|
|
|
3517
3705
|
meta: changes.meta
|
|
3518
3706
|
};
|
|
3519
3707
|
}
|
|
3520
|
-
async function fetchProgramChanges(client, config, programId, pageSize, includeRemoved, includeIneligible, includeOutOfScope) {
|
|
3708
|
+
async function fetchProgramChanges(client, config, programId, pageSize, includeRemoved, includeIneligible, includeOutOfScope, maxChanges = pageSize) {
|
|
3521
3709
|
const handle = programSearchText(programId);
|
|
3522
|
-
const
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
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 };
|
|
3546
3747
|
}
|
|
3547
3748
|
function addProgramResourceLinks(program) {
|
|
3548
3749
|
const id = stringField(program, "id");
|
|
@@ -4136,6 +4337,23 @@ function programFetchError(programId, error) {
|
|
|
4136
4337
|
suggested_fix: "Retry the program lookup or verify the BBRadar program id."
|
|
4137
4338
|
};
|
|
4138
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
|
+
}
|
|
4139
4357
|
function programSearchText(programId) {
|
|
4140
4358
|
const colonIndex = programId.indexOf(":");
|
|
4141
4359
|
const handle = colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
@@ -4999,6 +5217,21 @@ function toRateLimitPayload(decision) {
|
|
|
4999
5217
|
function rateLimitForTool(toolName, config) {
|
|
5000
5218
|
return toolName === "export_targets" ? config.exportRateLimitPerMinute : config.defaultRateLimitPerMinute;
|
|
5001
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
|
+
}
|
|
5002
5235
|
function toQuery(values) {
|
|
5003
5236
|
return compactRecord(values);
|
|
5004
5237
|
}
|