eve 0.27.0 → 0.27.1
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 +18 -0
- package/dist/src/channel/resolve-active-session.d.ts +4 -0
- package/dist/src/channel/resolve-active-session.js +1 -0
- package/dist/src/channel/routes.d.ts +11 -0
- package/dist/src/cli/dev/tui/vercel-trusted-sources-policy.d.ts +1 -1
- package/dist/src/cli/dev/tui/vercel-trusted-sources.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +16 -16
- package/dist/src/compiled/@ai-sdk/anthropic/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/google/index.d.ts +9 -2
- package/dist/src/compiled/@ai-sdk/google/index.js +6 -6
- package/dist/src/compiled/@ai-sdk/mcp/index.d.ts +18 -3
- package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/openai/index.d.ts +285 -24
- package/dist/src/compiled/@ai-sdk/openai/index.js +2 -2
- package/dist/src/compiled/@ai-sdk/otel/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/provider-utils/index.d.ts +7 -3
- package/dist/src/compiled/@vercel/oidc/index.js +28 -2
- package/dist/src/compiled/@vercel/sandbox/api-client/api-client.d.ts +11 -0
- package/dist/src/compiled/@vercel/sandbox/api-client/validators.d.ts +7 -0
- package/dist/src/compiled/@vercel/sandbox/command.d.ts +8 -0
- package/dist/src/compiled/@vercel/sandbox/execution-context.d.ts +116 -0
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +3 -1
- package/dist/src/compiled/@vercel/sandbox/index.js +20 -19
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +13 -5
- package/dist/src/compiled/@vercel/sandbox/sandbox-user.d.ts +212 -0
- package/dist/src/compiled/@vercel/sandbox/sandbox.d.ts +133 -2
- package/dist/src/compiled/@vercel/sandbox/session.d.ts +2 -1
- package/dist/src/compiled/@vercel/sandbox/snapshot.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/create-hook.d.ts +41 -22
- package/dist/src/compiled/@workflow/core/flushable-stream.d.ts +29 -0
- package/dist/src/compiled/@workflow/core/global.d.ts +2 -0
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/private.d.ts +6 -17
- package/dist/src/compiled/@workflow/core/private.js +1 -1
- package/dist/src/compiled/@workflow/core/replay-payload-cache.d.ts +56 -0
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +11 -0
- package/dist/src/compiled/@workflow/core/runtime/helpers.d.ts +6 -4
- package/dist/src/compiled/@workflow/core/runtime/resume-hook.d.ts +4 -1
- package/dist/src/compiled/@workflow/core/runtime/step-executor.d.ts +39 -0
- package/dist/src/compiled/@workflow/core/runtime.js +3 -3
- package/dist/src/compiled/@workflow/core/serialization.d.ts +44 -4
- package/dist/src/compiled/@workflow/core/symbols.d.ts +13 -0
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/error-codes.d.ts +2 -0
- package/dist/src/compiled/@workflow/errors/index.d.ts +14 -0
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/world/events.d.ts +15 -0
- package/dist/src/compiled/@workflow/world/hooks.d.ts +2 -1
- package/dist/src/compiled/@workflow/world/interfaces.d.ts +77 -4
- package/dist/src/compiled/@workflow/world-local/index.js +4 -4
- package/dist/src/compiled/@workflow/world-vercel/index.js +16 -16
- package/dist/src/compiled/_chunks/node/{auth-BuQkq4Gr.js → auth-l7F2PF2b.js} +2 -2
- package/dist/src/compiled/_chunks/node/{version-DD-FX9rK.js → version-CjHTLx2a.js} +1 -1
- package/dist/src/compiled/_chunks/node/virtual-loader-Cwgujv7l.js +1 -0
- package/dist/src/compiled/_chunks/workflow/attribute-changes-Dk1fP6vt.js +20 -0
- package/dist/src/compiled/_chunks/workflow/{chunk-BHKSVoKr.js → chunk-BTyA9uPd.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/dist-B3qkUnLJ.js +3 -0
- package/dist/src/compiled/_chunks/workflow/{dist-CSfKNxQW.js → dist-BX517Nmz.js} +2 -2
- package/dist/src/compiled/_chunks/workflow/{dist-zLevmMyU.js → dist-C_VDTghO.js} +10 -10
- package/dist/src/compiled/_chunks/workflow/{dist-CttsoN4D.js → dist-CrxV2rsR.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{functions-PT9ri9Hu.js → functions-Dv8dc_-I.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/resume-hook-czpG5xnP.js +1 -0
- package/dist/src/compiled/_chunks/workflow/run-6zaMjFY6.js +1 -0
- package/dist/src/compiled/_chunks/workflow/sleep-B2u06FWc.js +1 -0
- package/dist/src/compiled/_chunks/workflow/{src-CQuMexnO.js → src-D1EltLDA.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{token-CaY0nA5J.js → token-Cz223z3C.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/token-DWqA5-DA.js +1 -0
- package/dist/src/compiled/_chunks/workflow/token-util-B6qBs3-0.js +27 -0
- package/dist/src/compiled/_chunks/workflow/{undici-fb54XL8i.js → undici-C2ZhYnV9.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/wrapper-Cc2otuUR.js +6 -0
- package/dist/src/compiled/env-runner/index.js +1 -1
- package/dist/src/compiled/env-runner/node-worker.js +1 -1
- package/dist/src/compiled/eventsource-parser/stream/index.d.ts +16 -3
- package/dist/src/compiled/jose/index.js +1 -1
- package/dist/src/compiler/compile-from-memory.d.ts +5 -0
- package/dist/src/compiler/compile-from-memory.js +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +10 -10
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/execution/deliver-payloads.d.ts +1 -1
- package/dist/src/execution/deliver-payloads.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/harness/session-limit-continuation.d.ts +8 -0
- package/dist/src/harness/session-limit-continuation.js +1 -1
- package/dist/src/harness/session-limit-enforcement.d.ts +9 -6
- package/dist/src/harness/session-limit-enforcement.js +1 -1
- package/dist/src/harness/stale-input-responses.d.ts +21 -3
- package/dist/src/harness/stale-input-responses.js +2 -2
- package/dist/src/harness/tool-loop.js +2 -2
- package/dist/src/harness/turn-cancellation.d.ts +13 -0
- package/dist/src/harness/turn-cancellation.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +1 -1
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.8/node_modules/@vercel/sdk/esm/models/updateprojectblock.js +1 -0
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.8/node_modules/@vercel/sdk/esm/models/updateprojectprojectsbranchmatcher.js +1 -0
- package/dist/src/packages/eve-catalog/src/index.js +1 -1
- package/dist/src/public/channels/discord/defaults.js +1 -1
- package/dist/src/public/channels/linear/api.d.ts +2 -0
- package/dist/src/public/channels/linear/api.js +3 -1
- package/dist/src/public/channels/linear/defaults.js +2 -2
- package/dist/src/public/channels/linear/hitl.d.ts +8 -13
- package/dist/src/public/channels/linear/hitl.js +3 -3
- package/dist/src/public/channels/linear/index.d.ts +1 -1
- package/dist/src/public/channels/linear/index.js +1 -1
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +20 -0
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/defaults.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +31 -0
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +2 -2
- package/dist/src/public/channels/slack/slackChannel.d.ts +72 -4
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/defaults.js +2 -2
- package/dist/src/public/channels/teams/index.d.ts +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +7 -2
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/defaults.js +1 -1
- package/dist/src/public/definitions/channel.d.ts +1 -1
- package/dist/src/runtime/framework-tools/connection-search-dynamic.js +2 -1
- package/dist/src/setup/scaffold/create/add-to-project.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +2 -2
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +90 -5
- package/dist/src/setup/scaffold/update/channels.js +2 -2
- package/dist/src/setup/scaffold/update/connections.js +1 -1
- package/docs/agent-config.md +10 -5
- package/docs/channels/slack.mdx +76 -10
- package/docs/channels/teams.mdx +10 -3
- package/docs/guides/deployment/vercel.mdx +1 -1
- package/docs/guides/frontend/overview.mdx +2 -0
- package/package.json +22 -22
- package/dist/src/compiled/@workflow/core/step-hydration-cache.d.ts +0 -148
- package/dist/src/compiled/_chunks/node/virtual-loader-B8K8w9cr.js +0 -1
- package/dist/src/compiled/_chunks/workflow/attribute-changes-CXiYB8h9.js +0 -20
- package/dist/src/compiled/_chunks/workflow/dist-BYo8Gno2.js +0 -3
- package/dist/src/compiled/_chunks/workflow/resume-hook-CHXbXuRZ.js +0 -1
- package/dist/src/compiled/_chunks/workflow/run-BjtAGa3_.js +0 -1
- package/dist/src/compiled/_chunks/workflow/sleep-BJAv0MFP.js +0 -1
- package/dist/src/compiled/_chunks/workflow/token-D4VIwGlD.js +0 -1
- package/dist/src/compiled/_chunks/workflow/wrapper-ulTvoTdW.js +0 -6
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.1/node_modules/@vercel/sdk/esm/models/updateprojectprojectsaction.js +0 -1
- package/dist/src/node_modules/.pnpm/@vercel_sdk@1.28.1/node_modules/@vercel/sdk/esm/models/updateprojectprojectsoptionsallowlist.js +0 -1
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/lib/primitives.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/lib/schemas.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/models/aclaction.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/models/sdkvalidationerror.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/defaultToZeroValue.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/fp.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/primitives.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/rfcdate.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/smartUnion.js +0 -0
- /package/dist/src/node_modules/.pnpm/{@vercel_sdk@1.28.1 → @vercel_sdk@1.28.8}/node_modules/@vercel/sdk/esm/types/unrecognized.js +0 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{registerTelegramFreeformPrompt,renderTelegramInputRequest}from"#public/channels/telegram/hitl.js";function defaultTelegramAuth(e){let t=e.from;if(!t)return null;let n={chat_id:e.chat.id,chat_type:e.chat.type,message_id:e.messageId,user_id:t.id};e.chat.title!==void 0&&(n.chat_title=e.chat.title),e.messageThreadId!==void 0&&(n.message_thread_id=String(e.messageThreadId)),t.username!==void 0&&(n.username=t.username);let r=e.chat.type===`group`||e.chat.type===`supergroup`,i=r?`telegram:${e.chat.id}:${t.id}`:`telegram:${t.id}`;return{attributes:n,authenticator:`telegram-webhook`,issuer:r?`telegram:${e.chat.id}`:`telegram`,principalId:i,principalType:t.isBot?`service`:`user`}}async function defaultOnMessage(e,t){return shouldDispatchTelegramMessage(t,e.telegram.botUsername)?(await e.telegram.startTyping(),{auth:defaultTelegramAuth(t)}):null}const defaultEvents={async"turn.started"(e,t,n){await t.telegram.startTyping()},async"actions.requested"(e,t,n){await t.telegram.startTyping()},async"input.requested"(e,t,i){for(let i of e.requests){let e=renderTelegramInputRequest(i,t.state),a=await t.telegram.post({reply_markup:e.replyMarkup,text:e.text});e.freeformRequestId!==void 0&&a.id&®isterTelegramFreeformPrompt(t.state,{messageId:a.id,requestId:e.freeformRequestId})}},async"message.completed"(e,t,n){e.finishReason===`tool-calls`||!e.message||await t.telegram.post(e.message)},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.telegram.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
1
|
+
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{registerTelegramFreeformPrompt,renderTelegramInputRequest}from"#public/channels/telegram/hitl.js";function defaultTelegramAuth(e){let t=e.from;if(!t)return null;let n={chat_id:e.chat.id,chat_type:e.chat.type,message_id:e.messageId,user_id:t.id};e.chat.title!==void 0&&(n.chat_title=e.chat.title),e.messageThreadId!==void 0&&(n.message_thread_id=String(e.messageThreadId)),t.username!==void 0&&(n.username=t.username);let r=e.chat.type===`group`||e.chat.type===`supergroup`,i=r?`telegram:${e.chat.id}:${t.id}`:`telegram:${t.id}`;return{attributes:n,authenticator:`telegram-webhook`,issuer:r?`telegram:${e.chat.id}`:`telegram`,principalId:i,principalType:t.isBot?`service`:`user`}}async function defaultOnMessage(e,t){return shouldDispatchTelegramMessage(t,e.telegram.botUsername)?(await e.telegram.startTyping(),{auth:defaultTelegramAuth(t)}):null}const defaultEvents={async"turn.started"(e,t,n){await t.telegram.startTyping()},async"authorization.completed"(e,t,n){e.outcome===`authorized`&&await t.telegram.startTyping()},async"actions.requested"(e,t,n){await t.telegram.startTyping()},async"input.requested"(e,t,i){for(let i of e.requests){let e=renderTelegramInputRequest(i,t.state),a=await t.telegram.post({reply_markup:e.replyMarkup,text:e.text});e.freeformRequestId!==void 0&&a.id&®isterTelegramFreeformPrompt(t.state,{messageId:a.id,requestId:e.freeformRequestId})}},async"message.completed"(e,t,n){e.finishReason===`tool-calls`||!e.message||await t.telegram.post(e.message)},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.telegram.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
|
|
2
2
|
`))},async"session.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.telegram.post([`This session could not recover from an error${i}.`,``,`Start a new message to continue.`,...a?[``,`Error id: ${a}`]:[]].join(`
|
|
3
3
|
`))}};function shouldDispatchTelegramMessage(e,t){if(e.from?.isBot===!0||e.chat.type===`channel`)return!1;let n=e.text||e.caption;return n.trim().length>0||e.attachments.length>0?!!(e.chat.type===`private`||e.replyToMessage?.from?.isBot===!0||isBotCommand(n,t)||t!==void 0&&mentionsBot(n,t)):!1}function isBotCommand(e,t){let n=/^\/(?<command>[A-Za-z0-9_]+)(?:@(?<target>[A-Za-z0-9_]+))?(?:\s|$)/u.exec(e);if(!n)return!1;let r=n.groups?.target;return r===void 0?!0:t!==void 0&&r.toLowerCase()===t.toLowerCase()}function mentionsBot(e,t){return e.toLowerCase().includes(`@${t.toLowerCase()}`)}export{defaultEvents,defaultOnMessage,defaultTelegramAuth};
|
|
@@ -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, GetSessionFn, WebSocketMessage, WebSocketPeer, WebSocketRouteDefinition, WebSocketRouteHandler, WebSocketRouteHooks, WebSocketUpgradeRequest, WebSocketUpgradeResult, } 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";
|
|
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 +1,2 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{z}from"#compiled/zod/index.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection-key.js";import{ConnectionAuthorizationFailedError,isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{resolveAuthorizationCallbackUrl,stampChallengeDisplayName}from"#runtime/connections/scoped-authorization.js";import{resolveConnectionAuthorization}from"#runtime/connections/resolve-authorization.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),CONNECTION_SEARCH_INPUT_SCHEMA=z.strictObject({connection:z.string().describe(`Optional: limit search to a specific connection name.`).optional(),keywords:z.string().describe(`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`),limit:z.number().describe(`Max results to return. Default 10.`).optional()}),connectionSchema=z.looseObject({}),CONNECTION_SEARCH_RESULT_ITEM_SCHEMA=z.strictObject({connection:z.string(),description:z.string(),error:z.string().optional(),inputSchema:connectionSchema.optional(),needsAuthorization:z.boolean().optional(),outputSchema:connectionSchema.optional(),qualifiedName:z.string().optional(),tool:z.string().optional()}),CONNECTION_SEARCH_OUTPUT_SCHEMA=z.array(CONNECTION_SEARCH_RESULT_ITEM_SCHEMA),ConnectionSearchResultsKey=new ContextKey(`eve.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}async function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n===void 0)return;let r=await resolveConnectionAuthorization(n);if(supportsInteractiveAuthorization(r))return r}async function completePendingAuthorizations(e){let t=loadContext(),
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{z}from"#compiled/zod/index.js";import{toError}from"#shared/errors.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection-key.js";import{ConnectionAuthorizationFailedError,isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{resolveAuthorizationCallbackUrl,stampChallengeDisplayName}from"#runtime/connections/scoped-authorization.js";import{resolveConnectionAuthorization}from"#runtime/connections/resolve-authorization.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),CONNECTION_SEARCH_INPUT_SCHEMA=z.strictObject({connection:z.string().describe(`Optional: limit search to a specific connection name.`).optional(),keywords:z.string().describe(`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`),limit:z.number().describe(`Max results to return. Default 10.`).optional()}),connectionSchema=z.looseObject({}),CONNECTION_SEARCH_RESULT_ITEM_SCHEMA=z.strictObject({connection:z.string(),description:z.string(),error:z.string().optional(),inputSchema:connectionSchema.optional(),needsAuthorization:z.boolean().optional(),outputSchema:connectionSchema.optional(),qualifiedName:z.string().optional(),tool:z.string().optional()}),CONNECTION_SEARCH_OUTPUT_SCHEMA=z.array(CONNECTION_SEARCH_RESULT_ITEM_SCHEMA),ConnectionSearchResultsKey=new ContextKey(`eve.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}async function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n===void 0)return;let r=await resolveConnectionAuthorization(n);if(supportsInteractiveAuthorization(r))return r}async function completePendingAuthorizations(e){let t=loadContext(),n=new Set;for(let r of e.getConnections()){let i=getAuthorizationResult(r.connectionName);if(!i)continue;let a=await resolveInteractiveAuth(e,r.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(r.connectionName,a),s=await a.completeAuthorization({callbackUrl:i.hookUrl,connection:{url:r.url??``},principal:o,resume:i.resume,callback:i.callback});writeCachedToken(t,r.connectionName,principalKey(o),s),n.add(r.connectionName)}return n}async function executeConnectionSearch(e){let t=loadContext(),i=t.get(ConnectionRegistryKey);if(i===void 0)return[];let o=await completePendingAuthorizations(i),c=e.limit??10,l=tokenize(e.keywords),u=[],d=[],f=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections();if(e.connection&&f.length===0)throw Error(`Connection "${e.connection}" is not registered. Available connections: ${i.getConnectionNames().join(`, `)}.`);let m=[];for(let e of f){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){if(o.has(e.connectionName)){logger.warn(`connection still unauthorized after authorization`,{connection:e.connectionName}),d.push({connection:e.connectionName,description:e.description,error:`Authorization for "${e.connectionName}" did not take effect; the token was rejected after sign-in.`});continue}let t=await resolveInteractiveAuth(i,e.connectionName);if(t){let r=getHookUrl(e.connectionName);if(r){let i=resolveConnectionPrincipal(e.connectionName,t),a=resolveAuthorizationCallbackUrl({authorization:t,callbackUrl:r});try{let{challenge:n,resume:r}=await t.startAuthorization({callbackUrl:a,connection:{url:e.url??``},principal:i});m.push({name:e.connectionName,challenge:stampChallengeDisplayName(n,t),hookUrl:a,resume:r})}catch(t){let r=toError(t);logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:r}),d.push({connection:e.connectionName,description:e.description,error:`Failed to start authorization for "${e.connectionName}": ${r.message}`});continue}}}d.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),d.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let r=toError(t);logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:r}),d.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${r.message}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,outputSchema:n.outputSchema,qualifiedName:qualifiedConnectionToolName(e.connectionName,n.name),tool:n.name},score:t})}}if(m.length>0)return requestAuthorization(m);let h=d.filter(e=>e.error!==void 0);if(f.length>0&&h.length===f.length)throw Error(h.map(e=>e.error).join(`
|
|
2
|
+
`));u.sort((e,t)=>t.score-e.score);let g=u.slice(0,c).map(e=>e.item);if(g.length>0){let e=[...g,...d],n=t.get(ConnectionSearchResultsKey)??[],r=new Map(n.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return t.set(ConnectionSearchResultsKey,[...r.values()]),e}return f.map(e=>d.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection_search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,t)=>{let n=loadContext().get(ConnectionRegistryKey);if(!n||n.getConnections().length===0)return null;let i=n.getConnections().map(e=>e.connectionName),s=extractDiscoveredTools(t.messages),l=loadContext().get(ConnectionSearchResultsKey)??[],u=new Map;for(let e of l)e.qualifiedName&&u.set(e.qualifiedName,e);for(let e of s)e.qualifiedName&&u.set(e.qualifiedName,e);let p=[...u.values()],h={};h.connection_search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`linear__list_issues\`) in your next response. Available connections: ${i.join(`, `)}.`,inputSchema:CONNECTION_SEARCH_INPUT_SCHEMA,async execute(e){return executeConnectionSearch(e)},outputSchema:CONNECTION_SEARCH_OUTPUT_SCHEMA};for(let e of p){let t=e.connection,i=e.tool,s=n.getConnectionApproval(t);h[qualifiedConnectionToolName(t,i)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},approval:s,outputSchema:e.outputSchema,async execute(e,n){let s=loadContext().get(ConnectionRegistryKey),l=s.getConnections().find(e=>e.connectionName===t),u=await resolveInteractiveAuth(s,t),p=!1;if(u){let e=getAuthorizationResult(t);if(e){p=!0;let n=loadContext(),i=resolveConnectionPrincipal(t,u),a=await u.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:l?.url??``},principal:i,resume:e.resume,callback:e.callback});writeCachedToken(n,t,principalKey(i),a)}}try{return await s.getClient(t).executeTool(i,e,{abortSignal:n.abortSignal})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!u)throw e;if(p)throw new ConnectionAuthorizationFailedError(t,{retryable:!1,reason:`token_rejected_after_authorization`,message:`Connection "${t}" rejected the token immediately after authorization.`});let n=getHookUrl(t);if(!n)throw e;let r=resolveConnectionPrincipal(t,u),i=resolveAuthorizationCallbackUrl({authorization:u,callbackUrl:n}),{challenge:a,resume:s}=await u.startAuthorization({callbackUrl:i,connection:{url:l?.url??``},principal:r});return requestAuthorization([{name:t,challenge:stampChallengeDisplayName(a,u),hookUrl:i,resume:s}])}}}}return h}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`eve:connection-search-dynamic`,sourceKind:`module`,logicalPath:`eve:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"../update/package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{agentTemplateFiles,formatEveDependencySpecifier,resolveEvePackageContract}from"./project.js";import{join}from"node:path";import{readFile}from"node:fs/promises";const DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`];function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function hasDeclaredDependency(e,t){if(!isJsonObject(e))return!1;for(let n of DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function addAgentToProject(i){let a=i.packageManager??`pnpm`,o=join(i.projectRoot,`package.json`);if(!await pathExists(o))throw Error(`Cannot add an eve agent to "${i.projectRoot}" because it has no package.json. Run \`eve init <name>\` to create a new project instead.`);let s=agentTemplateFiles(i.model),c=[];for(let t of Object.keys(s))await pathExists(join(i.projectRoot,t))&&c.push(t);if(c.length===0&&await pathExists(join(i.projectRoot,`agent`))&&c.push(`agent/`),c.length>0)throw Error(`Cannot add an eve agent to "${i.projectRoot}" because it already has: ${c.join(`, `)}.`);let l=resolveEvePackageContract(i.evePackage),u=resolveVersionToken(`aiPackageVersion`,i.aiPackageVersion??`^7.0.
|
|
1
|
+
import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"../update/package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{agentTemplateFiles,formatEveDependencySpecifier,resolveEvePackageContract}from"./project.js";import{join}from"node:path";import{readFile}from"node:fs/promises";const DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`];function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function hasDeclaredDependency(e,t){if(!isJsonObject(e))return!1;for(let n of DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function addAgentToProject(i){let a=i.packageManager??`pnpm`,o=join(i.projectRoot,`package.json`);if(!await pathExists(o))throw Error(`Cannot add an eve agent to "${i.projectRoot}" because it has no package.json. Run \`eve init <name>\` to create a new project instead.`);let s=agentTemplateFiles(i.model),c=[];for(let t of Object.keys(s))await pathExists(join(i.projectRoot,t))&&c.push(t);if(c.length===0&&await pathExists(join(i.projectRoot,`agent`))&&c.push(`agent/`),c.length>0)throw Error(`Cannot add an eve agent to "${i.projectRoot}" because it already has: ${c.join(`, `)}.`);let l=resolveEvePackageContract(i.evePackage),u=resolveVersionToken(`aiPackageVersion`,i.aiPackageVersion??`^7.0.34`),d=resolveVersionToken(`connectPackageVersion`,i.connectPackageVersion??`0.4.2`),f=resolveVersionToken(`zodPackageVersion`,i.zodPackageVersion??`4.4.3`),p=[];for(let[e,n]of Object.entries(s)){let r=join(i.projectRoot,e);await writeTextFile(r,n),p.push(r)}let m=JSON.parse(await readFile(o,`utf8`)),h={"@vercel/connect":d,ai:u,eve:formatEveDependencySpecifier(l.version),zod:f},g={};for(let[e,t]of Object.entries(h))hasDeclaredDependency(m,e)||(g[e]=t);let _={};Object.keys(g).length>0&&(_.dependencies=g),isPackageManagerWorkspaceMember(a,i.projectRoot)||(_.nodeEngineRequirement=l.nodeEngine);let v=await patchPackageJson(o,_),y=(await patchWorkspaceRootPackageJson(a,i.projectRoot,{aiPackageVersion:u,nodeEngineRequirement:l.nodeEngine})).nodeEngineOverride??v.nodeEngineOverride;return await applyPackageManagerWorkspaceConfiguration({packageManager:a,projectRoot:i.projectRoot}),{filesWritten:p,dependenciesAdded:Object.keys(g).sort(),nodeEngineOverride:y}}export{addAgentToProject};
|
|
@@ -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.
|
|
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`,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__",
|
|
@@ -93,4 +93,4 @@ export default defineAgent({
|
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
|
-
`:BASE_AGENT_TEMPLATE,...SHARED_TEMPLATE_FILES,"package.json":packageJsonTemplate({includeRootOnlyFields:e.includeRootOnlyPackageJsonFields,packageManager:e.packageManager})}}async function assertCanCreateInPlace(e,t){if(!await pathExists(e))return;let r=(await readdir(e)).filter(e=>!ALLOWED_CREATE_IN_PLACE_ENTRIES.has(e));if(r.length>0&&!t){let e=r.slice(0,5).join(`, `),t=r.length>5?`, and ${r.length-5} more`:``;throw Error(`Cannot create project in current directory because it is not empty. Found: ${e}${t}. Use an empty directory.`)}}async function scaffoldBaseProject(t){let s=resolve(t.targetDirectory??process.cwd(),t.projectName),c=t.projectName===`.`,l=t.overwriteExisting??!1,u=t.byokProvider??!1,d=t.packageManager??`pnpm`,f=resolveEvePackageContract(t.evePackage),p=pinnedNodeEngineMajor(f.nodeEngine),m=resolve(t.workspaceProbeDirectory??s),h=isPackageManagerWorkspaceMember(d,m);if(c)await assertCanCreateInPlace(s,l);else if(await pathExists(s))throw Error(`Cannot create project because "${s}" already exists.`);let g={appName:basename(s),model:t.model,eveVersion:f.version,aiPackageVersion:resolveVersionToken(`aiPackageVersion`,t.aiPackageVersion??`^7.0.
|
|
96
|
+
`:BASE_AGENT_TEMPLATE,...SHARED_TEMPLATE_FILES,"package.json":packageJsonTemplate({includeRootOnlyFields:e.includeRootOnlyPackageJsonFields,packageManager:e.packageManager})}}async function assertCanCreateInPlace(e,t){if(!await pathExists(e))return;let r=(await readdir(e)).filter(e=>!ALLOWED_CREATE_IN_PLACE_ENTRIES.has(e));if(r.length>0&&!t){let e=r.slice(0,5).join(`, `),t=r.length>5?`, and ${r.length-5} more`:``;throw Error(`Cannot create project in current directory because it is not empty. Found: ${e}${t}. Use an empty directory.`)}}async function scaffoldBaseProject(t){let s=resolve(t.targetDirectory??process.cwd(),t.projectName),c=t.projectName===`.`,l=t.overwriteExisting??!1,u=t.byokProvider??!1,d=t.packageManager??`pnpm`,f=resolveEvePackageContract(t.evePackage),p=pinnedNodeEngineMajor(f.nodeEngine),m=resolve(t.workspaceProbeDirectory??s),h=isPackageManagerWorkspaceMember(d,m);if(c)await assertCanCreateInPlace(s,l);else if(await pathExists(s))throw Error(`Cannot create project because "${s}" already exists.`);let g={appName:basename(s),model:t.model,eveVersion:f.version,aiPackageVersion:resolveVersionToken(`aiPackageVersion`,t.aiPackageVersion??`^7.0.34`),connectPackageVersion:resolveVersionToken(`connectPackageVersion`,t.connectPackageVersion??`0.4.2`),zodPackageVersion:resolveVersionToken(`zodPackageVersion`,t.zodPackageVersion??`4.4.3`),typescriptPackageVersion:resolveVersionToken(`typescriptPackageVersion`,t.typescriptPackageVersion??`7.0.2`),nodeTypesVersion:p,nodeEngine:p};await mkdir(s,{recursive:!0});for(let[e,i]of Object.entries(templateFiles({byokProvider:u,includeRootOnlyPackageJsonFields:!h,packageManager:d}))){let a=`${s}/${e}`,o=await pathExists(a);await writeTextFile(a,renderTemplate(i,g),{force:c&&l}),o&&await t.onOverwriteFile?.(a)}return await applyPackageManagerWorkspaceConfiguration({packageManager:d,projectRoot:s,workspaceProbeRoot:m,onWorkspaceRootMutation:t.onWorkspaceRootMutation}),await patchWorkspaceRootPackageJson(d,m,{aiPackageVersion:g.aiPackageVersion,nodeEngineRequirement:f.nodeEngine,onWorkspaceRootMutation:t.onWorkspaceRootMutation}),s}async function isEveProject(e){for(let n of SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS)try{return await stat(join(e,`agent`,`agent${n}`)),!0}catch{}return!1}export{CURRENT_DIRECTORY_PROJECT_NAME,DEFAULT_AI_PACKAGE_VERSION,DEFAULT_CONNECT_PACKAGE_VERSION,DEFAULT_EVE_PACKAGE_CONTRACT,DEFAULT_ZOD_PACKAGE_VERSION,ROOT_ONLY_PACKAGE_JSON_TEMPLATE_SUFFIX,agentTemplateFiles,byokProviderEnvVar,formatEveDependencySpecifier,isEveProject,modelProviderSlug,resolveEvePackageContract,scaffoldBaseProject};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const WEB_APP_TEMPLATE_FILES: {
|
|
2
2
|
readonly "agent/channels/eve.ts": 'import { eveChannel } from "eve/channels/eve";\nimport { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";\n\nexport default eveChannel({\n auth: [\n // Lets the eve TUI and your Vercel deployments reach the deployed agent.\n vercelOidc(),\n // Open on localhost for `eve dev` and the REPL; ignored in production.\n localDev(),\n // This placeholder will not allow browser requests in production.\n // Replace it with your app\'s auth provider, like Auth.js or Clerk,\n // or use none() for a public demo.\n placeholderAuth(),\n ],\n});\n';
|
|
3
|
-
readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport type { UserContent } from "ai";\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon } from "lucide-react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n} from "@/components/ai-elements/conversation";\nimport {\n PromptInput,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n} from "@/components/ai-elements/prompt-input";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\n\nconst AGENT_NAME = "__EVE_INIT_APP_NAME__";\n\ntype AgentStatus = ReturnType<typeof useEveAgent>["status"];\n\nexport function AgentChat() {\n const agent = useEveAgent();\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isEmpty = agent.data.messages.length === 0;\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if ((text.length === 0 && message.files.length === 0) || isBusy) return;\n\n if (message.files.length === 0) {\n await agent.send({ message: text });\n return;\n }\n\n const parts: UserContent = [];\n if (text.length > 0) {\n parts.push({ text, type: "text" });\n }\n for (const file of message.files) {\n parts.push({\n data: file.url,\n filename: file.filename,\n mediaType: file.mediaType,\n type: "file",\n });\n }\n\n await agent.send({ message: parts });\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea placeholder="Send a message…" />\n <PromptInputSubmit onStop={
|
|
3
|
+
readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport type { UserContent } from "ai";\nimport { Client, type HandleMessageStreamEvent } from "eve/client";\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon } from "lucide-react";\nimport { useCallback, useRef, useState } from "react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n} from "@/components/ai-elements/conversation";\nimport {\n PromptInput,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n} from "@/components/ai-elements/prompt-input";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\n\nconst AGENT_NAME = "__EVE_INIT_APP_NAME__";\n\ntype AgentStatus = ReturnType<typeof useEveAgent>["status"];\ntype CancellationState = "idle" | "requested" | "cancelling";\n\ntype Cancellation = {\n requested: boolean;\n sentTurnId?: string;\n turnId?: string;\n};\n\nexport function AgentChat() {\n const [session] = useState(() =>\n new Client({ host: "", preserveCompletedSessions: true }).session(),\n );\n const cancellationRef = useRef<Cancellation>({ requested: false });\n const [cancellationError, setCancellationError] = useState<string>();\n const [cancellationState, setCancellationState] = useState<CancellationState>("idle");\n\n const cancelTurn = useCallback(\n (turnId: string) => {\n const cancellation = cancellationRef.current;\n if (!cancellation.requested || cancellation.sentTurnId === turnId) {\n return;\n }\n\n cancellation.sentTurnId = turnId;\n setCancellationState("cancelling");\n\n void session.cancel({ turnId }).catch((error: unknown) => {\n if (cancellationRef.current !== cancellation) {\n return;\n }\n\n cancellation.requested = false;\n cancellation.sentTurnId = undefined;\n setCancellationError(toErrorMessage(error));\n setCancellationState("idle");\n });\n },\n [session],\n );\n\n const handleEvent = useCallback(\n (event: HandleMessageStreamEvent) => {\n if (event.type !== "turn.started") {\n return;\n }\n\n const cancellation = cancellationRef.current;\n cancellation.turnId = event.data.turnId;\n cancelTurn(event.data.turnId);\n },\n [cancelTurn],\n );\n\n const agent = useEveAgent({ onEvent: handleEvent, session });\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isEmpty = agent.data.messages.length === 0;\n const errorMessage = cancellationError ?? agent.error?.message;\n const submitStatus = isBusy && cancellationState !== "idle" ? "submitted" : agent.status;\n\n const prepareTurn = () => {\n cancellationRef.current = { requested: false };\n setCancellationError(undefined);\n setCancellationState("idle");\n };\n\n const requestCancellation = () => {\n if (!isBusy || cancellationState !== "idle") {\n return;\n }\n\n const cancellation = cancellationRef.current;\n cancellation.requested = true;\n setCancellationError(undefined);\n setCancellationState("requested");\n\n if (cancellation.turnId !== undefined) {\n cancelTurn(cancellation.turnId);\n }\n };\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if ((text.length === 0 && message.files.length === 0) || isBusy) return;\n\n prepareTurn();\n\n if (message.files.length === 0) {\n await agent.send({ message: text });\n return;\n }\n\n const parts: UserContent = [];\n if (text.length > 0) {\n parts.push({ text, type: "text" });\n }\n for (const file of message.files) {\n parts.push({\n data: file.url,\n filename: file.filename,\n mediaType: file.mediaType,\n type: "file",\n });\n }\n\n await agent.send({ message: parts });\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea placeholder="Send a message…" />\n <PromptInputSubmit onStop={requestCancellation} status={submitStatus} />\n </PromptInput>\n );\n\n return (\n <main className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">\n {isEmpty ? null : (\n <header className="flex h-14 shrink-0 items-center justify-center gap-3 pl-4 pr-2">\n <span className="flex min-w-0 items-center gap-2">\n <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>\n <StatusDot status={agent.status} />\n </span>\n </header>\n )}\n\n {errorMessage ? (\n <div className="mx-auto w-full max-w-3xl shrink-0 px-4 pt-2 sm:px-6">\n <div className="flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm">\n <AlertCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />\n <div>\n <p className="font-medium">Request failed</p>\n <p className="mt-0.5 text-muted-foreground">{errorMessage}</p>\n </div>\n </div>\n </div>\n ) : null}\n\n {isEmpty ? null : (\n <Conversation className="min-h-0 flex-1">\n <ConversationContent className="mx-auto w-full max-w-3xl gap-6 px-4 py-6 sm:px-6">\n {agent.data.messages.map((message, index) => (\n <AgentMessage\n canRespond={!isBusy}\n isStreaming={\n agent.status === "streaming" && index === agent.data.messages.length - 1\n }\n key={message.id}\n message={message}\n onInputResponses={(inputResponses) => {\n prepareTurn();\n return agent.send({ inputResponses });\n }}\n />\n ))}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n )}\n\n <div\n className={cn(\n "mx-auto w-full px-4 sm:px-6",\n isEmpty\n ? "flex max-w-xl flex-1 flex-col items-center justify-center gap-8 pb-[10vh]"\n : "max-w-3xl shrink-0 pb-6",\n )}\n >\n {isEmpty ? (\n <div className="flex flex-col items-center gap-3 text-center">\n <h1 className="font-medium text-5xl tracking-tighter">{AGENT_NAME}</h1>\n </div>\n ) : null}\n <div className="w-full">{composer}</div>\n </div>\n </main>\n );\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : "Unable to cancel the response.";\n}\n\nfunction StatusDot({ status }: { readonly status: AgentStatus }) {\n const isLive = status === "submitted" || status === "streaming";\n const tone =\n status === "error"\n ? "bg-destructive"\n : isLive\n ? "bg-emerald-500"\n : status === "ready"\n ? "bg-muted-foreground"\n : "bg-muted-foreground/50";\n\n return (\n <span className="relative flex size-1">\n {isLive ? (\n <span\n className={cn(\n "absolute inline-flex size-full animate-ping rounded-full opacity-75",\n tone,\n )}\n />\n ) : null}\n <span className={cn("relative inline-flex size-1 rounded-full transition-colors", tone)} />\n </span>\n );\n}\n';
|
|
4
4
|
readonly "app/_components/agent-message.tsx": '"use client";\n\nimport type {\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessagePart,\n} from "eve/react";\nimport {\n CheckCircleIcon,\n ExternalLinkIcon,\n FileIcon,\n ImageIcon,\n KeyRoundIcon,\n XCircleIcon,\n} from "lucide-react";\nimport { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";\nimport { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";\nimport {\n Tool,\n ToolContent,\n ToolHeader,\n ToolInput,\n ToolOutput,\n} from "@/components/ai-elements/tool";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\n\nexport type AgentInputResponse = {\n readonly optionId?: string;\n readonly requestId: string;\n readonly text?: string;\n};\n\ntype EveFilePart = Extract<EveMessagePart, { type: "file" }>;\n\nexport function AgentMessage({\n canRespond,\n isStreaming,\n message,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly isStreaming: boolean;\n readonly message: EveMessage;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const lastTextIndex = message.parts.reduce(\n (last, part, index) => (part.type === "text" ? index : last),\n -1,\n );\n\n return (\n <Message\n data-optimistic={message.metadata?.optimistic ? "true" : undefined}\n from={message.role}\n >\n <MessageContent>\n {message.parts.map((part, index) => (\n <AgentMessagePart\n canRespond={canRespond}\n key={partKey(part, index)}\n onInputResponses={onInputResponses}\n part={part}\n showCaret={isStreaming && message.role === "assistant" && index === lastTextIndex}\n />\n ))}\n </MessageContent>\n </Message>\n );\n}\n\nfunction AgentMessagePart({\n canRespond,\n onInputResponses,\n part,\n showCaret,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveMessagePart;\n readonly showCaret: boolean;\n}) {\n switch (part.type) {\n case "step-start":\n return null;\n case "text":\n return (\n <MessageResponse caret="block" isAnimating={showCaret}>\n {part.text}\n </MessageResponse>\n );\n case "reasoning":\n return (\n <Reasoning defaultOpen isStreaming={part.state === "streaming"}>\n <ReasoningTrigger />\n <ReasoningContent>{part.text}</ReasoningContent>\n </Reasoning>\n );\n case "file":\n return <AttachmentPart part={part} />;\n case "authorization":\n return <AuthorizationPrompt part={part} />;\n case "dynamic-tool":\n return (\n <Tool\n defaultOpen={part.state === "approval-requested" || part.state === "approval-responded"}\n >\n <ToolHeader\n state={part.state}\n title={part.toolName}\n toolName={part.toolName}\n type="dynamic-tool"\n />\n <ToolContent>\n <ToolInput input={part.input} />\n <InputRequestActions\n canRespond={canRespond}\n part={part}\n onInputResponses={onInputResponses}\n />\n <ToolOutput errorText={part.errorText} output={part.output} />\n </ToolContent>\n </Tool>\n );\n }\n}\n\nfunction AttachmentPart({ part }: { readonly part: EveFilePart }) {\n const label = part.filename ?? "Attachment";\n const detail = [part.mediaType, formatBytes(part.size)].filter(Boolean).join(" - ");\n const isImage = part.mediaType.startsWith("image/") && part.url !== undefined;\n const Icon = isImage ? ImageIcon : FileIcon;\n const body = (\n <span className="flex max-w-sm items-center gap-3 rounded-md border bg-background/60 p-2 text-sm">\n {isImage ? (\n <img alt={label} className="size-12 shrink-0 rounded-sm object-cover" src={part.url} />\n ) : (\n <span className="flex size-10 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground">\n <Icon className="size-4" />\n </span>\n )}\n <span className="min-w-0 flex-1">\n <span className="block truncate font-medium">{label}</span>\n {detail ? <span className="block truncate text-muted-foreground">{detail}</span> : null}\n </span>\n {part.url ? <ExternalLinkIcon className="size-4 shrink-0 text-muted-foreground" /> : null}\n </span>\n );\n\n return part.url ? (\n <a href={part.url} rel="noreferrer" target="_blank">\n {body}\n </a>\n ) : (\n body\n );\n}\n\nfunction AuthorizationPrompt({ part }: { readonly part: EveAuthorizationPart }) {\n const isAuthorized = part.state === "completed" && part.outcome === "authorized";\n const isCompleted = part.state === "completed";\n const Icon = isAuthorized ? CheckCircleIcon : isCompleted ? XCircleIcon : KeyRoundIcon;\n const instructions = part.authorization?.instructions;\n const shouldShowInstructions = instructions !== undefined && instructions !== part.description;\n\n return (\n <div\n className={cn(\n "space-y-3 rounded-md border p-3",\n isAuthorized\n ? "border-emerald-500/30 bg-emerald-500/5"\n : isCompleted\n ? "border-destructive/30 bg-destructive/5"\n : "border-blue-500/30 bg-blue-500/5",\n )}\n >\n <div className="flex items-start gap-3">\n <span\n className={cn(\n "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full",\n isAuthorized\n ? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"\n : isCompleted\n ? "bg-destructive/10 text-destructive"\n : "bg-blue-500/10 text-blue-700 dark:text-blue-300",\n )}\n >\n <Icon className="size-4" />\n </span>\n <div className="min-w-0 flex-1 space-y-2">\n <p className="font-medium text-sm">{authorizationTitle(part)}</p>\n <p className="text-muted-foreground text-sm">{authorizationDescription(part)}</p>\n {shouldShowInstructions ? (\n <p className="text-muted-foreground text-sm">{instructions}</p>\n ) : null}\n {part.state === "required" && part.authorization?.userCode ? (\n <div className="flex flex-wrap items-center gap-2 text-sm">\n <span className="text-muted-foreground">Code</span>\n <code className="rounded-md bg-background px-2 py-1 font-mono">\n {part.authorization.userCode}\n </code>\n </div>\n ) : null}\n {part.state === "required" && part.authorization?.url ? (\n <Button asChild size="sm">\n <a href={part.authorization.url} rel="noreferrer" target="_blank">\n <ExternalLinkIcon className="size-4" />\n Sign in with {part.displayName}\n </a>\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n\nfunction authorizationTitle(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return `Connect ${part.displayName}`;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected`;\n }\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}`;\n}\n\nfunction authorizationDescription(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return part.description;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected.`;\n }\n const tail = part.reason !== undefined ? ` (${part.reason})` : "";\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}${tail}.`;\n}\n\nfunction formatAuthorizationOutcome(outcome: NonNullable<EveAuthorizationPart["outcome"]>): string {\n switch (outcome) {\n case "authorized":\n return "authorized";\n case "declined":\n return "declined";\n case "failed":\n return "failed";\n case "timed-out":\n return "timed out";\n }\n}\n\nfunction formatBytes(size: number | undefined): string | undefined {\n if (size === undefined) {\n return undefined;\n }\n if (size < 1024) {\n return `${size} B`;\n }\n if (size < 1024 * 1024) {\n return `${(size / 1024).toFixed(1)} KB`;\n }\n return `${(size / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction InputRequestActions({\n canRespond,\n onInputResponses,\n part,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveDynamicToolPart;\n}) {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (!inputRequest) {\n return null;\n }\n\n const inputResponse = part.toolMetadata?.eve?.inputResponse;\n const selectedOption = inputRequest.options?.find(\n (option) => option.id === inputResponse?.optionId,\n );\n\n return (\n <div className="space-y-3 rounded-md border border-yellow-500/30 bg-yellow-500/5 p-3">\n <p className="text-muted-foreground text-sm">{inputRequest.prompt}</p>\n {inputResponse ? (\n <p className="font-medium text-sm">\n Responded: {selectedOption?.label ?? inputResponse.text ?? inputResponse.optionId}\n </p>\n ) : (\n <div className="flex flex-wrap gap-2">\n {inputRequest.options?.map((option) => (\n <Button\n disabled={!canRespond}\n key={option.id}\n onClick={() => {\n void onInputResponses([\n {\n optionId: option.id,\n requestId: inputRequest.requestId,\n },\n ]);\n }}\n size="sm"\n type="button"\n variant={option.style === "danger" ? "destructive" : "default"}\n >\n {option.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction partKey(part: EveMessagePart, index: number): string {\n switch (part.type) {\n case "authorization":\n return `authorization:${part.turnId}:${part.stepIndex}:${part.name}`;\n case "dynamic-tool":\n return part.toolCallId;\n default:\n return `${part.type}:${index}`;\n }\n}\n';
|
|
5
5
|
readonly "app/globals.css": '@import "tailwindcss";\n@source "../node_modules/streamdown/dist/*.js";\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-destructive: var(--destructive);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n --font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;\n --font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, monospace;\n}\n\n:root {\n color-scheme: light;\n /* Soft neutral page with white elevated surfaces so cards/composer pop. */\n --background: oklch(0.971 0 0);\n --foreground: oklch(0.16 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.16 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.16 0 0);\n --primary: oklch(0.19 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.94 0 0);\n --secondary-foreground: oklch(0.19 0 0);\n --muted: oklch(0.94 0 0);\n --muted-foreground: oklch(0.6 0 0);\n --accent: oklch(0.94 0 0);\n --accent-foreground: oklch(0.19 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.916 0 0);\n --input: oklch(0.916 0 0);\n --ring: oklch(0.708 0 0);\n --radius: 0.625rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n color-scheme: dark;\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n }\n}\n\n* {\n border-color: var(--border);\n}\n\nhtml {\n height: 100%;\n}\n\nbody {\n min-height: 100%;\n margin: 0;\n background: var(--background);\n font-family: var(--font-sans);\n}\n\nbutton,\ninput,\ntextarea {\n font: inherit;\n}\n';
|
|
6
6
|
readonly "app/layout.tsx": 'import type { Metadata } from "next";\nimport { Geist, Geist_Mono } from "next/font/google";\nimport type { ReactNode } from "react";\nimport { TooltipProvider } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport "./globals.css";\n\nconst sans = Geist({\n variable: "--font-sans",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nconst mono = Geist_Mono({\n variable: "--font-mono",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nexport const metadata: Metadata = {\n title: "__EVE_INIT_APP_NAME__",\n description: "A Next.js starter for eve agents with AI Elements.",\n};\n\nexport default function RootLayout({ children }: { readonly children: ReactNode }) {\n return (\n <html className={cn(sans.variable, mono.variable)} lang="en">\n <body>\n <TooltipProvider>{children}</TooltipProvider>\n </body>\n </html>\n );\n}\n';
|
|
@@ -16,8 +16,10 @@ export default eveChannel({
|
|
|
16
16
|
`,"app/_components/agent-chat.tsx":`"use client";
|
|
17
17
|
|
|
18
18
|
import type { UserContent } from "ai";
|
|
19
|
+
import { Client, type HandleMessageStreamEvent } from "eve/client";
|
|
19
20
|
import { useEveAgent } from "eve/react";
|
|
20
21
|
import { AlertCircleIcon } from "lucide-react";
|
|
22
|
+
import { useCallback, useRef, useState } from "react";
|
|
21
23
|
import {
|
|
22
24
|
Conversation,
|
|
23
25
|
ConversationContent,
|
|
@@ -35,16 +37,92 @@ import { AgentMessage } from "./agent-message";
|
|
|
35
37
|
const AGENT_NAME = "__EVE_INIT_APP_NAME__";
|
|
36
38
|
|
|
37
39
|
type AgentStatus = ReturnType<typeof useEveAgent>["status"];
|
|
40
|
+
type CancellationState = "idle" | "requested" | "cancelling";
|
|
41
|
+
|
|
42
|
+
type Cancellation = {
|
|
43
|
+
requested: boolean;
|
|
44
|
+
sentTurnId?: string;
|
|
45
|
+
turnId?: string;
|
|
46
|
+
};
|
|
38
47
|
|
|
39
48
|
export function AgentChat() {
|
|
40
|
-
const
|
|
49
|
+
const [session] = useState(() =>
|
|
50
|
+
new Client({ host: "", preserveCompletedSessions: true }).session(),
|
|
51
|
+
);
|
|
52
|
+
const cancellationRef = useRef<Cancellation>({ requested: false });
|
|
53
|
+
const [cancellationError, setCancellationError] = useState<string>();
|
|
54
|
+
const [cancellationState, setCancellationState] = useState<CancellationState>("idle");
|
|
55
|
+
|
|
56
|
+
const cancelTurn = useCallback(
|
|
57
|
+
(turnId: string) => {
|
|
58
|
+
const cancellation = cancellationRef.current;
|
|
59
|
+
if (!cancellation.requested || cancellation.sentTurnId === turnId) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cancellation.sentTurnId = turnId;
|
|
64
|
+
setCancellationState("cancelling");
|
|
65
|
+
|
|
66
|
+
void session.cancel({ turnId }).catch((error: unknown) => {
|
|
67
|
+
if (cancellationRef.current !== cancellation) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
cancellation.requested = false;
|
|
72
|
+
cancellation.sentTurnId = undefined;
|
|
73
|
+
setCancellationError(toErrorMessage(error));
|
|
74
|
+
setCancellationState("idle");
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
[session],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const handleEvent = useCallback(
|
|
81
|
+
(event: HandleMessageStreamEvent) => {
|
|
82
|
+
if (event.type !== "turn.started") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cancellation = cancellationRef.current;
|
|
87
|
+
cancellation.turnId = event.data.turnId;
|
|
88
|
+
cancelTurn(event.data.turnId);
|
|
89
|
+
},
|
|
90
|
+
[cancelTurn],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const agent = useEveAgent({ onEvent: handleEvent, session });
|
|
41
94
|
const isBusy = agent.status === "submitted" || agent.status === "streaming";
|
|
42
95
|
const isEmpty = agent.data.messages.length === 0;
|
|
96
|
+
const errorMessage = cancellationError ?? agent.error?.message;
|
|
97
|
+
const submitStatus = isBusy && cancellationState !== "idle" ? "submitted" : agent.status;
|
|
98
|
+
|
|
99
|
+
const prepareTurn = () => {
|
|
100
|
+
cancellationRef.current = { requested: false };
|
|
101
|
+
setCancellationError(undefined);
|
|
102
|
+
setCancellationState("idle");
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const requestCancellation = () => {
|
|
106
|
+
if (!isBusy || cancellationState !== "idle") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const cancellation = cancellationRef.current;
|
|
111
|
+
cancellation.requested = true;
|
|
112
|
+
setCancellationError(undefined);
|
|
113
|
+
setCancellationState("requested");
|
|
114
|
+
|
|
115
|
+
if (cancellation.turnId !== undefined) {
|
|
116
|
+
cancelTurn(cancellation.turnId);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
43
119
|
|
|
44
120
|
const handleSubmit = async (message: PromptInputMessage) => {
|
|
45
121
|
const text = message.text.trim();
|
|
46
122
|
if ((text.length === 0 && message.files.length === 0) || isBusy) return;
|
|
47
123
|
|
|
124
|
+
prepareTurn();
|
|
125
|
+
|
|
48
126
|
if (message.files.length === 0) {
|
|
49
127
|
await agent.send({ message: text });
|
|
50
128
|
return;
|
|
@@ -69,7 +147,7 @@ export function AgentChat() {
|
|
|
69
147
|
const composer = (
|
|
70
148
|
<PromptInput onSubmit={handleSubmit}>
|
|
71
149
|
<PromptInputTextarea placeholder="Send a message…" />
|
|
72
|
-
<PromptInputSubmit onStop={
|
|
150
|
+
<PromptInputSubmit onStop={requestCancellation} status={submitStatus} />
|
|
73
151
|
</PromptInput>
|
|
74
152
|
);
|
|
75
153
|
|
|
@@ -84,13 +162,13 @@ export function AgentChat() {
|
|
|
84
162
|
</header>
|
|
85
163
|
)}
|
|
86
164
|
|
|
87
|
-
{
|
|
165
|
+
{errorMessage ? (
|
|
88
166
|
<div className="mx-auto w-full max-w-3xl shrink-0 px-4 pt-2 sm:px-6">
|
|
89
167
|
<div className="flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm">
|
|
90
168
|
<AlertCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />
|
|
91
169
|
<div>
|
|
92
170
|
<p className="font-medium">Request failed</p>
|
|
93
|
-
<p className="mt-0.5 text-muted-foreground">{
|
|
171
|
+
<p className="mt-0.5 text-muted-foreground">{errorMessage}</p>
|
|
94
172
|
</div>
|
|
95
173
|
</div>
|
|
96
174
|
</div>
|
|
@@ -107,7 +185,10 @@ export function AgentChat() {
|
|
|
107
185
|
}
|
|
108
186
|
key={message.id}
|
|
109
187
|
message={message}
|
|
110
|
-
onInputResponses={(inputResponses) =>
|
|
188
|
+
onInputResponses={(inputResponses) => {
|
|
189
|
+
prepareTurn();
|
|
190
|
+
return agent.send({ inputResponses });
|
|
191
|
+
}}
|
|
111
192
|
/>
|
|
112
193
|
))}
|
|
113
194
|
</ConversationContent>
|
|
@@ -134,6 +215,10 @@ export function AgentChat() {
|
|
|
134
215
|
);
|
|
135
216
|
}
|
|
136
217
|
|
|
218
|
+
function toErrorMessage(error: unknown): string {
|
|
219
|
+
return error instanceof Error ? error.message : "Unable to cancel the response.";
|
|
220
|
+
}
|
|
221
|
+
|
|
137
222
|
function StatusDot({ status }: { readonly status: AgentStatus }) {
|
|
138
223
|
const isLive = status === "submitted" || status === "streaming";
|
|
139
224
|
const tone =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"../create/web-template.js";import{DEFAULT_EVE_PACKAGE_CONTRACT,resolveEvePackageContract}from"../create/project.js";import{basename,join,resolve}from"node:path";import{readFile,readdir}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/eve/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,NEXT_PACKAGE_NAME=`next`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH);function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readPackageJsonObject(e){if(!await pathExists(e))return;let t=JSON.parse(await readFile(e,`utf8`));return isJsonObject(t)?t:void 0}async function readDependencyVersion(e,t){let n=await readPackageJsonObject(e);if(n===void 0||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){let n=await readPackageJsonObject(e);return n!==void 0&&packageJsonHasDependency(n,t)}async function isNextJsProject(e){return hasPackageDependency(join(e,`package.json`),NEXT_PACKAGE_NAME)}const VERCEL_HOST_FRAMEWORK_PRESETS={"@sveltejs/kit":`sveltekit`,[NEXT_PACKAGE_NAME]:`nextjs`,nuxt:`nuxtjs`,nuxt3:`nuxtjs`,"nuxt-edge":`nuxtjs`,"nuxt-nightly":`nuxtjs`};async function resolveVercelHostFrameworkPreset(e){let t=await readPackageJsonObject(join(e,`package.json`));if(t!==void 0){for(let[e,n]of Object.entries(VERCEL_HOST_FRAMEWORK_PRESETS))if(packageJsonHasDependency(t,e))return n}}async function hasVercelHostFramework(e){return await resolveVercelHostFrameworkPreset(e)!==void 0}async function ensurePackageDependency(e,t,n){return!await pathExists(e)||await readDependencyVersion(e,t)===n?[]:(await patchPackageJson(e,{dependencies:{[t]:n}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{evePackage:e?.evePackage??DEFAULT_EVE_PACKAGE_CONTRACT,aiPackageVersion:e?.aiPackageVersion??`^7.0.
|
|
1
|
+
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"../create/web-template.js";import{DEFAULT_EVE_PACKAGE_CONTRACT,resolveEvePackageContract}from"../create/project.js";import{basename,join,resolve}from"node:path";import{readFile,readdir}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/eve/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,NEXT_PACKAGE_NAME=`next`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`],WEB_NEXT_CONFIG_PATH=`next.config.ts`,WEB_COMPETING_NEXT_CONFIG_PATHS=[`next.config.js`,`next.config.mjs`,WEB_NEXT_CONFIG_PATH,`next.config.mts`].filter(e=>e!==WEB_NEXT_CONFIG_PATH);function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readPackageJsonObject(e){if(!await pathExists(e))return;let t=JSON.parse(await readFile(e,`utf8`));return isJsonObject(t)?t:void 0}async function readDependencyVersion(e,t){let n=await readPackageJsonObject(e);if(n===void 0||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){let n=await readPackageJsonObject(e);return n!==void 0&&packageJsonHasDependency(n,t)}async function isNextJsProject(e){return hasPackageDependency(join(e,`package.json`),NEXT_PACKAGE_NAME)}const VERCEL_HOST_FRAMEWORK_PRESETS={"@sveltejs/kit":`sveltekit`,[NEXT_PACKAGE_NAME]:`nextjs`,nuxt:`nuxtjs`,nuxt3:`nuxtjs`,"nuxt-edge":`nuxtjs`,"nuxt-nightly":`nuxtjs`};async function resolveVercelHostFrameworkPreset(e){let t=await readPackageJsonObject(join(e,`package.json`));if(t!==void 0){for(let[e,n]of Object.entries(VERCEL_HOST_FRAMEWORK_PRESETS))if(packageJsonHasDependency(t,e))return n}}async function hasVercelHostFramework(e){return await resolveVercelHostFrameworkPreset(e)!==void 0}async function ensurePackageDependency(e,t,n){return!await pathExists(e)||await readDependencyVersion(e,t)===n?[]:(await patchPackageJson(e,{dependencies:{[t]:n}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{evePackage:e?.evePackage??DEFAULT_EVE_PACKAGE_CONTRACT,aiPackageVersion:e?.aiPackageVersion??`^7.0.34`,nextPackageVersion:e?.nextPackageVersion??`16.3.0-preview.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(t,n,i,s,u){let f=join(t,`package.json`);if(!await pathExists(f))return{mutations:[]};let m=resolveEvePackageContract(s.evePackage),h=pinnedNodeEngineMajor(m.nodeEngine),g={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:resolveVersionToken(`aiPackageVersion`,s.aiPackageVersion),eve:formatEveDependencySpecifier(m.version),next:resolveVersionToken(`nextPackageVersion`,s.nextPackageVersion),react:resolveVersionToken(`reactPackageVersion`,s.reactPackageVersion),"react-dom":resolveVersionToken(`reactDomPackageVersion`,s.reactDomPackageVersion),streamdown:resolveVersionToken(`streamdownPackageVersion`,s.streamdownPackageVersion),zod:resolveVersionToken(`zodPackageVersion`,s.zodPackageVersion)},_={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":h,"@types/react":resolveVersionToken(`typesReactPackageVersion`,s.typesReactPackageVersion),"@types/react-dom":resolveVersionToken(`typesReactDomPackageVersion`,s.typesReactDomPackageVersion),typescript:`6.0.3`},v=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts,y=isPackageManagerWorkspaceMember(n,i),b={dependencies:g,devDependencies:_,scripts:v};y||(b.nodeEngineRequirement=m.nodeEngine);let x=await patchPackageJson(f,b),S=(await patchWorkspaceRootPackageJson(n,i,{aiPackageVersion:g.ai,nodeEngineRequirement:m.nodeEngine,onWorkspaceRootMutation:u})).nodeEngineOverride??x.nodeEngineOverride;return{mutations:[{path:f,dependencies:Object.keys(g),devDependencies:Object.keys(_),scripts:Object.keys(v)}],nodeEngineOverride:S}}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){if(!e.startsWith(`slack/`)||e.length===6)throw Error(`Invalid Slack connector UID "${e}".`);return`import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
2
2
|
import { slackChannel } from "eve/channels/slack";
|
|
3
3
|
|
|
4
4
|
export default slackChannel({
|
|
5
5
|
credentials: connectSlackCredentials(${JSON.stringify(e)}),
|
|
6
6
|
});
|
|
7
|
-
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t).replaceAll(`__EVE_INIT_WITH_EVE_OPTIONS__`,``)}async function ensureWebVercelJson(e){return await pathExists(e)?`skipped`:(await writeTextFile(e,`${JSON.stringify({$schema:`https://openapi.vercel.sh/vercel.json`},null,2)}\n`,{force:!0}),`written`)}async function findCompetingNextConfigFiles(e){let t=[];for(let n of WEB_COMPETING_NEXT_CONFIG_PATHS){let i=join(e,n);await pathExists(i)&&t.push(i)}return t}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),n=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await isNextJsProject(e.projectRoot))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=resolveWebPackageVersions(e.webPackageVersions),o=e.packageManager??`pnpm`,c=resolve(e.workspaceProbeDirectory??e.projectRoot),l=await patchWebPackageJson(e.projectRoot,o,c,a,e.onWorkspaceRootMutation),d=[],f=[],p=[],g=[],_=basename(resolve(e.projectRoot));if(e.configureVercelServices??!0){let t=join(e.projectRoot,`vercel.json`);await ensureWebVercelJson(t)===`written`?d.push(t):g.push(t)}let v=await applyPackageManagerWorkspaceConfiguration({packageManager:o,projectRoot:e.projectRoot,workspaceProbeRoot:c,onWorkspaceRootMutation:e.onWorkspaceRootMutation});d.push(...v.filesWritten),g.push(...v.filesSkipped);for(let[t,n]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/eve.ts`&&!e.force&&await pathExists(a)){g.push(a);continue}let o=await pathExists(a);await writeTextFile(a,renderWebAppTemplate(n,_),{force:!0}),d.push(a),o&&f.push(a)}p.push(...await findCompetingNextConfigFiles(e.projectRoot));let y=[...new Set(d)],b=[...new Set(g)].filter(e=>!y.includes(e)),x={kind:`web`,action:n?`overwritten`:`created`,filesWritten:y,filesSkipped:b,packageJsonUpdated:l.mutations};return f.length>0&&(x.filesOverwritten=f),p.length>0&&(x.competingNextConfigFiles=p),l.nodeEngineOverride!==void 0&&(x.nodeEngineOverride=l.nodeEngineOverride),x}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),n=await pathExists(t);if(!e.force&&n)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let o=resolveVersionToken(`connectPackageVersion`,e.connectPackageVersion??`0.4.
|
|
7
|
+
`}function renderWebAppTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t).replaceAll(`__EVE_INIT_WITH_EVE_OPTIONS__`,``)}async function ensureWebVercelJson(e){return await pathExists(e)?`skipped`:(await writeTextFile(e,`${JSON.stringify({$schema:`https://openapi.vercel.sh/vercel.json`},null,2)}\n`,{force:!0}),`written`)}async function findCompetingNextConfigFiles(e){let t=[];for(let n of WEB_COMPETING_NEXT_CONFIG_PATHS){let i=join(e,n);await pathExists(i)&&t.push(i)}return t}async function ensureChannel(e){switch(e.kind){case`slack`:return ensureSlackChannel({...e,kind:`slack`});case`web`:return ensureWebChannel({...e,kind:`web`})}}async function ensureWebChannel(e){let t=join(e.projectRoot,`package.json`),n=await pathExists(join(e.projectRoot,`app/page.tsx`));if(!e.force&&await isNextJsProject(e.projectRoot))return{kind:`web`,action:`skipped`,skipReason:`nextjs-project`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let a=resolveWebPackageVersions(e.webPackageVersions),o=e.packageManager??`pnpm`,c=resolve(e.workspaceProbeDirectory??e.projectRoot),l=await patchWebPackageJson(e.projectRoot,o,c,a,e.onWorkspaceRootMutation),d=[],f=[],p=[],g=[],_=basename(resolve(e.projectRoot));if(e.configureVercelServices??!0){let t=join(e.projectRoot,`vercel.json`);await ensureWebVercelJson(t)===`written`?d.push(t):g.push(t)}let v=await applyPackageManagerWorkspaceConfiguration({packageManager:o,projectRoot:e.projectRoot,workspaceProbeRoot:c,onWorkspaceRootMutation:e.onWorkspaceRootMutation});d.push(...v.filesWritten),g.push(...v.filesSkipped);for(let[t,n]of Object.entries(WEB_APP_TEMPLATE_FILES)){let a=join(e.projectRoot,t);if(t===`agent/channels/eve.ts`&&!e.force&&await pathExists(a)){g.push(a);continue}let o=await pathExists(a);await writeTextFile(a,renderWebAppTemplate(n,_),{force:!0}),d.push(a),o&&f.push(a)}p.push(...await findCompetingNextConfigFiles(e.projectRoot));let y=[...new Set(d)],b=[...new Set(g)].filter(e=>!y.includes(e)),x={kind:`web`,action:n?`overwritten`:`created`,filesWritten:y,filesSkipped:b,packageJsonUpdated:l.mutations};return f.length>0&&(x.filesOverwritten=f),p.length>0&&(x.competingNextConfigFiles=p),l.nodeEngineOverride!==void 0&&(x.nodeEngineOverride=l.nodeEngineOverride),x}async function ensureSlackChannel(e){let t=join(e.projectRoot,`agent/channels/slack.ts`),n=await pathExists(t);if(!e.force&&n)return{kind:`slack`,action:`skipped`,filesWritten:[],filesSkipped:[t],packageJsonUpdated:[]};let o=resolveVersionToken(`connectPackageVersion`,e.connectPackageVersion??`0.4.2`),s=await ensurePackageDependency(join(e.projectRoot,`package.json`),`@vercel/connect`,o),c=e.slackConnectorSlug??await deriveSlackConnectorSlug(e.projectRoot);await writeTextFile(t,buildSlackTemplate(e.slackConnectorUid??`slack/${c}`),{force:e.force});let l={kind:`slack`,action:n?`overwritten`:`created`,filesWritten:[t],filesSkipped:[],packageJsonUpdated:s,slackConnectorSlug:c};return n&&(l.filesOverwritten=[t]),l}async function listAuthoredChannels(e){let r=join(e,`agent/channels`),i;try{i=await readdir(r,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}let a=[];for(let e of i){if(e.isFile()){let n=getSupportedModuleBaseName(e.name);n!==null&&a.push(n);continue}if(e.isDirectory())try{(await readdir(join(r,e.name))).some(e=>matchesSupportedModuleBaseName(e,`connection`))&&a.push(e.name)}catch{}}return a.sort()}export{DEFAULT_SLACK_CONNECTOR_SLUG,SLACK_CHANNEL_DEFAULT_ROUTE,deriveSlackConnectorSlug,ensureChannel,hasVercelHostFramework,isNextJsProject,listAuthoredChannels,normalizeSlackConnectorSlug,resolveVercelHostFrameworkPreset};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{endpointForProtocol}from"../connections/catalog.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.
|
|
1
|
+
import{endpointForProtocol}from"../connections/catalog.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{patchPackageJson}from"./package-json.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const DEFAULT_CONNECT_PACKAGE_VERSION=`0.4.2`,CONNECT_PACKAGE_NAME=`@vercel/connect`,USER_AUTHORED_CONNECTION_DIR=`agent/connections`;function resolveAuth(e){return e.auth??{kind:`none`}}function envKeysForAuth(e){switch(e.kind){case`bearer-env`:return[e.envVar];case`header`:return e.headers.map(e=>e.envVar);case`connect`:case`none`:return[]}}function authBlock(e){switch(e.kind){case`connect`:return` auth: connect("${e.connector}"),\n`;case`bearer-env`:return` auth: { getToken: async () => ({ token: process.env.${e.envVar}! }) },\n`;case`header`:return` headers: () => ({\n${e.headers.map(e=>` "${e.header}": process.env.${e.envVar}!,`).join(`
|
|
2
2
|
`)}\n }),\n`;case`none`:return``}}function renderMcpTemplate(e,t,n){return`${n.kind===`connect`?`import { connect } from "@vercel/connect/eve";
|
|
3
3
|
import { defineMcpClientConnection } from "eve/connections";
|
|
4
4
|
`:`import { defineMcpClientConnection } from "eve/connections";
|
package/docs/agent-config.md
CHANGED
|
@@ -139,11 +139,16 @@ Input and output budgets are checked independently. The model call that crosses
|
|
|
139
139
|
either limit is allowed to finish because providers only report exact token
|
|
140
140
|
usage after a call completes. Before the next model call, eve pauses the
|
|
141
141
|
session and sends a deterministic continuation prompt with two options:
|
|
142
|
-
**
|
|
143
|
-
and output windows reset together), and **Stop**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
**Approve** grants a fresh budget window of the configured size (both input
|
|
143
|
+
and output windows reset together), and **Stop** cancels the in-flight turn
|
|
144
|
+
through the standard cancellation path (`turn.cancelled` → `session.waiting`)
|
|
145
|
+
— a user decision, not an error. The session stays resumable; because it is
|
|
146
|
+
still over budget, the next message re-raises the prompt. Declining a
|
|
147
|
+
delegated child's prompt cancels the root turn, which cascades to the whole
|
|
148
|
+
delegation tree — the delegating parent never receives an error result it
|
|
149
|
+
could retry against a fresh quota share. A reply that answers neither option
|
|
150
|
+
re-raises the prompt; the reply stays in history and is processed once the
|
|
151
|
+
budget is granted.
|
|
147
152
|
|
|
148
153
|
Sessions that cannot reach a human — task-mode runs such as schedules and
|
|
149
154
|
subagents without input proxying — skip the prompt and fail the next model
|
package/docs/channels/slack.mdx
CHANGED
|
@@ -36,7 +36,11 @@ export default slackChannel({
|
|
|
36
36
|
});
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
`connectSlackCredentials` returns `{ botToken, webhookVerifier }`, keeping token rotation, multi-workspace tenancy, and request verification inside Connect rather than your code.
|
|
39
|
+
`connectSlackCredentials` returns `{ botToken, webhookVerifier }`, keeping token rotation, multi-workspace tenancy, and request verification inside Connect rather than your code.
|
|
40
|
+
|
|
41
|
+
### Deploy
|
|
42
|
+
|
|
43
|
+
Deploy once the trigger destination and channel file are ready:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
46
|
VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1 vercel deploy --prod
|
|
@@ -44,17 +48,79 @@ VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1 vercel deploy --prod
|
|
|
44
48
|
|
|
45
49
|
`VERCEL_USE_EXPERIMENTAL_FRAMEWORKS=1` lets the Vercel CLI recognize eve as a framework during the build. eve's own setup commands set the same flag.
|
|
46
50
|
|
|
47
|
-
## How the channel handles
|
|
51
|
+
## How the channel handles inbound events
|
|
52
|
+
|
|
53
|
+
### Message hooks
|
|
54
|
+
|
|
55
|
+
Message hooks return `{ auth }` to dispatch, `null` to drop, or `{ auth, context }` to inject background into history.
|
|
56
|
+
|
|
57
|
+
- `onMessage(ctx, message)` handles Slack `message` events. `ctx.isBotMentioned()` and `ctx.isSubscribed()` support mention and active-thread policies; check `message.author?.isBot` when bot messages should be ignored.
|
|
58
|
+
- `onAppMention(ctx, message)` handles only `app_mention` and takes precedence over `onMessage`. Its default derives workspace-scoped auth and posts `Thinking…`.
|
|
59
|
+
- `onDirectMessage(ctx, message)` handles only DMs and takes precedence over `onMessage`. Bot-authored messages and edits are filtered first; Slack requires `message.im` and `im:history`.
|
|
60
|
+
|
|
61
|
+
| Incoming event | Handler order |
|
|
62
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
63
|
+
| App mention | `onAppMention` → `onMessage` → `onEvent` → built-in mention default |
|
|
64
|
+
| Direct message | `onDirectMessage` → `onMessage` → `onEvent` → built-in DM default |
|
|
65
|
+
| Other Slack message | `onMessage` → `onEvent` → ignore |
|
|
66
|
+
| Other Events API event | `onEvent` → ignore |
|
|
67
|
+
|
|
68
|
+
Only the first available handler runs. A message hook returning `null` drops the message; it does not continue down the table.
|
|
69
|
+
|
|
70
|
+
`onInteraction(action, ctx)` separately handles `block_actions` callbacks not consumed by HITL. eve attaches the triggering Slack user id to the same model message as its text, preserving speaker attribution without profile lookups.
|
|
71
|
+
|
|
72
|
+
#### Continue conversations without repeated mentions
|
|
73
|
+
|
|
74
|
+
Use `onMessage` to handle explicit mentions and continue replies in threads with an active eve session:
|
|
75
|
+
|
|
76
|
+
```ts title="agent/channels/slack.ts"
|
|
77
|
+
export default slackChannel({
|
|
78
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
79
|
+
async onMessage(ctx, message) {
|
|
80
|
+
if (message.author?.isBot) return null;
|
|
81
|
+
const isDirectMessage = message.raw.channel_type === "im";
|
|
82
|
+
return isDirectMessage || ctx.isBotMentioned() || (await ctx.isSubscribed())
|
|
83
|
+
? { auth: null }
|
|
84
|
+
: null;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`isBotMentioned()` identifies an explicit mention. `isSubscribed()` checks whether the message belongs to a thread with an active eve session. For Vercel Connect, open **Advanced** when creating the connector and add `message.channels` under **Trigger Event Types** and `channels:history` under **Bot Scopes**. Private channels additionally need `message.groups` and `groups:history`.
|
|
90
|
+
|
|
91
|
+
### Other Events API callbacks
|
|
48
92
|
|
|
49
|
-
|
|
93
|
+
Use `onEvent` for subscribed events such as `reaction_added`, `team_join`, or `channel_created`. It receives the raw, open-ended Slack event and owns control flow. `ctx.receive(options)` is the current Slack channel's pre-bound form of the schedule API's `receive(slack, options)`: call it zero, one, or many times to start turns. Each call accepts `message`, `target`, and `auth`, and returns the resulting session.
|
|
94
|
+
|
|
95
|
+
```ts title="agent/channels/slack.ts"
|
|
96
|
+
import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
97
|
+
import { slackChannel } from "eve/channels/slack";
|
|
98
|
+
|
|
99
|
+
const onboardingChannels = ["C0123ABC", "C0456DEF"];
|
|
100
|
+
|
|
101
|
+
export default slackChannel({
|
|
102
|
+
credentials: connectSlackCredentials("slack/my-agent"),
|
|
103
|
+
async onEvent({ receive }, event) {
|
|
104
|
+
if (event.type !== "team_join") return;
|
|
105
|
+
|
|
106
|
+
await Promise.all(
|
|
107
|
+
onboardingChannels.map((channelId) =>
|
|
108
|
+
receive({
|
|
109
|
+
message: `A user joined the Slack workspace. Onboard them from this event:\n${JSON.stringify(event)}`,
|
|
110
|
+
target: { channelId },
|
|
111
|
+
auth: null,
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
50
118
|
|
|
51
|
-
|
|
119
|
+
The webhook invocation already keeps an awaited `onEvent` handler alive. Use `ctx.waitUntil(promise)` for deliberately detached work, matching a handler-form schedule. `ctx.slack.request(operation, body)` provides workspace-scoped Slack Web API access, while `ctx.envelope` carries delivery metadata such as `team_id`, `event_id`, and `event_time`. Calls to `ctx.receive` automatically seed the callback's team id into Slack session state.
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
- `onDirectMessage(ctx, message)` handles `message.im` events (needs `im:history` scope). Bot-authored messages and edits are filtered out first.
|
|
55
|
-
- `onInteraction(action, ctx)` handles `block_actions` callbacks not consumed by HITL.
|
|
121
|
+
`onEvent` is the raw fallback after the message hooks. If an event is not claimed by `onAppMention`, `onDirectMessage`, or `onMessage`, an authored `onEvent` receives it; otherwise eve applies the built-in mention/DM default or ignores it.
|
|
56
122
|
|
|
57
|
-
|
|
123
|
+
`onEvent` covers JSON `event_callback` deliveries only. URL verification, slash commands, and interactive payloads do not reach it. Add every desired event and required OAuth scope to the Slack app's Event Subscriptions configuration; eve can only handle events Slack sends.
|
|
58
124
|
|
|
59
125
|
You get the triggering mention by default, but not the earlier replies in the thread. Enable `threadContext` to fetch and inject them with every message attributed by stable Slack user id. Use `since: "last-agent-reply"` so repeated mentions inject only what is new:
|
|
60
126
|
|
|
@@ -78,7 +144,7 @@ export default slackChannel({
|
|
|
78
144
|
|
|
79
145
|
### Slack API calls outside a handler
|
|
80
146
|
|
|
81
|
-
Inside webhook-side handlers (`onAppMention`, `onInteraction`, `events`),
|
|
147
|
+
Inside webhook-side handlers (`onAppMention`, `onEvent`, `onInteraction`, `events`),
|
|
82
148
|
`ctx.slack.request(operation, body)` is the raw-API escape hatch. Outside
|
|
83
149
|
those contexts there is no handle — a schedule resolving reactions on old
|
|
84
150
|
messages, for example, has no inbound Slack request. For that, call the
|
|
@@ -105,7 +171,7 @@ when you need the bearer token itself.
|
|
|
105
171
|
|
|
106
172
|
### Delivery
|
|
107
173
|
|
|
108
|
-
The default handlers reply in-thread and show progress. Typing indicators post automatically: `Thinking…` on inbound, `Working…` on `turn.started`, a truncated reasoning snippet on `reasoning.appended`, and an action label on `actions.requested` — the tool name plus its most telling argument (`grep useEveAgent`, `read_file agent/agent.ts`), the subagent or remote-agent name for dispatched calls, and `+N more` when the model requests several actions at once. The model's own pre-tool narration, when present, takes precedence over the derived label. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use the five-second refresh interval to avoid one Slack request per token. Override `events["reasoning.appended"]` if you prefer generic wording. Override
|
|
174
|
+
The default handlers reply in-thread and show progress. Typing indicators post automatically: `Thinking…` on inbound, `Working…` on `turn.started`, a truncated reasoning snippet on `reasoning.appended`, and an action label on `actions.requested` — the tool name plus its most telling argument (`grep useEveAgent`, `read_file agent/agent.ts`), the subagent or remote-agent name for dispatched calls, and `+N more` when the model requests several actions at once. The model's own pre-tool narration, when present, takes precedence over the derived label. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use the five-second refresh interval to avoid one Slack request per token. Override `events["reasoning.appended"]` if you prefer generic wording. Override an inbound handler or the `events` handlers to customize.
|
|
109
175
|
|
|
110
176
|
Outbound text preserves bare `@` tokens as literal text. To mention a user, embed Slack's `<@USER_ID>` syntax directly or use `channel.thread.mentionUser(userId)`.
|
|
111
177
|
|
package/docs/channels/teams.mdx
CHANGED
|
@@ -28,17 +28,24 @@ By default the channel mounts at `POST /eve/v1/teams`. Point your Azure Bot or T
|
|
|
28
28
|
|
|
29
29
|
The default `onMessage` handles two cases: personal-chat messages, and channel or group-chat messages that mention the bot directly. Ambient resource-specific-consent messages are dropped unless you override it. Before dispatch, eve strips the mention, adds a `<teams_context>` block, and scopes channel and group threads by root activity id (`replyToId ?? id`).
|
|
30
30
|
|
|
31
|
+
For example, use the contextual `isSubscribed()` helper to continue active threads without requiring another mention:
|
|
32
|
+
|
|
31
33
|
```ts
|
|
32
34
|
import { defaultTeamsAuth, teamsChannel } from "eve/channels/teams";
|
|
33
35
|
|
|
34
36
|
export default teamsChannel({
|
|
35
|
-
onMessage(ctx, message) {
|
|
36
|
-
if (message.
|
|
37
|
-
|
|
37
|
+
async onMessage(ctx, message) {
|
|
38
|
+
if (message.from.role === "bot" || message.from.id === message.recipient.id) return null;
|
|
39
|
+
const isDirectMessage = message.scope === "personal";
|
|
40
|
+
return isDirectMessage || message.isBotMentioned || (await ctx.isSubscribed())
|
|
41
|
+
? { auth: defaultTeamsAuth(message) }
|
|
42
|
+
: null;
|
|
38
43
|
},
|
|
39
44
|
});
|
|
40
45
|
```
|
|
41
46
|
|
|
47
|
+
`isSubscribed()` checks whether the conversation has previously involved the agent.
|
|
48
|
+
|
|
42
49
|
### Delivery
|
|
43
50
|
|
|
44
51
|
Replies post as Markdown (`textFormat: "markdown"`), with oversized text split across messages and a typing indicator sent on turn start and action requests.
|
|
@@ -60,7 +60,7 @@ eve deploy
|
|
|
60
60
|
Vercel uses the generated output to configure these services:
|
|
61
61
|
|
|
62
62
|
- **Web runtime**: serves health, session, stream, channel, callback, and schedule routes
|
|
63
|
-
- **Vercel Workflow**: persists and resumes durable runs
|
|
63
|
+
- **Vercel Workflow**: persists and resumes durable runs, with optimistic replay preconditions enabled so stale event-log snapshots reload before they can commit
|
|
64
64
|
- **Vercel Cron**: invokes authored schedules
|
|
65
65
|
- **Vercel Sandbox**: runs sandbox sessions selected by `defaultBackend()`
|
|
66
66
|
|