import doormileAxios, { DOORMILE_TOKEN_KEY, DOORMILE_USER_KEY, clearStoredSession } from './client'; // The Doormile Express admin API — one named async export per // `api.doormile.com/api/v1/admin/*` endpoint. // // Ported verbatim from the Express Console's `pages/api/doormileApi.js`, since // this is the contract the backend actually speaks; only the client import // changed. Every function unwraps the envelope for its caller: list/GET // functions return `response.data.data` directly, mutations return the full // `response.data` (`{ success, data | message }`) so callers can check // `res.success`. // // Pages consume these through the React Query hooks in `@/lib/doormileHooks` // rather than calling them directly, so cache keys stay coherent across the app. const buildQuery = (params = {}) => { const usp = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') usp.append(key, value); }); const qs = usp.toString(); return qs ? `?${qs}` : ''; }; // ==============================|| Auth ||============================== // export const loginAdmin = async (email, password) => { const response = await doormileAxios.post('/admin/login', { email, password }); if (response.data.success) { localStorage.setItem(DOORMILE_TOKEN_KEY, response.data.token); localStorage.setItem(DOORMILE_USER_KEY, JSON.stringify(response.data.user)); } return response.data; }; export const logoutAdmin = () => { // Clears the auxiliary identity keys too — see clearStoredSession in client.js. clearStoredSession(); }; // Drains a paginated list endpoint instead of taking whatever the server's // default page size happens to be. // // GET /admin/customers defaults to `pagesize: 20` — confirmed live against an // account with 37 customers, which came back as // { pageno: 1, pagesize: 20, pages: 2, total: 37 }. Every caller of // getAdminCustomers was therefore seeing 20 of 37 and had no way to know: // the Customers page reported "Total Customers 20", and the customer lookups // on the orders pages silently missed anyone on page 2. // // (express-console-api.md claims the default is 500. It is not, at least not // for this endpoint. Trust the envelope, not the doc.) const PAGE_SIZE = 100; // the doc's own example value — safer than assuming the 1000 cap holds const MAX_PAGES = 12; const fetchAllPages = async (path) => { const first = await doormileAxios.get(`${path}${buildQuery({ pageno: 1, pagesize: PAGE_SIZE })}`); const body = first.data || {}; const rows = body.data || []; const total = Number(body.total); if (!Number.isFinite(total) || rows.length >= total) return rows; const pageCount = Math.min(Math.ceil(total / PAGE_SIZE), MAX_PAGES); for (let page = 2; page <= pageCount; page += 1) { const next = await doormileAxios.get(`${path}${buildQuery({ pageno: page, pagesize: PAGE_SIZE })}`); const more = next.data?.data || []; if (!more.length) break; rows.push(...more); } return rows; }; // ==============================|| Dashboard, profile, reports ||============================== // export const getDashboard = async () => { const response = await doormileAxios.get('/admin/dashboard'); return response.data.data; }; // jupiter used fromdate/todate + applocationid (city); Doormile uses from/to // and no city filter at all (filters by hubid instead — see // jupiter2doormile.md §2.3/§6). Response carries by_location/by_hub/by_tenant/ // by_rider blocks per that doc, not documented in express-console-api.md. export const getReports = async (from, to, tenantid, locationid, hubid) => { const response = await doormileAxios.get(`/admin/reports${buildQuery({ from, to, tenantid, locationid, hubid })}`); return response.data.data; }; // Confirmed against jupiter2doormile.md (Status: Done) as the replacement for // jupiter's getlocationsummary — per-tenant-site report breakdown. Not in // express-console-api.md. export const getLocationsSummary = async (tenantid, locationid, from, to) => { const response = await doormileAxios.get(`/admin/locations/summary${buildQuery({ tenantid, locationid, from, to })}`); return response.data.data; }; export const getProfile = async () => { const response = await doormileAxios.get('/admin/profile'); return response.data.data; }; export const updateProfilePassword = async (current_password, new_password) => { const response = await doormileAxios.put('/admin/profile/password', { current_password, new_password }); return response.data; }; // ==============================|| App users (staff logins) ||============================== // export const getAppUsers = async () => { const response = await doormileAxios.get('/admin/users'); return response.data.data; }; export const createAppUser = async (data) => { const response = await doormileAxios.post('/admin/users', data); return response.data; }; export const updateAppUser = async (id, data) => { const response = await doormileAxios.put(`/admin/users/${id}`, data); return response.data; }; export const deleteAppUser = async (id) => { const response = await doormileAxios.delete(`/admin/users/${id}`); return response.data; }; // ==============================|| Partners (fleet / rider suppliers) ||============================== // export const getPartners = async () => { const response = await doormileAxios.get('/admin/partners'); return response.data.data; }; export const createPartner = async (data) => { const response = await doormileAxios.post('/admin/partners', data); return response.data; }; export const getPartner = async (id) => { const response = await doormileAxios.get(`/admin/partners/${id}`); return response.data.data; }; export const updatePartner = async (id, data) => { const response = await doormileAxios.put(`/admin/partners/${id}`, data); return response.data; }; export const deletePartner = async (id) => { const response = await doormileAxios.delete(`/admin/partners/${id}`); return response.data; }; // ==============================|| Tenants (client companies) ||============================== // export const getAdminTenants = async () => { const response = await doormileAxios.get('/admin/tenants'); return response.data.data; }; export const createAdminTenant = async (data) => { const response = await doormileAxios.post('/admin/tenants', data); return response.data; }; /** * Onboard a client: the tenant, its console login (doormile_auth) and its * appusers row, in one server transaction. Only the onboarding owner login * (admin@doormile.com) may call it; anyone else gets 403. * @param {{ companyname: string, contactname: string, email: string, phone: string, * password: string, applocationid: number, requiredeliveryotp?: boolean }} data * @returns {Promise<{ success: boolean, data: { tenant: object, login: object } }>} */ export const onboardClient = async (data) => { const response = await doormileAxios.post('/admin/clients/onboard', data); return response.data; }; /** * Edit an onboarded client and its login (owner login only). `authid` is the * login's id from GET /admin/clients/onboarded. Only fields sent change; * `password` resets the login's password when non-empty. */ export const updateOnboardedClient = async (authid, data) => { const response = await doormileAxios.put(`/admin/clients/${encodeURIComponent(authid)}`, data); return response.data; }; /** * Remove a client's console login (owner login only). The company and its * order history are kept; the client is marked Inactive if no login remains. */ export const deleteOnboardedClient = async (authid) => { const response = await doormileAxios.delete(`/admin/clients/${encodeURIComponent(authid)}`); return response.data; }; /** Operating cities a new client can be placed in (applocations; owner login only). */ export const getOnboardingCities = async () => { const response = await doormileAxios.get('/admin/clients/cities'); return response.data.data; }; /** Clients that have a console login, newest first (owner login only). */ export const getOnboardedClients = async () => { const response = await doormileAxios.get('/admin/clients/onboarded'); return response.data.data; }; export const getAdminTenant = async (id) => { const response = await doormileAxios.get(`/admin/tenants/${id}`); return response.data.data; }; export const updateAdminTenant = async (id, data) => { // requiredeliveryotp is a pointer server-side — omit the key entirely to leave the setting alone. const response = await doormileAxios.put(`/admin/tenants/${id}`, data); return response.data; }; export const deleteAdminTenant = async (id) => { const response = await doormileAxios.delete(`/admin/tenants/${id}`); return response.data; }; export const getTenantLocations = async (tenantId) => { const response = await doormileAxios.get(`/admin/tenants/${tenantId}/locations`); return response.data.data; }; export const createTenantLocation = async (tenantId, data) => { const response = await doormileAxios.post(`/admin/tenants/${tenantId}/locations`, data); return response.data; }; export const updateTenantLocation = async (locationId, data) => { // Not nested under the tenant — PUT /admin/tenantlocations/:id. const response = await doormileAxios.put(`/admin/tenantlocations/${locationId}`, data); return response.data; }; // ==============================|| Tenant customers (a client's own end customers) ||============================== // export const getTenantCustomers = async () => fetchAllPages('/admin/tenantcustomers'); export const createTenantCustomer = async (data) => { const response = await doormileAxios.post('/admin/tenantcustomers', data); return response.data; }; export const getTenantCustomer = async (id) => { const response = await doormileAxios.get(`/admin/tenantcustomers/${id}`); return response.data.data; }; export const updateTenantCustomer = async (id, data) => { const response = await doormileAxios.put(`/admin/tenantcustomers/${id}`, data); return response.data; }; export const deleteTenantCustomer = async (id) => { const response = await doormileAxios.delete(`/admin/tenantcustomers/${id}`); return response.data; }; // ==============================|| B2C app customers ||============================== // export const getAdminCustomers = async () => fetchAllPages('/admin/customers'); // POST /admin/customers. // // express-console-api.md lists this resource as GET + PATCH only, and states // that a customer there comes into existence through a booking. Added on // explicit instruction to create into the store the Customers page actually // reads. If the route genuinely does not exist the call returns 404/405, and // the assistant surfaces that verbatim with the endpoint name rather than // failing silently — so an unsupported route is immediately visible. // // Body mirrors this resource's own GET shape. export const createAdminCustomer = async (data) => { const response = await doormileAxios.post('/admin/customers', data); return response.data; }; export const updateAdminCustomer = async (id, data) => { const response = await doormileAxios.patch(`/admin/customers/${id}`, data); return response.data; }; // ==============================|| Hubs ||============================== // export const getHubs = async () => { const response = await doormileAxios.get('/admin/hubs'); return response.data.data; }; export const createHub = async (data) => { const response = await doormileAxios.post('/admin/hubs', data); return response.data; }; export const getHub = async (id) => { const response = await doormileAxios.get(`/admin/hubs/${id}`); return response.data.data; }; export const updateHub = async (id, data) => { const response = await doormileAxios.put(`/admin/hubs/${id}`, data); return response.data; }; export const deleteHub = async (id) => { // Soft delete server-side. const response = await doormileAxios.delete(`/admin/hubs/${id}`); return response.data; }; // ==============================|| Vehicles ||============================== // export const getVehicles = async () => { const response = await doormileAxios.get('/admin/vehicles'); return response.data.data; }; export const createVehicle = async (data) => { const response = await doormileAxios.post('/admin/vehicles', data); return response.data; }; export const getVehicle = async (id) => { const response = await doormileAxios.get(`/admin/vehicles/${id}`); return response.data.data; }; export const updateVehicle = async (id, data) => { const response = await doormileAxios.put(`/admin/vehicles/${id}`, data); return response.data; }; export const deleteVehicle = async (id) => { // Soft delete server-side. const response = await doormileAxios.delete(`/admin/vehicles/${id}`); return response.data; }; // ==============================|| Milers (riders) ||============================== // export const getMilers = async () => { // Tenant-scoped server-side from the token — no tenantid param needed/accepted. const response = await doormileAxios.get('/admin/milers'); return response.data.data; }; export const createMiler = async (data) => { // configid defaults to 1001 server-side (the miler-app login partition) — do not override it. const response = await doormileAxios.post('/admin/milers', data); return response.data; }; export const getMiler = async (id) => { const response = await doormileAxios.get(`/admin/milers/${id}`); return response.data.data; }; export const updateMiler = async (id, data) => { const response = await doormileAxios.put(`/admin/milers/${id}`, data); return response.data; }; export const blockMiler = async (id, data) => { const response = await doormileAxios.put(`/admin/milers/${id}/block`, data); return response.data; }; export const assignMilerVehicle = async (id, data) => { const response = await doormileAxios.put(`/admin/milers/${id}/assign-vehicle`, data); return response.data; }; export const notifyMiler = async (milerProfileId, title, message) => { const response = await doormileAxios.post(`/admin/milers/${milerProfileId}/notify`, { title, message }); return response.data; }; // Confirmed against jupiter2doormile.md (Status: Done — tested against a real // request), not in express-console-api.md. jupiter used fromdate/todate; // Doormile uses from/to (both YYYY-MM-DD). Aggregate rider KPI counts + // current position (currentlatitude/currentlongitude/lastpingat per the doc — // unconfirmed beyond those three field names). export const getMilerSummary = async (applocationid, from, to, tenantid, hubid) => { const response = await doormileAxios.get(`/admin/milers/summary${buildQuery({ applocationid, from, to, tenantid, hubid })}`); return response.data.data; }; // Replaces jupiter's `riderlogs` table — the rider's GPS/status ping history. export const getMilerLogs = async (id, from, to, limit) => { const response = await doormileAxios.get(`/admin/milers/${id}/logs${buildQuery({ from, to, limit })}`); return response.data.data; }; // No jupiter equivalent — new in Doormile. export const getMilerActivity = async (id, from, to) => { const response = await doormileAxios.get(`/admin/milers/${id}/activity${buildQuery({ from, to })}`); return response.data.data; }; // ==============================|| Bookings ||============================== // export const getBookings = async (pageno, pagesize) => { // If caller specifically requested page > 1 with small page size: if (pageno > 1 && pagesize && pagesize < 100) { const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`); return response.data.data || []; } // The backend hard-caps pagesize at 100 per request. // Drain all pages so no bookings are silently lost. const firstRes = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno: 1, pagesize: 100 })}`); const body = firstRes.data || {}; const firstRows = body.data || []; const total = Number.isFinite(Number(body.total)) ? Number(body.total) : firstRows.length; if (firstRows.length >= total || firstRows.length < 100) { return firstRows; } const allRows = [...firstRows]; const totalPages = Math.ceil(total / 100); for (let p = 2; p <= totalPages; p++) { try { const res = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno: p, pagesize: 100 })}`); const rows = res.data?.data || []; if (!rows.length) break; allRows.push(...rows); if (allRows.length >= total) break; } catch { break; } } return allRows; }; // Same request as getBookings, but keeps the envelope's `total`/`page` instead // of throwing them away. // // getBookings returns `response.data.data` — just the rows — which means no // caller can tell a full result from a truncated one. `pagesize` is capped at // 100 server-side, and the default is 20 — NOT "default 500, cap 1000", which // is what express-console-api.md said until it was corrected. Asking for 1000 // returns 100 rows and an envelope whose own `pagesize` reads 100, so the // under-fetch is silent unless you check it. Any account with more than 100 // bookings therefore gets a partial list from a single call. That's tolerable // for a paginated table (it fetches // the next page as you scroll) but NOT for anything that counts or sums, which // would report a confidently wrong number. // // Kept as a separate export rather than changing getBookings' return shape: // four callers (fetchDeliveries, fetchCountAPI, orders.js, the assistant's own // per-order lookup) already destructure it as a bare array. export const getBookingsPage = async (pageno, pagesize) => { const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`); const body = response.data || {}; return { rows: body.data || [], // `total` is documented as present on list responses; fall back to the row // count so a backend that omits it degrades to "this is everything" rather // than to NaN-driven pagination. total: Number.isFinite(Number(body.total)) ? Number(body.total) : (body.data || []).length, page: Number(body.page) || pageno }; }; export const createExpressBooking = async (data) => { // tenantid is forced to the caller's own tenant server-side on client logins. // CityGate applies: pickup pincode prefix must be an open city (641/600/560/500/629). const response = await doormileAxios.post('/admin/expressbooking', data); return response.data; }; export const createExpressBookingBulk = async (bookings) => { const BATCH_SIZE = 200; const list = Array.isArray(bookings) ? bookings : []; if (list.length <= BATCH_SIZE) { const response = await doormileAxios.post('/admin/expressbooking/bulk', { bookings: list }); return response.data; } // Automatic chunking for requests exceeding the backend's 200 per-call limit (e.g. 1,000 orders) const chunks = []; for (let i = 0; i < list.length; i += BATCH_SIZE) { chunks.push(list.slice(i, i + BATCH_SIZE)); } const results = []; let totalCreated = 0; let lastErrorMessage = ''; for (const chunk of chunks) { try { const response = await doormileAxios.post('/admin/expressbooking/bulk', { bookings: chunk }); const body = response.data || {}; results.push(body); if (body.success !== false) { totalCreated += chunk.length; } else { lastErrorMessage = body.message || 'One batch failed'; } } catch (err) { lastErrorMessage = err.response?.data?.message || err.message || 'Batch request failed'; results.push({ success: false, message: lastErrorMessage }); } } const anySuccess = totalCreated > 0; const allSuccess = totalCreated === list.length; return { success: anySuccess, message: allSuccess ? `${list.length} orders created successfully` : `${totalCreated} of ${list.length} orders created${lastErrorMessage ? ` (${lastErrorMessage})` : ''}`, data: results.flatMap((r) => r.data || []), total: totalCreated, batches: chunks.length }; }; export const getBooking = async (id) => { const response = await doormileAxios.get(`/admin/bookings/${id}`); return response.data.data; }; export const assignMilerToBooking = async (id, data) => { const response = await doormileAxios.post(`/admin/bookings/${id}/assign-miler`, data); return response.data; }; // Doormile-native bulk assign: picks real milers via Redis GEO + AI scoring, no // external solver involved. Commits server-side in this one call — unlike the // workolik solver flow, there's no separate preview/reconcile/commit step. // Sequencing (POST /optimization/doormile/sequence) is not deployed yet, so // riders with more than one stop come back unsequenced (step: 0) — see // doormile-flow.md's State table. export const batchAssignBookings = async (bookingIds, maxPerRider = 5) => { const response = await doormileAxios.post('/hub/bookings/batch-assign', { bookingids: bookingIds, max_per_rider: maxPerRider }); return response.data; }; export const assignVehicleToBooking = async (id, data) => { const response = await doormileAxios.post(`/admin/bookings/${id}/assign-vehicle`, data); return response.data; }; export const updateBookingStatus = async (id, data) => { const response = await doormileAxios.put(`/admin/bookings/${id}/status`, data); return response.data; }; export const cancelBooking = async (id, data) => { const response = await doormileAxios.post(`/admin/bookings/${id}/cancel`, data); return response.data; }; export const bulkCancelBookings = async (bookingIds) => { const response = await doormileAxios.post('/admin/bookings/bulk-cancel', { bookingIds }); return response.data; }; // Confirmed against jupiter2doormile.md (Status: Done), not in // express-console-api.md. export const getBookingTrack = async (id) => { const response = await doormileAxios.get(`/admin/bookings/${id}/track`); return response.data.data; }; // ==============================|| Consignments ||============================== // export const getConsignments = async () => { const response = await doormileAxios.get('/admin/consignments'); return response.data.data; }; // Confirmed against jupiter2doormile.md (Status: Done) — the actual GPS/status // trail for a consignment, not in express-console-api.md. export const getConsignmentLogs = async (id) => { const response = await doormileAxios.get(`/admin/consignments/${id}/logs`); return response.data.data; }; export const getConsignment = async (id) => { const response = await doormileAxios.get(`/admin/consignments/${id}`); return response.data.data; }; export const trackConsignment = async (trackingno) => { const response = await doormileAxios.get(`/admin/consignments/track/${trackingno}`); return response.data.data; }; export const updateConsignmentStatus = async (id, data) => { const response = await doormileAxios.put(`/admin/consignments/${id}/status`, data); return response.data; }; // ==============================|| Tripsheets (hub-to-hub transport) ||============================== // export const getTripsheets = async () => { const response = await doormileAxios.get('/admin/tripsheets'); return response.data.data; }; export const createTripsheet = async (data) => { const response = await doormileAxios.post('/admin/tripsheets', data); return response.data; }; export const getTripsheet = async (id) => { const response = await doormileAxios.get(`/admin/tripsheets/${id}`); return response.data.data; }; export const addTripsheetItem = async (id, consignmentid) => { const response = await doormileAxios.post(`/admin/tripsheets/${id}/items`, { consignmentid }); return response.data; }; export const removeTripsheetItem = async (id, itemId) => { const response = await doormileAxios.delete(`/admin/tripsheets/${id}/items/${itemId}`); return response.data; }; export const dispatchTripsheet = async (id) => { const response = await doormileAxios.put(`/admin/tripsheets/${id}/dispatch`); return response.data; }; export const arriveTripsheet = async (id) => { const response = await doormileAxios.put(`/admin/tripsheets/${id}/arrive`); return response.data; }; // ==============================|| Pricing ||============================== // export const getAdminPricing = async () => { const response = await doormileAxios.get('/admin/pricing'); return response.data.data; }; export const createAdminPricing = async (data) => { const response = await doormileAxios.post('/admin/pricing', data); return response.data; }; export const updateAdminPricing = async (id, data) => { const response = await doormileAxios.put(`/admin/pricing/${id}`, data); return response.data; }; export const deleteAdminPricing = async (id) => { const response = await doormileAxios.delete(`/admin/pricing/${id}`); return response.data; }; export const simulatePricing = async (data) => { const response = await doormileAxios.post('/admin/pricing/simulate', data); return response.data; }; export const quotePricing = async (data) => { // Same handler as simulate. const response = await doormileAxios.post('/admin/pricing/quote', data); return response.data; }; export const getDoormilePricing = async () => { const response = await doormileAxios.get('/admin/doormile-pricing'); return response.data.data; }; export const createDoormilePricing = async (data) => { const response = await doormileAxios.post('/admin/doormile-pricing', data); return response.data; }; export const updateDoormilePricing = async (id, data) => { const response = await doormileAxios.put(`/admin/doormile-pricing/${id}`, data); return response.data; }; export const deleteDoormilePricing = async (id) => { // Soft delete server-side. const response = await doormileAxios.delete(`/admin/doormile-pricing/${id}`); return response.data; }; // ==============================|| Exceptions ||============================== // export const getExceptions = async () => { const response = await doormileAxios.get('/admin/exceptions'); return response.data.data; }; export const createException = async (data) => { const response = await doormileAxios.post('/admin/exceptions', data); return response.data; }; export const getException = async (id) => { const response = await doormileAxios.get(`/admin/exceptions/${id}`); return response.data.data; }; export const updateExceptionStatus = async (id, status, resolution) => { // status: 'Resolved' | 'Closed'. const response = await doormileAxios.put(`/admin/exceptions/${id}/status`, { status, resolution }); return response.data; }; // ==============================|| Competitive intel ||============================== // // Confirmed live: this resource paginates on page/limit, not the // pageno/pagesize convention used everywhere else in the API — and defaults // to only 50 rows (there are 339+ competitor branches on a real tenant), so // callers must page through it explicitly rather than trusting one fetch to // return everything. Returns the full envelope (data/limit/page/total) so // the caller can build pagination controls. export const getCompetitorBranches = async (page, limit) => { const response = await doormileAxios.get(`/admin/competitor-branches${buildQuery({ page, limit })}`); return response.data; }; export const createCompetitorBranch = async (data) => { const response = await doormileAxios.post('/admin/competitor-branches', data); return response.data; }; export const updateCompetitorBranch = async (id, data) => { const response = await doormileAxios.put(`/admin/competitor-branches/${id}`, data); return response.data; }; export const deleteCompetitorBranch = async (id) => { const response = await doormileAxios.delete(`/admin/competitor-branches/${id}`); return response.data; }; // Same page/limit pagination convention as competitor-branches (confirmed // live) — 368+ rows on a real tenant, default page returns only 100. export const getCarrierPricing = async (page, limit) => { const response = await doormileAxios.get(`/admin/carrier-pricing${buildQuery({ page, limit })}`); return response.data; }; export const createCarrierPricing = async (data) => { const response = await doormileAxios.post('/admin/carrier-pricing', data); return response.data; }; export const updateCarrierPricing = async (id, data) => { const response = await doormileAxios.put(`/admin/carrier-pricing/${id}`, data); return response.data; }; export const deleteCarrierPricing = async (id) => { const response = await doormileAxios.delete(`/admin/carrier-pricing/${id}`); return response.data; }; // ==============================|| AI agent registry ||============================== // // /admin/ai/* — Doormile staff only (a partner-tenant login gets 403). Reads // are open to admin/manager/executive; every write is admin-only and audited // server-side. See docs/agent-platform-plan.md. export const getAiAgents = async () => { const response = await doormileAxios.get('/admin/ai/agents'); return response.data.data; }; export const getAiSkills = async () => { const response = await doormileAxios.get('/admin/ai/skills'); return response.data.data; }; export const getAiTools = async () => { const response = await doormileAxios.get('/admin/ai/tools'); return response.data.data; }; export const getAiRegistryAudit = async (limit) => { const response = await doormileAxios.get(`/admin/ai/audit${buildQuery({ limit })}`); return response.data.data; }; /** @param {string} id @param {{ enabled?: boolean, thresholds?: Record }} patch */ export const updateAiSkill = async (id, patch) => { const response = await doormileAxios.patch(`/admin/ai/skills/${encodeURIComponent(id)}`, patch); return response.data; }; /** @param {{ agentid: string, title: string, category?: string, description?: string, sampleprompt?: string, tools: string[] }} skill */ export const createAiSkill = async (skill) => { const response = await doormileAxios.post('/admin/ai/skills', skill); return response.data; }; /** * Autonomy ON needs `confirm` set to the agent id — the server refuses it * otherwise, so a stray click cannot let an agent act without a human. * @param {string} id @param {{ autonomous?: boolean, model?: string, confirm?: string }} patch */ export const updateAiAgent = async (id, patch) => { const response = await doormileAxios.patch(`/admin/ai/agents/${encodeURIComponent(id)}`, patch); return response.data; }; /** * What the agents did over the last `days` (1–30): runs and failures per * AI_engine agent, decisions by type/outcome, live heartbeat. `receiving` is * false when the backend is not subscribed to AI_engine telemetry. * @param {number} days */ /** * What is actually wired, for the Skills & Tools banner: whether AI_engine is * reading the registry (it polls ~every 30 s; the backend records each read), * telemetry, live agents, and whether the Test tab has a model. */ export const getAiStatus = async () => { const response = await doormileAxios.get('/admin/ai/status'); return response.data.data; }; export const getAiInsights = async (days = 7) => { const response = await doormileAxios.get(`/admin/ai/insights${buildQuery({ days })}`); return response.data.data; }; /** * Agent Studio's Test tab: one prompt through Claude with a skill's tools. * Read tools run with personal data redacted; write tools come back as * proposals and are never executed. Admin only. 503 (code * PLAYGROUND_NOT_CONFIGURED) when the server has no Claude client; 429 when * the per-user limit is reached. * @param {{ agentid: string, skillid?: string, prompt: string }} body */ export const runAiPlayground = async (body) => { const response = await doormileAxios.post('/admin/ai/playground/run', body); return response.data.data; }; /** Recent agent decisions, newest first. `before` is the last id of the previous page. */ export const getAiDecisions = async ({ type, before, limit } = {}) => { const response = await doormileAxios.get(`/admin/ai/decisions${buildQuery({ type, before, limit })}`); return response.data.data; };