eve 0.26.2 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/src/harness/compaction-prompt.d.ts +19 -1
  3. package/dist/src/harness/compaction-prompt.js +7 -5
  4. package/dist/src/harness/compaction.d.ts +4 -11
  5. package/dist/src/harness/compaction.js +1 -1
  6. package/dist/src/harness/prompt-cache.d.ts +16 -5
  7. package/dist/src/harness/prompt-cache.js +1 -1
  8. package/dist/src/harness/token-estimate.d.ts +9 -0
  9. package/dist/src/harness/token-estimate.js +1 -0
  10. package/dist/src/internal/application/package.js +1 -1
  11. package/dist/src/internal/nitro/host/copy-host-middleware.js +1 -1
  12. package/dist/src/public/channels/auth.d.ts +59 -9
  13. package/dist/src/public/channels/auth.js +1 -1
  14. package/dist/src/public/next/index.js +1 -1
  15. package/dist/src/public/nuxt/index.d.ts +1 -1
  16. package/dist/src/public/nuxt/index.js +1 -1
  17. package/dist/src/public/nuxt/module.d.ts +10 -24
  18. package/dist/src/public/nuxt/module.js +1 -1
  19. package/dist/src/public/nuxt/routing.d.ts +5 -39
  20. package/dist/src/public/nuxt/routing.js +1 -1
  21. package/dist/src/public/nuxt/vercel-services.d.ts +117 -0
  22. package/dist/src/public/nuxt/vercel-services.js +1 -0
  23. package/dist/src/runtime/framework-tools/todo.js +1 -1
  24. package/dist/src/runtime/governance/auth/http-basic.js +1 -1
  25. package/dist/src/setup/scaffold/create/project.js +1 -1
  26. package/dist/src/{public/next → shared}/resolve-eve-binary.d.ts +1 -1
  27. package/docs/extensions.md +115 -77
  28. package/docs/guides/auth-and-route-protection.md +14 -1
  29. package/docs/guides/frontend/nuxt.mdx +22 -1
  30. package/docs/meta.json +1 -0
  31. package/package.json +1 -1
  32. package/dist/src/public/nuxt/vercel-json.d.ts +0 -17
  33. package/dist/src/public/nuxt/vercel-json.js +0 -1
  34. /package/dist/src/{public/next → shared}/resolve-eve-binary.js +0 -0
@@ -0,0 +1,117 @@
1
+ interface VercelRequestPathTransform {
2
+ readonly args: string;
3
+ readonly op: "set";
4
+ readonly type: "request.path";
5
+ }
6
+ interface VercelServiceRouteDestination {
7
+ readonly service?: string;
8
+ readonly type?: string;
9
+ }
10
+ interface VercelRouteConfig {
11
+ readonly destination?: string | VercelServiceRouteDestination;
12
+ readonly handle?: string;
13
+ readonly src?: string;
14
+ readonly transforms?: readonly VercelRequestPathTransform[];
15
+ readonly [key: string]: unknown;
16
+ }
17
+ interface VercelServiceConfig {
18
+ readonly framework?: string;
19
+ readonly routes?: readonly VercelRouteConfig[];
20
+ readonly [key: string]: unknown;
21
+ }
22
+ /**
23
+ * The top-level Vercel Build Output route that sends eve transport requests to
24
+ * the generated eve service.
25
+ */
26
+ export type EveVercelServiceRoute = {
27
+ readonly destination: {
28
+ readonly service: string;
29
+ readonly type: "service";
30
+ };
31
+ readonly src: string;
32
+ };
33
+ /**
34
+ * A service-scoped route carrying the `request.path` transform that pins the
35
+ * path the eve runtime observes to the eve transport namespace.
36
+ */
37
+ export type EveVercelServiceRequestPathRoute = {
38
+ readonly src: string;
39
+ readonly transforms: readonly [VercelRequestPathTransform];
40
+ };
41
+ /**
42
+ * The generated eve service entry written into the Vercel Build Output
43
+ * `services` record.
44
+ */
45
+ export type EveVercelGeneratedService = {
46
+ readonly buildCommand: string;
47
+ readonly framework: "eve";
48
+ readonly routes: readonly VercelRouteConfig[];
49
+ readonly root: string;
50
+ };
51
+ /**
52
+ * Minimal shape of the Nitro Vercel build-output config the module merges the
53
+ * generated eve service into.
54
+ */
55
+ export interface NitroVercelBuildConfig {
56
+ version?: number;
57
+ routes?: unknown[];
58
+ services?: Record<string, VercelServiceConfig>;
59
+ [key: string]: unknown;
60
+ }
61
+ /**
62
+ * Result of {@link ensureEveVercelServicesConfig}: `root` when `vercel.json`
63
+ * already declares stable services (the user owns routing; nothing is
64
+ * generated), `generated` with the service record to merge into the Nitro
65
+ * Vercel build config otherwise.
66
+ */
67
+ export type EnsureEveVercelServicesConfigResult = {
68
+ readonly mode: "root";
69
+ } | {
70
+ readonly mode: "generated";
71
+ readonly services: Record<string, EveVercelGeneratedService>;
72
+ };
73
+ /**
74
+ * Build the top-level Build Output route that exposes the eve service on the
75
+ * eve transport namespace (`/eve/v1/*`).
76
+ */
77
+ export declare function createEveServiceRoute(serviceName?: string): EveVercelServiceRoute;
78
+ /**
79
+ * Build the eve service's own route that sets `request.path` so the eve
80
+ * runtime observes the transport path regardless of how the platform routed
81
+ * the request into the service.
82
+ */
83
+ export declare function createEveServiceRequestPathRoute(): EveVercelServiceRequestPathRoute;
84
+ /**
85
+ * Resolve the stable Vercel services configuration for a Nuxt + eve
86
+ * deployment.
87
+ *
88
+ * When `vercel.json` (looked up from the linked Vercel project root, falling
89
+ * back to the Nuxt root) already declares stable `services`, it must include
90
+ * an eve service and the module generates nothing. Otherwise this prepares a
91
+ * generated eve service — creating its isolated build root under
92
+ * `.eve/vercel-services/eve` so the eve build output cannot collide with the
93
+ * Nuxt Build Output — and returns the service record plus the public service
94
+ * route for the caller to merge into the Nitro Vercel build config. A legacy
95
+ * `experimentalServices` field is ignored with a migration warning: Vercel no
96
+ * longer routes it.
97
+ */
98
+ export declare function ensureEveVercelServicesConfig(input: {
99
+ readonly appRoot: string;
100
+ readonly eveBuildCommand?: string;
101
+ readonly nuxtRoot: string;
102
+ }): Promise<EnsureEveVercelServicesConfigResult>;
103
+ /**
104
+ * Merge the generated eve service and its public route into a Nitro Vercel
105
+ * build config (`nitro.vercel.config`).
106
+ *
107
+ * The service route is inserted before an existing `handle: "filesystem"`
108
+ * route, or prepended when none exists — Nitro appends its own generated
109
+ * routes (including the filesystem handle) after this config's routes, so the
110
+ * eve route always resolves before the Nuxt app's filesystem routing. An eve
111
+ * service already configured by the user is preserved and only gains the
112
+ * `request.path` route; everything else passes through untouched.
113
+ */
114
+ export declare function mergeEveVercelConfig(existing: NitroVercelBuildConfig | undefined, generated: Extract<EnsureEveVercelServicesConfigResult, {
115
+ mode: "generated";
116
+ }>): NitroVercelBuildConfig;
117
+ export {};
@@ -0,0 +1 @@
1
+ import{EVE_ROUTE_PREFIX}from"#protocol/routes.js";import{dirname,join,relative}from"node:path";import{mkdir,readFile}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}from"#shared/vercel-output-directory.js";import{resolveEveBinaryPath}from"#shared/resolve-eve-binary.js";const VERCEL_JSON_FILE_NAME=`vercel.json`,EVE_SERVICE_ROUTE_SRC=`^${EVE_ROUTE_PREFIX}/(.*)$`,EVE_SERVICE_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/$1`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function toPosixRelative(e,t){let n=relative(e,t);return n.length===0?`.`:n.replaceAll(`\\`,`/`)}function quoteShellArgument(e){return`'${e.replaceAll(`'`,`'\\''`)}'`}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 normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.services;if(t!==void 0&&!isRecord(t)&&!(Array.isArray(t)&&t.every(e=>isRecord(e)&&typeof e.name==`string`&&e.name.trim().length>0)))throw Error(`${VERCEL_JSON_FILE_NAME} services must be a JSON object or named service array.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findEveService(e){return Object.values(e).find(e=>e.framework===`eve`)}function assertRootServicesIncludeEve(t){if(findEveService(t)===void 0)throw Error(`${VERCEL_JSON_FILE_NAME} already defines services, so the eve Nuxt module cannot add a generated eve service. Add an eve service (framework "eve") and a rewrite from ${EVE_ROUTE_PREFIX}/(.*) to it in ${VERCEL_JSON_FILE_NAME}, or remove services from ${VERCEL_JSON_FILE_NAME}.`)}function createEveServiceRoute(e=`eve`){return{destination:{service:e,type:`service`},src:EVE_SERVICE_ROUTE_SRC}}function createEveServiceRequestPathRoute(){return{src:EVE_SERVICE_ROUTE_SRC,transforms:[{args:EVE_SERVICE_ROUTE_PATH,op:`set`,type:`request.path`}]}}function isEveServiceRoute(e,t){let n=e.destination;return e.src===EVE_SERVICE_ROUTE_SRC&&isRecord(n)&&n.type===`service`&&n.service===t}function insertEveServiceRoute(e,t){let n=e.filter(e=>!(isRecord(e)&&isEveServiceRoute(e,t))),r=n.findIndex(e=>isRecord(e)&&e.handle===`filesystem`);return r===-1?[createEveServiceRoute(t),...n]:[...n.slice(0,r),createEveServiceRoute(t),...n.slice(r)]}function insertEveServiceRequestPathRoute(e){let t=(e??[]).filter(e=>e.src!==EVE_SERVICE_ROUTE_SRC);return[createEveServiceRequestPathRoute(),...t]}function createGeneratedServiceBuild(e){let t=join(e.nuxtRoot,`.eve/vercel-services`,`eve`),r=join(t,`.vercel`,`output`),i=join(e.nuxtRoot,`.vercel`,`output`),a=toPosixRelative(t,e.appRoot),c=toPosixRelative(e.appRoot,r),l=toPosixRelative(e.appRoot,i),u=e.eveBuildCommand??`node ${quoteShellArgument(toPosixRelative(e.appRoot,resolveEveBinaryPath(e.nuxtRoot)))} build`;return{buildCommand:`cd ${quoteShellArgument(a)} && export ${EVE_INTERNAL_BUILD_OUTPUT_DIRECTORY_ENV}=${quoteShellArgument(c)} && export ${EVE_INTERNAL_HOST_BUILD_OUTPUT_DIRECTORY_ENV}=${quoteShellArgument(l)} && ${u}`,root:toPosixRelative(e.nuxtRoot,t),rootDirectory:t}}async function ensureEveVercelServicesConfig(e){let r=await findClosestLinkedVercelDirectory(e.nuxtRoot),a=await readVercelJsonConfig(join(r===void 0?e.nuxtRoot:dirname(r),VERCEL_JSON_FILE_NAME)),o=createServiceConfigRecord(a.services);if(Object.keys(o).length>0)return assertRootServicesIncludeEve(o),{mode:`root`};a.experimentalServices!==void 0&&console.warn(`[eve] ${VERCEL_JSON_FILE_NAME} defines experimentalServices, which Vercel no longer routes. The eve Nuxt module now generates the stable services config automatically — remove experimentalServices from ${VERCEL_JSON_FILE_NAME}.`);let s=createGeneratedServiceBuild(e);return await mkdir(s.rootDirectory,{recursive:!0}),{mode:`generated`,services:{eve:{buildCommand:s.buildCommand,framework:`eve`,routes:[createEveServiceRequestPathRoute()],root:s.root}}}}function mergeEveVercelConfig(e,t){let n=e?.services??{},r=Object.entries(n).find(([e,t])=>e===`eve`||t.framework===`eve`),i=r?.[0]??`eve`,a=r?{...n,[i]:{...r[1],routes:insertEveServiceRequestPathRoute(r[1].routes)}}:{...n,...t.services};return{version:3,...e,routes:insertEveServiceRoute(e?.routes??[],i),services:a}}export{createEveServiceRequestPathRoute,createEveServiceRoute,ensureEveVercelServicesConfig,mergeEveVercelConfig};
@@ -1,3 +1,3 @@
1
- import{z}from"#compiled/zod/index.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";const TodoStateKey=new ContextKey(`eve.todo`);function formatTodoSummary(e){return e.items.length===0?void 0:`[Your task list was preserved across context compaction]\n${e.items.map(e=>`- [${e.status===`completed`?`x`:e.status===`cancelled`?`-`:` `}] [${e.priority}] ${e.content}`).join(`
1
+ import{z}from"#compiled/zod/index.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{TODO_COMPACTION_PRESERVATION_LABEL}from"#harness/compaction-prompt.js";const TodoStateKey=new ContextKey(`eve.todo`);function formatTodoSummary(e){return e.items.length===0?void 0:`${TODO_COMPACTION_PRESERVATION_LABEL}\n${e.items.map(e=>`- [${e.status===`completed`?`x`:e.status===`cancelled`?`-`:` `}] [${e.priority}] ${e.content}`).join(`
2
2
  `)}`}function getTodoCompactionMessage(){let e=loadContext().get(TodoStateKey);if(e===void 0||e.items.length===0)return;let n=formatTodoSummary(e);if(n!==void 0)return{content:n,role:`user`}}function formatTodoResult(e){let{items:t}=e,n={cancelled:0,completed:0,in_progress:0,pending:0,total:t.length};for(let e of t)n[e.status]++;return{counts:n,todos:t}}function executeTodoTool(e){let n=loadContext(),{todos:r}=e??{};if(r!==void 0){let e={items:[...r]};return n.set(TodoStateKey,e),formatTodoResult(e)}return formatTodoResult(n.ensure(TodoStateKey,()=>({items:[]})))}const TODO_ITEM_SCHEMA=z.strictObject({content:z.string().describe(`Brief description of the task.`),priority:z.enum([`high`,`medium`,`low`]).describe(`Priority level of the task.`),status:z.enum([`pending`,`in_progress`,`completed`,`cancelled`]).describe(`Current status of the task.`)}),TODO_INPUT_SCHEMA=z.strictObject({todos:z.array(TODO_ITEM_SCHEMA).describe(`The updated todo list. Omit to read the current list without modifying it.`).optional()}),countSchema=z.number().int().min(0),TODO_OUTPUT_SCHEMA=z.strictObject({counts:z.strictObject({cancelled:countSchema,completed:countSchema,in_progress:countSchema,pending:countSchema,total:countSchema}),todos:z.array(TODO_ITEM_SCHEMA)}),TODO_TOOL_DEFINITION={description:[`Use this tool to create and manage a structured task list for the current session.`,`This helps you track progress, organize complex tasks, and demonstrate thoroughness.`,``,`When to use:`,`- Complex multistep tasks requiring 3 or more distinct steps`,`- When the user provides multiple tasks or a numbered list`,`- After receiving new instructions, to capture requirements`,`- After completing a task, to mark it complete and add follow-ups`,``,`When NOT to use:`,`- Single, straightforward tasks that need no tracking`,`- Purely conversational or informational requests`,``,`Usage:`,"- Call with `todos` to replace the entire list (full replacement write)","- Call without `todos` to read the current list",`- Both return the full current list with status counts`,`- Mark tasks in_progress when you start, completed when done`,`- Only have ONE task in_progress at a time`].join(`
3
3
  `),execute:async e=>executeTodoTool(e??{}),inputSchema:TODO_INPUT_SCHEMA,logicalPath:`eve:framework/todo`,name:`todo`,outputSchema:TODO_OUTPUT_SCHEMA,sourceId:`eve:todo-tool`,sourceKind:`module`};export{TODO_INPUT_SCHEMA,TODO_OUTPUT_SCHEMA,TODO_TOOL_DEFINITION,TodoStateKey,executeTodoTool,getTodoCompactionMessage};
@@ -1 +1 @@
1
- import{createHash,timingSafeEqual}from"node:crypto";function authenticateHttpBasicStrategy(e){let t=parseBasicAuthorizationHeader(e.authorization);return t===null||t.username!==e.strategy.username||!timingSafeStringEquals(t.password,e.strategy.password)?{kind:`not-authenticated`}:{kind:`authenticated`,principal:{attributes:Object.freeze({}),authenticator:`http-basic`,claims:Object.freeze({}),principalId:e.strategy.username,principalType:`user`}}}function parseBasicAuthorizationHeader(e){let t=/^Basic\s+(.+)$/i.exec(e);if(t===null)return null;let n=t[1];if(n===void 0)return null;let r;try{r=Buffer.from(n,`base64`).toString(`utf8`)}catch{return null}let i=r.indexOf(`:`);return i===-1?null:{password:r.slice(i+1),username:r.slice(0,i)}}function timingSafeStringEquals(e,n){return timingSafeEqual(hashString(e),hashString(n))}function hashString(t){return createHash(`sha256`).update(t,`utf8`).digest()}export{authenticateHttpBasicStrategy};
1
+ import{createHash,timingSafeEqual}from"node:crypto";function authenticateHttpBasicStrategy(e){let t=parseBasicAuthorizationHeader(e.authorization);return t===null||normalizeCredential(t.username)!==normalizeCredential(e.strategy.username)||!timingSafeStringEquals(normalizeCredential(t.password),normalizeCredential(e.strategy.password))?{kind:`not-authenticated`}:{kind:`authenticated`,principal:{attributes:Object.freeze({}),authenticator:`http-basic`,claims:Object.freeze({}),principalId:e.strategy.username,principalType:`user`}}}function parseBasicAuthorizationHeader(e){let t=/^Basic\s+(.+)$/i.exec(e);if(t===null)return null;let n=t[1];if(n===void 0)return null;let r;try{r=Buffer.from(n,`base64`).toString(`utf8`)}catch{return null}let i=r.indexOf(`:`);return i===-1?null:{password:r.slice(i+1),username:r.slice(0,i)}}function normalizeCredential(e){return e.normalize(`NFC`)}function timingSafeStringEquals(e,n){return timingSafeEqual(hashString(e),hashString(n))}function hashString(t){return createHash(`sha256`).update(t,`utf8`).digest()}export{authenticateHttpBasicStrategy};
@@ -1,4 +1,4 @@
1
- import{pinnedNodeEngineMajor}from"../../node-engine.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`^7.0.26`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.0`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.26.2`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e){return{"agent/agent.ts":BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
1
+ import{pinnedNodeEngineMajor}from"../../node-engine.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`^7.0.26`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.0`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.27.0`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e){return{"agent/agent.ts":BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
2
2
 
3
3
  export default defineAgent({
4
4
  model: "__EVE_INIT_MODEL__",
@@ -9,4 +9,4 @@
9
9
  * derive the bin path from the package root. Falls back to the conventional
10
10
  * app-local path when eve cannot be resolved (e.g. before install).
11
11
  */
12
- export declare function resolveEveBinaryPath(nextRoot: string): string;
12
+ export declare function resolveEveBinaryPath(appRoot: string): string;
@@ -1,39 +1,48 @@
1
1
  ---
2
2
  title: "Extensions"
3
- description: "Package tools, connections, skills, and hooks as a reusable package and mount it into an agent."
3
+ description: "Publish reusable eve capabilities as an npm package, then install and mount them in an agent."
4
4
  ---
5
5
 
6
- An extension packages eve capabilities — tools, connections, skills, instructions, hooks as a reusable npm or local package. You author it as an agent-shaped directory; a consuming agent mounts it under `agent/extensions/`, and its contributions compose into the agent under a namespace. Nothing is copied — upgrades come through the package manager.
6
+ Extensions package eve tools, connections, skills, instruction fragments, and hooks. A publisher builds and distributes a package; a consumer installs it and mounts it in an agent.
7
7
 
8
- ## Authoring
8
+ This enables sharing many different capability sets. A browser extension might include several tools for navigating a site. A memory extension could use hooks to capture context and tools to recall it. A self-improving extension could pair hooks with dynamic instructions.
9
9
 
10
- Start from a scaffold:
10
+ ## Publisher: create and publish an extension
11
+
12
+ ### Create the package
13
+
14
+ Start with the extension scaffold:
11
15
 
12
16
  ```bash
13
17
  npx eve@latest extension init my-crm
14
18
  ```
15
19
 
16
- This creates the package, installs dependencies, and initializes Git. You get `extension/extension.ts`, TypeScript config, and a `package.json` ready to publish or mount. Add tools, skills, hooks, and connections under `extension/` yourself.
20
+ The command creates the package, installs dependencies, and initializes Git. It includes `extension/extension.ts`, TypeScript configuration, and the package metadata required to build and publish.
17
21
 
18
- An extension is an agent-shaped directory without `agent.ts` or `sandbox` (those belong to the consuming agent). Every slot works as it does in an agent, with names derived from paths.
22
+ An extension uses the same file conventions as an agent for its contributions:
19
23
 
20
24
  ```
21
25
  @acme/crm/
22
26
  package.json
23
27
  extension/
24
- extension.ts # the extension declaration — see Configuration
28
+ extension.ts
25
29
  tools/search.ts
26
30
  connections/api.ts
27
31
  skills/triage/SKILL.md
32
+ instructions.md
28
33
  hooks/audit.ts
29
- lib/http.ts # shared helpers, imported as ../lib/http
34
+ lib/http.ts
30
35
  ```
31
36
 
32
- Name tools and connections for what they do (`search`, not `crm_search`) the mount supplies the namespace. Shared code goes in `extension/lib/`, imported by relative path eve compiles the source, so relative imports need no `.js` extension.
37
+ Each listed slot accepts the same authored forms as its agent counterpart. Static and dynamic tools, skills, and instructions all work in an extension: `extension/instructions.ts` is as valid as `extension/instructions.md`, and `extension/tools/` can contain `defineDynamic(...)`.
38
+
39
+ Names come from paths, so call the tool `search`, not `crm_search`; the consumer's mount adds the `crm__` prefix. Keep shared code in `extension/lib/`.
33
40
 
34
- ### Configuration
41
+ Keep agent configuration, sandboxes, schedules, and nested extensions in the consumer's agent.
35
42
 
36
- Declare the extension in `extension/extension.ts` with `defineExtension`; its default export is the mount factory a consumer calls. Pass `config` — any [Standard Schema](https://standardschema.dev) (a Zod object here), like a tool's `inputSchema` — to accept consumer settings:
43
+ ### Add configuration and contributions
44
+
45
+ The publisher's `extension/extension.ts` default-exports a `defineExtension` handle. Give it a [Standard Schema](https://standardschema.dev) when consumers need to provide settings:
37
46
 
38
47
  ```ts title="extension/extension.ts"
39
48
  import { defineExtension } from "eve/extension";
@@ -42,40 +51,41 @@ import { z } from "zod";
42
51
  export default defineExtension({
43
52
  config: z.object({
44
53
  apiKey: z.string(),
45
- baseUrl: z.string().default("https://api.acme.example"),
54
+ baseUrl: z.string().url().default("https://api.acme.example"),
46
55
  }),
47
56
  });
48
57
  ```
49
58
 
50
- Config is optional `defineExtension()` with no schema. Read it off the handle, imported from the declaration; it's typed from the schema:
59
+ Contributions import that handle to read the validated configuration. Defaults have already been applied:
51
60
 
52
61
  ```ts title="extension/tools/search.ts"
53
62
  import { defineTool } from "eve/tools";
63
+ import { z } from "zod";
54
64
 
55
65
  import extension from "../extension";
56
66
 
57
67
  export default defineTool({
58
68
  description: "Search the CRM.",
59
- inputSchema: {/* ... */},
69
+ inputSchema: z.object({ query: z.string() }),
60
70
  async execute({ query }) {
61
- const { apiKey, baseUrl } = extension.config; // validated, defaults applied
71
+ const { apiKey, baseUrl } = extension.config;
72
+ return { query, baseUrl, authenticated: apiKey.length > 0 };
62
73
  },
63
74
  });
64
75
  ```
65
76
 
66
- Config is bound once when the consumer mounts the extension and stays constant for the session; per-request values belong in connection auth instead.
77
+ If no configuration is needed, export `defineExtension()` and let consumers re-export it directly. Config schemas must validate synchronously.
67
78
 
68
- ### State
79
+ `defineState` is automatically scoped to the publisher's package, so the same state name does not collide with the consumer or another extension.
69
80
 
70
- `defineState` is scoped to the extension's package automatically, so identically-named state never collides with the consuming agent or another extension. Author it exactly as in an agent — `defineState("budget", …)`.
81
+ ### Build and publish
71
82
 
72
- ## Publishing
73
-
74
- Declare separate authoring and distribution roots and run `eve extension build` (wired to `build`/`prepare`):
83
+ The scaffold's `package.json` declares separate source and distribution roots:
75
84
 
76
85
  ```jsonc title="package.json"
77
86
  {
78
- "name": "@acme/crm",
87
+ "name": "my-crm",
88
+ "version": "0.0.0",
79
89
  "type": "module",
80
90
  "eve": {
81
91
  "extension": {
@@ -84,86 +94,105 @@ Declare separate authoring and distribution roots and run `eve extension build`
84
94
  },
85
95
  },
86
96
  "files": ["dist"],
87
- "peerDependencies": { "eve": "*" },
88
- "devDependencies": { "eve": "x.y.z", "typescript": "^x" },
89
- "dependencies": { "zod": "^3" },
90
- "scripts": { "build": "eve extension build", "prepare": "eve extension build" },
91
- }
92
- ```
93
-
94
- Author the source with `moduleResolution: "bundler"` — eve compiles it, so relative imports need no `.js` extension:
95
-
96
- ```jsonc title="tsconfig.json"
97
- {
98
- "compilerOptions": {
99
- "module": "esnext",
100
- "moduleResolution": "bundler",
101
- "strict": true,
102
- "noEmit": true,
103
- "types": ["node"],
97
+ "exports": {
98
+ ".": {
99
+ "types": "./dist/index.d.ts",
100
+ "default": "./dist/index.mjs",
101
+ },
102
+ "./tools": {
103
+ "types": "./dist/tools/index.d.ts",
104
+ "default": "./dist/tools/index.mjs",
105
+ },
106
+ },
107
+ "scripts": {
108
+ "build": "eve extension build",
109
+ "prepare": "eve extension build",
110
+ "typecheck": "tsc",
111
+ },
112
+ "dependencies": {
113
+ "zod": "^x",
114
+ },
115
+ "devDependencies": {
116
+ "@types/node": "^x",
117
+ "eve": "x.y.z",
118
+ "typescript": "^x",
119
+ },
120
+ "peerDependencies": {
121
+ "eve": "*",
122
+ },
123
+ "engines": {
124
+ "node": ">=24",
104
125
  },
105
- "include": ["extension/**/*.ts"],
106
126
  }
107
127
  ```
108
128
 
109
- `eve extension build` transforms every JavaScript or TypeScript module into an agent-shaped `dist/extension` tree, copies skill packages and their assets, emits type declarations, and writes `dist/extension/_manifest.json`. The generated package entrypoints (`dist/index.mjs` and `dist/tools/index.mjs`) re-export those same dist modules, and the build fills the `exports` map so you never hand-list it. The published package therefore needs `dist/`, not the author's original TypeScript. Consumers read only `eve.extension.dist`, so a published package may omit `eve.extension.source`; keep `source` declared wherever `eve extension build` runs.
129
+ The scaffold omits `engines` when it creates a workspace package.
110
130
 
111
- The build uses the package `tsconfig.json` when it emits declarations. Its `include` must cover every JavaScript or TypeScript module in the extension source (and JavaScript modules require `allowJs`); the build fails before publishing if any distributed module would be missing its declaration.
131
+ Build the package with `eve extension build`:
112
132
 
113
- The manifest contains only its format, the diagnostic eve build version, and the versions of extension capabilities this package actually uses. It does not contain compiled tools, schemas, names, or executable definitions; the consuming eve still discovers and normalizes the agent-shaped dist tree.
133
+ ```bash
134
+ eve extension build
135
+ ```
114
136
 
115
- ### Workspace development
137
+ `eve extension build` writes an agent-shaped `dist/extension` tree, copies skill assets, emits declarations, and records compatibility metadata. It also manages the package exports for the mount factory (`@acme/crm`) and tool definitions (`@acme/crm/tools`). Publish `dist/`; consumers do not need the publisher's TypeScript source.
116
138
 
117
- During local development, `eve dev` automatically rebuilds mounted workspace extensions when their source changes.
139
+ The exact `eve` development pin controls the publisher's authoring API and build tooling. The wildcard peer lets the consumer provide the runtime copy of eve. At consumption time, eve checks generated metadata, not the npm peer range. Do not add eve to regular `dependencies`.
118
140
 
119
- ### Dependencies
141
+ Put runtime packages such as `zod` or an SDK in `dependencies`. If a dependency cannot be bundled, such as a native addon, tell consumers to add it to `build.externalDependencies` in `agent.ts`.
120
142
 
121
- `eve` is a required wildcard **peer** dependency: one eve lives in the consuming app and the extension's `eve/*` imports resolve to it. The extension's concrete eve version belongs in `devDependencies` for authoring types and build tooling. npm peer semver does not decide extension compatibility; eve validates the generated per-capability requirements. Do not mark the eve peer optional and do not add eve to regular `dependencies`.
143
+ Consumers can now add the built package to an agent.
122
144
 
123
- `eve extension init` pins `devDependencies.eve` to the exact eve release that created the scaffold:
145
+ ## Consumer: install and mount an extension
124
146
 
125
- ```jsonc title="package.json"
126
- {
127
- "peerDependencies": { "eve": "*" },
128
- "devDependencies": { "eve": "0.25.0" },
129
- }
130
- ```
131
-
132
- The exact pin makes builds reproducible. Keep it until the extension intentionally upgrades its eve authoring API. `eve extension build` records the capability contracts required by the build, and each consuming eve validates those requirements before it runs the extension.
147
+ A mount gives the publisher's contributions a namespace. Updating the package updates the mounted extension; nothing is copied into the consumer's agent.
133
148
 
134
- If the extension must support an older eve release, replace the development pin with that exact version, then typecheck and build with it. Test the same dist against both the oldest and latest supported consumers; rebuilding with each consumer would produce and test different artifacts.
149
+ ### Install the package
135
150
 
136
- Everything else the extension imports at execution time (SDKs, `zod`, …) goes in `dependencies`; each extension resolves its own versions. Build-only and test-only packages go in `devDependencies`.
151
+ Install the extension in the consumer's agent project:
137
152
 
138
- Those deps resolve from `node_modules` under `eve dev`/`eve eval` and are bundled into the deployable by the consuming agent's `eve build`. A dependency that can't be bundled (a native addon) must be listed in the **consuming agent's** `build.externalDependencies` — an extension can't declare build config, so note it in your README.
153
+ ```bash
154
+ npm install @acme/crm
155
+ ```
139
156
 
140
- ## Mounting
157
+ ### Mount it
141
158
 
142
- A consuming agent mounts an extension under `agent/extensions/` a single file, or a directory when it needs [overrides](#overrides). The namespace is the file basename or directory name; contributions compose as `<namespace>__<name>` (`crm__search`, `crm__api`).
159
+ Create a file under `agent/extensions/`. Its filename becomes the mount namespace. Call the publisher's default export when the extension needs configuration:
143
160
 
144
161
  ```ts title="agent/extensions/crm.ts"
145
162
  import crm from "@acme/crm";
146
163
 
147
- export default crm({ apiKey: process.env.CRM_API_KEY });
164
+ export default crm({ apiKey: process.env.CRM_API_KEY! });
148
165
  ```
149
166
 
150
- A no-config extension takes no factory call mount it with a bare re-export:
167
+ Set `CRM_API_KEY` in the consumer's environment, such as `.env.local` for local development.
168
+
169
+ The mount adds `crm__` to named contributions: `tools/search.ts` becomes `crm__search`, and `connections/api.ts` becomes `crm__api`.
170
+
171
+ For a publisher with no configuration, mount its default export directly:
151
172
 
152
173
  ```ts title="agent/extensions/gizmo.ts"
153
174
  export { default } from "@acme/gizmo";
154
175
  ```
155
176
 
156
- ### Overrides
177
+ The same mount shape works with an npm package, a workspace dependency, or a linked local package.
178
+
179
+ ### Override a contribution
157
180
 
158
- To override a mounted extension's contributions, author the mount as a directory: the declaration in `extension.ts`, override slots alongside it.
181
+ Use a directory mount to replace or remove a publisher contribution. Put the mount declaration in `extension.ts` and add overrides beside it:
159
182
 
160
183
  ```
161
184
  agent/extensions/crm/
162
- extension.ts # export default crm({ apiKey: process.env.CRM_API_KEY })
163
- tools/search.ts # composes as crm__search, shadowing the extension's own
185
+ extension.ts
186
+ tools/search.ts
164
187
  ```
165
188
 
166
- A file in an override slot composes under the mount namespace and wins on a name collision. Name it for the bare contribution name (`search`, not `crm__search`) — the directory supplies the prefix. To tweak the extension's own definition, import and re-define it:
189
+ ```ts title="agent/extensions/crm/extension.ts"
190
+ import crm from "@acme/crm";
191
+
192
+ export default crm({ apiKey: process.env.CRM_API_KEY! });
193
+ ```
194
+
195
+ A same-named consumer tool, connection, or skill wins. To adjust a publisher tool, import it from the package's `./tools` export and define it again:
167
196
 
168
197
  ```ts title="agent/extensions/crm/tools/search.ts"
169
198
  import { search } from "@acme/crm/tools";
@@ -173,7 +202,7 @@ import { always } from "eve/tools/approval";
173
202
  export default defineTool({ ...search, approval: always() });
174
203
  ```
175
204
 
176
- Or drop it entirely by opting out of the slot with `disableTool()`:
205
+ To remove a publisher tool, use `disableTool()` in its matching slot:
177
206
 
178
207
  ```ts title="agent/extensions/crm/tools/search.ts"
179
208
  import { disableTool } from "eve/tools";
@@ -181,13 +210,13 @@ import { disableTool } from "eve/tools";
181
210
  export default disableTool();
182
211
  ```
183
212
 
184
- An override targets one slot, matched by name and kind: a static file replaces the extension's static tool, a dynamic file replaces its dynamic resolver, and `disableTool()` removes whichever the extension put there. Because a dynamic tool wins over a same-named static one at runtime, replace or disable a dynamic tool through its own slot — a static file of the same name won't shadow it.
213
+ Hooks and instruction fragments are additive, so they cannot be replaced. To replace a dynamic tool, use a dynamic definition in the same slot; dynamic tools win over same-named static tools at runtime. `disableTool()` removes either kind.
185
214
 
186
- Overrides only work here — the `<namespace>__` prefix is reserved, so an agent-root contribution named `crm__…` is a build error and an extension can't be shadowed from outside its mount.
215
+ The `crm__` prefix is reserved for this directory mount. A consumer cannot override the extension from `agent/tools/`, `agent/connections/`, or another agent-root slot.
187
216
 
188
- ### Typed tool results
217
+ ### Use a publisher tool result in a hook
189
218
 
190
- A consuming agent can narrow a mounted extension's tool result in a hook: import the tool from the extension's `./tools` export and pass it to [`toolResultFrom`](/guides/hooks#narrowing-tool-results). It matches the namespaced result (`crm__search`) because identity keys off the tool definition, not its name.
219
+ To retain a publisher tool's result type in a consumer hook, import its definition from `./tools` and pass it to [`toolResultFrom`](/guides/hooks#narrowing-tool-results):
191
220
 
192
221
  ```ts title="agent/hooks/narrow-crm.ts"
193
222
  import { defineHook } from "eve/hooks";
@@ -198,14 +227,23 @@ export default defineHook({
198
227
  events: {
199
228
  "action.result"(event) {
200
229
  const match = toolResultFrom(event.data.result, search);
201
- if (match) console.log(match.output); // typed as search's output
230
+ if (match) console.log(match.output);
202
231
  },
203
232
  },
204
233
  });
205
234
  ```
206
235
 
207
- Matching keys off the tool's description, so keep extension tool descriptions distinct one shared with another tool makes the identity ambiguous and `toolResultFrom` stops matching.
236
+ `toolResultFrom` recognizes the mounted `crm__search` result from the original definition, not the namespaced string. Publishers should keep tool descriptions distinct so eve can assign each definition an unambiguous identity.
237
+
238
+ ### Compatibility
239
+
240
+ At build time, eve checks the publisher's generated capability metadata. If the extension needs an unsupported capability contract, upgrade eve or install a compatible extension release.
208
241
 
209
- ## Limits
242
+ ## What to read next
210
243
 
211
- An extension cannot declare a `sandbox`, agent config, schedules, or limits, and cannot mount other extensions — those are the consuming agent's to own (background scheduling, for instance, runs on the agent's deployment under its limits). An extension's tools run within the consuming agent's per-session limits.
244
+ - [Tools](/docs/tools): static tools, approval, and tool output
245
+ - [Dynamic capabilities](/docs/guides/dynamic-capabilities): dynamic tools, skills, and instructions
246
+ - [Instructions](/docs/instructions): static and TypeScript instructions
247
+ - [Skills](/docs/skills): package procedures and supporting files
248
+ - [Connections](/docs/connections): integrate external services
249
+ - [Hooks](/docs/guides/hooks): observe agent events
@@ -41,7 +41,7 @@ export default eveChannel({
41
41
  - returns `null` / `undefined`: skip to the next entry
42
42
  - **throws**: reject with a specific status
43
43
 
44
- If every entry skips, the request gets a `401`. An empty array `auth: []` rejects everything.
44
+ If every entry skips, the request gets a `401` whose `WWW-Authenticate` header advertises the challenge scheme(s) the configured entries declare — `Basic` for `httpBasic()`, `Bearer` for the token-based helpers (`jwtHmac`, `jwtEcdsa`, `oidc`, `vercelOidc`), both when you mix them, and `Bearer` as a fallback for entries that don't declare a scheme (custom `AuthFn`s, or an empty array). See [`withAuthChallenges`](#custom-verifiers) to declare a scheme on a custom `AuthFn`.
45
45
 
46
46
  ```ts
47
47
  import { type AuthFn, localDev, vercelOidc } from "eve/channels/auth";
@@ -96,6 +96,8 @@ Any other thrown error follows the normal channel failure path. When building a
96
96
  | `jwtEcdsa(...)` | You verify asymmetric JWTs minted by another system. |
97
97
  | `oidc(...)` | You want eve to verify OIDC-issued tokens from an arbitrary issuer. |
98
98
 
99
+ `httpBasic(credentials, { realm })` accepts an optional `realm`, rendered on the `WWW-Authenticate: Basic` challenge (e.g. `Basic realm="agent", charset="UTF-8"`) so browsers label their native login prompt. It defaults to `"eve"`, ensuring every Basic challenge includes the required realm. Usernames and passwords are normalized to Unicode NFC before comparison, matching the advertised UTF-8 credential encoding.
100
+
99
101
  Exercise caution for agents that process non-public, sensitive, regulated, or production data unless you have implemented other access controls.
100
102
 
101
103
  ### `localDev()`
@@ -131,6 +133,17 @@ vercelOidc({
131
133
 
132
134
  When none of the shipped helpers fit, write your own `AuthFn` (the array example above) or call the low-level verifiers directly. Each verifier is the pure function sitting behind the matching strategy helper, and returns `{ ok: true, sessionAuth }` or `{ ok: false }`:
133
135
 
136
+ A custom `AuthFn` doesn't declare a `WWW-Authenticate` scheme by default, so `routeAuth` falls back to `Bearer` for it. Wrap it with `withAuthChallenges(fn, challenges)` to declare the scheme(s) it actually satisfies, so a mixed `auth` array produces an accurate 401:
137
+
138
+ ```ts
139
+ import { withAuthChallenges, type AuthFn } from "eve/channels/auth";
140
+
141
+ const apiKeyAuth: AuthFn<Request> = withAuthChallenges(
142
+ (request) => (isValidApiKey(request) ? apiKeySessionAuth : null),
143
+ [{ scheme: "Bearer" }],
144
+ );
145
+ ```
146
+
134
147
  | Verifier | Behind | Input |
135
148
  | -------------------------------------- | -------------- | -------------------------------- |
136
149
  | `verifyHttpBasic(header, credentials)` | `httpBasic()` | raw `Authorization` header value |
@@ -74,7 +74,7 @@ For a public demo, use `none()` (also from `eve/channels/auth`) to skip authenti
74
74
  ## Dev vs deploy topology
75
75
 
76
76
  - **Local dev.** `npm run dev` starts the eve dev server next to `nuxt dev` and proxies the eve routes through it. As far as the browser knows, everything is the Nuxt origin.
77
- - **Vercel.** A single Vercel project carries both the Nuxt app and the eve runtime. The web app stays public; the runtime sits behind it on the same origin. Set `eveBuildCommand` when the agent needs its own build step:
77
+ - **Vercel.** The web app and the eve runtime deploy as a single project. On Vercel builds the module adds Build Output [`services`](https://vercel.com/docs/services) for eve and `routes` that send `/eve/v1/**` to that service before filesystem routing; the Nuxt app itself remains the default app. No `vercel.json` is required. By default the generated service runs the installed eve binary from the agent root, so the agent directory does not need its own `package.json`. When the agent needs its own build step, set `eveBuildCommand`:
78
78
 
79
79
  ```ts
80
80
  export default defineNuxtConfig({
@@ -92,6 +92,27 @@ For a public demo, use `none()` (also from `eve/channels/auth`) to skip authenti
92
92
  EVE_NUXT_PRODUCTION_PORT=5000 npm run build && npm run preview
93
93
  ```
94
94
 
95
+ ## Managing vercel.json yourself
96
+
97
+ When `vercel.json` declares [`services`](https://vercel.com/docs/services), the module generates nothing and your configuration owns routing. It must include the eve service (`framework: "eve"`) and a rewrite that exposes the eve transport, or the module fails the build:
98
+
99
+ ```json title="vercel.json"
100
+ {
101
+ "services": {
102
+ "web": { "root": ".", "framework": "nuxtjs" },
103
+ "eve": { "root": "agent", "framework": "eve", "buildCommand": "eve build" }
104
+ },
105
+ "rewrites": [{ "source": "/eve/v1/(.*)", "destination": { "service": "eve" } }]
106
+ }
107
+ ```
108
+
109
+ ### Migrating from experimentalServices
110
+
111
+ Earlier versions of the module wrote the legacy `experimentalServices` field into `vercel.json`. Vercel no longer routes that model — deployments build both services but every `/eve/v1/*` request returns a platform NOT_FOUND — so the module now ignores the field and warns when it sees one. Migrate either way:
112
+
113
+ - **Generated (default).** Delete `vercel.json` (or just its `experimentalServices` block) and set the project's Framework Preset back to Nuxt.js. The module generates the eve service and its routing on every Vercel build.
114
+ - **Hand-maintained.** Replace `experimentalServices` with the stable `services` and `rewrites` shown above, and keep the "Services" Framework Preset.
115
+
95
116
  ## What to read next
96
117
 
97
118
  - [`useEveAgent` (Vue)](./use-eve-agent-vue): the composable API
package/docs/meta.json CHANGED
@@ -9,6 +9,7 @@
9
9
  "skills",
10
10
  "channels",
11
11
  "connections",
12
+ "extensions",
12
13
  "sandbox",
13
14
  "subagents",
14
15
  "schedules",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.26.2",
3
+ "version": "0.27.0",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [
@@ -1,17 +0,0 @@
1
- export interface EnsureVercelJsonResult {
2
- readonly servicePrefix: string;
3
- }
4
- /**
5
- * Ensure `vercel.json` declares the Nuxt web service and the eve agent
6
- * service so a Vercel deployment ships both from one project.
7
- *
8
- * Existing services are preserved untouched; an already-configured eve
9
- * service's `routePrefix` wins over {@link input.servicePrefix}. The file is
10
- * only rewritten when the resulting config differs from what is on disk.
11
- */
12
- export declare function ensureEveVercelJson(input: {
13
- readonly appRoot: string;
14
- readonly eveBuildCommand: string;
15
- readonly nuxtRoot: string;
16
- readonly servicePrefix: string;
17
- }): Promise<EnsureVercelJsonResult>;
@@ -1 +0,0 @@
1
- import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,n){let r=relative(e,n);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}async function ensureEveVercelJson(t){let n=join(t.nuxtRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(n),a=resolveRelativeEntrypoint(t.nuxtRoot,t.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`eve`),c=findServiceByFramework(o,`nuxtjs`),l=s?.routePrefix??t.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`nuxtjs`,routePrefix:`/`}),s===void 0&&(u.eve={buildCommand:t.eveBuildCommand,entrypoint:a,framework:`eve`,routePrefix:l});let d={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u};return JSON.stringify(i)!==JSON.stringify(d)&&await writeFile(n,`${JSON.stringify(d,null,2)}\n`),{servicePrefix:l}}export{ensureEveVercelJson};