From 61974848fe6b361afc33eb791588d85ad6e30fc2 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 1 Oct 2026 14:47:48 -0700 Subject: [PATCH] fix: keep aircraft search results when one feed is down find_aircraft failed whenever either feed failed. It now answers from the feeds that responded, names the ones that did not, and searches only the military feed for registrations, the only feed that reports them. --- src/tools/queries/aviation.js | 40 ++++++++++++++++++++++------- src/tools/queries/aviation.test.mjs | 39 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/tools/queries/aviation.js b/src/tools/queries/aviation.js index f5bc416ba..0bf520d0d 100644 --- a/src/tools/queries/aviation.js +++ b/src/tools/queries/aviation.js @@ -161,13 +161,31 @@ export const findAircraft = defineTool({ icao24: 'id', registration: 'registration', }[key]; - const feeds = [services.aircraft, services.military].filter(Boolean); - const snapshots = await Promise.all( - feeds.map((feed) => feed.getSnapshot({}, { signal })), + // Only the military feed reports registrations. + const feeds = [ + ...(key === 'registration' + ? [] + : [{ name: 'civil', feed: services.aircraft }]), + { name: 'military', feed: services.military }, + ].filter(({ feed }) => feed); + if (!feeds.length) + throw new ToolError( + 'unavailable', + 'The military aircraft feed is not available here', + ); + const settled = await Promise.allSettled( + feeds.map(({ feed }) => feed.getSnapshot({}, { signal })), ); + signal?.throwIfAborted(); + // A search succeeds when any feed answers; missing feeds are named. + const unavailable = feeds + .filter((_, index) => settled[index].status === 'rejected') + .map(({ name }) => name); + if (unavailable.length === feeds.length) throw settled[0].reason; const seen = new Set(); - const rows = snapshots - .flatMap((snapshot) => snapshot.records) + const rows = settled + .filter((result) => result.status === 'fulfilled') + .flatMap((result) => result.value.records) .filter( (record) => String(record[field] || '') @@ -177,10 +195,14 @@ export const findAircraft = defineTool({ .filter((record) => !seen.has(record.id) && seen.add(record.id)) .map((record) => aircraftRow(record)); return { - summary: rows.length - ? `Found ${countNoun(rows.length, 'aircraft', 'aircraft')} with ${key} ${wanted}.` - : `No aircraft with ${key} ${wanted} is currently reported.`, - data: capRows(rows, args.limit), + summary: + (rows.length + ? `Found ${countNoun(rows.length, 'aircraft', 'aircraft')} with ${key} ${wanted}.` + : `No aircraft with ${key} ${wanted} is currently reported.`) + + (unavailable.length + ? ` The ${unavailable.join(' and ')} feed did not answer.` + : ''), + data: { ...capRows(rows, args.limit), unavailable_feeds: unavailable }, }; }, }); diff --git a/src/tools/queries/aviation.test.mjs b/src/tools/queries/aviation.test.mjs index 811a066c4..7594029ce 100644 --- a/src/tools/queries/aviation.test.mjs +++ b/src/tools/queries/aviation.test.mjs @@ -298,3 +298,42 @@ test('live source failures become tool errors with retry guidance', async () => error.message === 'OpenSky rate limited', ); }); + +test('aircraft searches keep the feeds that answered and name the others', async () => { + const down = { + getSnapshot: async () => { + throw new LiveSourceError('unavailable', 'OpenSky HTTP 503'); + }, + }; + const military = feed([ + record('ae1234', 38, -77, { callsign: 'RCH123', registration: '05-5140' }), + ]); + const catalog = composeCatalog({ + tools: coreTools, + services: { aircraft: down, military }, + }); + const byCallsign = await catalog.call('find_aircraft', { + callsign: 'rch123', + }); + assert.equal( + byCallsign.summary, + 'Found 1 aircraft with callsign RCH123. The civil feed did not answer.', + ); + assert.deepEqual(byCallsign.data.unavailable_feeds, ['civil']); + const civil = feed([]); + const byRegistration = await composeCatalog({ + tools: coreTools, + services: { aircraft: civil, military }, + }).call('find_aircraft', { registration: '05-5140' }); + assert.equal(byRegistration.data.rows[0].id, 'ae1234'); + assert.deepEqual(byRegistration.data.unavailable_feeds, []); + assert.equal(civil.calls.length, 0); + await assert.rejects( + composeCatalog({ + tools: coreTools, + services: { aircraft: down, military: down }, + }).call('find_aircraft', { callsign: 'RCH123' }), + (error) => + error.code === 'unavailable' && error.message === 'OpenSky HTTP 503', + ); +});