From 75956b87774e0f531f39364ef286701a9d8b754e Mon Sep 17 00:00:00 2001 From: Nicolas Bouliol Date: Fri, 16 Jan 2026 14:15:20 +0100 Subject: [PATCH] feat(ai-proxy): add infogreffe tools --- .../src/integrations/infogreffe/tools.ts | 37 ++++++ .../tools/advanced-company-search.ts | 82 ++++++++++++ .../infogreffe/tools/assess-company-risk.ts | 123 ++++++++++++++++++ .../tools/check-company-radiation.ts | 80 ++++++++++++ .../tools/find-related-companies.ts | 92 +++++++++++++ .../infogreffe/tools/get-company-details.ts | 46 +++++++ .../tools/get-financial-indicators.ts | 68 ++++++++++ .../infogreffe/tools/search-new-companies.ts | 55 ++++++++ .../tools/search-radiated-companies.ts | 53 ++++++++ .../src/integrations/infogreffe/utils.ts | 39 ++++++ packages/ai-proxy/src/integrations/tools.ts | 7 + 11 files changed, 682 insertions(+) create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/advanced-company-search.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/assess-company-risk.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/check-company-radiation.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/find-related-companies.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/get-company-details.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/get-financial-indicators.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/search-new-companies.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/tools/search-radiated-companies.ts create mode 100644 packages/ai-proxy/src/integrations/infogreffe/utils.ts diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools.ts b/packages/ai-proxy/src/integrations/infogreffe/tools.ts new file mode 100644 index 000000000..7fdf72a6b --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools.ts @@ -0,0 +1,37 @@ +import RemoteTool from '../../remote-tool'; +import createAdvancedCompanySearchTool from './tools/advanced-company-search'; +import createAssessCompanyRiskTool from './tools/assess-company-risk'; +import createCheckCompanyRadiationTool from './tools/check-company-radiation'; +import createFindRelatedCompaniesTool from './tools/find-related-companies'; +import createGetCompanyDetailsTool from './tools/get-company-details'; +import createGetFinancialIndicatorsTool from './tools/get-financial-indicators'; +import createSearchNewCompaniesTool from './tools/search-new-companies'; +import createSearchRadiatedCompaniesTool from './tools/search-radiated-companies'; + +export interface InfogreffeConfig { + apiKey: string; +} + +export default function getInfogreffeTools(config: InfogreffeConfig): RemoteTool[] { + const headers: Record = { + Authorization: `Apikey ${config.apiKey}`, + }; + + return [ + createCheckCompanyRadiationTool(headers), + createSearchRadiatedCompaniesTool(headers), + createSearchNewCompaniesTool(headers), + createGetCompanyDetailsTool(headers), + createAssessCompanyRiskTool(headers), + createGetFinancialIndicatorsTool(headers), + createFindRelatedCompaniesTool(headers), + createAdvancedCompanySearchTool(headers), + ].map( + tool => + new RemoteTool({ + sourceId: 'infogreffe', + sourceType: 'server', + tool, + }), + ); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/advanced-company-search.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/advanced-company-search.ts new file mode 100644 index 000000000..b11396dda --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/advanced-company-search.ts @@ -0,0 +1,82 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { buildQuery, searchV1Api } from '../utils'; + +export default function createAdvancedCompanySearchTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_advanced_company_search', + description: + 'Advanced multi-criteria search for French companies with combined filters (year, city, sector, legal form, etc.)', + schema: z.object({ + ville: z.string().optional().describe('City (e.g., PARIS)'), + code_postal: z.string().optional().describe('Postal code (e.g., 75001)'), + secteur_d_activite: z.string().optional().describe('Business sector'), + code_ape: z.string().optional().describe('APE/NAF code (e.g., 6420Z)'), + forme_juridique: z.string().optional().describe('Legal form'), + status: z + .enum(['active', 'closed']) + .default('active') + .describe('Status: active (registered) or closed (struck-off)'), + year: z.enum(['2022', '2023', '2024', '2025']).default('2024').describe('Year'), + limit: z.number().int().positive().default(20).describe('Maximum results (default: 20)'), + }), + func: async ({ + ville, + code_postal, + secteur_d_activite, + code_ape, + forme_juridique, + status, + year, + limit, + }) => { + const dataset = + status === 'closed' + ? `entreprises-radiees-en-${year}` + : `entreprises-immatriculees-en-${year}`; + + const query = buildQuery([ + ville && `ville:${ville}`, + code_postal && `code_postal:${code_postal}`, + secteur_d_activite && `secteur_d_activite:"${secteur_d_activite}"`, + code_ape && `code_ape:${code_ape}`, + forme_juridique && `forme_juridique:"${forme_juridique}"`, + ]); + + const result = await searchV1Api(headers, dataset, query, limit); + + return JSON.stringify({ + criteres: { + ville, + code_postal, + secteur_d_activite, + code_ape, + forme_juridique, + status, + year, + }, + total_resultats: result.nhits, + resultats_affiches: result.records.length, + entreprises: result.records.map(r => { + const fields = r.fields as Record; + + return { + siren: fields.siren, + denomination: fields.denomination, + adresse: fields.adresse, + code_postal: fields.code_postal, + ville: fields.ville, + forme_juridique: fields.forme_juridique, + secteur_d_activite: fields.secteur_d_activite, + code_ape: fields.code_ape, + date_immatriculation: fields.date_immatriculation, + date_radiation: fields.date_radiation, + }; + }), + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/assess-company-risk.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/assess-company-risk.ts new file mode 100644 index 000000000..7bfa2ebb9 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/assess-company-risk.ts @@ -0,0 +1,123 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { searchV1Api } from '../utils'; + +type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; + +interface RiskFactor { + type: string; + severity: RiskLevel; + description: string; + date?: string; + date_immatriculation?: string; +} + +const RECOMMENDATIONS: Record = { + CRITICAL: 'DO NOT ONBOARD - Company struck off or major risk', + HIGH: 'CAUTION - Manual verification required', + MEDIUM: 'VIGILANCE - Monitor closely', + LOW: 'Acceptable - Low risk', +}; + +function calculateRiskLevel(score: number): RiskLevel { + if (score >= 80) return 'CRITICAL'; + if (score >= 50) return 'HIGH'; + if (score >= 25) return 'MEDIUM'; + + return 'LOW'; +} + +export default function createAssessCompanyRiskTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_assess_company_risk', + description: + 'Assess risk for a French company for KYC/KYB. Checks radiations, company age, etc. Returns a risk score', + schema: z + .object({ + siren: z.string().length(9).optional().describe('SIREN number (9 digits)'), + denomination: z.string().optional().describe('Company name'), + }) + .refine(data => data.siren || data.denomination, { + message: 'Either siren or denomination is required', + }), + func: async ({ siren, denomination }) => { + const riskFactors: RiskFactor[] = []; + let riskScore = 0; + + const query = siren ? `siren:${siren}` : `denomination:${denomination}`; + + const radiationResults = await Promise.all( + [2025, 2024].map(async year => { + const result = await searchV1Api(headers, `entreprises-radiees-en-${year}`, query, 1); + + return { year, result }; + }), + ); + + for (const { year, result } of radiationResults) { + if (result.nhits > 0) { + const fields = result.records[0].fields as Record; + riskScore += 100; + riskFactors.push({ + type: 'RADIATION', + severity: 'CRITICAL', + description: `Company struck off in ${year}`, + date: fields.date_radiation as string, + }); + break; + } + } + + if (siren) { + const result = await searchV1Api( + headers, + 'entreprises-immatriculees-en-2024', + `siren:${siren}`, + 1, + ); + + if (result.nhits > 0) { + const fields = result.records[0].fields as Record; + const dateImmat = fields.date_immatriculation as string; + + if (dateImmat) { + const immatDate = new Date(dateImmat); + const ageDays = Math.floor((Date.now() - immatDate.getTime()) / (1000 * 60 * 60 * 24)); + + if (ageDays < 90) { + riskScore += 30; + riskFactors.push({ + type: 'YOUNG_COMPANY', + severity: 'HIGH', + description: `Very recent company (${ageDays} days)`, + date_immatriculation: dateImmat, + }); + } else if (ageDays < 180) { + riskScore += 15; + riskFactors.push({ + type: 'YOUNG_COMPANY', + severity: 'MEDIUM', + description: `Recent company (${ageDays} days)`, + date_immatriculation: dateImmat, + }); + } + } + } + } + + const riskLevel = calculateRiskLevel(riskScore); + + return JSON.stringify({ + siren, + denomination, + risk_score: riskScore, + risk_level: riskLevel, + risk_factors: riskFactors, + recommendation: RECOMMENDATIONS[riskLevel], + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/check-company-radiation.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/check-company-radiation.ts new file mode 100644 index 000000000..02912e7eb --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/check-company-radiation.ts @@ -0,0 +1,80 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { searchV1Api } from '../utils'; + +const AVAILABLE_YEARS = [2025, 2024, 2023, 2022] as const; + +export default function createCheckCompanyRadiationTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_check_company_radiation', + description: + 'Check if a French company (by SIREN or name) is struck off from RCS. If no year is specified, checks all available years (2022-2025)', + schema: z + .object({ + siren: z.string().length(9).optional().describe('SIREN number (9 digits)'), + denomination: z.string().optional().describe('Company name'), + year: z + .enum(['2022', '2023', '2024', '2025']) + .optional() + .describe('Specific year to check (optional, checks all years if not provided)'), + }) + .refine(data => data.siren || data.denomination, { + message: 'Either siren or denomination is required', + }), + func: async ({ siren, denomination, year }) => { + const query = siren ? `siren:${siren}` : `denomination:${denomination}`; + const yearsToCheck = year ? [parseInt(year, 10)] : [...AVAILABLE_YEARS]; + + const results = await Promise.all( + yearsToCheck.map(async checkYear => { + const dataset = `entreprises-radiees-en-${checkYear}`; + const result = await searchV1Api(headers, dataset, query, 10); + + return { checkYear, result }; + }), + ); + + const allResults: Array> = []; + + for (const { checkYear, result } of results) { + if (result.nhits > 0) { + for (const r of result.records) { + const fields = r.fields as Record; + allResults.push({ + annee: checkYear, + siren: fields.siren, + denomination: fields.denomination, + date_radiation: fields.date_radiation, + date_immatriculation: fields.date_immatriculation, + forme_juridique: fields.forme_juridique, + ville: fields.ville, + code_postal: fields.code_postal, + adresse: fields.adresse, + secteur_d_activite: fields.secteur_d_activite, + greffe: fields.greffe, + }); + } + } + } + + if (allResults.length === 0) { + return JSON.stringify({ + est_radiee: false, + annees_verifiees: yearsToCheck, + critere: { siren, denomination }, + message: 'No radiation found for this company', + }); + } + + return JSON.stringify({ + est_radiee: true, + annees_verifiees: yearsToCheck, + nb_resultats: allResults.length, + entreprises: allResults, + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/find-related-companies.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/find-related-companies.ts new file mode 100644 index 000000000..0cb405e4d --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/find-related-companies.ts @@ -0,0 +1,92 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { searchV1Api } from '../utils'; + +export default function createFindRelatedCompaniesTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_find_related_companies', + description: 'Find related French companies (same address, same sector in same area, etc.)', + schema: z.object({ + siren: z.string().length(9).optional().describe('Reference company SIREN'), + adresse: z.string().optional().describe('Address to search'), + search_type: z + .enum(['same_address', 'same_sector']) + .default('same_address') + .describe('Search type: same_address or same_sector (same sector + city)'), + limit: z.number().int().positive().default(20).describe('Maximum results (default: 20)'), + }), + func: async ({ siren, adresse, search_type, limit }) => { + let searchAddress = adresse; + let ville: unknown; + let secteur: unknown; + + if (siren && !adresse) { + const result = await searchV1Api( + headers, + 'entreprises-immatriculees-en-2024', + `siren:${siren}`, + 1, + ); + + if (result.nhits > 0) { + const fields = result.records[0].fields as Record; + searchAddress = fields.adresse as string; + ville = fields.ville; + secteur = fields.secteur_d_activite; + } + } + + if (!searchAddress && search_type === 'same_address') { + return JSON.stringify({ + error: 'Address required or valid SIREN to lookup address', + }); + } + + let query: string; + + if (search_type === 'same_address') { + query = `adresse:"${searchAddress}"`; + } else { + if (!ville || !secteur) { + return JSON.stringify({ + error: 'Cannot determine city and sector for same_sector search', + }); + } + + query = `ville:${ville} AND secteur_d_activite:"${secteur}"`; + } + + const result = await searchV1Api(headers, 'entreprises-immatriculees-en-2024', query, limit); + + return JSON.stringify({ + critere_recherche: { + siren_reference: siren, + adresse: searchAddress, + type: search_type, + }, + total_resultats: result.nhits, + entreprises_liees: result.records + .filter(r => { + const fields = r.fields as Record; + + return fields.siren !== siren; + }) + .map(r => { + const fields = r.fields as Record; + + return { + siren: fields.siren, + denomination: fields.denomination, + adresse: fields.adresse, + ville: fields.ville, + forme_juridique: fields.forme_juridique, + date_immatriculation: fields.date_immatriculation, + }; + }), + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/get-company-details.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/get-company-details.ts new file mode 100644 index 000000000..ce86bfb21 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/get-company-details.ts @@ -0,0 +1,46 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { searchV1Api } from '../utils'; + +export default function createGetCompanyDetailsTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_get_company_details', + description: 'Get full details of a French company (struck-off or registered) by SIREN or name', + schema: z + .object({ + siren: z.string().length(9).optional().describe('SIREN number (9 digits)'), + denomination: z.string().optional().describe('Company name'), + year: z + .enum(['2022', '2023', '2024', '2025']) + .default('2024') + .describe('Year to search (default: 2024)'), + }) + .refine(data => data.siren || data.denomination, { + message: 'Either siren or denomination is required', + }), + func: async ({ siren, denomination, year }) => { + const query = siren ? `siren:${siren}` : `denomination:${denomination}`; + + const [resultRad, resultImmat] = await Promise.all([ + searchV1Api(headers, `entreprises-radiees-en-${year}`, query, 5), + searchV1Api(headers, `entreprises-immatriculees-en-${year}`, query, 5), + ]); + + return JSON.stringify({ + critere: { siren, denomination }, + annee_recherchee: year, + radiations: { + total: resultRad.nhits, + entreprises: resultRad.records.map(r => r.fields), + }, + immatriculations: { + total: resultImmat.nhits, + entreprises: resultImmat.records.map(r => r.fields), + }, + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/get-financial-indicators.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/get-financial-indicators.ts new file mode 100644 index 000000000..edb3fc1b6 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/get-financial-indicators.ts @@ -0,0 +1,68 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { searchV1Api } from '../utils'; + +export default function createGetFinancialIndicatorsTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_get_financial_indicators', + description: + 'Get financial indicators for a French company (revenue, operating result, headcount) for the last 3 fiscal years', + schema: z.object({ + siren: z.string().length(9).describe('SIREN number (9 digits)'), + }), + func: async ({ siren }) => { + const result = await searchV1Api(headers, 'chiffres-cles-2024', `siren:${siren}`, 1); + + if (result.nhits === 0) { + return JSON.stringify({ + siren, + found: false, + message: 'No financial data available for this company', + }); + } + + const fields = result.records[0].fields as Record; + + return JSON.stringify({ + siren, + found: true, + denomination: fields.denomination, + forme_juridique: fields.forme_juridique, + secteur: fields.libelle_ape, + ville: fields.ville, + exercices: { + millesime_1: { + annee: fields.millesime_1, + ca: fields.ca_1 ?? 'Confidential', + tranche_ca: fields.tranche_ca_millesime_1, + resultat: fields.resultat_1 ?? 'Confidential', + effectif: fields.effectif_1 ?? 'Confidential', + duree: fields.duree_1, + date_cloture: fields.date_de_cloture_exercice_1, + }, + millesime_2: { + annee: fields.millesime_2, + ca: fields.ca_2 ?? 'Confidential', + tranche_ca: fields.tranche_ca_millesime_2, + resultat: fields.resultat_2 ?? 'Confidential', + effectif: fields.effectif_2 ?? 'Confidential', + duree: fields.duree_2, + date_cloture: fields.date_de_cloture_exercice_2, + }, + millesime_3: { + annee: fields.millesime_3, + ca: fields.ca_3 ?? 'Confidential', + tranche_ca: fields.tranche_ca_millesime_3, + resultat: fields.resultat_3 ?? 'Confidential', + effectif: fields.effectif_3 ?? 'Confidential', + duree: fields.duree_3, + date_cloture: fields.date_de_cloture_exercice_3, + }, + }, + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/search-new-companies.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/search-new-companies.ts new file mode 100644 index 000000000..d233afae4 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/search-new-companies.ts @@ -0,0 +1,55 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { buildQuery, searchV1Api } from '../utils'; + +export default function createSearchNewCompaniesTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_search_new_companies', + description: 'Search for newly registered French companies by various criteria', + schema: z.object({ + year: z.enum(['2022', '2023', '2024', '2025']).default('2024').describe('Registration year'), + siren: z.string().length(9).optional().describe('SIREN number'), + denomination: z.string().optional().describe('Company name'), + ville: z.string().optional().describe('City'), + region: z.string().optional().describe('Region'), + secteur_d_activite: z.string().optional().describe('Business sector'), + limit: z.number().int().positive().default(10).describe('Maximum results (default: 10)'), + }), + func: async ({ year, siren, denomination, ville, region, secteur_d_activite, limit }) => { + const dataset = `entreprises-immatriculees-en-${year}`; + + const query = buildQuery([ + siren && `siren:${siren}`, + denomination && `denomination:${denomination}`, + ville && `ville:${ville}`, + region && `region:${region}`, + secteur_d_activite && `secteur_d_activite:${secteur_d_activite}`, + ]); + + const result = await searchV1Api(headers, dataset, query, limit); + + return JSON.stringify({ + annee: year, + total_resultats: result.nhits, + resultats_affiches: result.records.length, + entreprises: result.records.map(r => { + const fields = r.fields as Record; + + return { + siren: fields.siren, + denomination: fields.denomination, + date_immatriculation: fields.date_immatriculation, + ville: fields.ville, + forme_juridique: fields.forme_juridique, + secteur_d_activite: fields.secteur_d_activite, + adresse: fields.adresse, + code_postal: fields.code_postal, + }; + }), + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/tools/search-radiated-companies.ts b/packages/ai-proxy/src/integrations/infogreffe/tools/search-radiated-companies.ts new file mode 100644 index 000000000..b3c1f1a87 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/tools/search-radiated-companies.ts @@ -0,0 +1,53 @@ +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; + +import { buildQuery, searchV1Api } from '../utils'; + +export default function createSearchRadiatedCompaniesTool( + headers: Record, +): DynamicStructuredTool { + return new DynamicStructuredTool({ + name: 'infogreffe_search_radiated_companies', + description: + 'Search for struck-off French companies by various criteria (city, sector, region, etc.)', + schema: z.object({ + year: z.enum(['2022', '2023', '2024', '2025']).default('2024').describe('Radiation year'), + ville: z.string().optional().describe('City (e.g., PARIS, LYON)'), + region: z.string().optional().describe('Region (e.g., Ile-de-France)'), + secteur_d_activite: z.string().optional().describe('Business sector'), + forme_juridique: z.string().optional().describe('Legal form (e.g., SARL, SAS)'), + limit: z.number().int().positive().default(10).describe('Maximum results (default: 10)'), + }), + func: async ({ year, ville, region, secteur_d_activite, forme_juridique, limit }) => { + const dataset = `entreprises-radiees-en-${year}`; + + const query = buildQuery([ + ville && `ville:${ville}`, + region && `region:${region}`, + secteur_d_activite && `secteur_d_activite:${secteur_d_activite}`, + forme_juridique && `forme_juridique:${forme_juridique}`, + ]); + + const result = await searchV1Api(headers, dataset, query, limit); + + return JSON.stringify({ + annee: year, + criteres: { ville, region, secteur_d_activite, forme_juridique }, + total_resultats: result.nhits, + resultats_affiches: result.records.length, + entreprises: result.records.map(r => { + const fields = r.fields as Record; + + return { + siren: fields.siren, + denomination: fields.denomination, + date_radiation: fields.date_radiation, + ville: fields.ville, + forme_juridique: fields.forme_juridique, + secteur_d_activite: fields.secteur_d_activite, + }; + }), + }); + }, + }); +} diff --git a/packages/ai-proxy/src/integrations/infogreffe/utils.ts b/packages/ai-proxy/src/integrations/infogreffe/utils.ts new file mode 100644 index 000000000..e14dd0910 --- /dev/null +++ b/packages/ai-proxy/src/integrations/infogreffe/utils.ts @@ -0,0 +1,39 @@ +const API_BASE_V1 = 'https://opendata.datainfogreffe.fr/api/records/1.0/search'; + +export interface DatainfogreffeRecord { + fields: Record; + recordid: string; +} + +export interface DatainfogreffeResponse { + nhits: number; + records: DatainfogreffeRecord[]; +} + +export async function searchV1Api( + headers: Record, + dataset: string, + query?: string, + rows = 10, +): Promise { + const params = new URLSearchParams({ dataset, rows: rows.toString() }); + if (query) params.set('q', query); + + const response = await fetch(`${API_BASE_V1}?${params}`, { headers }); + + if (!response.ok) { + if (response.status === 404) { + return { nhits: 0, records: [] }; + } + + throw new Error(`API error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export function buildQuery(parts: Array): string | undefined { + const filtered = parts.filter(Boolean); + + return filtered.length > 0 ? filtered.join(' AND ') : undefined; +} diff --git a/packages/ai-proxy/src/integrations/tools.ts b/packages/ai-proxy/src/integrations/tools.ts index 21a7a1a1b..ba4d0950d 100644 --- a/packages/ai-proxy/src/integrations/tools.ts +++ b/packages/ai-proxy/src/integrations/tools.ts @@ -1,17 +1,20 @@ import type RemoteTool from '../remote-tool'; import type { BraveConfig } from './brave/tools'; import type { GmailConfig } from './gmail/tools'; +import type { InfogreffeConfig } from './infogreffe/tools'; import type { SlackConfig } from './slack/tools'; import type { ZendeskConfig } from './zendesk/tools'; import getBraveTools from './brave/tools'; import getGmailTools from './gmail/tools'; +import getInfogreffeTools from './infogreffe/tools'; import getSlackTools from './slack/tools'; import getZendeskTools from './zendesk/tools'; export interface IntegrationConfigs { brave?: BraveConfig; gmail?: GmailConfig; + infogreffe?: InfogreffeConfig; slack?: SlackConfig; zendesk?: ZendeskConfig; } @@ -35,5 +38,9 @@ export default function getIntegratedTools(configs: IntegrationConfigs): RemoteT integratedTools.push(...getZendeskTools(configs.zendesk)); } + if (configs.infogreffe) { + integratedTools.push(...getInfogreffeTools(configs.infogreffe)); + } + return integratedTools; }