/**
* Tool Executor - Execute Claude tool calls via GraphQL
*
* Maps Claude tool calls to GraphQL queries against Neo4j database
* with intelligent caching to reduce API costs.
*/
import { apolloClient } from './apollo-client';
import {
SEARCH_MPS,
GET_MP,
GET_MP_SCORECARD,
GET_MP_SPEECHES,
SEARCH_BILLS,
GET_BILL,
GET_LATEST_BILL_BY_NUMBER,
GET_BILL_LOBBYING,
GET_BILL_DEBATES,
SEARCH_HANSARD,
GET_COMMITTEES,
GET_COMMITTEE,
GET_COMMITTEE_TESTIMONY,
GET_TOP_SPENDERS,
GET_CONFLICTS_OF_INTEREST,
SEARCH_LOBBY_REGISTRATIONS,
GET_RECENT_DEBATES,
// Government Spending queries
SEARCH_FEDERAL_CONTRACTS,
GET_TOP_CONTRACTORS,
SEARCH_POLITICAL_CONTRIBUTIONS,
GET_TOP_POLITICAL_DONORS,
GET_PARTY_FUNDRAISING,
SEARCH_FEDERAL_GRANTS,
GET_TOP_GRANT_RECIPIENTS,
GET_GRANT_PROGRAM_SPENDING,
TRACE_MONEY_FLOW,
ANALYZE_MP_FINANCES,
COMPARE_PARTY_FUNDING,
GOVERNMENT_SPENDING_ANALYSIS,
ANALYZE_INDUSTRY_INFLUENCE,
CONFLICT_OF_INTEREST_CHECK,
// Petitions queries
SEARCH_PETITIONS,
GET_PETITION_DETAILS,
GET_MP_PETITIONS,
// Advanced MP Analysis queries
GET_MP_VOTING_HISTORY,
GET_VOTE_DETAILS,
ANALYZE_PARTY_DISCIPLINE,
COMPARE_MP_PERFORMANCE,
TRACK_COMMITTEE_ACTIVITY,
ANALYZE_MP_VOTING_PARTICIPATION,
// Bill Analysis queries
COMPARE_PARTY_BILLS,
GET_BILL_LEGISLATIVE_PROGRESS,
ANALYZE_MP_BILLS,
// Lobbying queries
SEARCH_LOBBYING_COMMUNICATIONS,
GET_TOP_LOBBYING_CLIENTS,
// Cross-source search
SEARCH_TOPIC_ACROSS_SOURCES,
// GC InfoBase queries
GET_DEPARTMENT_SPENDING,
GET_DEPARTMENT_RESULTS,
COMPARE_PLAN_VS_ACTUAL,
GET_TOP_SPENDING_DEPARTMENTS,
SEARCH_GC_INFOBASE,
// ATIP queries
SEARCH_ATIP_REQUESTS,
GET_ATIP_STATS_BY_ORGANIZATION,
GET_ATIP_TRENDS,
// Consultations queries
SEARCH_CONSULTATIONS,
GET_OPEN_CONSULTATIONS,
GET_CONSULTATION_STATS_BY_ORGANIZATION,
// Departmental expenses queries
SEARCH_DEPARTMENTAL_TRAVEL,
SEARCH_DEPARTMENTAL_HOSPITALITY,
GET_TOP_TRAVELERS,
GET_DEPARTMENT_TRAVEL_SPENDING,
GET_DEPARTMENT_HOSPITALITY_SPENDING,
} from './queries';
import { toolCache } from './toolCache';
export interface ToolResult {
success: boolean;
data?: any;
error?: string;
cached?: boolean; // Indicates if result came from cache
_billContext?: {
number: string;
session: string;
title?: string;
};
}
/**
* Check if a tool is a bill-related tool
*/
function isBillTool(toolName: string): boolean {
return ['get_bill', 'get_bill_lobbying', 'get_bill_debates', 'search_bills'].includes(toolName);
}
/**
* Execute a tool call by name with given parameters
* Checks cache first, then executes and caches the result
*/
export async function executeToolCall(
toolName: string,
input: Record<string, any>
): Promise<ToolResult> {
// Check cache first
const cachedResult = toolCache.get(toolName, input);
if (cachedResult) {
// For bill tools, always ensure _billContext is present (even for cached results)
// so users can share their conversation to discussions
const result = { ...cachedResult, cached: true };
if (isBillTool(toolName) && !result._billContext && input.billNumber) {
result._billContext = {
number: input.billNumber,
session: input.session,
title: cachedResult.data?.title_en || cachedResult.data?.title_fr || cachedResult.data?.short_title_en,
};
}
return result;
}
// Cache miss - execute the tool
try {
console.log(`[ToolExecutor] Executing tool: ${toolName}`, input);
let result: ToolResult;
switch (toolName) {
// ============================================
// MP Tools
// ============================================
case 'search_mps': {
const { data } = await apolloClient.query({
query: SEARCH_MPS,
variables: {
searchTerm: input.searchTerm,
party: input.party,
current: input.current ?? true,
cabinetOnly: input.cabinetOnly ?? false,
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.searchMPs };
break;
}
case 'get_mp': {
const { data } = await apolloClient.query({
query: GET_MP,
variables: { id: input.mpId },
});
result = { success: true, data: data.mps[0] };
break;
}
case 'get_mp_scorecard': {
const { data } = await apolloClient.query({
query: GET_MP_SCORECARD,
variables: { mpId: input.mpId },
});
result = { success: true, data: data.mpScorecard };
break;
}
case 'get_mp_speeches': {
const { data } = await apolloClient.query({
query: GET_MP_SPEECHES,
variables: {
mpId: input.mpId,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.mps[0]?.statements };
break;
}
// ============================================
// Bill Tools
// ============================================
case 'search_bills': {
const { data } = await apolloClient.query({
query: SEARCH_BILLS,
variables: {
searchTerm: input.searchTerm,
session: input.session,
status: input.status,
billType: input.billType,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.searchBills };
break;
}
case 'get_bill': {
let bill;
let sessionUsed = input.session;
if (input.session) {
// Session specified - use exact query
const { data } = await apolloClient.query({
query: GET_BILL,
variables: {
number: input.billNumber,
session: input.session,
},
});
bill = data.bills[0];
} else {
// No session specified - get the latest version of this bill
const { data } = await apolloClient.query({
query: GET_LATEST_BILL_BY_NUMBER,
variables: {
number: input.billNumber,
},
});
bill = data.bills[0];
sessionUsed = bill?.session;
}
result = {
success: true,
data: bill,
_billContext: bill ? {
number: input.billNumber,
session: sessionUsed || input.session,
title: bill.title || bill.title_fr || bill.short_title_en || bill.short_title_fr,
} : undefined,
};
break;
}
case 'get_bill_lobbying': {
const { data } = await apolloClient.query({
query: GET_BILL_LOBBYING,
variables: {
billNumber: input.billNumber,
session: input.session,
},
});
result = {
success: true,
data: data.billLobbying,
_billContext: {
number: input.billNumber,
session: input.session,
},
};
break;
}
case 'get_bill_debates': {
const { data } = await apolloClient.query({
query: GET_BILL_DEBATES,
variables: {
billNumber: input.billNumber,
session: input.session,
limit: input.limit ?? 50,
},
});
result = {
success: true,
data: data.bills[0]?.mentioned_in,
_billContext: {
number: input.billNumber,
session: input.session,
title: data.bills[0]?.title_en || data.bills[0]?.title_fr,
},
};
break;
}
// ============================================
// Hansard/Debate Tools
// ============================================
case 'search_hansard': {
const { data } = await apolloClient.query({
query: SEARCH_HANSARD,
variables: {
query: input.searchTerm,
limit: input.limit ?? 50,
language: input.language ?? 'en',
},
});
// Post-process to filter by MP, dates, document type, procedural flag, committee
let results = data.searchHansard;
if (input.mpId && results) {
results = results.filter((s: any) => s.madeBy?.id === input.mpId);
}
if (input.documentType && results) {
results = results.filter((s: any) => s.partOf?.document_type === input.documentType);
}
if (input.excludeProcedural && results) {
results = results.filter((s: any) => s.procedural !== true);
}
if (input.committeeCode && results) {
// For committee filtering, we need to check if the document is Evidence (type E)
// and if it's presented to the specified committee
results = results.filter((s: any) => {
// Only filter Evidence documents by committee
if (s.partOf?.document_type !== 'E') return false;
return s.partOf?.presentedTo?.code === input.committeeCode;
});
}
if (input.startDate && results) {
results = results.filter((s: any) => s.partOf?.date >= input.startDate);
}
if (input.endDate && results) {
results = results.filter((s: any) => s.partOf?.date <= input.endDate);
}
// If there are results, automatically include navigation suggestion
if (results && results.length > 0) {
// Build URL with same parameters
const params = new URLSearchParams();
if (input.searchTerm) params.set('q', input.searchTerm);
if (input.mpId) params.set('mp', input.mpId);
if (input.documentType) params.set('docType', input.documentType);
if (input.excludeProcedural) params.set('excludeProcedural', 'true');
if (input.startDate) params.set('startDate', input.startDate);
if (input.endDate) params.set('endDate', input.endDate);
const url = `/hansard?${params.toString()}`;
result = {
success: true,
data: {
results: results,
_navigation: {
url,
message: `Found ${results.length} speeches. View them on a dedicated page with advanced filtering.`,
},
},
};
} else {
result = { success: true, data: results };
}
break;
}
case 'get_recent_debates': {
const { data } = await apolloClient.query({
query: GET_RECENT_DEBATES,
variables: {
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.recentDebates };
}
// ============================================
// Committee Tools
// ============================================
case 'get_committees': {
const { data } = await apolloClient.query({
query: GET_COMMITTEES,
variables: {
current: input.current ?? true,
},
});
result = { success: true, data: data.committees };
}
case 'get_committee': {
const { data } = await apolloClient.query({
query: GET_COMMITTEE,
variables: { code: input.committeeCode },
});
result = { success: true, data: data.committees[0] };
}
case 'get_committee_testimony': {
const { data } = await apolloClient.query({
query: GET_COMMITTEE_TESTIMONY,
variables: {
committeeCode: input.committeeCode,
startDate: input.startDate,
endDate: input.endDate,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.committees[0]?.testimony };
}
// ============================================
// Accountability/Lobbying Tools
// ============================================
case 'get_top_spenders': {
const { data } = await apolloClient.query({
query: GET_TOP_SPENDERS,
variables: {
fiscalYear: input.fiscalYear,
quarter: input.quarter,
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.topSpenders };
}
case 'detect_conflicts_of_interest': {
const { data } = await apolloClient.query({
query: GET_CONFLICTS_OF_INTEREST,
variables: {
mpId: input.mpId,
billNumber: input.billNumber,
},
});
result = { success: true, data: data.conflictsOfInterest };
}
case 'search_lobby_registrations': {
const { data } = await apolloClient.query({
query: SEARCH_LOBBY_REGISTRATIONS,
variables: {
clientName: input.clientName,
lobbyistName: input.lobbyistName,
subjectMatter: input.subjectMatter,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.searchLobbyRegistrations };
break;
}
// ============================================
// Government Spending Tools
// ============================================
case 'search_federal_contracts': {
const { data } = await apolloClient.query({
query: SEARCH_FEDERAL_CONTRACTS,
variables: {
vendorName: input.vendorName,
department: input.department,
year: input.year,
minAmount: input.minAmount,
maxAmount: input.maxAmount,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.contracts };
break;
}
case 'get_top_contractors': {
const { data } = await apolloClient.query({
query: GET_TOP_CONTRACTORS,
variables: {
department: input.department,
year: input.year,
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.topContractors };
break;
}
case 'search_political_contributions': {
const { data } = await apolloClient.query({
query: SEARCH_POLITICAL_CONTRIBUTIONS,
variables: {
donorName: input.donorName,
party: input.party,
year: input.year,
minAmount: input.minAmount,
maxAmount: input.maxAmount,
province: input.province,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.donations };
break;
}
case 'get_top_political_donors': {
const { data } = await apolloClient.query({
query: GET_TOP_POLITICAL_DONORS,
variables: {
party: input.party,
year: input.year,
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.topPoliticalDonors };
break;
}
case 'get_party_fundraising': {
const { data } = await apolloClient.query({
query: GET_PARTY_FUNDRAISING,
variables: {
year: input.year,
},
});
result = { success: true, data: data.partyFundraising };
break;
}
case 'search_federal_grants': {
const { data } = await apolloClient.query({
query: SEARCH_FEDERAL_GRANTS,
variables: {
recipientName: input.recipientName,
programName: input.programName,
department: input.department,
province: input.province,
year: input.year,
minAmount: input.minAmount,
maxAmount: input.maxAmount,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.grants };
break;
}
case 'get_top_grant_recipients': {
const { data } = await apolloClient.query({
query: GET_TOP_GRANT_RECIPIENTS,
variables: {
program: input.programName,
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.topGrantRecipients };
break;
}
case 'get_grant_program_spending': {
const { data } = await apolloClient.query({
query: GET_GRANT_PROGRAM_SPENDING,
variables: {
year: input.year,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.grantProgramSpending };
break;
}
case 'trace_money_flow': {
const { data } = await apolloClient.query({
query: TRACE_MONEY_FLOW,
variables: {
entityName: input.entityName,
year: input.year,
includeContracts: input.includeContracts ?? true,
includeGrants: input.includeGrants ?? true,
includeContributions: input.includeContributions ?? true,
includeLobbying: input.includeLobbying ?? true,
},
});
result = { success: true, data: data.traceMoneyFlow };
break;
}
case 'analyze_mp_finances': {
const { data } = await apolloClient.query({
query: ANALYZE_MP_FINANCES,
variables: {
mpId: input.mpId || input.mpName,
fiscalYear: input.fiscalYear,
includeLobbying: input.includeLobbying ?? true,
},
});
result = { success: true, data: data.analyzeMPFinances };
break;
}
case 'compare_party_funding': {
const { data } = await apolloClient.query({
query: COMPARE_PARTY_FUNDING,
variables: {
year: input.year,
includeDonorAnalysis: input.includeDonorAnalysis ?? true,
minDonation: input.minDonation ?? 0,
},
});
result = { success: true, data: data.comparePartyFunding };
break;
}
case 'government_spending_analysis': {
const { data } = await apolloClient.query({
query: GOVERNMENT_SPENDING_ANALYSIS,
variables: {
departmentName: input.departmentName,
year: input.year,
focus: input.focus ?? 'all',
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.governmentSpendingAnalysis };
break;
}
case 'analyze_industry_influence': {
const { data } = await apolloClient.query({
query: ANALYZE_INDUSTRY_INFLUENCE,
variables: {
industryKeyword: input.industryKeyword,
limit: input.limit ?? 10,
},
});
result = { success: true, data: data.analyzeIndustryInfluence };
break;
}
case 'conflict_of_interest_check': {
const { data } = await apolloClient.query({
query: CONFLICT_OF_INTEREST_CHECK,
variables: {
entityName: input.entityName,
year: input.year,
thresholdAmount: input.thresholdAmount ?? 100000,
},
});
result = { success: true, data: data.conflictOfInterestCheck };
break;
}
// ============================================
// Petitions Tools
// ============================================
case 'search_petitions': {
const { data } = await apolloClient.query({
query: SEARCH_PETITIONS,
variables: {
keyword: input.keyword,
sponsorName: input.sponsorName,
category: input.category,
status: input.status,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.petitions };
break;
}
case 'get_petition_details': {
const { data } = await apolloClient.query({
query: GET_PETITION_DETAILS,
variables: {
petitionNumber: input.petitionNumber,
},
});
result = { success: true, data: data.petitions[0] };
break;
}
case 'get_mp_petitions': {
const { data } = await apolloClient.query({
query: GET_MP_PETITIONS,
variables: {
mpId: input.mpId,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.mps[0]?.sponsoredPetitions };
break;
}
// ============================================
// Advanced MP Analysis Tools
// ============================================
case 'get_mp_voting_history': {
const { data } = await apolloClient.query({
query: GET_MP_VOTING_HISTORY,
variables: {
mpId: input.mpId,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.mps[0]?.votedConnection?.edges };
break;
}
case 'get_vote_details': {
const { data } = await apolloClient.query({
query: GET_VOTE_DETAILS,
variables: {
voteId: input.voteId,
includeBallots: input.includeBallots ?? true,
},
});
result = { success: true, data: data.votes[0] };
break;
}
case 'analyze_party_discipline': {
const { data } = await apolloClient.query({
query: ANALYZE_PARTY_DISCIPLINE,
variables: {
party: input.party,
},
});
result = { success: true, data: data.partyDisciplineAnalysis };
break;
}
case 'compare_mp_performance': {
const { data } = await apolloClient.query({
query: COMPARE_MP_PERFORMANCE,
variables: {
mpIds: input.mpIds,
},
});
result = { success: true, data: data.compareMPPerformance };
break;
}
case 'track_committee_activity': {
const { data } = await apolloClient.query({
query: TRACK_COMMITTEE_ACTIVITY,
variables: {
mpId: input.mpId,
},
});
result = { success: true, data: data.mpCommitteeActivity };
break;
}
case 'analyze_mp_voting_participation': {
const { data } = await apolloClient.query({
query: ANALYZE_MP_VOTING_PARTICIPATION,
variables: {
mpId: input.mpId,
},
});
result = { success: true, data: data.mpVotingParticipation };
break;
}
// ============================================
// Bill Analysis Tools
// ============================================
case 'compare_party_bills': {
const { data } = await apolloClient.query({
query: COMPARE_PARTY_BILLS,
});
result = { success: true, data: data.partyBillsComparison };
break;
}
case 'get_bill_legislative_progress': {
const { data } = await apolloClient.query({
query: GET_BILL_LEGISLATIVE_PROGRESS,
variables: {
billNumber: input.billNumber,
session: input.session,
},
});
result = { success: true, data: data.bills[0] };
break;
}
case 'analyze_mp_bills': {
const { data } = await apolloClient.query({
query: ANALYZE_MP_BILLS,
variables: {
mpId: input.mpId,
},
});
result = { success: true, data: data.mps[0] };
break;
}
// ============================================
// Extended Lobbying Tools
// ============================================
case 'search_lobbying_communications': {
const { data } = await apolloClient.query({
query: SEARCH_LOBBYING_COMMUNICATIONS,
variables: {
clientName: input.clientName,
lobbyistName: input.lobbyistName,
subjectKeyword: input.subjectKeyword,
institution: input.institution,
dateFrom: input.dateFrom,
dateTo: input.dateTo,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.lobbyCommunications };
break;
}
case 'get_top_lobbying_clients': {
const { data } = await apolloClient.query({
query: GET_TOP_LOBBYING_CLIENTS,
variables: {
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.topLobbyingClients };
break;
}
// ============================================
// Cross-Source Search Tools
// ============================================
case 'search_topic_across_sources': {
const { data } = await apolloClient.query({
query: SEARCH_TOPIC_ACROSS_SOURCES,
variables: {
searchTerm: input.topic,
sources: input.sources,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.searchAcrossSources };
break;
}
// ============================================
// GC InfoBase Tools (Departmental Performance & Spending)
// ============================================
case 'get_department_spending': {
const { data } = await apolloClient.query({
query: GET_DEPARTMENT_SPENDING,
variables: {
department: input.department,
fiscalYear: input.fiscalYear,
program: input.program,
},
});
result = { success: true, data: data.getDepartmentSpending };
break;
}
case 'get_department_results': {
const { data } = await apolloClient.query({
query: GET_DEPARTMENT_RESULTS,
variables: {
department: input.department,
fiscalYear: input.fiscalYear,
program: input.program,
},
});
result = { success: true, data: data.getDepartmentResults };
break;
}
case 'compare_plan_vs_actual': {
const { data } = await apolloClient.query({
query: COMPARE_PLAN_VS_ACTUAL,
variables: {
department: input.department,
fiscalYear: input.fiscalYear,
},
});
result = { success: true, data: data.comparePlanVsActual };
break;
}
case 'get_top_spending_departments': {
const { data } = await apolloClient.query({
query: GET_TOP_SPENDING_DEPARTMENTS,
variables: {
fiscalYear: input.fiscalYear,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getTopSpendingDepartments };
break;
}
case 'search_gc_infobase': {
const { data } = await apolloClient.query({
query: SEARCH_GC_INFOBASE,
variables: {
query: input.query,
fiscalYear: input.fiscalYear,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.searchGCInfoBase };
break;
}
// ============================================
// ATIP Request Tools (Access to Information)
// ============================================
case 'search_atip_requests': {
const { data } = await apolloClient.query({
query: SEARCH_ATIP_REQUESTS,
variables: {
query: input.query,
organization: input.organization,
year: input.year,
disposition: input.disposition,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.searchATIPRequests };
break;
}
case 'get_atip_organization_stats': {
const { data } = await apolloClient.query({
query: GET_ATIP_STATS_BY_ORGANIZATION,
variables: {
organization: input.organization,
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getATIPStatsByOrganization };
break;
}
case 'get_atip_trends': {
const { data } = await apolloClient.query({
query: GET_ATIP_TRENDS,
variables: {
organization: input.organization,
years: input.years ?? 5,
},
});
result = { success: true, data: data.getATIPTrends };
break;
}
// ============================================
// Government Consultations Tools
// ============================================
case 'search_consultations': {
const { data } = await apolloClient.query({
query: SEARCH_CONSULTATIONS,
variables: {
query: input.query,
organization: input.organization,
subject: input.subject,
status: input.status,
year: input.year,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.searchConsultations };
break;
}
case 'get_open_consultations': {
const { data } = await apolloClient.query({
query: GET_OPEN_CONSULTATIONS,
variables: {
organization: input.organization,
subject: input.subject,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.getOpenConsultations };
break;
}
case 'get_consultation_stats': {
const { data } = await apolloClient.query({
query: GET_CONSULTATION_STATS_BY_ORGANIZATION,
variables: {
organization: input.organization,
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getConsultationStatsByOrganization };
break;
}
// ============================================
// Departmental Travel & Hospitality Tools
// ============================================
case 'search_departmental_travel': {
const { data } = await apolloClient.query({
query: SEARCH_DEPARTMENTAL_TRAVEL,
variables: {
department: input.department,
name: input.name,
destination: input.destination,
year: input.year,
minAmount: input.minAmount,
disclosureGroup: input.disclosureGroup,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.searchDepartmentalTravel };
break;
}
case 'search_departmental_hospitality': {
const { data } = await apolloClient.query({
query: SEARCH_DEPARTMENTAL_HOSPITALITY,
variables: {
department: input.department,
name: input.name,
location: input.location,
year: input.year,
minAmount: input.minAmount,
disclosureGroup: input.disclosureGroup,
limit: input.limit ?? 50,
},
});
result = { success: true, data: data.searchDepartmentalHospitality };
break;
}
case 'get_top_travelers': {
const { data } = await apolloClient.query({
query: GET_TOP_TRAVELERS,
variables: {
department: input.department,
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getTopTravelers };
break;
}
case 'get_department_travel_spending': {
const { data } = await apolloClient.query({
query: GET_DEPARTMENT_TRAVEL_SPENDING,
variables: {
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getDepartmentTravelSpending };
break;
}
case 'get_department_hospitality_spending': {
const { data } = await apolloClient.query({
query: GET_DEPARTMENT_HOSPITALITY_SPENDING,
variables: {
year: input.year,
limit: input.limit ?? 20,
},
});
result = { success: true, data: data.getDepartmentHospitalitySpending };
break;
}
// ============================================
// Fact-Check Tools
// ============================================
case 'fact_check_claim': {
// Call the fact-check API endpoint
const response = await fetch('/api/factcheck/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
claim_text: input.claim,
mode: input.mode || 'fast',
}),
});
const data = await response.json();
if (data.success && data.factCheck) {
result = {
success: true,
data: {
verdict: data.factCheck.verdict,
confidence: data.factCheck.confidence,
rationale: data.factCheck.rationale,
rationale_short: data.factCheck.rationale_short,
citations: data.factCheck.citations,
cached: data.cached,
processing_time_ms: data.factCheck.processing_time_ms,
},
};
} else {
result = {
success: false,
error: data.error || 'Failed to verify claim',
};
}
break;
}
// ============================================
// Navigation Tools
// ============================================
case 'navigate_to_hansard': {
// Build URL with query parameters
const params = new URLSearchParams();
if (input.searchTerm) params.set('q', input.searchTerm);
if (input.mpId) params.set('mp', input.mpId);
if (input.party) params.set('party', input.party);
if (input.documentType) params.set('docType', input.documentType);
if (input.excludeProcedural) params.set('excludeProcedural', 'true');
if (input.startDate) params.set('startDate', input.startDate);
if (input.endDate) params.set('endDate', input.endDate);
const url = `/hansard?${params.toString()}`;
result = {
success: true,
data: {
_navigation: {
url,
message: `I've prepared a Hansard search page for you with ${input.searchTerm ? `"${input.searchTerm}"` : 'your search'}. Click the link below to view the full results with interactive cards and advanced filtering.`,
},
},
};
break;
}
default:
result = {
success: false,
error: `Unknown tool: ${toolName}`,
};
break;
}
// Cache successful results before returning
if (result.success) {
toolCache.set(toolName, input, result);
}
return result;
} catch (error) {
console.error(`[ToolExecutor] Error executing ${toolName}:`, error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
* Format tool result for Claude
*/
export function formatToolResult(result: ToolResult): string {
if (!result.success) {
return `Error: ${result.error}`;
}
if (!result.data) {
return 'No data found';
}
// Check if this is a navigation result with nested data
if (result.data._navigation) {
// If there's both results and navigation, show the results to Claude
// but indicate that navigation is available
if (result.data.results) {
return JSON.stringify({
...result.data.results,
_note: `Navigation prepared: ${result.data._navigation.message}`,
}, null, 2);
}
// If only navigation (no results), return navigation info
return JSON.stringify({
navigation: result.data._navigation,
message: 'Navigation link prepared for user',
}, null, 2);
}
// Return JSON string for Claude to interpret
return JSON.stringify(result.data, null, 2);
}
/**
* Check if a tool result contains navigation
*/
export function hasNavigation(result: ToolResult): boolean {
return result.success && result.data?._navigation !== undefined;
}
/**
* Extract navigation data from tool result
*/
export function extractNavigation(result: ToolResult): { url: string; message: string } | null {
if (hasNavigation(result)) {
return result.data._navigation;
}
return null;
}
/**
* Bill context type for discussion card
*/
export interface BillContext {
number: string;
session: string;
title?: string;
}
/**
* Check if a tool result contains bill context
*/
export function hasBillContext(result: ToolResult): boolean {
return result.success && result._billContext !== undefined;
}
/**
* Extract bill context from tool result
*/
export function extractBillContext(result: ToolResult): BillContext | null {
if (result.success && result._billContext) {
return result._billContext as BillContext;
}
return null;
}