eve 0.27.3 → 0.27.4
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.
- package/CHANGELOG.md +9 -0
- package/dist/src/channel/reset-session.d.ts +8 -0
- package/dist/src/channel/reset-session.js +1 -0
- package/dist/src/channel/routes.d.ts +24 -1
- package/dist/src/channel/send.js +1 -1
- package/dist/src/channel/types.d.ts +14 -0
- package/dist/src/chunks/{use-eve-agent-CbF0l_Fp.js → use-eve-agent-BtkhbY2I.js} +53 -4
- package/dist/src/chunks/{use-eve-agent-CgxB9WQv.js → use-eve-agent-NUjD28Iu.js} +53 -4
- package/dist/src/cli/dev/tui/blocks.d.ts +7 -0
- package/dist/src/cli/dev/tui/blocks.js +3 -3
- package/dist/src/cli/dev/tui/message-queue.d.ts +88 -0
- package/dist/src/cli/dev/tui/message-queue.js +1 -0
- package/dist/src/cli/dev/tui/runner.d.ts +33 -0
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/subagent-pump.d.ts +9 -0
- package/dist/src/cli/dev/tui/subagent-pump.js +1 -1
- package/dist/src/cli/dev/tui/terminal-renderer.d.ts +9 -0
- package/dist/src/cli/dev/tui/terminal-renderer.js +13 -11
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/session.d.ts +13 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/types.d.ts +10 -0
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/protocol/reset-session.d.ts +14 -0
- package/dist/src/protocol/reset-session.js +1 -0
- package/dist/src/protocol/routes.d.ts +5 -0
- package/dist/src/protocol/routes.js +1 -1
- package/dist/src/public/channels/eve.d.ts +2 -2
- package/dist/src/public/channels/eve.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/definitions/channel.d.ts +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/vercel-project-api.d.ts +1 -1
- package/dist/src/setup/vercel-project-api.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/channels/custom.mdx +38 -1
- package/docs/extensions.md +98 -18
- package/docs/guides/dev-tui.md +9 -2
- package/docs/reference/cli.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "#compiled/zod/index.js";
|
|
2
|
+
/** Outcome of retiring the owner of a continuation token. */
|
|
3
|
+
export type ResetStatus = "no_active_session" | "reset";
|
|
4
|
+
/** Successful response returned by the standard session-reset route. */
|
|
5
|
+
export type ResetResponse = {
|
|
6
|
+
readonly ok: true;
|
|
7
|
+
readonly previousSessionId: string;
|
|
8
|
+
readonly status: "reset";
|
|
9
|
+
} | {
|
|
10
|
+
readonly ok: true;
|
|
11
|
+
readonly status: "no_active_session";
|
|
12
|
+
};
|
|
13
|
+
/** Validates successful responses from the standard session-reset route. */
|
|
14
|
+
export declare const ResetResponseSchema: z.ZodType<ResetResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z}from"#compiled/zod/index.js";const ResetResponseSchema=z.discriminatedUnion(`status`,[z.object({ok:z.literal(!0),previousSessionId:z.string().min(1),status:z.literal(`reset`)}),z.object({ok:z.literal(!0),status:z.literal(`no_active_session`)})]);export{ResetResponseSchema};
|
|
@@ -17,6 +17,11 @@ export declare const EVE_INFO_ROUTE_PATH = "/eve/v1/info";
|
|
|
17
17
|
* Stable framework-owned route for creating a new session.
|
|
18
18
|
*/
|
|
19
19
|
export declare const EVE_CREATE_SESSION_ROUTE_PATH = "/eve/v1/session";
|
|
20
|
+
/**
|
|
21
|
+
* Stable framework-owned route for retiring the session that owns a client
|
|
22
|
+
* continuation token. The request body supplies the channel-local token.
|
|
23
|
+
*/
|
|
24
|
+
export declare const EVE_RESET_SESSION_ROUTE_PATH = "/eve/v1/session/reset";
|
|
20
25
|
/**
|
|
21
26
|
* Stable framework-owned route pattern for sending a message to an existing
|
|
22
27
|
* session.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const EVE_ROUTE_PREFIX=`/eve/v1`,EVE_HEALTH_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/health`,EVE_INFO_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/info`,EVE_CREATE_SESSION_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/session`,EVE_CONTINUE_SESSION_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId`,EVE_MESSAGE_STREAM_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId/stream`,EVE_CANCEL_TURN_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId/cancel`,EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/dev/schedules/:scheduleId`,EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/dev/runtime-artifacts`,EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH=`${EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH}/rebuild`;function createEveDevDispatchSchedulePath(e){return`${EVE_ROUTE_PREFIX}/dev/schedules/${encodeURIComponent(e)}`}const EVE_CONNECTION_CALLBACK_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/connections/:name/callback/:token`,EVE_CALLBACK_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/callback/:token`;function createEveMessageStreamRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}/stream`}function createEveContinueSessionRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}`}function createEveCancelTurnRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}/cancel`}function createEveConnectionCallbackRoutePath(e,t){return`${EVE_ROUTE_PREFIX}/connections/${encodeURIComponent(e)}/callback/${encodeURIComponent(t)}`}function createEveCallbackRoutePath(e){return`${EVE_ROUTE_PREFIX}/callback/${encodeURIComponent(e)}`}export{EVE_CALLBACK_ROUTE_PATTERN,EVE_CANCEL_TURN_ROUTE_PATTERN,EVE_CONNECTION_CALLBACK_ROUTE_PATTERN,EVE_CONTINUE_SESSION_ROUTE_PATTERN,EVE_CREATE_SESSION_ROUTE_PATH,EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN,EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH,EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH,EVE_HEALTH_ROUTE_PATH,EVE_INFO_ROUTE_PATH,EVE_MESSAGE_STREAM_ROUTE_PATTERN,EVE_ROUTE_PREFIX,createEveCallbackRoutePath,createEveCancelTurnRoutePath,createEveConnectionCallbackRoutePath,createEveContinueSessionRoutePath,createEveDevDispatchSchedulePath,createEveMessageStreamRoutePath};
|
|
1
|
+
const EVE_ROUTE_PREFIX=`/eve/v1`,EVE_HEALTH_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/health`,EVE_INFO_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/info`,EVE_CREATE_SESSION_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/session`,EVE_RESET_SESSION_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/session/reset`,EVE_CONTINUE_SESSION_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId`,EVE_MESSAGE_STREAM_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId/stream`,EVE_CANCEL_TURN_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/session/:sessionId/cancel`,EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/dev/schedules/:scheduleId`,EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH=`${EVE_ROUTE_PREFIX}/dev/runtime-artifacts`,EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH=`${EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH}/rebuild`;function createEveDevDispatchSchedulePath(e){return`${EVE_ROUTE_PREFIX}/dev/schedules/${encodeURIComponent(e)}`}const EVE_CONNECTION_CALLBACK_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/connections/:name/callback/:token`,EVE_CALLBACK_ROUTE_PATTERN=`${EVE_ROUTE_PREFIX}/callback/:token`;function createEveMessageStreamRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}/stream`}function createEveContinueSessionRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}`}function createEveCancelTurnRoutePath(e){return`${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(e)}/cancel`}function createEveConnectionCallbackRoutePath(e,t){return`${EVE_ROUTE_PREFIX}/connections/${encodeURIComponent(e)}/callback/${encodeURIComponent(t)}`}function createEveCallbackRoutePath(e){return`${EVE_ROUTE_PREFIX}/callback/${encodeURIComponent(e)}`}export{EVE_CALLBACK_ROUTE_PATTERN,EVE_CANCEL_TURN_ROUTE_PATTERN,EVE_CONNECTION_CALLBACK_ROUTE_PATTERN,EVE_CONTINUE_SESSION_ROUTE_PATTERN,EVE_CREATE_SESSION_ROUTE_PATH,EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN,EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH,EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH,EVE_HEALTH_ROUTE_PATH,EVE_INFO_ROUTE_PATH,EVE_MESSAGE_STREAM_ROUTE_PATTERN,EVE_RESET_SESSION_ROUTE_PATH,EVE_ROUTE_PREFIX,createEveCallbackRoutePath,createEveCancelTurnRoutePath,createEveConnectionCallbackRoutePath,createEveContinueSessionRoutePath,createEveDevDispatchSchedulePath,createEveMessageStreamRoutePath};
|
|
@@ -105,8 +105,8 @@ export interface EveChannel extends Channel {
|
|
|
105
105
|
/**
|
|
106
106
|
* Builds the default eve HTTP channel: a {@link defineChannel} instance serving the
|
|
107
107
|
* built-in `/eve/v1` routes (GET inspects the agent, POST creates a session, POST
|
|
108
|
-
* delivers a follow-up, POST cancels
|
|
109
|
-
* NDJSON event feed). Every route
|
|
108
|
+
* delivers a follow-up, POST cancels an active turn or retires a session, GET
|
|
109
|
+
* streams a session's NDJSON event feed). Every route
|
|
110
110
|
* runs {@link EveChannelInput.auth} via {@link routeAuth} before dispatching.
|
|
111
111
|
* Default-export the result as your `agent/channels/eve.ts` channel; reach for
|
|
112
112
|
* {@link defineChannel} directly only for a custom transport.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.js";import{EVE_CANCEL_TURN_ROUTE_PATTERN,EVE_INFO_ROUTE_PATH}from"#protocol/routes.js";import{EVE_MESSAGE_STREAM_CONTENT_TYPE,EVE_MESSAGE_STREAM_FORMAT,EVE_MESSAGE_STREAM_VERSION,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{readAgentInfoRouteResponse,readRouteAgent}from"#internal/nitro/routes/channel-route-context.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";const log=createLogger(`eve.channel`);function defaultEveAuth(e){return e.eve.caller}function eveChannel(e){let n=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),routes:[GET(EVE_INFO_ROUTE_PATH,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=readAgentInfoRouteResponse(n);return i===void 0?Response.json({error:`Agent info route requires internal channel dispatch context.`,ok:!1},{status:500}):await i()}),POST(`/eve/v1/session`,async(t,{send:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let a=i,o;try{o=await t.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof o!=`object`||!o)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let s=parseCreateBody(o);if(s instanceof Response)return s;let c=checkUploadPolicy(s,n);if(c!==null)return c;let l=await resolveOnMessage({auth:a,config:e,message:s.message,request:t});if(l instanceof Response)return l;if(!l.dispatch)return droppedMessageResponse();let u=`eve:${crypto.randomUUID()}`,d=await r(createSendPayload(s,mergeContext(s.context,l.context)),{auth:l.auth,callback:s.callback,continuationToken:u,mode:s.mode});return Response.json({continuationToken:d.continuationToken,ok:!0,sessionId:d.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:d.id},status:202})}),POST(`/eve/v1/session/:sessionId`,async(t,{send:r,getSession:i,params:a})=>{let o=await routeAuth(t,e.auth);if(o instanceof Response)return o;let s=o,c=a.sessionId;if(!c)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(c)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let l;try{l=await t.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof l!=`object`||!l)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let u=parseContinueBody(l);if(u instanceof Response)return u;let d=checkUploadPolicy(u,n);if(d!==null)return d;let f=u.context,p=s;if(u.message!==void 0){let n=await resolveOnMessage({auth:s,config:e,message:u.message,request:t,sessionId:c});if(n instanceof Response)return n;if(!n.dispatch)return droppedMessageResponse();f=mergeContext(u.context,n.context),p=n.auth}let m=await r({inputResponses:u.inputResponses,message:u.message,context:f,outputSchema:u.outputSchema},{auth:p,continuationToken:u.continuationToken});return Response.json({ok:!0,sessionId:m.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.id},status:200})}),POST(EVE_CANCEL_TURN_ROUTE_PATTERN,async(n,r)=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let a=r.params.sessionId;if(!a)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let o=await parseCancelTurnBody(n);if(o instanceof Response)return o;let s;try{let e=readRouteAgent(r);if(e===void 0)throw Error(`Missing route agent.`);s=await e.cancelTurn({sessionId:a,turnId:o.turnId})}catch(e){let n=logError(log,`cancel-turn request failed`,e,{sessionId:a});return Response.json({error:`Failed to cancel the turn.`,errorId:n,ok:!1},{status:500})}return Response.json({ok:!0,sessionId:a,status:s.status},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:a},status:202})}),GET(`/eve/v1/session/:sessionId/stream`,async(t,{getSession:n,params:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let u=r.sessionId;if(!u)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let d=parseStartIndex(t);if(d instanceof Response)return d;try{let e=serializeAsNdjson(await n(u).getEventStream({startIndex:d}));return new Response(e,{headers:{"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:u,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}function normalizeEveCors(e){if(e===void 0||e===!1)return!1;if(e===!0)return!0;let t={};return e.origin!==void 0&&(t.origin=normalizeEveCorsOrigin(e.origin)),e.methods!==void 0&&(t.methods=e.methods),e.allowedHeaders!==void 0&&(t.allowHeaders=e.allowedHeaders),e.exposedHeaders!==void 0&&(t.exposeHeaders=e.exposedHeaders),e.credentials!==void 0&&(t.credentials=e.credentials),e.maxAge!==void 0&&(t.maxAge=e.maxAge),e.preflightStatus!==void 0&&(t.preflight={statusCode:e.preflightStatus}),t}function normalizeEveCorsOrigin(e){return e===`*`||e===`null`?e:typeof e==`string`?[e]:e}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{r=await n({eve:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}async function parseCancelTurnBody(e){let t;try{t=await e.text()}catch{return Response.json({error:`Unreadable request body.`,ok:!1},{status:400})}if(t.trim().length===0)return{};let n;try{n=JSON.parse(t)}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof n!=`object`||!n||Array.isArray(n))return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let r=n.turnId;return r===void 0?{}:typeof r!=`string`||r.length===0?Response.json({error:`Expected 'turnId' to be a non-empty string.`,ok:!1},{status:400}):{turnId:r}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});if(hasInternalRefScheme(t.data))return Response.json({error:`File part 'data' must not use a framework-internal ref scheme.`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{hasInternalRefScheme}from"#internal/attachments/url-refs.js";import{EVE_CANCEL_TURN_ROUTE_PATTERN,EVE_INFO_ROUTE_PATH,EVE_RESET_SESSION_ROUTE_PATH}from"#protocol/routes.js";import{EVE_MESSAGE_STREAM_CONTENT_TYPE,EVE_MESSAGE_STREAM_FORMAT,EVE_MESSAGE_STREAM_VERSION,EVE_SESSION_ID_HEADER,EVE_STREAM_FORMAT_HEADER,EVE_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import{GET,POST,defineChannel}from"#public/definitions/channel.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{readAgentInfoRouteResponse,readRouteAgent}from"#internal/nitro/routes/channel-route-context.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";const log=createLogger(`eve.channel`);function defaultEveAuth(e){return e.eve.caller}function eveChannel(e){let n=mergeUploadPolicy(e.uploadPolicy);return defineChannel({cors:normalizeEveCors(e.cors),routes:[GET(EVE_INFO_ROUTE_PATH,async(t,n)=>{let r=await routeAuth(t,e.auth);if(r instanceof Response)return r;let i=readAgentInfoRouteResponse(n);return i===void 0?Response.json({error:`Agent info route requires internal channel dispatch context.`,ok:!1},{status:500}):await i()}),POST(`/eve/v1/session`,async(t,{send:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let a=i,o;try{o=await t.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof o!=`object`||!o)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let s=parseCreateBody(o);if(s instanceof Response)return s;let c=checkUploadPolicy(s,n);if(c!==null)return c;let l=await resolveOnMessage({auth:a,config:e,message:s.message,request:t});if(l instanceof Response)return l;if(!l.dispatch)return droppedMessageResponse();let u=`eve:${crypto.randomUUID()}`,d=await r(createSendPayload(s,mergeContext(s.context,l.context)),{auth:l.auth,callback:s.callback,continuationToken:u,mode:s.mode});return Response.json({continuationToken:d.continuationToken,ok:!0,sessionId:d.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:d.id},status:202})}),POST(EVE_RESET_SESSION_ROUTE_PATH,async(n,{reset:r})=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let a=await parseResetSessionBody(n);if(a instanceof Response)return a;let o;try{o=await r({continuationToken:a.continuationToken,reason:`Client requested session reset`})}catch(e){let n=logError(log,`session-reset request failed`,e);return Response.json({error:`Failed to reset the session.`,errorId:n,ok:!1},{status:500})}let s=o.status===`reset`?{ok:!0,previousSessionId:o.previousSessionId,status:`reset`}:{ok:!0,status:`no_active_session`};return Response.json(s,{headers:{"cache-control":`no-store`}})}),POST(`/eve/v1/session/:sessionId`,async(t,{send:r,getSession:i,params:a})=>{let o=await routeAuth(t,e.auth);if(o instanceof Response)return o;let s=o,c=a.sessionId;if(!c)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(c)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let l;try{l=await t.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof l!=`object`||!l)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let u=parseContinueBody(l);if(u instanceof Response)return u;let d=checkUploadPolicy(u,n);if(d!==null)return d;let f=u.context,p=s;if(u.message!==void 0){let n=await resolveOnMessage({auth:s,config:e,message:u.message,request:t,sessionId:c});if(n instanceof Response)return n;if(!n.dispatch)return droppedMessageResponse();f=mergeContext(u.context,n.context),p=n.auth}let m=await r({inputResponses:u.inputResponses,message:u.message,context:f,outputSchema:u.outputSchema},{auth:p,continuationToken:u.continuationToken});return Response.json({ok:!0,sessionId:m.id},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:m.id},status:200})}),POST(EVE_CANCEL_TURN_ROUTE_PATTERN,async(n,r)=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let a=r.params.sessionId;if(!a)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let o=await parseCancelTurnBody(n);if(o instanceof Response)return o;let s;try{let e=readRouteAgent(r);if(e===void 0)throw Error(`Missing route agent.`);s=await e.cancelTurn({sessionId:a,turnId:o.turnId})}catch(e){let n=logError(log,`cancel-turn request failed`,e,{sessionId:a});return Response.json({error:`Failed to cancel the turn.`,errorId:n,ok:!1},{status:500})}return Response.json({ok:!0,sessionId:a,status:s.status},{headers:{"cache-control":`no-store`,[EVE_SESSION_ID_HEADER]:a},status:202})}),GET(`/eve/v1/session/:sessionId/stream`,async(t,{getSession:n,params:r})=>{let i=await routeAuth(t,e.auth);if(i instanceof Response)return i;let a=r.sessionId;if(!a)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let d=parseStartIndex(t);if(d instanceof Response)return d;try{let e=serializeAsNdjson(await n(a).getEventStream({startIndex:d}));return new Response(e,{headers:{"cache-control":`no-store, no-transform`,"content-type":EVE_MESSAGE_STREAM_CONTENT_TYPE,"x-accel-buffering":`no`,[EVE_SESSION_ID_HEADER]:a,[EVE_STREAM_FORMAT_HEADER]:EVE_MESSAGE_STREAM_FORMAT,[EVE_STREAM_VERSION_HEADER]:EVE_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}function normalizeEveCors(e){if(e===void 0||e===!1)return!1;if(e===!0)return!0;let t={};return e.origin!==void 0&&(t.origin=normalizeEveCorsOrigin(e.origin)),e.methods!==void 0&&(t.methods=e.methods),e.allowedHeaders!==void 0&&(t.allowHeaders=e.allowedHeaders),e.exposedHeaders!==void 0&&(t.exposeHeaders=e.exposedHeaders),e.credentials!==void 0&&(t.credentials=e.credentials),e.maxAge!==void 0&&(t.maxAge=e.maxAge),e.preflightStatus!==void 0&&(t.preflight={statusCode:e.preflightStatus}),t}function normalizeEveCorsOrigin(e){return e===`*`||e===`null`?e:typeof e==`string`?[e]:e}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{r=await n({eve:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultEveAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}async function parseResetSessionBody(e){let t;try{t=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof t!=`object`||!t||Array.isArray(t))return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let n=t.continuationToken;return typeof n!=`string`||n.length===0?Response.json({error:`Expected 'continuationToken' to be a non-empty string.`,ok:!1},{status:400}):{continuationToken:n}}async function parseCancelTurnBody(e){let t;try{t=await e.text()}catch{return Response.json({error:`Unreadable request body.`,ok:!1},{status:400})}if(t.trim().length===0)return{};let n;try{n=JSON.parse(t)}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof n!=`object`||!n||Array.isArray(n))return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let r=n.turnId;return r===void 0?{}:typeof r!=`string`||r.length===0?Response.json({error:`Expected 'turnId' to be a non-empty string.`,ok:!1},{status:400}):{turnId:r}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});if(hasInternalRefScheme(t.data))return Response.json({error:`File part 'data' must not use a framework-internal ref scheme.`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
|
|
2
2
|
${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number(t);return!/^-?\d+$/.test(t)||!Number.isSafeInteger(n)?Response.json({error:`Expected startIndex to be an integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({start(e){e.enqueue(t.encode(`
|
|
3
3
|
`))},transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{defaultEveAuth,eveChannel};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { defineChannel, GET, POST, PUT, PATCH, DELETE, WS, type CancelFn, type CancelOptions, type CancelTurnResult, type Channel, type ChannelCors, type ChannelCorsOptions, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, type HttpRouteDefinition, type WebSocketMessage, type WebSocketPeer, type WebSocketRouteDefinition, type WebSocketRouteHandler, type WebSocketRouteHooks, type WebSocketUpgradeRequest, type WebSocketUpgradeResult, } from "#public/definitions/channel.js";
|
|
1
|
+
export { defineChannel, GET, POST, PUT, PATCH, DELETE, WS, type CancelFn, type CancelOptions, type CancelTurnResult, type Channel, type ChannelCors, type ChannelCorsOptions, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type ResetFn, type ResetOptions, type ResetResult, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, type HttpRouteDefinition, type WebSocketMessage, type WebSocketPeer, type WebSocketRouteDefinition, type WebSocketRouteHandler, type WebSocketRouteHooks, type WebSocketUpgradeRequest, type WebSocketUpgradeResult, } from "#public/definitions/channel.js";
|
|
2
2
|
export { createWebSocketUpgradeServer, type WebSocketUpgradeServerBridge, } from "#channel/websocket-upgrade-server.js";
|
|
3
3
|
import type { Channel, InferChannelMetadata } from "#public/definitions/channel.js";
|
|
4
4
|
/**
|
|
@@ -12,7 +12,7 @@ export type { CancelTurnInput, CancelTurnResult, GetEventStreamOptions } from "#
|
|
|
12
12
|
export type { Session, SessionHandle } from "#channel/session.js";
|
|
13
13
|
export type { ChannelCors, ChannelCorsOptions } from "#channel/cors.js";
|
|
14
14
|
export { GET, POST, PUT, PATCH, DELETE, WS } from "#channel/routes.js";
|
|
15
|
-
export type { CancelFn, CancelOptions, HttpRouteDefinition, RouteDefinition, RouteHandlerArgs, SendFn, SendOptions, SendPayload, ResolveActiveSessionFn, GetSessionFn, WebSocketMessage, WebSocketPeer, WebSocketRouteDefinition, WebSocketRouteHandler, WebSocketRouteHooks, WebSocketUpgradeRequest, WebSocketUpgradeResult, } from "#channel/routes.js";
|
|
15
|
+
export type { CancelFn, CancelOptions, ResetFn, ResetOptions, ResetResult, HttpRouteDefinition, RouteDefinition, RouteHandlerArgs, SendFn, SendOptions, SendPayload, ResolveActiveSessionFn, GetSessionFn, WebSocketMessage, WebSocketPeer, WebSocketRouteDefinition, WebSocketRouteHandler, WebSocketRouteHooks, WebSocketUpgradeRequest, WebSocketUpgradeResult, } from "#channel/routes.js";
|
|
16
16
|
/**
|
|
17
17
|
* HTTP method a route handles. Defaults to `"POST"` — almost every route
|
|
18
18
|
* is a webhook. Override only when authoring a non-webhook route such as a
|
|
@@ -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.34`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.27.
|
|
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.34`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.27.4`,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__",
|
|
@@ -24,7 +24,7 @@ export interface VercelProjectSearchPage {
|
|
|
24
24
|
export declare function parseVercelJson(stdout: string, description: string): unknown;
|
|
25
25
|
/** Converts a scoped API denial into the Vercel re-authentication action. */
|
|
26
26
|
export declare function requireVercelTeamAccess(failure: VercelCaptureFailure): never;
|
|
27
|
-
/** Lists
|
|
27
|
+
/** Lists up to the maximum 100 Vercel scopes supported by the CLI. */
|
|
28
28
|
export declare function listTeams(projectRoot: string, options?: VercelProjectOperationOptions): Promise<VercelTeamListEntry[]>;
|
|
29
29
|
/** Lists the 20 most recent Vercel projects in one account scope. */
|
|
30
30
|
export declare function listRecentProjects(projectRoot: string, team: string, options?: VercelProjectOperationOptions): Promise<VercelProjectListEntry[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{array,boolean,number,object,string}from"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.js";import"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.js";import{isForbiddenApiFailure}from"./vercel-api-failure.js";import{captureVercel}from"#setup/primitives/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";const VERCEL_PROJECT_REQUEST_TIMEOUT_MS=15e3,VercelTeamListEntrySchema=object({name:string(),slug:string(),current:boolean()}),VercelProjectListEntrySchema=object({name:string(),id:string()}),VercelPaginationSchema=object({next:number().int().nonnegative().nullable().optional()}),VercelTeamPageSchema=object({teams:array(VercelTeamListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>
|
|
1
|
+
import{array,boolean,number,object,string}from"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.js";import"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.js";import{isForbiddenApiFailure}from"./vercel-api-failure.js";import{captureVercel}from"#setup/primitives/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";const VERCEL_PROJECT_REQUEST_TIMEOUT_MS=15e3,VercelTeamListEntrySchema=object({name:string(),slug:string(),current:boolean()}),VercelProjectListEntrySchema=object({name:string(),id:string()}),VercelPaginationSchema=object({next:number().int().nonnegative().nullable().optional()}),VercelTeamPageSchema=object({teams:array(VercelTeamListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>e.teams),VercelProjectPageSchema=object({projects:array(VercelProjectListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>({items:e.projects,next:e.pagination?.next??void 0}));function parseVercelJson(e,t){try{return JSON.parse(e)}catch{throw Error(`Could not parse ${t} JSON from Vercel CLI output.`)}}function requireVercelTeamAccess(e){let t=e.stderr.trim();throw new HumanActionRequiredError({kind:`vercel-forbidden`,command:`vercel login`,reason:`Vercel denied access to this scope.${t?` ${t}`:``} Re-authenticate (for example to complete a team's SSO) or switch to a team you can access.`})}function isUnsupportedTeamListFailure(e){let t=`${e.stderr}\n${e.stdout}`;return/(?:unknown|unexpected|invalid).*(?:--format|--limit)/iu.test(t)}function requireVercelCliUpgrade(e){throw new HumanActionRequiredError({kind:`vercel-cli-upgrade`,command:`vercel upgrade`,reason:`The installed Vercel CLI does not support the team-list options eve needs. ${e.message} Upgrade it and retry.`})}async function captureTeamPage(e,t,n){let r=await captureVercel(n,{cwd:e,signal:t.signal});if(t.signal?.throwIfAborted(),!r.ok)throw isForbiddenApiFailure(r.failure)&&requireVercelTeamAccess(r.failure),isUnsupportedTeamListFailure(r.failure)&&requireVercelCliUpgrade(r.failure),Error(`Could not list Vercel teams. ${r.failure.message}`);return r.stdout}async function listTeams(e,t={}){let n=await captureTeamPage(e,t,[`teams`,`ls`,`--format`,`json`,`--limit`,`100`]),r=VercelTeamPageSchema.safeParse(parseVercelJson(n,`teams`));if(!r.success)throw Error(`Could not read teams from Vercel CLI JSON output.`);return r.data}async function fetchProjectPage(e,t,n){let r=[`project`,`ls`,`--format`,`json`,`--scope`,t];n.search!==void 0&&r.push(`--filter`,n.search),n.next!==void 0&&r.push(`--next`,String(n.next));let i=await captureVercel(r,{cwd:e,signal:n.signal,timeoutMs:VERCEL_PROJECT_REQUEST_TIMEOUT_MS});if(n.signal?.throwIfAborted(),!i.ok)throw isForbiddenApiFailure(i.failure)&&requireVercelTeamAccess(i.failure),Error(`Could not list Vercel projects in ${t}. ${i.failure.message}`);let s=VercelProjectPageSchema.safeParse(parseVercelJson(i.stdout,`projects`));if(!s.success)throw Error(`Could not read projects from Vercel CLI JSON output.`);return s.data}async function listRecentProjects(e,t,n={}){return(await fetchProjectPage(e,t,n)).items}function projectSearchRank(e,t){let n=e.name.toLowerCase(),r=t.toLowerCase();return n===r?0:n.startsWith(r)?1:2}function rankProjectSearchResults(e,t){let n=t.trim();return[...e].sort((e,t)=>projectSearchRank(e,n)-projectSearchRank(t,n))}async function searchProjects(e,t,n,r={}){let i=n.trim();if(i.length===0)throw Error(`Project search query cannot be empty.`);let a=await fetchProjectPage(e,t,{...r,search:i}),o=rankProjectSearchResults(a.items,i);return a.next===void 0?{projects:o}:{projects:o,next:a.next}}export{VERCEL_PROJECT_REQUEST_TIMEOUT_MS,listRecentProjects,listTeams,parseVercelJson,rankProjectSearchResults,requireVercelTeamAccess,searchProjects};
|
package/dist/src/svelte/index.js
CHANGED
package/dist/src/vue/index.js
CHANGED
package/docs/channels/custom.mdx
CHANGED
|
@@ -48,6 +48,7 @@ Declare routes with the `POST()` and `GET()` helpers. Each route handler receive
|
|
|
48
48
|
|
|
49
49
|
- `send(message, { auth, continuationToken, state?, title? })` starts or resumes a session. `title` overrides the new workflow session's display title without changing the model message. Returns a `Session`.
|
|
50
50
|
- `cancel({ continuationToken, turnId? })` requests cancellation of the active turn on the session that owns the token. See [Cancel a turn](#cancel-a-turn).
|
|
51
|
+
- `reset({ continuationToken, reason? })` retires the session that owns the token so the next `send()` starts a fresh session. See [Reset a session](#reset-a-session).
|
|
51
52
|
- `getSession(sessionId)` looks up an existing session. The returned `Session` exposes `getEventStream({ startIndex? })` for streaming and `cancel({ turnId? })` for session-id-addressed cancellation.
|
|
52
53
|
- `receive(channel, ...)` hands inbound work to a different channel for cross-channel hand-off.
|
|
53
54
|
- `params` holds route parameters extracted from the path pattern.
|
|
@@ -85,6 +86,40 @@ Semantics:
|
|
|
85
86
|
|
|
86
87
|
When you already hold a `Session` — returned by `send()` or `getSession(sessionId)` — call `session.cancel({ turnId? })` to request the same cancellation addressed by session id.
|
|
87
88
|
|
|
89
|
+
## Reset a session
|
|
90
|
+
|
|
91
|
+
Identity-addressed channels can consume commands such as `/new` in their route, without sending the command to the model. Call `reset` with the same channel-local continuation token used by `send()`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { defineChannel, POST } from "eve/channels";
|
|
95
|
+
|
|
96
|
+
export default defineChannel({
|
|
97
|
+
routes: [
|
|
98
|
+
POST("/messages", async (request, { reset, send }) => {
|
|
99
|
+
const body = await request.json();
|
|
100
|
+
|
|
101
|
+
if (body.message.trim().toLowerCase() === "/new") {
|
|
102
|
+
await reset({
|
|
103
|
+
continuationToken: body.conversationId,
|
|
104
|
+
reason: "User requested /new",
|
|
105
|
+
});
|
|
106
|
+
return Response.json({ message: "Started a new conversation." });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const session = await send(body.message, {
|
|
110
|
+
auth: null,
|
|
111
|
+
continuationToken: body.conversationId,
|
|
112
|
+
});
|
|
113
|
+
return Response.json({ sessionId: session.id });
|
|
114
|
+
}),
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`reset` returns `{ status: "reset", previousSessionId }` when it observed an owner, or `{ status: "no_active_session" }` when the token was already free. Both outcomes are successful. It terminally cancels the observed workflow run and waits until its non-retained continuation hook no longer blocks reuse, so the next `send()` using the same token creates a new session with fresh history and state.
|
|
120
|
+
|
|
121
|
+
The replacement session owns a new session-scoped sandbox. eve initializes it lazily on that session's first sandbox use, so it never reuses the prior session's workspace or sandbox state. Resetting does not clear history in place, roll back tool calls, or delete the previous run or its sandbox; normal retention and backend cleanup policies still apply. Side effects or partial output that were already committed remain committed. Authenticate and deduplicate inbound command webhooks before calling this helper: a delayed duplicate `/new` can otherwise reset a newer session that has already reclaimed the same token.
|
|
122
|
+
|
|
88
123
|
## CORS
|
|
89
124
|
|
|
90
125
|
Custom HTTP channels leave CORS untouched unless you opt in. Pass `cors: true`
|
|
@@ -125,7 +160,7 @@ export default defineChannel({
|
|
|
125
160
|
});
|
|
126
161
|
```
|
|
127
162
|
|
|
128
|
-
`WS()` handlers receive the same helpers as HTTP route handlers: `send`, `cancel`, `getSession`, `receive`, `params`, `waitUntil`, and `requestIp`. The returned hooks are eve-owned structural types compatible with Nitro/H3 websocket routing, including `upgrade`, `open`, `message`, `close`, and `error`.
|
|
163
|
+
`WS()` handlers receive the same helpers as HTTP route handlers: `send`, `cancel`, `reset`, `getSession`, `receive`, `params`, `waitUntil`, and `requestIp`. The returned hooks are eve-owned structural types compatible with Nitro/H3 websocket routing, including `upgrade`, `open`, `message`, `close`, and `error`.
|
|
129
164
|
|
|
130
165
|
### Node upgrade server escape hatch
|
|
131
166
|
|
|
@@ -245,6 +280,8 @@ Custom channels write their own function that joins the identity fields. The fra
|
|
|
245
280
|
|
|
246
281
|
When the identity that should address a session is not known until later, the channel can re-key the parked session by calling `session.setContinuationToken(...)`. Pass the channel-local raw token; the runtime preserves the current channel namespace.
|
|
247
282
|
|
|
283
|
+
Re-keying changes the address of the current session. `reset` is different: it terminally retires the current session and makes its existing address available to a later `send()`. `cancel` is narrower still: it stops only the active turn and leaves the session, history, and continuation-token ownership intact.
|
|
284
|
+
|
|
248
285
|
The `context(state, session)` config option builds the per-step `channel` argument handed to every event handler. It receives the channel's live adapter `state` and a `SessionHandle`, and returns the channel-owned context (thread handles, API clients, late-bound callbacks). The framework injects [`ChannelSessionOps`](#define-a-channel) and passes the result as the second positional argument to each handler. Closing over `session` lets the factory register callbacks that re-key the session later. State mutations made through the returned context are written back to adapter state.
|
|
249
286
|
|
|
250
287
|
```ts
|
package/docs/extensions.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Extensions"
|
|
3
|
-
description: "
|
|
3
|
+
description: "Package reusable eve capabilities and mount them from npm or a monorepo workspace."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Extensions package eve tools, connections, skills, instruction fragments, and hooks.
|
|
6
|
+
Extensions package eve tools, connections, skills, instruction fragments, and hooks. An author builds an extension package; each agent that uses it declares the package as a dependency and mounts it. The package can be published to a registry or kept private inside a monorepo workspace.
|
|
7
7
|
|
|
8
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
|
-
##
|
|
10
|
+
## Author: create an extension
|
|
11
11
|
|
|
12
12
|
### Create the package
|
|
13
13
|
|
|
@@ -42,7 +42,7 @@ Keep agent configuration, sandboxes, schedules, and nested extensions in the con
|
|
|
42
42
|
|
|
43
43
|
### Add configuration and contributions
|
|
44
44
|
|
|
45
|
-
The
|
|
45
|
+
The author's `extension/extension.ts` default-exports a `defineExtension` handle. Give it a [Standard Schema](https://standardschema.dev) when consumers need to provide settings:
|
|
46
46
|
|
|
47
47
|
```ts title="extension/extension.ts"
|
|
48
48
|
import { defineExtension } from "eve/extension";
|
|
@@ -76,9 +76,9 @@ export default defineTool({
|
|
|
76
76
|
|
|
77
77
|
If no configuration is needed, export `defineExtension()` and let consumers re-export it directly. Config schemas must validate synchronously.
|
|
78
78
|
|
|
79
|
-
`defineState` is automatically scoped to the
|
|
79
|
+
`defineState` is automatically scoped to the extension package, so the same state name does not collide with the consumer or another extension.
|
|
80
80
|
|
|
81
|
-
### Build and publish
|
|
81
|
+
### Build and optionally publish
|
|
82
82
|
|
|
83
83
|
The scaffold's `package.json` declares separate source and distribution roots:
|
|
84
84
|
|
|
@@ -134,17 +134,17 @@ Build the package with `eve extension build`:
|
|
|
134
134
|
eve extension build
|
|
135
135
|
```
|
|
136
136
|
|
|
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
|
|
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 author's TypeScript source.
|
|
138
138
|
|
|
139
|
-
The exact `eve` development pin controls the
|
|
139
|
+
The exact `eve` development pin controls the extension 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`.
|
|
140
140
|
|
|
141
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`.
|
|
142
142
|
|
|
143
|
-
Consumers can now add the built package to an agent.
|
|
143
|
+
Consumers can now add the built package to an agent. A workspace-only extension uses the same package contract but does not need to be published; see [Use an extension in a workspace](#use-an-extension-in-a-workspace).
|
|
144
144
|
|
|
145
145
|
## Consumer: install and mount an extension
|
|
146
146
|
|
|
147
|
-
A mount gives the
|
|
147
|
+
A mount gives the extension's contributions a namespace. Updating the package updates the mounted extension; nothing is copied into the consumer's agent.
|
|
148
148
|
|
|
149
149
|
### Install the package
|
|
150
150
|
|
|
@@ -156,7 +156,7 @@ pnpm add @acme/crm
|
|
|
156
156
|
|
|
157
157
|
### Mount it
|
|
158
158
|
|
|
159
|
-
Create a file under `agent/extensions/`. Its filename becomes the mount namespace. Call the
|
|
159
|
+
Create a file under `agent/extensions/`. Its filename becomes the mount namespace. Call the extension's default export when it needs configuration:
|
|
160
160
|
|
|
161
161
|
```ts title="agent/extensions/crm.ts"
|
|
162
162
|
import crm from "@acme/crm";
|
|
@@ -168,7 +168,7 @@ Set `CRM_API_KEY` in the consumer's environment, such as `.env.local` for local
|
|
|
168
168
|
|
|
169
169
|
The mount adds `crm__` to named contributions: `tools/search.ts` becomes `crm__search`, and `connections/api.ts` becomes `crm__api`.
|
|
170
170
|
|
|
171
|
-
For
|
|
171
|
+
For an extension with no configuration, mount its default export directly:
|
|
172
172
|
|
|
173
173
|
```ts title="agent/extensions/gizmo.ts"
|
|
174
174
|
export { default } from "@acme/gizmo";
|
|
@@ -176,9 +176,89 @@ export { default } from "@acme/gizmo";
|
|
|
176
176
|
|
|
177
177
|
The same mount shape works with an npm package, a workspace dependency, or a linked local package.
|
|
178
178
|
|
|
179
|
+
### Use an extension in a workspace
|
|
180
|
+
|
|
181
|
+
A workspace extension is a regular extension package kept in the same monorepo as its consumers. It is useful when several agents need the same capabilities, or when a private capability should evolve alongside the agents that use it.
|
|
182
|
+
|
|
183
|
+
For example, a pnpm workspace can keep one extension next to two independently deployable agents:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
acme-agents/
|
|
187
|
+
├── pnpm-workspace.yaml
|
|
188
|
+
├── packages/
|
|
189
|
+
│ └── shared-capabilities/
|
|
190
|
+
│ ├── package.json
|
|
191
|
+
│ └── extension/
|
|
192
|
+
│ ├── extension.ts
|
|
193
|
+
│ ├── tools/
|
|
194
|
+
│ ├── skills/
|
|
195
|
+
│ └── hooks/
|
|
196
|
+
└── agents/
|
|
197
|
+
├── support/
|
|
198
|
+
│ ├── package.json
|
|
199
|
+
│ └── agent/extensions/shared.ts
|
|
200
|
+
└── operations/
|
|
201
|
+
├── package.json
|
|
202
|
+
└── agent/extensions/shared.ts
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Make both the extension and agent directories workspace members:
|
|
206
|
+
|
|
207
|
+
```yaml title="pnpm-workspace.yaml"
|
|
208
|
+
packages:
|
|
209
|
+
- "agents/*"
|
|
210
|
+
- "packages/*"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
You can scaffold the extension from a directory already covered by the workspace configuration:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
cd packages
|
|
217
|
+
npx eve@latest extension init shared-capabilities
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Give the generated package the name consumers will import. Add `"private": true` if it should never be published:
|
|
221
|
+
|
|
222
|
+
```jsonc title="packages/shared-capabilities/package.json"
|
|
223
|
+
{
|
|
224
|
+
"name": "@acme/shared-capabilities",
|
|
225
|
+
"private": true,
|
|
226
|
+
"eve": {
|
|
227
|
+
"extension": {
|
|
228
|
+
"source": "./extension",
|
|
229
|
+
"dist": "./dist/extension",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Each consuming agent declares its own workspace dependency:
|
|
236
|
+
|
|
237
|
+
```jsonc title="agents/support/package.json"
|
|
238
|
+
{
|
|
239
|
+
"dependencies": {
|
|
240
|
+
"@acme/shared-capabilities": "workspace:*",
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Then each agent mounts the package:
|
|
246
|
+
|
|
247
|
+
```ts title="agents/support/agent/extensions/shared.ts"
|
|
248
|
+
export { default } from "@acme/shared-capabilities";
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The mount is intentionally per agent. Each consumer chooses its own mount namespace and, for a configured extension, passes its own configuration. For example, `shared.ts` contributes `shared__search`, while mounting the same package as `company.ts` in another agent contributes `company__search`.
|
|
252
|
+
|
|
253
|
+
#### Develop from source
|
|
254
|
+
|
|
255
|
+
When `eve dev` starts a consuming agent, it builds mounted, source-backed extensions found inside the same workspace before compiling the agent. It watches the extension source and relevant package and TypeScript configuration, then rebuilds only the affected extension. If an extension edit fails to build, the previous successful development generation keeps running.
|
|
256
|
+
|
|
257
|
+
Production `eve build` expects the extension distribution to exist already. Keep `eve extension build` in the extension package's `build` and `prepare` scripts, as the scaffold does, and run workspace builds in dependency order so extensions build before their consuming agents.
|
|
258
|
+
|
|
179
259
|
### Override a contribution
|
|
180
260
|
|
|
181
|
-
Use a directory mount to replace or remove
|
|
261
|
+
Use a directory mount to replace or remove an extension contribution. Put the mount declaration in `extension.ts` and add overrides beside it:
|
|
182
262
|
|
|
183
263
|
```
|
|
184
264
|
agent/extensions/crm/
|
|
@@ -192,7 +272,7 @@ import crm from "@acme/crm";
|
|
|
192
272
|
export default crm({ apiKey: process.env.CRM_API_KEY! });
|
|
193
273
|
```
|
|
194
274
|
|
|
195
|
-
A same-named consumer tool, connection, or skill wins. To adjust
|
|
275
|
+
A same-named consumer tool, connection, or skill wins. To adjust an extension tool, import it from the package's `./tools` export and define it again:
|
|
196
276
|
|
|
197
277
|
```ts title="agent/extensions/crm/tools/search.ts"
|
|
198
278
|
import { search } from "@acme/crm/tools";
|
|
@@ -202,7 +282,7 @@ import { always } from "eve/tools/approval";
|
|
|
202
282
|
export default defineTool({ ...search, approval: always() });
|
|
203
283
|
```
|
|
204
284
|
|
|
205
|
-
To remove
|
|
285
|
+
To remove an extension tool, use `disableTool()` in its matching slot:
|
|
206
286
|
|
|
207
287
|
```ts title="agent/extensions/crm/tools/search.ts"
|
|
208
288
|
import { disableTool } from "eve/tools";
|
|
@@ -214,9 +294,9 @@ Hooks and instruction fragments are additive, so they cannot be replaced. To rep
|
|
|
214
294
|
|
|
215
295
|
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.
|
|
216
296
|
|
|
217
|
-
### Use
|
|
297
|
+
### Use an extension tool result in a hook
|
|
218
298
|
|
|
219
|
-
To retain
|
|
299
|
+
To retain an extension tool's result type in a consumer hook, import its definition from `./tools` and pass it to [`toolResultFrom`](/guides/hooks#narrowing-tool-results):
|
|
220
300
|
|
|
221
301
|
```ts title="agent/hooks/narrow-crm.ts"
|
|
222
302
|
import { defineHook } from "eve/hooks";
|
|
@@ -237,7 +317,7 @@ export default defineHook({
|
|
|
237
317
|
|
|
238
318
|
### Compatibility
|
|
239
319
|
|
|
240
|
-
At build time, eve checks the
|
|
320
|
+
At build time, eve checks the extension's generated capability metadata. If the extension needs an unsupported capability contract, upgrade eve or install a compatible extension release.
|
|
241
321
|
|
|
242
322
|
## What to read next
|
|
243
323
|
|
package/docs/guides/dev-tui.md
CHANGED
|
@@ -71,7 +71,8 @@ Chat and freeform `ask_question` inputs behave like a shell line editor.
|
|
|
71
71
|
|
|
72
72
|
| Key | Action |
|
|
73
73
|
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
74
|
-
| `Enter` | Submit the message or question response.
|
|
74
|
+
| `Enter` | Submit the message or question response. While a turn is running, queue the message for the next turn. |
|
|
75
|
+
| `Esc` | While a turn runs: pop the oldest queued message and steer with it; with nothing queued, press twice to cancel. |
|
|
75
76
|
| `Shift+Enter` | Insert a newline without sending (needs a terminal that reports modified keys). |
|
|
76
77
|
| `Ctrl+C` | Interrupt a running turn. At the chat or freeform-question prompt, clear non-empty input; when empty, quit. |
|
|
77
78
|
| `↑` / `↓` | Move between input lines; at a chat-buffer edge, navigate messages you have sent this session. |
|
|
@@ -82,7 +83,13 @@ Chat and freeform `ask_question` inputs behave like a shell line editor.
|
|
|
82
83
|
|
|
83
84
|
In terminals that support bracketed paste, pasting multi-line text into chat or a freeform question inserts it intact and renders one row per line rather than submitting at the first line. `Shift+Enter` adds a line by hand. The input grows down to the available terminal height, then scrolls to keep the caret visible; `Enter` submits the whole response.
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
### Queue and steer while the agent works
|
|
87
|
+
|
|
88
|
+
Sending a message while a turn is still running does not interrupt it — the message joins a queue of up to five, pinned in a panel directly above the input with one line per message. When the turn ends, the queued messages coalesce into the next turn's message.
|
|
89
|
+
|
|
90
|
+
`Esc` steers instead of waiting: it pops the oldest queued message, cancels the running turn cooperatively, and submits the popped message as the replacement turn. In the transcript, a steered message carries an accent `↑` on its own line above its gutter bar; a queued message that waited for the boundary carries it below. Any remaining messages stay queued behind it. With nothing queued, the first `Esc` arms cancellation and a second `Esc` cancels the turn. Unlike `Ctrl+C` — which drops the stream client-side — a cancelled turn ends cleanly on the server and the session keeps its context; the conversation picks up at the next prompt.
|
|
91
|
+
|
|
92
|
+
If a turn fails terminally (the server session dies or the connection drops), the TUI starts a fresh session and notes it inline so you can keep going. Server-side context resets with the old session. Messages still queued when a turn is interrupted or fails are restored into the next prompt's input instead of being sent blind.
|
|
86
93
|
|
|
87
94
|
## Answer the agent inline
|
|
88
95
|
|
package/docs/reference/cli.md
CHANGED
|
@@ -173,7 +173,7 @@ For bearer tokens or custom schemes, pass explicit headers with `-H`.
|
|
|
173
173
|
|
|
174
174
|
Local dev records the last ready URL per resolved app root in `.eve/dev-server-state.v1.json`. A second interactive `eve dev` reconnects only when that URL is loopback and healthy; each terminal UI creates a fresh client session while sharing the server process. A stale or malformed record is replaced when eve starts a new server. Passing `--host`, `--port`, or a `PORT` environment value skips reconnection and reports a healthy recorded server instead.
|
|
175
175
|
|
|
176
|
-
Local dev keeps immutable runtime source snapshots under `.eve/dev-runtime/snapshots/` so in-flight turns hold a consistent code revision while new turns pick up rebuilds. The terminal REPL keeps its logical session across successful rebuilds, so the next turn continues the conversation on the latest generation;
|
|
176
|
+
Local dev keeps immutable runtime source snapshots under `.eve/dev-runtime/snapshots/` so in-flight turns hold a consistent code revision while new turns pick up rebuilds. The terminal REPL keeps its logical session across successful rebuilds, so the next turn continues the conversation on the latest generation; `/new` terminally retires that session before clearing the transcript, and the next prompt starts a fresh session with a new session-scoped sandbox on first sandbox use. After a generation is superseded, `eve dev` retains it for at least 30 minutes and also retains the five most recently superseded generations, regardless of the configured Workflow World. The active generation is never pruned. Old runtime snapshots and local sandbox templates are pruned in the background. For manual cleanup, stop `eve dev` before deleting `.eve/dev-runtime/snapshots/` or `.eve/sandbox-cache/local/templates/`. A turn that remains unfinished beyond the automatic retention window can no longer resume after its generation is pruned.
|
|
177
177
|
|
|
178
178
|
## `eve logs`
|
|
179
179
|
|