Spaces:
Paused
Paused
Download scripts/e2e-agent.mjs from IlyasDaoud/omnia-agent-api: direct link, hf CLI and curl.
- Browser
- Download file 9.83 kB
-
https://huggingface.co/spaces/IlyasDaoud/omnia-agent-api/resolve/main/scripts/e2e-agent.mjs
- Command line
-
hf download hf://spaces/IlyasDaoud/omnia-agent-api/scripts/e2e-agent.mjs
-
curl -L -o e2e-agent.mjs https://huggingface.co/spaces/IlyasDaoud/omnia-agent-api/resolve/main/scripts/e2e-agent.mjs
9.83 kB
| /** | |
| * OMNIA Agent β repeatable end-to-end smoke test (no secrets printed). | |
| * | |
| * Drives the live agent over its SSE endpoint with the customer prompts from the | |
| * brief and asserts the assembled checkout / clarify / refusal. Also checks CORS | |
| * for 127.0.0.1 and that signed-out marketplace bookings are rejected (401). | |
| * | |
| * Usage: node scripts/e2e-agent.mjs (needs the 4 backends running) | |
| * npm run e2e | |
| * | |
| * Requires Node 18+ (global fetch). Reads no .env and prints no credentials. | |
| */ | |
| const AGENT = process.env.AGENT_URL ?? 'http://localhost:3000'; | |
| const STAYS = process.env.STAYS_URL ?? 'http://localhost:3001'; | |
| const EATS = process.env.EATS_URL ?? 'http://localhost:3002'; | |
| const RIDES = process.env.RIDES_URL ?? 'http://localhost:3003'; | |
| const CITY = { | |
| Tanger: [35.7595, -5.834], | |
| Rabat: [34.0209, -6.8416], | |
| Marrakech: [31.6295, -7.9811], | |
| Agadir: [30.4278, -9.5981], | |
| }; | |
| let passed = 0; | |
| let failed = 0; | |
| const fails = []; | |
| function check(name, cond, detail = '') { | |
| if (cond) { | |
| passed++; | |
| console.log(` β ${name}`); | |
| } else { | |
| failed++; | |
| fails.push(name); | |
| console.log(` β ${name}${detail ? ` β ${detail}` : ''}`); | |
| } | |
| } | |
| /** POST a prompt to the agent and collect the parsed SSE events. */ | |
| async function runAgent(prompt, city) { | |
| const [lat, lon] = CITY[city] ?? CITY.Rabat; | |
| const res = await fetch(`${AGENT}/agent/run`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ prompt, history: [], location: { lat, lon, city, country: 'Morocco' } }), | |
| }); | |
| const text = await res.text(); | |
| const events = []; | |
| let tokens = ''; | |
| for (const line of text.split('\n')) { | |
| if (!line.startsWith('data:')) continue; | |
| const p = line.slice(5).trim(); | |
| if (!p || p === '[DONE]') continue; | |
| try { | |
| const e = JSON.parse(p); | |
| events.push(e); | |
| if (e.type === 'token') tokens += e.text; | |
| } catch { | |
| /* ignore non-JSON frames */ | |
| } | |
| } | |
| return { | |
| tokens: tokens.trim(), | |
| checkout: events.find((e) => e.type === 'checkout'), | |
| clarify: events.find((e) => e.type === 'clarify'), | |
| }; | |
| } | |
| const itemNames = (d) => (d?.items ?? []).map((i) => i.name.toLowerCase()).join(' | '); | |
| async function main() { | |
| console.log(`\nOMNIA Agent E2E β agent=${AGENT}\n`); | |
| // ββ 0) CORS: 127.0.0.1 must be allowed ββββββββββββββββββββββββββββββββββ | |
| console.log('[CORS] 127.0.0.1 origin allowed'); | |
| try { | |
| const r = await fetch(`${AGENT}/health`, { headers: { Origin: 'http://127.0.0.1:5180' } }); | |
| const acao = r.headers.get('access-control-allow-origin'); | |
| check('agent /health reflects 127.0.0.1 origin', acao === 'http://127.0.0.1:5180', `ACAO=${acao}`); | |
| const body = await r.json(); | |
| check('public /health hides key-pool detail', body.groqKeys === undefined, JSON.stringify(Object.keys(body))); | |
| const kr = await fetch(`${AGENT}/health/keys`); | |
| check('gated /health/keys reachable in dev', kr.ok, `status=${kr.status}`); | |
| } catch (e) { | |
| check('agent reachable', false, String(e)); | |
| return finish(); | |
| } | |
| // ββ 1) Eats: pepperoni pizza + coke in Tanger βββββββββββββββββββββββββββ | |
| console.log('\n[1] "Order me a pepperoni pizza and a coke in Tanger"'); | |
| { | |
| const r = await runAgent('Order me a pepperoni pizza and a coke in Tanger', 'Tanger'); | |
| const d = r.checkout?.drafts?.[0]; | |
| check('returns an eats checkout', d?.marketplace === 'eats', r.tokens.slice(0, 80)); | |
| check('order includes a pizza', /pizza|margher|pepperoni/.test(itemNames(d)), itemNames(d)); | |
| check('order total > 0', (d?.total ?? 0) > 0); | |
| // Coke isn't in the seed β it must be substituted with a drink or flagged, never silently dropped. | |
| const cokeFlagged = /coke/i.test(r.tokens) && /(isn'?t on|left (it|them) off|instead|added)/i.test(r.tokens); | |
| const hasDrink = /juice|tea|soda|water|cola|lemonade|smoothie/i.test(itemNames(d)); | |
| check('unavailable coke is substituted or flagged (not silently dropped)', cokeFlagged || hasDrink, r.tokens.slice(0, 160)); | |
| } | |
| // ββ 2) Ride: from X to Y, ~12 km (the parseEndpoints fix) βββββββββββββββ | |
| console.log('\n[2] "Book me an economy ride from Rabat Agdal station to Rabat airport, about 12 km"'); | |
| { | |
| const r = await runAgent('Book me an economy ride from Rabat Agdal station to Rabat airport, about 12 km', 'Rabat'); | |
| const d = r.checkout?.drafts?.[0]; | |
| check('returns a rides checkout', d?.marketplace === 'rides'); | |
| check('pickup = "Rabat Agdal station"', d?.pickup === 'Rabat Agdal station', `pickup=${d?.pickup}`); | |
| check('dropoff = "Rabat airport"', d?.dropoff === 'Rabat airport', `dropoff=${d?.dropoff}`); | |
| check('distance = 12 km', d?.distanceKm === 12, `km=${d?.distanceKm}`); | |
| check('offers alternative ride tiers', (d?.rideOptions?.length ?? 0) > 1); | |
| } | |
| // ββ 3) Stay: riad in Marrakech, 2 guests, 3 nights, under 900/night βββββ | |
| console.log('\n[3] "Book a riad in Marrakech for 2 guests for 3 nights under 900 MAD per night"'); | |
| { | |
| const r = await runAgent('Book a riad in Marrakech for 2 guests for 3 nights under 900 MAD per night', 'Marrakech'); | |
| const d = r.checkout?.drafts?.[0]; | |
| check('returns a stays checkout', d?.marketplace === 'stays'); | |
| check('nights = 3', d?.nights === 3, `nights=${d?.nights}`); | |
| check('guests = 2', d?.guests === 2, `guests=${d?.guests}`); | |
| check('per-night within 900', d ? d.total / (d.nights || 1) <= 900 : false, `total=${d?.total}`); | |
| } | |
| // ββ 3b) Misspelled city in the PROMPT must still resolve to Marrakech ββββ | |
| console.log('\n[3b] "Book a riad in marakech for 2 guests for 3 nights" (misspelled; location=Tanger)'); | |
| { | |
| const r = await runAgent('Book a riad in marakech for 2 guests for 3 nights', 'Tanger'); | |
| const d = r.checkout?.drafts?.[0]; | |
| check('misspelled prompt city β stays checkout', d?.marketplace === 'stays', r.tokens.slice(0, 80)); | |
| check('resolves to Marrakech inventory', /marrakech/i.test(d?.subtitle ?? ''), `subtitle=${d?.subtitle}`); | |
| } | |
| // ββ 3c) Misspelled city in the LOCATION (prompt names no city) ββββββββββ | |
| console.log('\n[3c] "Book me a riad for 2 guests for 3 nights" (location city="marakech")'); | |
| { | |
| const r = await runAgent('Book me a riad for 2 guests for 3 nights', 'marakech'); | |
| const d = r.checkout?.drafts?.[0]; | |
| check('misspelled location city β stays checkout (not empty)', d?.marketplace === 'stays', r.tokens.slice(0, 80)); | |
| } | |
| // ββ 4) Trip with no party size β must ASK guests first ββββββββββββββββββ | |
| console.log('\n[4] "Plan my full trip to Agadir" (expects a clarifying question)'); | |
| { | |
| const r = await runAgent('Plan my full trip to Agadir', 'Agadir'); | |
| check('asks a clarifying question', Boolean(r.clarify), r.tokens.slice(0, 80)); | |
| check('question is about guests', /guest/i.test(r.clarify?.question ?? ''), r.clarify?.question); | |
| check('does not pre-book', !r.checkout); | |
| } | |
| // ββ 5) Full trip with guests + nights + budget β receipt + itinerary ββββ | |
| console.log('\n[5] "Plan my full trip to Agadir for 2 guests for 3 nights under 3000 MAD"'); | |
| { | |
| const r = await runAgent('Plan my full trip to Agadir for 2 guests for 3 nights under 3000 MAD', 'Agadir'); | |
| const c = r.checkout; | |
| check('returns a multi-action checkout', (c?.drafts?.length ?? 0) >= 2, `drafts=${c?.drafts?.length}`); | |
| check('includes a trip plan', Boolean(c?.trip)); | |
| check('itinerary has days', (c?.trip?.days?.length ?? 0) >= 3, `days=${c?.trip?.days?.length}`); | |
| check('budget cap recorded = 3000', c?.trip?.budget?.budget === 3000, `budget=${c?.trip?.budget?.budget}`); | |
| check('budget breakdown present', c?.trip?.budget && ['stay', 'ride', 'food', 'total'].every((k) => typeof c.trip.budget[k] === 'number')); | |
| } | |
| // ββ 6) Unsupported city β polite refusal, no booking ββββββββββββββββββββ | |
| console.log('\n[6] "Book me a hotel and dinner in Paris for 2 nights" (expects refusal)'); | |
| { | |
| const r = await runAgent('Book me a hotel and dinner in Paris for 2 nights', 'Rabat'); | |
| check("refuses (doesn't operate in Paris)", /doesn'?t operate in Paris/i.test(r.tokens), r.tokens.slice(0, 100)); | |
| check('does not book', !r.checkout); | |
| } | |
| // ββ 7) Signed-out bookings must be rejected (401), never silent βββββββββ | |
| console.log('\n[7] Signed-out Buy Now β marketplace APIs reject without a token (401)'); | |
| { | |
| const post = (url, body) => | |
| fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then((r) => r.status); | |
| check('Eats /orders β 401', (await post(`${EATS}/orders`, { vendorId: 'x', items: [] })) === 401); | |
| check('Stays /bookings β 401', (await post(`${STAYS}/bookings`, { listingId: 'x', nights: 1 })) === 401); | |
| check('Rides /trips β 401', (await post(`${RIDES}/trips`, { rideClassId: 'x' })) === 401); | |
| } | |
| // ββ 8) Signed-in Buy Now β needs a real Clerk session (manual) ββββββββββ | |
| console.log('\n[8] Signed-in Buy Now'); | |
| console.log(' ~ SKIPPED: requires a real Clerk session token (sign in with Google in the UI, then tap Buy now).'); | |
| finish(); | |
| } | |
| function finish() { | |
| console.log(`\nββββββββββββββββββββββββββββ\n${passed} passed, ${failed} failed`); | |
| if (fails.length) console.log(`Failed: ${fails.join('; ')}`); | |
| process.exit(failed ? 1 : 0); | |
| } | |
| main().catch((e) => { | |
| console.error('E2E runner crashed:', e); | |
| process.exit(1); | |
| }); | |