eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1 lines • 7.13 kB
JavaScript
import{dirname,join,relative}from"node:path";import{EVE_PUBLIC_ROUTE_PREFIX_ENV,normalizePublicRoutePrefix}from"#shared/public-route-prefix.js";import{mkdir,readFile,writeFile}from"node:fs/promises";import{EVE_INTERNAL_BUILD_OUTPUT_DIRECTORY_ENV,EVE_INTERNAL_HOST_BUILD_OUTPUT_DIRECTORY_ENV}from"#internal/application/paths.js";import{findClosestLinkedVercelDirectory,findClosestVercelOutputDirectory}from"#shared/vercel-output-directory.js";const VERCEL_JSON_FILE_NAME=`vercel.json`,VERCEL_OUTPUT_CONFIG_FILE_NAME=`.vercel/output/config.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function hasServices(e){return e!==void 0&&Object.keys(createServiceConfigRecord(e)).length>0}function isNamedServiceConfigArray(e){return Array.isArray(e)}function createServiceConfigRecord(e){if(e===void 0)return{};if(isNamedServiceConfigArray(e)){let t={};for(let n of e)if(typeof n.name==`string`&&n.name.trim().length>0){let{name:e,...r}=n;t[e]=r}return t}return e}function resolveRelativeEntrypoint(e,t){let r=relative(e,t);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function quoteShellArgument(e){return`'${e.replaceAll(`'`,`'\\''`)}'`}function createGeneratedServiceBuild(e){let n=join(e.nextRoot,`.eve/vercel-services`,e.serviceName),a=join(n,`.vercel`,`output`),o=resolveRelativeEntrypoint(n,e.agent.appRoot),s=resolveRelativeEntrypoint(e.agent.appRoot,a),u=resolveRelativeEntrypoint(e.agent.appRoot,e.hostOutputDirectory),d=normalizePublicRoutePrefix(e.agent.publicRoutePrefix),f=d===void 0?``:` && export ${EVE_PUBLIC_ROUTE_PREFIX_ENV}=${quoteShellArgument(d)}`;return{buildCommand:`cd ${quoteShellArgument(o)} && export ${EVE_INTERNAL_BUILD_OUTPUT_DIRECTORY_ENV}=${quoteShellArgument(s)} && export ${EVE_INTERNAL_HOST_BUILD_OUTPUT_DIRECTORY_ENV}=${quoteShellArgument(u)}${f} && ${e.agent.buildCommand}`,root:resolveRelativeEntrypoint(e.nextRoot,n),rootDirectory:n}}async function resolveVercelOutputConfigLocation(n){let r=await findClosestLinkedVercelDirectory(n),i=r===void 0?n:dirname(r),a=await findClosestVercelOutputDirectory(n);return a===void 0?r===void 0?{canWriteGeneratedOutput:!!process.env.VERCEL,outputConfigPath:join(n,VERCEL_OUTPUT_CONFIG_FILE_NAME),projectRoot:i}:{canWriteGeneratedOutput:!0,outputConfigPath:join(r,`output`,`config.json`),projectRoot:i}:{canWriteGeneratedOutput:!0,outputConfigPath:join(a,`config.json`),projectRoot:i}}function normalizeVercelServicesConfig(e,t){if(!isRecord(e))throw Error(`${t} must contain a JSON object.`);let n=e.services;if(n!==void 0&&!isRecord(n)&&!(Array.isArray(n)&&n.every(e=>isRecord(e)&&typeof e.name==`string`&&e.name.trim().length>0)))throw Error(`${t} services must be a JSON object or named service array.`);let r=e.routes;if(r!==void 0&&!Array.isArray(r))throw Error(`${t} routes must be an array.`);return e}async function readVercelServicesConfig(e,t){try{return normalizeVercelServicesConfig(JSON.parse(await readFile(e,`utf8`)),t)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceEntryByFramework(e,t){return Object.entries(e).map(([e,t])=>({name:e,service:t})).find(e=>e.service.framework===t)}function findServiceEntryByName(e,t){let n=e[t];return n===void 0?void 0:{name:t,service:n}}function resolveServicePrefix(e){if(e!==void 0){if(typeof e.routePrefix==`string`&&e.routePrefix.trim().length>0)return e.routePrefix.trim();if(typeof e.mount==`string`&&e.mount.trim().length>0)return e.mount.trim();if(isRecord(e.mount)&&typeof e.mount.path==`string`&&e.mount.path.trim().length>0)return e.mount.path.trim()}}function resolveConfiguredServicePrefix(e){let t=findConfiguredEveServiceEntry(e.services,e.agent)?.service;return resolveServicePrefix(t)??e.agent.servicePrefix}function findConfiguredEveServiceEntry(e,t){if(t.name!==void 0){let n=findServiceEntryByName(e,createEveServiceName(t.name));if(n?.service.framework===`eve`)return n}return t.name===void 0?findServiceEntryByFramework(e,`eve`):void 0}function assertRootServicesIncludeEve(e){let t=[];for(let n of e.agents){let r=findConfiguredEveServiceEntry(e.services,n)?.service;if(r===void 0)throw Error(`${VERCEL_JSON_FILE_NAME} already defines services, so withEve cannot add generated eve services through ${VERCEL_OUTPUT_CONFIG_FILE_NAME}. Add the eve service for ${n.name??`the default agent`} to ${VERCEL_JSON_FILE_NAME}, or remove services from ${VERCEL_JSON_FILE_NAME}.`);t.push({name:n.name,servicePrefix:resolveServicePrefix(r)??n.servicePrefix})}return t}function createEveServiceRouteSrc(e){return e.length===0?`^/eve/v1/(.*)$`:`^${escapeRegExp(e.startsWith(`/`)?e:`/${e}`)}/eve/v1/(.*)$`}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function createEveServiceName(e){return e===void 0?`eve`:`eve-${e}`}function isEveServiceRoute(e,t,n){let r=e.destination;return e.src===n&&isRecord(r)&&r.type===`service`&&r.service===t}function createEveServiceRoute(e,t){return{destination:{service:e,type:`service`},src:t}}function isEveServiceRequestPathRoute(e,t){return e.src===t}function createEveServiceRequestPathRoute(e){return{src:e,transforms:[{args:`/eve/v1/$1`,op:`set`,type:`request.path`}]}}function insertEveServiceRequestPathRoute(e,t){let n=(e??[]).filter(e=>!isEveServiceRequestPathRoute(e,t));return[createEveServiceRequestPathRoute(t),...n]}function insertEveServiceRoutes(e,t){let n=e.filter(e=>!t.some(t=>isEveServiceRoute(e,t.serviceName,t.routeSrc))),r=n.findIndex(e=>e.handle===`filesystem`),i=t.map(e=>createEveServiceRoute(e.serviceName,e.routeSrc));return r===-1?[...i,...n]:[...n.slice(0,r),...i,...n.slice(r)]}async function ensureEveVercelOutputConfig(n){let{canWriteGeneratedOutput:r,outputConfigPath:i,projectRoot:o}=await resolveVercelOutputConfigLocation(n.nextRoot),c=(await readVercelServicesConfig(join(o,VERCEL_JSON_FILE_NAME),VERCEL_JSON_FILE_NAME)).services;if(hasServices(c))return{agents:assertRootServicesIncludeEve({agents:n.agents,services:createServiceConfigRecord(c)})};let l=await readVercelServicesConfig(i,VERCEL_OUTPUT_CONFIG_FILE_NAME),u=createServiceConfigRecord(l.services),d=n.agents.map(e=>({name:e.name,servicePrefix:resolveConfiguredServicePrefix({agent:e,services:u})}));if(!r)return{agents:d};let p={...u},m=[];for(let t of n.agents){let r=findConfiguredEveServiceEntry(u,t),o=r?.name??createEveServiceName(t.name),s=createEveServiceRouteSrc(t.publicRoutePrefix);if(r===void 0){let r=createGeneratedServiceBuild({agent:t,hostOutputDirectory:dirname(i),nextRoot:n.nextRoot,serviceName:o});await mkdir(r.rootDirectory,{recursive:!0});let c={buildCommand:r.buildCommand,framework:`eve`,routes:insertEveServiceRequestPathRoute(void 0,s),root:r.root};t.publicRoutePrefix.length>0&&(c.routePrefix=t.publicRoutePrefix),p[o]=c}else p[o]={...r.service,routes:insertEveServiceRequestPathRoute(r.service.routes,s)};m.push({routeSrc:s,serviceName:o})}let{services:h,...g}=l,_={...g,routes:insertEveServiceRoutes(l.routes??[],m),services:p,version:3};return JSON.stringify(l)!==JSON.stringify(_)&&(await mkdir(dirname(i),{recursive:!0}),await writeFile(i,`${JSON.stringify(_,null,2)}\n`)),{agents:d}}export{ensureEveVercelOutputConfig};