eve 0.69.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/src/acp/adapter.js +1 -1
- package/dist/src/channel/task-card.d.ts +3 -3
- package/dist/src/channel/task-card.js +1 -1
- package/dist/src/channel/types.d.ts +18 -0
- package/dist/src/chunks/{use-eve-agent-Dab2MnT2.js → use-eve-agent-CJQeE7-t.js} +1873 -1236
- package/dist/src/chunks/{use-eve-agent-D_5129A9.js → use-eve-agent-DdemtY6C.js} +1874 -1234
- package/dist/src/cli/dev/command.js +1 -1
- package/dist/src/cli/dev/console-records-preload.d.ts +1 -0
- package/dist/src/cli/dev/console-records-preload.js +1 -0
- package/dist/src/cli/dev/console-records.d.ts +17 -0
- package/dist/src/cli/dev/console-records.js +3 -0
- package/dist/src/cli/dev/diagnostic-sink.d.ts +1 -0
- package/dist/src/cli/dev/local-server-process.js +1 -1
- package/dist/src/cli/dev/tui/blocks.d.ts +2 -1
- package/dist/src/cli/dev/tui/blocks.js +3 -3
- package/dist/src/cli/dev/tui/conversation-view.d.ts +54 -0
- package/dist/src/cli/dev/tui/conversation-view.js +1 -0
- package/dist/src/cli/dev/tui/flow-drawer.d.ts +5 -2
- package/dist/src/cli/dev/tui/flow-drawer.js +1 -1
- package/dist/src/cli/dev/tui/log-display-mode.d.ts +4 -10
- package/dist/src/cli/dev/tui/log-display-mode.js +1 -1
- package/dist/src/cli/dev/tui/message-queue.d.ts +7 -63
- package/dist/src/cli/dev/tui/message-queue.js +3 -1
- package/dist/src/cli/dev/tui/prompt-commands.js +1 -1
- package/dist/src/cli/dev/tui/question-panel.d.ts +3 -1
- package/dist/src/cli/dev/tui/question-panel.js +1 -1
- package/dist/src/cli/dev/tui/runner.d.ts +56 -193
- package/dist/src/cli/dev/tui/runner.js +3 -3
- package/dist/src/cli/dev/tui/task-activity.d.ts +11 -7
- package/dist/src/cli/dev/tui/task-activity.js +1 -1
- package/dist/src/cli/dev/tui/terminal-renderer.d.ts +15 -37
- package/dist/src/cli/dev/tui/terminal-renderer.js +10 -13
- package/dist/src/cli/dev/tui/terminal-tool-part.d.ts +2 -0
- package/dist/src/cli/dev/tui/terminal-tool-part.js +1 -0
- package/dist/src/cli/dev/tui/test/fake-eve-server.d.ts +34 -0
- package/dist/src/cli/dev/tui/test/fake-eve-server.js +1 -0
- package/dist/src/cli/dev/tui/test/index.d.ts +1 -0
- package/dist/src/cli/dev/tui/test/index.js +1 -1
- package/dist/src/cli/dev/tui/tool-block-groups.js +1 -1
- package/dist/src/cli/dev/tui/tool-presentation.d.ts +1 -0
- package/dist/src/cli/dev/tui/tool-presentation.js +1 -1
- package/dist/src/cli/dev/tui/tool-rows.d.ts +1 -0
- package/dist/src/cli/dev/tui/tool-rows.js +2 -2
- package/dist/src/cli/dev/tui/transcript-parts.d.ts +73 -0
- package/dist/src/cli/dev/tui/transcript-parts.js +3 -0
- package/dist/src/cli/dev/tui/transcript.d.ts +47 -0
- package/dist/src/cli/dev/tui/transcript.js +1 -0
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/cli/dev/tui/turn-clock.d.ts +11 -26
- package/dist/src/cli/dev/tui/turn-clock.js +1 -1
- package/dist/src/cli/dev/tui/types.d.ts +2 -4
- package/dist/src/cli/dev/ui-options.js +1 -1
- package/dist/src/client/agent-stream-follower.d.ts +26 -0
- package/dist/src/client/agent-stream-follower.js +1 -0
- package/dist/src/client/authorization-message-parts.js +1 -1
- package/dist/src/client/conversation-client.d.ts +40 -0
- package/dist/src/client/conversation-client.js +1 -0
- package/dist/src/client/conversation-reducer.d.ts +29 -0
- package/dist/src/client/conversation-reducer.js +1 -0
- package/dist/src/client/conversation-state.d.ts +110 -0
- package/dist/src/client/conversation-state.js +1 -0
- package/dist/src/client/eve-agent-projection.d.ts +24 -7
- package/dist/src/client/eve-agent-projection.js +1 -1
- package/dist/src/client/eve-agent-store-helpers.d.ts +21 -2
- package/dist/src/client/eve-agent-store-helpers.js +2 -2
- package/dist/src/client/eve-agent-store-state.d.ts +15 -3
- package/dist/src/client/eve-agent-store.d.ts +10 -1
- package/dist/src/client/eve-agent-store.js +1 -1
- package/dist/src/client/index.d.ts +4 -1
- package/dist/src/client/index.js +1 -1
- package/dist/src/client/message-reducer-primitives.d.ts +1 -2
- package/dist/src/client/message-reducer-primitives.js +1 -1
- package/dist/src/client/message-reducer-types.d.ts +9 -2
- package/dist/src/client/message-reducer.js +1 -1
- package/dist/src/client/message-response.js +1 -1
- package/dist/src/client/message-run-parts.d.ts +10 -6
- package/dist/src/client/message-run-parts.js +1 -1
- package/dist/src/client/optimistic-message-submissions.d.ts +6 -4
- package/dist/src/client/optimistic-message-submissions.js +1 -1
- package/dist/src/client/reducer.d.ts +5 -1
- package/dist/src/client/session-turn-dispatch.d.ts +15 -0
- package/dist/src/client/session-turn-dispatch.js +1 -0
- package/dist/src/client/session-utils.d.ts +27 -10
- package/dist/src/client/session-utils.js +1 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/types.d.ts +2 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +18 -18
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/compiler/load-binding-namespace.js +1 -1
- package/dist/src/context/keys.d.ts +5 -2
- package/dist/src/context/keys.js +1 -1
- package/dist/src/context/provider.d.ts +5 -9
- package/dist/src/context/run-step.d.ts +4 -4
- package/dist/src/context/run-step.js +1 -1
- package/dist/src/evals/runner/derive-run-facts.js +1 -1
- package/dist/src/evals/runner/execute-task.js +1 -1
- package/dist/src/evals/session.js +2 -2
- package/dist/src/evals/types.d.ts +16 -0
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/execution/agent-sessions/context.d.ts +4 -2
- package/dist/src/execution/agent-sessions/context.js +1 -1
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/cancelled-model-call-batch.js +1 -1
- package/dist/src/execution/coordination-dispatch-shared.d.ts +6 -4
- package/dist/src/execution/coordination-dispatch-shared.js +1 -1
- package/dist/src/execution/create-session-step.d.ts +4 -2
- package/dist/src/execution/create-session-step.js +1 -1
- package/dist/src/execution/durable-session-store.d.ts +10 -4
- package/dist/src/execution/durable-session-store.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/legacy-session/completion-step.d.ts +2 -0
- package/dist/src/execution/legacy-session/completion-step.js +1 -1
- package/dist/src/execution/legacy-session/interrupt-step.js +1 -1
- package/dist/src/execution/legacy-session/prepare-step.d.ts +1 -0
- package/dist/src/execution/legacy-session/prepare-step.js +1 -1
- package/dist/src/execution/legacy-session/snapshot.d.ts +8 -4
- package/dist/src/execution/legacy-session/snapshot.js +1 -1
- package/dist/src/execution/legacy-session/turn-workflow.js +1 -1
- package/dist/src/execution/proxied-deliver-step.js +1 -1
- package/dist/src/execution/publish-session-events.d.ts +27 -7
- package/dist/src/execution/publish-session-events.js +1 -1
- package/dist/src/execution/reconcile-session-continuation-token.d.ts +2 -2
- package/dist/src/execution/runtime-context.js +1 -1
- package/dist/src/execution/session/active-turn.d.ts +97 -0
- package/dist/src/execution/session/active-turn.js +1 -0
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/finalization.js +1 -1
- package/dist/src/execution/session/handoff.d.ts +5 -3
- package/dist/src/execution/session/handoff.js +1 -1
- package/dist/src/execution/session/input-queue.d.ts +19 -13
- package/dist/src/execution/session/input-queue.js +1 -1
- package/dist/src/execution/session/next-input.d.ts +2 -10
- package/dist/src/execution/session/next-input.js +1 -1
- package/dist/src/execution/session/program.d.ts +2 -0
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/state-cursor.d.ts +22 -16
- package/dist/src/execution/session/state-cursor.js +1 -1
- package/dist/src/execution/session/state-delta.d.ts +8 -1
- package/dist/src/execution/session/state-delta.js +1 -1
- package/dist/src/execution/session/turn-event-handler.js +1 -1
- package/dist/src/execution/session/turn-step-result.js +1 -1
- package/dist/src/execution/session/turn-step-types.d.ts +13 -4
- package/dist/src/execution/session/turn-step.js +1 -1
- package/dist/src/execution/session/turn-waiting-step.js +1 -1
- package/dist/src/execution/session/turn.d.ts +8 -1
- package/dist/src/execution/session/turn.js +1 -1
- package/dist/src/execution/session.d.ts +5 -5
- package/dist/src/execution/session.js +2 -2
- package/dist/src/execution/settle-cancelled-turn-step.d.ts +5 -4
- package/dist/src/execution/settle-cancelled-turn-step.js +1 -1
- package/dist/src/execution/tasks/start.d.ts +2 -2
- package/dist/src/execution/tasks/task-id-input.d.ts +1 -1
- package/dist/src/execution/tasks/task-id-input.js +1 -1
- package/dist/src/execution/terminal-session-completion-step.d.ts +2 -0
- package/dist/src/execution/terminal-session-completion-step.js +1 -1
- package/dist/src/execution/terminal-session-failure-step.d.ts +2 -0
- package/dist/src/execution/terminal-session-failure-step.js +1 -1
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/tools/connection-target.d.ts +5 -0
- package/dist/src/execution/tools/connection-target.js +1 -1
- package/dist/src/execution/tools/connection-tools.js +2 -2
- package/dist/src/execution/tools/workflow/start.d.ts +3 -3
- package/dist/src/harness/authorization.d.ts +15 -2
- package/dist/src/harness/authorization.js +1 -1
- package/dist/src/harness/cancelled-turn-emission.d.ts +2 -1
- package/dist/src/harness/cancelled-turn-emission.js +1 -1
- package/dist/src/harness/coordination.d.ts +5 -3
- package/dist/src/harness/emission.d.ts +5 -2
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/harness/held-requests.d.ts +18 -0
- package/dist/src/harness/held-requests.js +1 -0
- package/dist/src/harness/hitl/approval-input-requests.d.ts +16 -0
- package/dist/src/harness/hitl/approval-input-requests.js +1 -1
- package/dist/src/harness/input-extraction.js +1 -1
- package/dist/src/harness/input-requests.d.ts +0 -4
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/model-call-error.js +1 -1
- package/dist/src/harness/proxy-input-requests.d.ts +4 -4
- package/dist/src/harness/session-limit-enforcement.js +1 -1
- package/dist/src/harness/stale-input-responses.js +1 -1
- package/dist/src/harness/tool-loop.js +2 -2
- package/dist/src/harness/turn-tag-state.d.ts +2 -0
- package/dist/src/harness/turn-tag-state.js +1 -1
- package/dist/src/harness/types.d.ts +23 -11
- package/dist/src/instrumentation/channel-delivery.d.ts +1 -0
- package/dist/src/instrumentation/channel-delivery.js +1 -1
- package/dist/src/instrumentation/lifecycle.d.ts +4 -0
- package/dist/src/instrumentation/memory.d.ts +2 -0
- package/dist/src/instrumentation/memory.js +1 -1
- package/dist/src/instrumentation/native-events.d.ts +1 -0
- package/dist/src/instrumentation/native-events.js +1 -1
- package/dist/src/instrumentation/runtime.d.ts +4 -3
- package/dist/src/instrumentation/runtime.js +1 -1
- package/dist/src/instrumentation/session-init.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-module-loader.d.ts +14 -6
- package/dist/src/internal/authored-module-loader.js +1 -1
- package/dist/src/internal/authored-package-boundary.d.ts +1 -1
- package/dist/src/internal/authored-package-boundary.js +1 -1
- package/dist/src/internal/bundler/extension-mount-plugin.js +1 -1
- package/dist/src/internal/workflow-bundle/dynamic-remote-agent-transform.js +1 -1
- package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.d.ts +27 -1
- package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.js +1 -1
- package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +2 -2
- package/dist/src/protocol/message.d.ts +41 -3
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/public/channels/slack/action-status.d.ts +2 -2
- package/dist/src/public/channels/slack/action-status.js +1 -1
- package/dist/src/public/channels/slack/approval-cards.d.ts +15 -0
- package/dist/src/public/channels/slack/approval-cards.js +2 -0
- package/dist/src/public/channels/slack/auth.d.ts +2 -0
- package/dist/src/public/channels/slack/auth.js +1 -1
- package/dist/src/public/channels/slack/connections.d.ts +15 -11
- package/dist/src/public/channels/slack/connections.js +1 -1
- package/dist/src/public/channels/slack/defaults.d.ts +1 -15
- package/dist/src/public/channels/slack/defaults.js +3 -4
- package/dist/src/public/channels/slack/hitl.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +10 -2
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +2 -0
- package/dist/src/public/channels/slack/index.js +1 -1
- package/dist/src/public/channels/slack/input-response.js +1 -1
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/limits.d.ts +5 -3
- package/dist/src/public/channels/slack/private-approval-delivery.js +1 -1
- package/dist/src/public/channels/slack/renderers.d.ts +1 -1
- package/dist/src/public/channels/slack/renderers.js +1 -1
- package/dist/src/public/channels/slack/reply-delivery.d.ts +72 -0
- package/dist/src/public/channels/slack/reply-delivery.js +1 -0
- package/dist/src/public/channels/slack/sign-in-cancel.d.ts +20 -0
- package/dist/src/public/channels/slack/sign-in-cancel.js +1 -0
- package/dist/src/public/channels/slack/slackChannel.d.ts +15 -0
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/task-card.js +1 -1
- package/dist/src/public/channels/teams/defaults.d.ts +1 -1
- package/dist/src/public/channels/teams/defaults.js +2 -2
- package/dist/src/public/definitions/channel.d.ts +1 -0
- package/dist/src/public/definitions/channel.js +1 -1
- package/dist/src/public/next/index.js +1 -1
- package/dist/src/public/next/server.d.ts +2 -0
- package/dist/src/public/next/server.js +1 -2
- package/dist/src/public/nuxt/module.d.ts +6 -0
- package/dist/src/public/nuxt/module.js +1 -1
- package/dist/src/react/index.d.ts +3 -0
- package/dist/src/react/index.js +1 -1
- package/dist/src/react/use-eve-agent.d.ts +7 -5
- package/dist/src/react/use-eve-agent.js +1 -1
- package/dist/src/runtime/connections/scoped-authorization.d.ts +2 -0
- package/dist/src/runtime/connections/scoped-authorization.js +1 -1
- package/dist/src/setup/boxes/deploy-project.js +1 -1
- package/dist/src/setup/integrations/web/setup.js +6 -2
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/create/web-template.d.ts +2 -2
- package/dist/src/setup/scaffold/create/web-template.js +28 -9
- package/dist/src/setup/vercel-project-framework.js +1 -1
- package/dist/src/shared/display-name.d.ts +15 -0
- package/dist/src/shared/display-name.js +1 -0
- package/dist/src/shared/framework-eve-server.d.ts +43 -0
- package/dist/src/shared/framework-eve-server.js +2 -0
- package/dist/src/shared/token-usage.d.ts +2 -0
- package/dist/src/shared/token-usage.js +1 -1
- package/dist/src/shared/trace-root.d.ts +8 -0
- package/dist/src/shared/trace-root.js +1 -0
- package/dist/src/subagents/hitl-proxy.d.ts +3 -3
- package/dist/src/subagents/hitl-proxy.js +1 -1
- package/dist/src/subagents/invocation.d.ts +3 -1
- package/dist/src/subagents/tool.js +1 -1
- package/dist/src/svelte/index.d.ts +3 -0
- package/dist/src/svelte/index.js +2 -2
- package/dist/src/svelte/use-eve-agent.d.ts +7 -4
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/tools/provided/ask-question-workflow.js +1 -1
- package/dist/src/tools/provided/load-skill.js +1 -1
- package/dist/src/tools/provided/sleep-workflow.js +1 -1
- package/dist/src/tracing/agent-action-instrumentation.js +1 -1
- package/dist/src/tracing/agent-approval-instrumentation.js +1 -1
- package/dist/src/tracing/agent-memory-instrumentation.js +1 -1
- package/dist/src/tracing/agent-otel-attributes.d.ts +6 -0
- package/dist/src/tracing/agent-otel-attributes.js +1 -1
- package/dist/src/tracing/agent-otel-provider.js +2 -2
- package/dist/src/tracing/agent-otel-runtime-context.d.ts +1 -1
- package/dist/src/tracing/agent-otel-runtime-context.js +1 -1
- package/dist/src/tracing/agent-otel-session-context.js +1 -1
- package/dist/src/tracing/agent-span-contract.js +1 -1
- package/dist/src/tracing/agent-tool-instrumentation.js +1 -1
- package/dist/src/tracing/agent-trace-context-codec.d.ts +4 -0
- package/dist/src/tracing/agent-trace-context-codec.js +1 -1
- package/dist/src/tracing/agent-trace-span-processor.js +1 -1
- package/dist/src/tracing/agent-trace-state.d.ts +3 -0
- package/dist/src/tracing/conversation-context.d.ts +10 -0
- package/dist/src/tracing/conversation-context.js +1 -1
- package/dist/src/tracing/otel-registration.js +1 -1
- package/dist/src/vue/index.d.ts +3 -0
- package/dist/src/vue/index.js +2 -2
- package/dist/src/vue/use-eve-agent.d.ts +7 -3
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/channels/slack.mdx +60 -13
- package/docs/concepts/built-in-tools.md +3 -2
- package/docs/concepts/sessions-runs-and-streaming.md +4 -4
- package/docs/connections/overview.mdx +9 -3
- package/docs/evals/reporters.mdx +4 -0
- package/docs/extensions.md +11 -0
- package/docs/guides/client/streaming.mdx +7 -6
- package/docs/guides/deployment/vercel.mdx +2 -1
- package/docs/guides/dev-tui.md +15 -15
- package/docs/guides/dynamic-capabilities.md +1 -1
- package/docs/guides/frontend/nuxt.mdx +24 -4
- package/docs/guides/frontend/overview.mdx +44 -38
- package/docs/guides/frontend/use-eve-agent-svelte.mdx +4 -10
- package/docs/guides/frontend/use-eve-agent-vue.mdx +4 -10
- package/docs/guides/instrumentation/otel.mdx +24 -6
- package/docs/guides/remote-agents.md +26 -8
- package/docs/reference/cli.md +4 -4
- package/docs/tools/human-in-the-loop.md +4 -4
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/tasks.md +8 -8
- package/package.json +1 -1
- package/dist/src/cli/dev/tui/steering-stream.d.ts +0 -15
- package/dist/src/cli/dev/tui/steering-stream.js +0 -1
- package/dist/src/cli/dev/tui/subagent-pump.d.ts +0 -134
- package/dist/src/cli/dev/tui/subagent-pump.js +0 -1
- package/dist/src/public/nuxt/dev-server.d.ts +0 -25
- package/dist/src/public/nuxt/dev-server.js +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInputRequestMessage,renderInputRequestMessage}from"#public/channels/teams/hitl.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.from.role===`bot`||t.from.id===t.recipient.id||t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}const defaultEvents={async"turn.started"(e,t,n){await t.thread.startTyping()},async"actions.requested"(e,t,n){await t.thread.startTyping()},async"input.requested"(e,t,n){for(let n of e.requests){let e=await t.thread.post(renderInputRequestMessage(n,{adaptiveCardVersion:t.adaptiveCardVersion,replyToActivityId:t.teams.replyToActivityId}));n.kind===`tool-approval`&&e.id&&(t.state.pendingApprovalCards={...t.state.pendingApprovalCards,[n.requestId]:{activityId:e.id,prompt:n.prompt}})}},async"approval.settled"(e,t,n){let r=t.state.pendingApprovalCards??{},i=r[e.requestId];if(i===void 0)return;let a=t.state.approvalResponderAccounts?.[e.responderPrincipalId],o=e.outcome===`approved`?`Approved`:`Cancelled`,s=a?.name??a?.id;await t.thread.update(i.activityId,renderAnsweredInputRequestMessage({includeText:!1,label:s===void 0?o:`${o} by ${s}`,prompt:i.prompt}));let c={...r};delete c[e.requestId],t.state.pendingApprovalCards=c},async"message.completed"(e,t,n){if(e.finishReason!==`tool-calls`&&e.message)for(let n of splitTeamsMessageText(e.message))await t.thread.post(n)},async"session.failed"(e,t){let n=formatErrorHint(e),r=extractErrorId(e.details);await t.thread.post([`This session could not recover from an error${n}.`,``,`Start a new Teams conversation to continue.`,...r?[``,`Error id: ${r}`]:[]].join(`
|
|
1
|
+
import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{displayProperName}from"#shared/display-name.js";import{parseJsonObject}from"#shared/json.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInputRequestMessage,renderInputRequestMessage}from"#public/channels/teams/hitl.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.from.role===`bot`||t.from.id===t.recipient.id||t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}const defaultEvents={async"turn.started"(e,t,n){await t.thread.startTyping()},async"actions.requested"(e,t,n){await t.thread.startTyping()},async"input.requested"(e,t,n){for(let n of e.requests){let e=await t.thread.post(renderInputRequestMessage(n,{adaptiveCardVersion:t.adaptiveCardVersion,replyToActivityId:t.teams.replyToActivityId}));n.kind===`tool-approval`&&e.id&&(t.state.pendingApprovalCards={...t.state.pendingApprovalCards,[n.requestId]:{activityId:e.id,prompt:n.prompt}})}},async"approval.settled"(e,t,n){let r=t.state.pendingApprovalCards??{},i=r[e.requestId];if(i===void 0)return;let a=t.state.approvalResponderAccounts?.[e.responderPrincipalId],o=e.outcome===`approved`?`Approved`:`Cancelled`,s=a?.name??a?.id;await t.thread.update(i.activityId,renderAnsweredInputRequestMessage({includeText:!1,label:s===void 0?o:`${o} by ${s}`,prompt:i.prompt}));let c={...r};delete c[e.requestId],t.state.pendingApprovalCards=c},async"message.completed"(e,t,n){if(e.finishReason!==`tool-calls`&&e.message)for(let n of splitTeamsMessageText(e.message))await t.thread.post(n)},async"session.failed"(e,t){let n=formatErrorHint(e),r=extractErrorId(e.details);await t.thread.post([`This session could not recover from an error${n}.`,``,`Start a new Teams conversation to continue.`,...r?[``,`Error id: ${r}`]:[]].join(`
|
|
2
2
|
`))},async"turn.failed"(e,t,n){let r=formatErrorHint(e),i=extractErrorId(e.details);await t.thread.post([`I hit an error while handling your request${r}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...i?[``,`Error id: ${i}`]:[]].join(`
|
|
3
|
-
`))},async"authorization.required"(e,t,n){let r=e.authorization?.displayName??formatConnectionDisplayName(e.name),i=e.authorization?.url,a=i?`Authorization required for ${r}: ${i}`:`Authorization required for ${r}.`,o=await t.thread.post({attachments:[{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,actions:i?[{title:`Sign in with ${r}`,type:`Action.OpenUrl`,url:i}]:[],body:[{text:`Authorization required for ${r}`,type:`TextBlock`,weight:`Bolder`,wrap:!0},{text:t.state.triggeringUser?`Requested by ${t.state.triggeringUser.name??t.state.triggeringUser.id}.`:`No triggering user is available for a private prompt.`,type:`TextBlock`,wrap:!0}],type:`AdaptiveCard`,version:t.adaptiveCardVersion}),contentType:`application/vnd.microsoft.card.adaptive`}],text:a});o.id&&(t.state.pendingAuthActivityId=o.id)},async"authorization.completed"(e,t,n){e.outcome===`authorized`&&await t.thread.startTyping();let r=t.state.pendingAuthActivityId;if(!r)return;let i=buildAuthCompletedText({displayName:e.authorization?.displayName??formatConnectionDisplayName(e.name),outcome:e.outcome,reason:e.reason});await t.thread.update(r,renderAnsweredInputRequestMessage({prompt:i})),t.state.pendingAuthActivityId=null}};function formatConnectionDisplayName(e){return
|
|
3
|
+
`))},async"authorization.required"(e,t,n){let r=e.authorization?.displayName??formatConnectionDisplayName(e.name),i=e.authorization?.url,a=i?`Authorization required for ${r}: ${i}`:`Authorization required for ${r}.`,o=await t.thread.post({attachments:[{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,actions:i?[{title:`Sign in with ${r}`,type:`Action.OpenUrl`,url:i}]:[],body:[{text:`Authorization required for ${r}`,type:`TextBlock`,weight:`Bolder`,wrap:!0},{text:t.state.triggeringUser?`Requested by ${t.state.triggeringUser.name??t.state.triggeringUser.id}.`:`No triggering user is available for a private prompt.`,type:`TextBlock`,wrap:!0}],type:`AdaptiveCard`,version:t.adaptiveCardVersion}),contentType:`application/vnd.microsoft.card.adaptive`}],text:a});o.id&&(t.state.pendingAuthActivityId=o.id)},async"authorization.completed"(e,t,n){e.outcome===`authorized`&&await t.thread.startTyping();let r=t.state.pendingAuthActivityId;if(!r)return;let i=buildAuthCompletedText({displayName:e.authorization?.displayName??formatConnectionDisplayName(e.name),outcome:e.outcome,reason:e.reason});await t.thread.update(r,renderAnsweredInputRequestMessage({prompt:i})),t.state.pendingAuthActivityId=null}};function formatConnectionDisplayName(e){return displayProperName(e)}function buildAuthCompletedText(e){if(e.outcome===`authorized`)return`${e.displayName} connected.`;let t=e.reason===void 0?``:` (${e.reason})`;return`${e.displayName} authorization ${e.outcome}${t}.`}function teamsMentionUser(e){let t=e.name??e.id;return{mentioned:{id:e.id,name:e.name},text:`<at>${t}</at>`,type:`mention`}}export{buildAuthCompletedText,defaultEvents,defaultOnMessage,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser};
|
|
@@ -126,6 +126,7 @@ export interface ChannelEvents<TCtx = void> {
|
|
|
126
126
|
readonly "message.appended"?: ChannelEventHandler<"message.appended", TCtx>;
|
|
127
127
|
readonly "reasoning.appended"?: ChannelEventHandler<"reasoning.appended", TCtx>;
|
|
128
128
|
readonly "reasoning.completed"?: ChannelEventHandler<"reasoning.completed", TCtx>;
|
|
129
|
+
readonly "step.started"?: ChannelEventHandler<"step.started", TCtx>;
|
|
129
130
|
readonly "step.completed"?: ChannelEventHandler<"step.completed", TCtx>;
|
|
130
131
|
readonly "input.requested"?: ChannelEventHandler<"input.requested", TCtx>;
|
|
131
132
|
readonly "input.resolved"?: ChannelEventHandler<"input.resolved", TCtx>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createMetadataAudienceProjector}from"#channel/audience.js";import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{normalizeChannelCors}from"#channel/cors.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,WS}from"#channel/routes.js";const DISABLED_ROUTE_SENTINEL_KIND=`eve:disabled-channel`;function disableRoute(){return{kind:DISABLED_ROUTE_SENTINEL_KIND}}function isDisabledRouteSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_ROUTE_SENTINEL_KIND}function defineChannel(e){let t=buildAdapter(e),n=normalizeChannelCors(e.cors);return{__kind:CHANNEL_SENTINEL,routes:e.routes,adapter:t,cors:n,receive:e.receive,turnPolicy:e.turnPolicy}}const eventTypes=Object.keys({"approval.candidate":null,"approval.settled":null,"context.cleared":null,"compaction.requested":null,"compaction.completed":null,"turn.started":null,"actions.requested":null,"action.partial":null,"action.result":null,"message.completed":null,"message.appended":null,"reasoning.appended":null,"reasoning.completed":null,"step.completed":null,"input.requested":null,"input.resolved":null,"task.started":null,"task.settled":null,"turn.waiting":null,"turn.failed":null,"turn.completed":null,"turn.cancelled":null,"session.failed":null,"session.completed":null,"session.waiting":null,"authorization.required":null,"authorization.completed":null});function buildAdapter(e){let t=e.state!=null,n=e.context!=null,r=e.fetchFile!==void 0,i=e.metadata,a=i!==void 0,o=e.audience,s=t||n||a,c={},l=!1,u={kind:e.kindHint??`defineChannel`},d=e.events;for(let e of eventTypes){let t=d?.[e];t&&(l=!0,c[e]=(n,r)=>{let{session:i,...a}=r,o={...a,continuation:i?.continuation===void 0?void 0:{token:i.continuation.token,alias:e=>i.continuation?.alias(e)}};if(e===`session.failed`)return t(n,o);let s=buildCallbackContext();return t(n,o,s)})}return!s&&!l&&!r?{kind:e.kindHint??HTTP_ADAPTER_KIND,...o===void 0?void 0:{instrumentation:{audience:o}}}:{kind:e.kindHint??`defineChannel`,state:t?{...e.state}:{},fetchFile:e.fetchFile,instrumentation:i===void 0&&o===void 0?void 0:{...i===void 0?void 0:{metadata(e){let{audience:t,...n}=i(e);return n}},...o===void 0?i===void 0?void 0:{audience:createMetadataAudienceProjector(u,i)}:{audience:o}},createAdapterContext(t){let r=t.state,i=t.session;return{...n?e.context(r,i):{},state:r,ctx:t.ctx,session:i}},deliver(t,n){return e.deliver===void 0?defaultDeliverResult(t):e.deliver(t,n)},...c}}export{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,WS,defineChannel,disableRoute,isDisabledRouteSentinel};
|
|
1
|
+
import{createMetadataAudienceProjector}from"#channel/audience.js";import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{normalizeChannelCors}from"#channel/cors.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,WS}from"#channel/routes.js";const DISABLED_ROUTE_SENTINEL_KIND=`eve:disabled-channel`;function disableRoute(){return{kind:DISABLED_ROUTE_SENTINEL_KIND}}function isDisabledRouteSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_ROUTE_SENTINEL_KIND}function defineChannel(e){let t=buildAdapter(e),n=normalizeChannelCors(e.cors);return{__kind:CHANNEL_SENTINEL,routes:e.routes,adapter:t,cors:n,receive:e.receive,turnPolicy:e.turnPolicy}}const eventTypes=Object.keys({"approval.candidate":null,"approval.settled":null,"context.cleared":null,"compaction.requested":null,"compaction.completed":null,"turn.started":null,"actions.requested":null,"action.partial":null,"action.result":null,"message.completed":null,"message.appended":null,"reasoning.appended":null,"reasoning.completed":null,"step.started":null,"step.completed":null,"input.requested":null,"input.resolved":null,"task.started":null,"task.settled":null,"turn.waiting":null,"turn.failed":null,"turn.completed":null,"turn.cancelled":null,"session.failed":null,"session.completed":null,"session.waiting":null,"authorization.required":null,"authorization.completed":null});function buildAdapter(e){let t=e.state!=null,n=e.context!=null,r=e.fetchFile!==void 0,i=e.metadata,a=i!==void 0,o=e.audience,s=t||n||a,c={},l=!1,u={kind:e.kindHint??`defineChannel`},d=e.events;for(let e of eventTypes){let t=d?.[e];t&&(l=!0,c[e]=(n,r)=>{let{session:i,...a}=r,o={...a,continuation:i?.continuation===void 0?void 0:{token:i.continuation.token,alias:e=>i.continuation?.alias(e)}};if(e===`session.failed`)return t(n,o);let s=buildCallbackContext();return t(n,o,s)})}return!s&&!l&&!r?{kind:e.kindHint??HTTP_ADAPTER_KIND,...o===void 0?void 0:{instrumentation:{audience:o}}}:{kind:e.kindHint??`defineChannel`,state:t?{...e.state}:{},fetchFile:e.fetchFile,instrumentation:i===void 0&&o===void 0?void 0:{...i===void 0?void 0:{metadata(e){let{audience:t,...n}=i(e);return n}},...o===void 0?i===void 0?void 0:{audience:createMetadataAudienceProjector(u,i)}:{audience:o}},createAdapterContext(t){let r=t.state,i=t.session;return{...n?e.context(r,i):{},state:r,ctx:t.ctx,session:i}},deliver(t,n){return e.deliver===void 0?defaultDeliverResult(t):e.deliver(t,n)},...c}}export{DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,WS,defineChannel,disableRoute,isDisabledRouteSentinel};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveEveDestinationPrefix}from"./server.js";import{ensureEveVercelOutputConfig}from"./vercel-output-config.js";import{EVE_ROUTE_PREFIX}from"#protocol/routes.js";import{joinEveRoutePath}from"#shared/eve-route-path.js";import{resolve}from"node:path";import{findEveProjectContext}from"#internal/project-context.js";import{assertValidPublicAgentName}from"#internal/agent-name.js";import{quoteVercelShellArgument,toVercelRelativePath}from"#internal/vercel/build-command.js";import{resolveEveBinaryPath}from"#shared/resolve-eve-binary.js";const EVE_NEXT_SERVICE_PREFIX=`/_eve_internal/eve`,EVE_NEXT_PRODUCTION_PORT_ENV=`EVE_NEXT_PRODUCTION_PORT`;function resolveApplicationRoot(e){return e===void 0||e.length===0?process.cwd():resolve(process.cwd(),e)}function
|
|
1
|
+
import{EVE_NEXT_HOST,resolveEveDestinationPrefix}from"./server.js";import{ensureEveVercelOutputConfig}from"./vercel-output-config.js";import{EVE_ROUTE_PREFIX}from"#protocol/routes.js";import{joinEveRoutePath}from"#shared/eve-route-path.js";import{resolve}from"node:path";import{findEveProjectContext}from"#internal/project-context.js";import{assertValidPublicAgentName}from"#internal/agent-name.js";import{quoteVercelShellArgument,toVercelRelativePath}from"#internal/vercel/build-command.js";import{resolveEveBinaryPath}from"#shared/resolve-eve-binary.js";import{resolveDevServerTimeout}from"#shared/framework-eve-server.js";const EVE_NEXT_SERVICE_PREFIX=`/_eve_internal/eve`,EVE_NEXT_PRODUCTION_PORT_ENV=`EVE_NEXT_PRODUCTION_PORT`;function resolveApplicationRoot(e){return e===void 0||e.length===0?process.cwd():resolve(process.cwd(),e)}function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`eve Next.js service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function createNamedAgentRoutePrefix(e){return joinRoutePrefix(`/eve`,e)}function createNamedAgentServicePrefix(e,t){return joinRoutePrefix(e,t)}function createAgentRewriteSource(e){return joinEveRoutePath(e,`${EVE_ROUTE_PREFIX}/:path+`)}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(e){let t=process.env[EVE_NEXT_PRODUCTION_PORT_ENV],n=t===void 0||t.trim().length===0?4274:Number.parseInt(t,10);if(t!==void 0&&t.trim().length>0&&String(n)!==t.trim())throw Error(`${EVE_NEXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);let r=n+e;if(r<1||r>65535)throw Error(`${EVE_NEXT_PRODUCTION_PORT_ENV} plus the eve agent count exceeds 65535.`);return r}function resolveProductionDestination(e){if(process.env.VERCEL)return{destinationPrefix:e.servicePrefix};let t=process.env.EVE_NEXT_PRODUCTION_ORIGIN;if(t!==void 0&&t.trim().length>0)return{destinationPrefix:joinRoutePrefix(normalizeOrigin(t),e.servicePrefix)};let n=`http://127.0.0.1:${String(readLocalProductionPort(e.localProductionPortOffset))}`;return{destinationPrefix:n,localServerOrigin:n}}function createEveRewriteRule(e){let t=createAgentRewriteSource(e.publicRoutePrefix);return{destination:joinRoutePrefix(e.destinationPrefix,`${EVE_ROUTE_PREFIX}/:path+`),source:t}}async function resolveExistingRewrites(e){return await e?.()}function mergeRewriteRules(e,t){return e===void 0?{beforeFiles:t}:isRewriteSections(e)?{...e,beforeFiles:[...t,...e.beforeFiles??[]]}:{afterFiles:e,beforeFiles:t}}function isRewriteSections(e){return!Array.isArray(e)}async function resolveNextConfig(e,t,n){return typeof e==`function`?await e(t,n):e}function assertValidAgentName(e){assertValidPublicAgentName(e,`eve Next.js agent name`)}function assertValidWithEveOptions(e){if(e.eveRoot!==void 0&&e.agents!==void 0)throw Error(`withEve cannot combine eveRoot with agents. Use one configuration form.`);if(e.agents===void 0)return;let t=Object.keys(e.agents);if(t.length===0)throw Error(`withEve agents must contain at least one named eve agent.`);for(let e of t)assertValidAgentName(e)}function createDefaultBuildCommand(e){let t=toVercelRelativePath(e.agentRoot,resolveEveBinaryPath(e.agentRoot));return`node ${quoteVercelShellArgument(t)} build`}async function normalizeAgentsConfig(e){let t=normalizeRoutePrefix(e.servicePrefix??`/_eve_internal/eve`),resolveBuildCommand=(t,n)=>n??e.eveBuildCommand??createDefaultBuildCommand({agentRoot:t});if(e.agents===void 0){let n=resolveApplicationRoot(e.eveRoot),r=await findEveProjectContext(n);if(r?.kind===`workspace`&&r.workspace.root===n&&r.workspace.members.length>0)return r.workspace.members.map((e,n)=>({appRoot:e.appRoot,buildCommand:resolveBuildCommand(e.appRoot,void 0),localProductionPortOffset:n,name:e.name,publicRoutePrefix:createNamedAgentRoutePrefix(e.name),servicePrefix:createNamedAgentServicePrefix(t,e.name),workspaceMember:!0}));let i=n;return[{appRoot:i,buildCommand:resolveBuildCommand(i,void 0),localProductionPortOffset:0,publicRoutePrefix:``,servicePrefix:t}]}let n=Object.entries(e.agents);if(n.length===0)throw Error(`withEve agents must contain at least one named eve agent.`);return n.map(([e,n],r)=>{assertValidAgentName(e);let i=typeof n==`string`?{root:n}:n,a=resolveApplicationRoot(i.root);return{appRoot:a,buildCommand:resolveBuildCommand(a,i.buildCommand),localProductionPortOffset:r,name:e,publicRoutePrefix:createNamedAgentRoutePrefix(e),servicePrefix:normalizeRoutePrefix(i.servicePrefix??createNamedAgentServicePrefix(t,e))}})}async function buildAgentWorkspaceExtensions(e){let[{buildWorkspaceExtensions:t},{DiscoveryProjectResolutionError:n}]=await Promise.all([import(`#internal/nitro/host/workspace-extensions.js`),import(`#discover/project.js`)]);for(let r of e)try{await t(r.appRoot)}catch(e){if(e instanceof n)continue;throw e}}function withEve(e,t={}){let n=process.cwd(),r=resolveDevServerTimeout(t.devServerTimeoutMs,EVE_NEXT_HOST);return assertValidWithEveOptions(t),async function(i,a){let[o,s]=await Promise.all([normalizeAgentsConfig(t),resolveNextConfig(e,i,a)]);i===`phase-production-build`&&await buildAgentWorkspaceExtensions(o);let c=s.rewrites,l=await ensureEveVercelOutputConfig({agents:o.map(e=>{let t={appRoot:e.appRoot,buildCommand:e.buildCommand,name:e.name,publicRoutePrefix:e.publicRoutePrefix,servicePrefix:e.servicePrefix};return e.workspaceMember===!0&&(t.workspaceMember=!0),t}),nextRoot:n});if(process.env.VERCEL)return s;let u=new Map(l.agents.map(e=>[e.name,e])),d=o.map(e=>{let t=u.get(e.name),n=resolveProductionDestination({localProductionPortOffset:e.localProductionPortOffset,servicePrefix:t?.servicePrefix??e.servicePrefix});return{...e,productionDestination:n}});return{...s,async rewrites(){let[e,t]=await Promise.all([resolveExistingRewrites(c),Promise.all(d.map(async e=>createEveRewriteRule({destinationPrefix:await resolveEveDestinationPrefix({appRoot:e.appRoot,devServerTimeoutMs:r,logLabel:e.name,phase:i,productionDestinationPrefix:e.productionDestination.destinationPrefix,productionServerOrigin:e.productionDestination.localServerOrigin,workspaceAgentName:e.workspaceMember===!0?e.name:void 0}),publicRoutePrefix:e.publicRoutePrefix})))]);return mergeRewriteRules(e,t)}}}}export{EVE_NEXT_SERVICE_PREFIX,withEve};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type EveFrameworkHost } from "#shared/framework-eve-server.js";
|
|
1
2
|
export declare const NEXT_PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
3
|
+
export declare const EVE_NEXT_HOST: EveFrameworkHost;
|
|
2
4
|
export declare function resolveEveDestinationPrefix(input: {
|
|
3
5
|
readonly appRoot: string;
|
|
4
6
|
readonly devServerTimeoutMs?: number;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`);t=n.pop()??``;for(let e of n)writeLine(e)}}}function startServerProcess(e){return new Promise((t,n)=>{let r=spawn(e.command,e.args,{cwd:e.cwd,env:{...process.env,...e.env},stdio:[`ignore`,`pipe`,`pipe`]}),i=createEveDevOutputWriter({logLabel:e.logLabel,stream:process.stderr}),a=createEveDevOutputWriter({logLabel:e.logLabel,stream:process.stdout}),o=setTimeout(()=>{r.kill(),n(Error(`Timed out after ${e.timeoutMs??DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for eve to print its server URL.`))},e.timeoutMs??DEFAULT_SERVER_READY_TIMEOUT_MS),cleanup=()=>{clearTimeout(o),r.off(`error`,handleError),r.off(`exit`,handleEarlyExit)},flushOutput=()=>{a.flush(),i.flush()},handleError=e=>{flushOutput(),cleanup(),n(e)},handleEarlyExit=(e,t)=>{flushOutput(),cleanup(),n(Error(`eve server process exited before printing its server URL (code ${String(e)}, signal ${String(t)}).`))},handleOutput=e=>{let n=findLocalServerOrigin(e.toString(`utf8`));n!==void 0&&(cleanup(),t({origin:n,process:r}))};r.once(`error`,handleError),r.once(`exit`,handleEarlyExit),r.stdout.on(`data`,e=>{a.write(e),handleOutput(e)}),r.stderr.on(`data`,e=>{i.write(e),handleOutput(e)})})}function installProcessShutdown(e){let t=e.process;if(t===void 0)return e;let close=()=>{t.killed||t.kill()};return process.once(`beforeExit`,close),process.once(`exit`,close),e}function startEveDevServer(e,t,n,r){return startServerProcess({args:[createEveBinaryPath(),`dev`,`--no-ui`,`--port`,`0`,...r===void 0?[]:[`--agent`,r]],command:process.execPath,cwd:e,logLabel:n,timeoutMs:t}).then(e=>installProcessShutdown(e))}function startEveProductionServer(e){let t=new URL(e.origin),n=t.port,r=join(e.appRoot,`.output`,`server`,`index.mjs`);if(!existsSync(r))throw Error(`eve production output is missing at ${r}. Run eve build from ${e.appRoot} before starting Next.js.`);return startServerProcess({args:[r],command:process.execPath,cwd:e.appRoot,env:{HOST:t.hostname,NITRO_HOST:t.hostname,NITRO_PORT:n,PORT:n}}).then(installProcessShutdown)}async function resolveSharedEveDevServer(e,t,n,r){let i=await readUsableEveDevServerRegistry(e);if(i!==void 0)return{origin:i};let a=await acquireEveDevServerLock(e,t);try{let i=await readUsableEveDevServerRegistry(e);if(i!==void 0)return{origin:i};let a=await startEveDevServer(e,t,n,r);return await writeEveDevServerRegistry(e,a),a}finally{await a()}}async function resolveEveDestinationPrefix(e){let t=getGlobalState();if(process.env.NODE_ENV===`production`){if(e.phase===`phase-production-build`)return e.productionDestinationPrefix;let n=`production:${e.appRoot}`,r=t.servers.get(n);return r===void 0&&(r=process.env.VERCEL||e.productionServerOrigin===void 0?void 0:startEveProductionServer({appRoot:e.appRoot,origin:e.productionServerOrigin}),r!==void 0&&(r=r.catch(e=>{throw t.servers.delete(n),e}),t.servers.set(n,r))),r===void 0?e.productionDestinationPrefix:(await r).origin}let n=readEveBaseUrlEnvironment();if(n!==void 0)return n;if(process.env.NODE_ENV!==`development`)return e.productionDestinationPrefix;let r=`dev:${e.appRoot}`,i=t.servers.get(r);return i===void 0&&(i=resolveSharedEveDevServer(e.appRoot,e.devServerTimeoutMs??18e4,e.logLabel,e.workspaceAgentName).catch(e=>{throw t.servers.delete(r),e}),t.servers.set(r,i)),(await i).origin}export{NEXT_PHASE_PRODUCTION_BUILD,resolveEveDestinationPrefix};
|
|
1
|
+
import{resolveSharedEveDevServer,startEveProductionServer}from"#shared/framework-eve-server.js";const NEXT_PHASE_PRODUCTION_BUILD=`phase-production-build`,EVE_NEXT_HOST={label:`Next.js`,slug:`next`},globalStateSymbol=Symbol.for(`eve.next.state`);function getGlobalState(){let e=globalThis;return e[globalStateSymbol]??={servers:new Map},e[globalStateSymbol]}function readEveBaseUrlEnvironment(){let e=process.env.EVE_BASE_URL;if(e!==void 0&&e.trim().length!==0)return new URL(e).origin}async function resolveEveDestinationPrefix(e){let t=getGlobalState();if(process.env.NODE_ENV===`production`){if(e.phase===`phase-production-build`)return e.productionDestinationPrefix;let n=`production:${e.appRoot}`,r=t.servers.get(n);return r===void 0&&(r=process.env.VERCEL||e.productionServerOrigin===void 0?void 0:startEveProductionServer({appRoot:e.appRoot,host:EVE_NEXT_HOST,origin:e.productionServerOrigin}),r!==void 0&&(r=r.catch(e=>{throw t.servers.delete(n),e}),t.servers.set(n,r))),r===void 0?e.productionDestinationPrefix:(await r).origin}let n=readEveBaseUrlEnvironment();if(n!==void 0)return n;if(process.env.NODE_ENV!==`development`)return e.productionDestinationPrefix;let r=`dev:${e.appRoot}`,i=t.servers.get(r);return i===void 0&&(i=resolveSharedEveDevServer({appRoot:e.appRoot,host:EVE_NEXT_HOST,logLabel:e.logLabel,timeoutMs:e.devServerTimeoutMs,workspaceAgentName:e.workspaceAgentName}).catch(e=>{throw t.servers.delete(r),e}),t.servers.set(r,i)),(await i).origin}export{EVE_NEXT_HOST,NEXT_PHASE_PRODUCTION_BUILD,resolveEveDestinationPrefix};
|
|
@@ -3,6 +3,12 @@ import type { NuxtModule } from "@nuxt/schema";
|
|
|
3
3
|
* Options for the eve Nuxt module.
|
|
4
4
|
*/
|
|
5
5
|
export interface EveNuxtModuleOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum time in milliseconds to wait for the eve development server to
|
|
8
|
+
* start, including waiting for another Nuxt process to start it. Defaults
|
|
9
|
+
* to 180000 (three minutes).
|
|
10
|
+
*/
|
|
11
|
+
devServerTimeoutMs?: number;
|
|
6
12
|
/**
|
|
7
13
|
* Path to the eve application root, resolved relative to the Nuxt project
|
|
8
14
|
* root unless absolute. Defaults to the Nuxt project root. The dev server is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinRoutePrefix,normalizeOrigin,resolveProductionTarget}from"./routing.js";import{
|
|
1
|
+
import{joinRoutePrefix,normalizeOrigin,resolveProductionTarget}from"./routing.js";import{EVE_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";import{resolveDevServerTimeout,resolveSharedEveDevServer}from"#shared/framework-eve-server.js";import{addImports,defineNuxtModule,extendRouteRules}from"@nuxt/kit";import{ensureEveVercelServicesConfig,mergeEveVercelConfig}from"#shared/vercel-services.js";const EVE_NUXT_HOST={label:`Nuxt`,slug:`nuxt`};function resolveApplicationRoot(e,t){return t===void 0||t.length===0?e:isAbsolute(t)?t:resolve(e,t)}async function resolveEveProxyTarget(e){if(!e.dev)return resolveProductionTarget();let t=process.env.EVE_BASE_URL?.trim();if(t&&t.length>0)return joinRoutePrefix(normalizeOrigin(t),EVE_ROUTE_PREFIX);let n=await resolveSharedEveDevServer({appRoot:e.appRoot,host:EVE_NUXT_HOST,timeoutMs:e.devServerTimeoutMs});return n.process!==void 0&&e.onDevServerSpawned?.(n.process),joinRoutePrefix(n.origin,EVE_ROUTE_PREFIX)}const eveNuxtModule=defineNuxtModule({meta:{name:`eve`,configKey:`eve`,compatibility:{nuxt:`>=4.0.0`}},defaults:{},async setup(e,t){let n=t.options.rootDir,r=resolveApplicationRoot(n,e.eveRoot),i=resolveDevServerTimeout(e.devServerTimeoutMs,EVE_NUXT_HOST);if(addImports({name:`useEveAgent`,from:`eve/vue`}),!t.options.dev&&process.env.VERCEL){let i=await ensureEveVercelServicesConfig({appRoot:r,eveBuildCommand:e.eveBuildCommand,frameworkName:`Nuxt`,hostRoot:n});if(i.mode===`generated`){let e=t.options.nitro;e.vercel={...e.vercel,config:mergeEveVercelConfig(e.vercel?.config,i)}}}else t.hook(`modules:done`,async()=>{let e=await resolveEveProxyTarget({appRoot:r,dev:t.options.dev,devServerTimeoutMs:i,onDevServerSpawned:e=>{t.hook(`close`,()=>{e.killed||e.kill()})}});extendRouteRules(`${EVE_ROUTE_PREFIX}/**`,{proxy:`${e}/**`})})}});export{eveNuxtModule as default};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { useEveAgent, type PrepareSend, type UseEveAgentHelpers, type UseEveAgentOptions, type UseEveAgentSnapshot, type UseEveAgentStatus, } from "#react/use-eve-agent.js";
|
|
2
2
|
export { type EveAgentReducer, type EveAgentReducerEvent, type ClientInputRespondedEvent, type ClientMessageFailedEvent, type ClientMessageSubmittedEvent, } from "#client/reducer.js";
|
|
3
|
+
export { conversationReducer } from "#client/conversation-reducer.js";
|
|
4
|
+
export { openConversationInputs } from "#client/conversation-state.js";
|
|
5
|
+
export type { AgentObservation, ConversationAgentSession, ConversationInput, ConversationState, ConversationTask, ConversationTaskCall, ConversationTurn, } from "#client/conversation-state.js";
|
|
3
6
|
export { defaultMessageReducer, type EveAuthorizationChallenge, type EveAuthorizationOutcome, type EveAuthorizationPart, type EveMessageData, type EveDynamicToolPart, type EveMessageInputRequest, type EveMessage, type EveMessageMetadata, type EveMessagePart, type EveMessageToolMetadata, } from "#client/message-reducer.js";
|
package/dist/src/react/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defaultMessageReducer}from"#client/message-reducer.js";import{useEveAgent}from"#react/use-eve-agent.js";export{defaultMessageReducer,useEveAgent};
|
|
1
|
+
import{openConversationInputs}from"#client/conversation-state.js";import{conversationReducer}from"#client/conversation-reducer.js";import{defaultMessageReducer}from"#client/message-reducer.js";import{useEveAgent}from"#react/use-eve-agent.js";export{conversationReducer,defaultMessageReducer,openConversationInputs,useEveAgent};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type EveAgentStoreCallbacks, type EveAgentStoreSnapshot, type EveAgentStoreStatus, type PrepareSend } from "#client/eve-agent-store.js";
|
|
2
2
|
import type { EveAgentReducer } from "#client/reducer.js";
|
|
3
3
|
import type { ClientSession } from "#client/session.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { ConversationState } from "#client/conversation-state.js";
|
|
5
5
|
import type { MessageStreamEvent } from "#protocol/message.js";
|
|
6
6
|
import type { UserContent } from "ai";
|
|
7
7
|
import type { CancelSessionResult, ClientAuth, HeadersValue, RespondTurnOptions, SendTurnOptions, ClientSessionState } from "#client/types.js";
|
|
@@ -17,9 +17,9 @@ export type { PrepareSend };
|
|
|
17
17
|
*/
|
|
18
18
|
export type UseEveAgentStatus = EveAgentStoreStatus;
|
|
19
19
|
/**
|
|
20
|
-
* Snapshot of an eve agent session: `
|
|
21
|
-
* (the
|
|
22
|
-
* and `error`.
|
|
20
|
+
* Snapshot of an eve agent session: `conversation` (canonical state), `data`
|
|
21
|
+
* (the selected view), `events` (the authoritative server stream), `session`
|
|
22
|
+
* (resumable cursor), `status`, and `error`.
|
|
23
23
|
*/
|
|
24
24
|
export type UseEveAgentSnapshot<TData> = EveAgentStoreSnapshot<TData>;
|
|
25
25
|
/**
|
|
@@ -82,6 +82,8 @@ export interface UseEveAgentOptions<TData> extends EveAgentStoreCallbacks<TData>
|
|
|
82
82
|
* @default true
|
|
83
83
|
*/
|
|
84
84
|
readonly optimistic?: boolean;
|
|
85
|
+
/** Follow each subagent call's session into `conversation.agents` while mounted. @default false */
|
|
86
|
+
readonly followSubagents?: boolean;
|
|
85
87
|
/**
|
|
86
88
|
* Prewarm an owned session when true. React observes this value across renders;
|
|
87
89
|
* changing it from false to true prepares the current session, and reset checks
|
|
@@ -103,7 +105,7 @@ export interface UseEveAgentOptions<TData> extends EveAgentStoreCallbacks<TData>
|
|
|
103
105
|
readonly resume?: boolean;
|
|
104
106
|
readonly session?: ClientSession;
|
|
105
107
|
}
|
|
106
|
-
export declare function useEveAgent(options?: UseEveAgentOptions<
|
|
108
|
+
export declare function useEveAgent(options?: UseEveAgentOptions<ConversationState>): UseEveAgentHelpers<ConversationState>;
|
|
107
109
|
export declare function useEveAgent<TData>(options: UseEveAgentOptions<TData> & {
|
|
108
110
|
readonly reducer: EveAgentReducer<TData>;
|
|
109
111
|
}): UseEveAgentHelpers<TData>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EveAgentStore,attachEveAgentStore,detachEveAgentStore}from"#client/eve-agent-store.js";import{
|
|
1
|
+
import{EveAgentStore,attachEveAgentStore,detachEveAgentStore}from"#client/eve-agent-store.js";import{conversationReducer}from"#client/conversation-reducer.js";import{useCallback,useEffect,useMemo,useRef,useState,useSyncExternalStore}from"react";import{resolveEveAgentHost}from"#client/agent-host.js";function useEveAgent(e={}){let t=useRef(void 0),n=useRef(e.resume??!1),[r,i]=useState(n.current),[a,o]=useState(0),s=e.prewarm??!1;if(!t.current){if(n.current&&e.initialSession===void 0&&e.session===void 0)throw Error(`useEveAgent({ resume: true }) requires initialSession or session.`);let r=e.reducer??conversationReducer;t.current=new EveAgentStore({auth:e.auth,headers:e.headers,host:resolveEveAgentHost({agent:e.agent,host:e.host}),initialEvents:e.initialEvents,initialSession:e.initialSession,optimistic:e.optimistic,followSubagents:e.followSubagents,reducer:r,session:e.session})}let c=t.current;c.setCallbacks({onError:e.onError,onEvent:e.onEvent,onFinish:e.onFinish,onSessionChange:e.onSessionChange,prepareSend:e.prepareSend});let l=useCallback(e=>c.subscribe(e),[c]),u=useSyncExternalStore(l,()=>c.snapshot,()=>c.snapshot);useEffect(()=>{let e=setTimeout(()=>attachEveAgentStore(c),0);return()=>{clearTimeout(e),detachEveAgentStore(c)}},[c]),useEffect(()=>{if(!s)return;let e=setTimeout(()=>void c.prewarm().catch(()=>{}),0);return()=>clearTimeout(e)},[a,s,c]),useEffect(()=>{if(!n.current)return;let e=!0,finish=()=>{e&&i(!1)},t=setTimeout(()=>void c.resume().then(finish,finish),0);return()=>{e=!1,clearTimeout(t)}},[c]);let d=useCallback(()=>c.cancel(),[c]),f=useCallback(()=>{c.reset(),o(e=>e+1)},[c]),p=useCallback(()=>c.prewarm(),[c]),m=useCallback(()=>c.resume(),[c]),h=useCallback((e,t)=>c.send({...t,message:e}),[c]),g=useCallback((e,t)=>c.send({...t,inputResponses:e}),[c]),_=r&&u.status===`ready`?{...u,status:`resuming`}:u;return useMemo(()=>({..._,cancel:d,prewarm:p,reset:f,respond:g,resume:m,send:h}),[d,p,f,g,m,h,_])}export{useEveAgent};
|
|
@@ -33,6 +33,8 @@ export declare function createAuthorizationExecution(options?: {
|
|
|
33
33
|
readonly completeAuthorization?: typeof completeScopedAuthorization;
|
|
34
34
|
}): {
|
|
35
35
|
complete: (scoped: ScopedAuthorization) => Promise<void>;
|
|
36
|
+
/** Whether this execution completed a sign-in for `scoped`. */
|
|
37
|
+
isJustAuthorized(scoped: ScopedAuthorization): boolean;
|
|
36
38
|
getToken(scoped: ScopedAuthorization): Promise<TokenResult>;
|
|
37
39
|
requireAuth: (scoped: ScopedAuthorization, cause?: unknown) => never;
|
|
38
40
|
handleError(error: unknown, scoped?: ScopedAuthorization): Promise<AuthorizationSignal>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{contextStorage,loadContext}from"#context/container.js";import{AuthKey}from"#context/keys.js";import{consumeAuthorizationResult,createAuthorizationAttempt,requestAuthorization}from"#harness/authorization.js";import{ConnectionAuthorizationFailedError,ConnectionAuthorizationRequiredError,isConnectionAuthorizationRequiredError}from"#connections/errors.js";import{supportsInteractiveAuthorization}from"#shared/connection-types.js";import{isAsyncIterable}from"#shared/async-iterable.js";import{evictCachedToken,readCachedToken,writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal,resolveConnectionPrincipalFromAuth}from"#runtime/connections/principal.js";const LOCAL_HTTP_VERCEL_CONNECT_HOSTNAMES=new Set([`127.0.0.1`,`[::1]`]);function createAuthorizationExecution(e={}){let t=new Set;async function complete(n){let r=n.instanceId??n.scope;!t.has(r)&&await(e.completeAuthorization??completeScopedAuthorization)(n)&&t.add(r)}function requireAuth(e,n){throw new ScopedAuthorizationRequiredError(e,t.has(e.instanceId??e.scope),n)}return{complete,async getToken(e){await complete(e);try{return await resolveScopedToken(e)}catch(t){if(!isConnectionAuthorizationRequiredError(t))throw t;return requireAuth(e,t)}},requireAuth,async handleError(e,n){return n!==void 0&&isConnectionAuthorizationRequiredError(e)?await handleAuthorizationError(new ScopedAuthorizationRequiredError(n,t.has(n.instanceId??n.scope),e),{evictToken:!1}):await handleAuthorizationError(e)},run:executeWithAuthorization}}var ScopedAuthorizationRequiredError=class extends Error{scoped;justAuthorized;constructor(e,t,n){super(`Authorization required.`,{cause:n}),this.name=`ScopedAuthorizationRequiredError`,this.scoped=e,this.justAuthorized=t}};function isScopedAuthorizationRequiredError(e){return e instanceof Error&&e.name===`ScopedAuthorizationRequiredError`}async function handleAuthorizationError(e,t={evictToken:!0}){if(!isScopedAuthorizationRequiredError(e))throw e;let{scoped:n}=e;if(e.justAuthorized)throw new ConnectionAuthorizationFailedError(n.scope,{message:`Authorization for "${n.scope}" failed: the service rejected the token immediately after authorization.`,reason:`token_rejected_after_authorization`,retryable:!1});t.evictToken&&await evictScopedToken(n);let r=await startScopedAuthorization(n);if(r!==void 0)return r;throw supportsInteractiveAuthorization(n.authorization)?new ConnectionAuthorizationFailedError(n.scope,{message:`Authorization for "${n.scope}" requires sign-in, but no authorization callback URL could be minted for this run (missing session context).`,reason:`authorization_callback_unavailable`,retryable:!1}):e.cause??new ConnectionAuthorizationRequiredError(n.scope)}function executeWithAuthorization(e){try{let t=e();return isAsyncIterable(t)?handleIterable(t):Promise.resolve(t).catch(handleAuthorizationError)}catch(e){return handleAuthorizationError(e)}}async function*handleIterable(e){try{for await(let t of e)yield t}catch(e){yield await handleAuthorizationError(e)}}async function resolveScopedToken(e){let{scope:t,authorization:n,connection:r}=e,i=e.instanceId??t,a=contextStorage.getStore(),o=resolveScopedPrincipal(e,a);if(a===void 0)return await n.getToken({connection:r,principal:o});let s=principalKey(o),c=readCachedToken(a,i,s);if(c!==void 0)return c;let l=await n.getToken({connection:r,principal:o});return writeCachedToken(a,i,s,l),l}async function evictScopedToken(e){let{scope:t,authorization:n,connection:r}=e,i=e.instanceId??t,a=contextStorage.getStore();if(a===void 0)return;let o;try{o=resolveScopedPrincipal(e,a),evictCachedToken(a,i,principalKey(o))}catch{return}try{await n.evict?.({connection:r,principal:o})}catch{}}async function completeScopedAuthorization(e){let{scope:t,authorization:n,connection:r}=e;if(!supportsInteractiveAuthorization(n))return!1;let i=consumeAuthorizationResult(t,e.instanceId);if(i===void 0)return!1;let a=n,o=loadContext(),s=i.principal??resolveScopedPrincipal(e,o),c=await a.completeAuthorization({callbackUrl:i.hookUrl,connection:r,principal:s,resume:i.resume,callback:i.callback});return writeCachedToken(o,e.instanceId??t,principalKey(s),c),!0}async function startScopedAuthorization(e){let{scope:t,authorization:n,connection:r}=e;if(!supportsInteractiveAuthorization(n))return;let i=createAuthorizationAttempt(t);if(i===void 0)return;let a=n,o=resolveScopedPrincipal(e),s=
|
|
1
|
+
import{contextStorage,loadContext}from"#context/container.js";import{AuthKey}from"#context/keys.js";import{consumeAuthorizationResult,createAuthorizationAttempt,requestAuthorization}from"#harness/authorization.js";import{ConnectionAuthorizationFailedError,ConnectionAuthorizationRequiredError,isConnectionAuthorizationRequiredError}from"#connections/errors.js";import{supportsInteractiveAuthorization}from"#shared/connection-types.js";import{isAsyncIterable}from"#shared/async-iterable.js";import{evictCachedToken,readCachedToken,writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal,resolveConnectionPrincipalFromAuth}from"#runtime/connections/principal.js";const LOCAL_HTTP_VERCEL_CONNECT_HOSTNAMES=new Set([`127.0.0.1`,`[::1]`]);function createAuthorizationExecution(e={}){let t=new Set;async function complete(n){let r=n.instanceId??n.scope;!t.has(r)&&await(e.completeAuthorization??completeScopedAuthorization)(n)&&t.add(r)}function requireAuth(e,n){throw new ScopedAuthorizationRequiredError(e,t.has(e.instanceId??e.scope),n)}return{complete,isJustAuthorized(e){return t.has(e.instanceId??e.scope)},async getToken(e){await complete(e);try{return await resolveScopedToken(e)}catch(t){if(!isConnectionAuthorizationRequiredError(t))throw t;return requireAuth(e,t)}},requireAuth,async handleError(e,n){return n!==void 0&&isConnectionAuthorizationRequiredError(e)?await handleAuthorizationError(new ScopedAuthorizationRequiredError(n,t.has(n.instanceId??n.scope),e),{evictToken:!1}):await handleAuthorizationError(e)},run:executeWithAuthorization}}var ScopedAuthorizationRequiredError=class extends Error{scoped;justAuthorized;constructor(e,t,n){super(`Authorization required.`,{cause:n}),this.name=`ScopedAuthorizationRequiredError`,this.scoped=e,this.justAuthorized=t}};function isScopedAuthorizationRequiredError(e){return e instanceof Error&&e.name===`ScopedAuthorizationRequiredError`}async function handleAuthorizationError(e,t={evictToken:!0}){if(!isScopedAuthorizationRequiredError(e))throw e;let{scoped:n}=e;if(e.justAuthorized)throw new ConnectionAuthorizationFailedError(n.scope,{message:`Authorization for "${n.scope}" failed: the service rejected the token immediately after authorization.`,reason:`token_rejected_after_authorization`,retryable:!1});t.evictToken&&await evictScopedToken(n);let r=await startScopedAuthorization(n);if(r!==void 0)return r;throw supportsInteractiveAuthorization(n.authorization)?new ConnectionAuthorizationFailedError(n.scope,{message:`Authorization for "${n.scope}" requires sign-in, but no authorization callback URL could be minted for this run (missing session context).`,reason:`authorization_callback_unavailable`,retryable:!1}):e.cause??new ConnectionAuthorizationRequiredError(n.scope)}function executeWithAuthorization(e){try{let t=e();return isAsyncIterable(t)?handleIterable(t):Promise.resolve(t).catch(handleAuthorizationError)}catch(e){return handleAuthorizationError(e)}}async function*handleIterable(e){try{for await(let t of e)yield t}catch(e){yield await handleAuthorizationError(e)}}async function resolveScopedToken(e){let{scope:t,authorization:n,connection:r}=e,i=e.instanceId??t,a=contextStorage.getStore(),o=resolveScopedPrincipal(e,a);if(a===void 0)return await n.getToken({connection:r,principal:o});let s=principalKey(o),c=readCachedToken(a,i,s);if(c!==void 0)return c;let l=await n.getToken({connection:r,principal:o});return writeCachedToken(a,i,s,l),l}async function evictScopedToken(e){let{scope:t,authorization:n,connection:r}=e,i=e.instanceId??t,a=contextStorage.getStore();if(a===void 0)return;let o;try{o=resolveScopedPrincipal(e,a),evictCachedToken(a,i,principalKey(o))}catch{return}try{await n.evict?.({connection:r,principal:o})}catch{}}async function completeScopedAuthorization(e){let{scope:t,authorization:n,connection:r}=e;if(!supportsInteractiveAuthorization(n))return!1;let i=consumeAuthorizationResult(t,e.instanceId);if(i===void 0)return!1;let a=n,o=loadContext(),s=i.principal??resolveScopedPrincipal(e,o),c=await a.completeAuthorization({callbackUrl:i.hookUrl,connection:r,principal:s,resume:i.resume,callback:i.callback});return writeCachedToken(o,e.instanceId??t,principalKey(s),c),!0}async function startScopedAuthorization(e){let{scope:t,authorization:n,connection:r}=e;if(!supportsInteractiveAuthorization(n))return;let i=createAuthorizationAttempt(t);if(i===void 0)return;let a=n,o=resolveScopedPrincipal(e),s=e.boundResponder??contextStorage.getStore()?.get(AuthKey)??void 0,c=resolveAuthorizationCallbackUrl({authorization:n,callbackUrl:i.hookUrl}),{challenge:l,resume:u}=await a.startAuthorization({callbackUrl:c,connection:r,principal:o});return requestAuthorization([{attemptId:i.attemptId,challenge:stampChallengeDisplayName(l,n),grant:n.vercelConnect?.connector,hookUrl:c,instanceId:e.instanceId,name:t,principal:o,principalId:s?.principalId,requester:s,resume:u}])}function resolveScopedPrincipal(e,t){return e.boundResponder===void 0?resolveConnectionPrincipal(e.scope,e.authorization,t):resolveConnectionPrincipalFromAuth(e.scope,e.authorization,e.boundResponder,t)}function resolveAuthorizationCallbackUrl(e){if(e.authorization.vercelConnect===void 0)return e.callbackUrl;let t;try{t=new URL(e.callbackUrl)}catch{return e.callbackUrl}return t.protocol!==`http:`||!LOCAL_HTTP_VERCEL_CONNECT_HOSTNAMES.has(t.hostname)?e.callbackUrl:(t.hostname=`localhost`,t.toString())}function stampChallengeDisplayName(e,t){let n=t.displayName??e.displayName;return n===e.displayName?e:{...e,displayName:n}}export{completeScopedAuthorization,createAuthorizationExecution,evictScopedToken,handleAuthorizationError,resolveAuthorizationCallbackUrl,resolveScopedToken,stampChallengeDisplayName};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployResult,projectResolutionFromDeployment}from"../project-resolution.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{syncHostFrameworkPreset}from"../vercel-project-framework.js";import{detectPackageManager}from"#setup/package-manager.js";import{runVercel}from"#setup/primitives/run-vercel.js";import{HumanActionRequiredError}from"#setup/human-action.js";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{packageManagerInstallSucceeded,runPackageManagerInstall}from"#setup/primitives/pm/run.js";const VERCEL_DEPLOY_ENV={VERCEL_USE_EXPERIMENTAL_FRAMEWORKS:`1`};function deployProject(e){let t=e.deps??{runVercel,detectPackageManager,runPackageManagerInstall,detectDeployment,syncHostFrameworkPreset};return{id:`deploy-project`,shouldRun(t){return e.skip||!t.deploymentPending?!1:hasVercelProject(t)||e.ensureLinkedProject===`interactive-vercel-link`},async gather(){return{headless:e.headless??!1}},async perform({state:n,input:r,signal:i}){let a=requireProjectPath(n),{log:o}=e.prompter,s=createPromptCommandOutput(o),c=n.project;if(!isProjectResolved(c)){if(r.headless)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Deployment needs this directory linked to a Vercel project.`});if(o.message(`Linking this directory to a Vercel project before deployment...`),!await t.runVercel([`link`],{cwd:a,signal:i}))throw i?.throwIfAborted(),Error(`Vercel project linking failed. Deployment did not start.`);if(c=mergeProjectResolution(c,projectResolutionFromDeployment(await t.detectDeployment(a,{signal:i}))),!isProjectResolved(c))throw Error(`Vercel project linking failed. Deployment did not start.`)}if(await t.syncHostFrameworkPreset(e.prompter,a,s,i?{signal:i}:{}),!n.deploymentDependenciesInstalled){let e=await t.detectPackageManager(a),n=await withPhase(o,`Installing project dependencies before deployment (${e.kind} install)...`,()=>t.runPackageManagerInstall(e.kind,a,{onOutput:s,signal:i}));if(!packageManagerInstallSucceeded(n))throw i?.throwIfAborted(),Error(`Dependency installation failed. Deployment did not start.`)}let l=[`deploy`,`--prod`,`--yes`,...r.headless?[`--non-interactive`]:[]],u=await withPhase(o,`Deploying the agent to Vercel production...`,()=>t.runVercel(l,{cwd:a,extraEnv:VERCEL_DEPLOY_ENV,nonInteractive:r.headless,onOutput:s,signal:i}));if(i?.throwIfAborted(),!u)throw o.error("`vercel deploy --prod` failed.
|
|
1
|
+
import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployResult,projectResolutionFromDeployment}from"../project-resolution.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{syncHostFrameworkPreset}from"../vercel-project-framework.js";import{detectPackageManager}from"#setup/package-manager.js";import{runVercel}from"#setup/primitives/run-vercel.js";import{HumanActionRequiredError}from"#setup/human-action.js";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{packageManagerInstallSucceeded,runPackageManagerInstall}from"#setup/primitives/pm/run.js";const VERCEL_DEPLOY_ENV={VERCEL_USE_EXPERIMENTAL_FRAMEWORKS:`1`};function deployProject(e){let t=e.deps??{runVercel,detectPackageManager,runPackageManagerInstall,detectDeployment,syncHostFrameworkPreset};return{id:`deploy-project`,shouldRun(t){return e.skip||!t.deploymentPending?!1:hasVercelProject(t)||e.ensureLinkedProject===`interactive-vercel-link`},async gather(){return{headless:e.headless??!1}},async perform({state:n,input:r,signal:i}){let a=requireProjectPath(n),{log:o}=e.prompter,s=createPromptCommandOutput(o),c=n.project;if(!isProjectResolved(c)){if(r.headless)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Deployment needs this directory linked to a Vercel project.`});if(o.message(`Linking this directory to a Vercel project before deployment...`),!await t.runVercel([`link`],{cwd:a,signal:i}))throw i?.throwIfAborted(),Error(`Vercel project linking failed. Deployment did not start.`);if(c=mergeProjectResolution(c,projectResolutionFromDeployment(await t.detectDeployment(a,{signal:i}))),!isProjectResolved(c))throw Error(`Vercel project linking failed. Deployment did not start.`)}if(await t.syncHostFrameworkPreset(e.prompter,a,s,i?{signal:i}:{}),!n.deploymentDependenciesInstalled){let e=await t.detectPackageManager(a),n=await withPhase(o,`Installing project dependencies before deployment (${e.kind} install)...`,()=>t.runPackageManagerInstall(e.kind,a,{onOutput:s,signal:i}));if(!packageManagerInstallSucceeded(n))throw i?.throwIfAborted(),Error(`Dependency installation failed. Deployment did not start.`)}let l=[`deploy`,`--prod`,`--yes`,`--logs`,...r.headless?[`--non-interactive`]:[]],u=await withPhase(o,`Deploying the agent to Vercel production...`,()=>t.runVercel(l,{cwd:a,extraEnv:VERCEL_DEPLOY_ENV,nonInteractive:r.headless,onOutput:s,signal:i}));if(i?.throwIfAborted(),!u)throw o.error("`vercel deploy --prod` failed. Check the build logs above for the first error, then retry. To retrieve the logs again, run `vercel inspect <deployment-url> --logs` from this project."),Error("Deployment failed. Check the build logs above, or run `vercel inspect <deployment-url> --logs` from this project.");let d=await withPhase(o,`Pulling Vercel environment variables into .env.local...`,()=>t.runVercel([`env`,`pull`,`--yes`],{cwd:a,nonInteractive:r.headless,onOutput:s,signal:i}));i?.throwIfAborted(),d||o.warning(`Deployment succeeded, but pulling Vercel environment variables did not complete.`);let f=await t.detectDeployment(a,{signal:i}),p=f.state===`deployed`?f.productionUrl:void 0;return p===void 0?o.warning(`Deployment succeeded, but eve could not verify its production URL.`):o.info(`Production URL: ${p}`),{project:projectResolutionFromDeployResult(c,{deployed:!0,productionUrl:p}),deploymentPending:!1,deploymentDependenciesInstalled:!0}},apply(e,t){return{...e,project:t.project,deploymentPending:t.deploymentPending,deploymentDependenciesInstalled:t.deploymentDependenciesInstalled}}}}export{deployProject};
|
|
@@ -15,7 +15,11 @@ export default nextConfig;
|
|
|
15
15
|
|
|
16
16
|
export default await withEve({
|
|
17
17
|
services: {
|
|
18
|
-
web: {
|
|
18
|
+
web: {
|
|
19
|
+
framework: "nextjs",
|
|
20
|
+
root: "apps/web",
|
|
21
|
+
buildCommand: "node ../../node_modules/next/dist/bin/next build",
|
|
22
|
+
},
|
|
19
23
|
},
|
|
20
24
|
routes: [
|
|
21
25
|
{ src: "^(.*)$", destination: { type: "service", service: "web" } },
|
|
@@ -27,4 +31,4 @@ import { withEve } from "eve/next";
|
|
|
27
31
|
const nextConfig: NextConfig = {};
|
|
28
32
|
|
|
29
33
|
export default withEve(nextConfig);
|
|
30
|
-
`,NEXT_HOSTED_CONFIG,PEER_SERVICE_NEXT_CONFIG]);let l;if(e.hosting===`vercel`){let e=join(r.environmentRoot,`vercel.ts`),t=join(r.environmentRoot,`vercel.json`);if(await assertInstallerOwned(e,[PEER_SERVICE_VERCEL_CONFIG]),await n.pathExists(t))throw Error(`Could not configure Vercel services because ${t} already exists. Preserve it and compose eve/vercel manually.`);await n.writeTextFile(c,PEER_SERVICE_NEXT_CONFIG,{force:!0}),await n.writeTextFile(e,PEER_SERVICE_VERCEL_CONFIG,{force:!0}),await configurePeerServiceScripts(r.environmentRoot,n),l=`dev:all`}else await n.writeTextFile(c,NEXT_HOSTED_CONFIG,{force:!0}),l=`dev:web`;return t.presenter.log.success(`Configured channel: web`),{facts:[{label:``,value:`Start locally with \`${runScriptCommand(e.packageManager,l)}\`.`}]}}const WEB_SETUP=defineSetupIntegration({kind:`web`,label:`Web Chat`,hint:`Browser-based chat interface`,prepare:prepareWebSetup,apply:applyWebSetup});export{WEB_SETUP,applyWebSetup,prepareWebSetup};
|
|
34
|
+
`,NEXT_HOSTED_CONFIG,PEER_SERVICE_NEXT_CONFIG]);let l;if(e.hosting===`vercel`){let e=join(r.environmentRoot,`vercel.ts`),t=join(r.environmentRoot,`vercel.json`);if(await assertInstallerOwned(e,[PEER_SERVICE_VERCEL_CONFIG,PEER_SERVICE_VERCEL_CONFIG.replace(/ web: \{[^}]+\},/,` web: { framework: "nextjs", root: "apps/web" },`)]),await n.pathExists(t))throw Error(`Could not configure Vercel services because ${t} already exists. Preserve it and compose eve/vercel manually.`);await n.writeTextFile(c,PEER_SERVICE_NEXT_CONFIG,{force:!0}),await n.writeTextFile(e,PEER_SERVICE_VERCEL_CONFIG,{force:!0}),await configurePeerServiceScripts(r.environmentRoot,n),l=`dev:all`}else await n.writeTextFile(c,NEXT_HOSTED_CONFIG,{force:!0}),l=`dev:web`;return t.presenter.log.success(`Configured channel: web`),{facts:[{label:``,value:`Start locally with \`${runScriptCommand(e.packageManager,l)}\`.`}]}}const WEB_SETUP=defineSetupIntegration({kind:`web`,label:`Web Chat`,hint:`Browser-based chat interface`,prepare:prepareWebSetup,apply:applyWebSetup});export{WEB_SETUP,applyWebSetup,prepareWebSetup};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{parseChatGptModelSelection}from"../../../shared/chatgpt-model.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{blockingCreateInPlaceEntries}from"../create-in-place.js";import{DEFAULT_CONNECT_PACKAGE_VERSION,resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_CHANNEL_TEMPLATES}from"./web-template.js";import{AGENT_INSTRUCTIONS_TEMPLATE}from"./instructions-template.js";import{SCAFFOLDED_AGENT_PATHS}from"./agent-paths.js";import{mkdir,readdir,stat}from"node:fs/promises";import{basename,join,resolve}from"node:path";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,DEFAULT_AI_PACKAGE_VERSION=`^7.0.105`,DEFAULT_ZOD_PACKAGE_VERSION=`4.5.4`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.
|
|
1
|
+
import{pinnedNodeEngineMajor}from"../../node-engine.js";import{parseChatGptModelSelection}from"../../../shared/chatgpt-model.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{pathExists,writeTextFile}from"../files.js";import{blockingCreateInPlaceEntries}from"../create-in-place.js";import{DEFAULT_CONNECT_PACKAGE_VERSION,resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{WEB_CHANNEL_TEMPLATES}from"./web-template.js";import{AGENT_INSTRUCTIONS_TEMPLATE}from"./instructions-template.js";import{SCAFFOLDED_AGENT_PATHS}from"./agent-paths.js";import{mkdir,readdir,stat}from"node:fs/promises";import{basename,join,resolve}from"node:path";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,DEFAULT_AI_PACKAGE_VERSION=`^7.0.105`,DEFAULT_ZOD_PACKAGE_VERSION=`4.5.4`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.70.0`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e,t){return{[SCAFFOLDED_AGENT_PATHS.config]:renderAgentTemplate(e,t),[SCAFFOLDED_AGENT_PATHS.channel]:WEB_CHANNEL_TEMPLATES.default,[SCAFFOLDED_AGENT_PATHS.instructions]:AGENT_INSTRUCTIONS_TEMPLATE}}function renderAgentTemplate(e,t){let n=parseChatGptModelSelection(e);return n===void 0?BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t)):`import { defineAgent } from "eve";\nimport { chatgpt } from "eve/models/openai";\n\nexport default defineAgent({\n model: chatgpt(${JSON.stringify(n)}),\n${reasoningTemplateLine(t)}});\n`}function reasoningTemplateLine(e){return e===void 0||e===`provider-default`?``:` reasoning: "${e}",\n`}function renderTemplate(e,t){return e===BASE_AGENT_TEMPLATE&&parseChatGptModelSelection(t.model)!==void 0?renderAgentTemplate(t.model,t.reasoning):e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_REASONING__`,reasoningTemplateLine(t.reasoning)).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__",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const WEB_APP_TEMPLATE_FILES: {
|
|
2
|
-
readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport type { UserContent } from "ai";\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon, BrainIcon, PlusIcon, SquareIcon } from "lucide-react";\nimport { useState } from "react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n ConversationTopFade,\n} from "@/components/ai-elements/conversation";\nimport { Message, MessageContent } from "@/components/ai-elements/message";\nimport {\n PromptInput,\n PromptInputButton,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n usePromptInputAttachments,\n} from "@/components/ai-elements/prompt-input";\nimport { Shimmer } from "@/components/ai-elements/shimmer";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\nimport { WEB_CHAT_AGENT } from "@/app/eve-agent";\n\nconst DEFAULT_AGENT_NAME = "__EVE_INIT_APP_NAME__";\nconst AGENT_NAME = WEB_CHAT_AGENT ?? DEFAULT_AGENT_NAME;\n\nexport function AgentChat({\n sessionId,\n sessionless = false,\n}: {\n readonly sessionId?: string;\n readonly sessionless?: boolean;\n}) {\n const [cancellationError, setCancellationError] = useState<string>();\n const [hasInputText, setHasInputText] = useState(false);\n const agent = useEveAgent({\n agent: WEB_CHAT_AGENT,\n initialSession:\n sessionId === undefined\n ? undefined\n : {\n sessionId,\n streamIndex: 0,\n },\n resume: sessionId !== undefined,\n onSessionChange(session) {\n if (sessionId === undefined && session !== undefined) {\n // Next patches window.history to navigate, which would detach the active stream.\n History.prototype.replaceState.call(\n window.history,\n window.history.state,\n "",\n `/s/${encodeURIComponent(session.sessionId)}`,\n );\n }\n },\n });\n\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isResuming = agent.status === "resuming";\n const isEmpty = agent.data.messages.length === 0;\n const lastMessage = agent.data.messages.at(-1);\n const isPendingAssistantShell =\n lastMessage?.role === "assistant" &&\n lastMessage.parts.every((part) => part.type === "step-start");\n const showPendingThinking =\n isBusy &&\n (agent.status === "submitted" || lastMessage?.role !== "assistant" || isPendingAssistantShell);\n const turnFailure = isBusy || isResuming ? undefined : getLatestTurnFailure(agent.events);\n const errorMessage = cancellationError ?? agent.error?.message ?? turnFailure;\n const hasConversationContent = sessionless || !isEmpty || errorMessage !== undefined;\n const showConversationLayout = isResuming || hasConversationContent;\n const activeSessionId = sessionId ?? agent.session?.sessionId;\n\n const requestCancellation = () => {\n setCancellationError(undefined);\n void agent.cancel().catch((error: unknown) => {\n setCancellationError(toErrorMessage(error));\n });\n };\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if ((text.length === 0 && message.files.length === 0) || isResuming) return;\n\n setHasInputText(false);\n setCancellationError(undefined);\n const options = isBusy ? { turnPolicy: "steer" as const } : undefined;\n\n if (message.files.length === 0) {\n await agent.send(text, options);\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(parts, options);\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea\n disabled={isResuming}\n onChange={(event) => setHasInputText(event.currentTarget.value.trim().length > 0)}\n placeholder="Send a message…"\n />\n <ComposerAction\n hasInputText={hasInputText}\n isBusy={isBusy}\n isResuming={isResuming}\n onCancel={requestCancellation}\n />\n </PromptInput>\n );\n\n return (\n <main className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">\n {showConversationLayout ? (\n <ChatHeader canStartNewChat={activeSessionId !== undefined} />\n ) : null}\n\n {showConversationLayout ? (\n <Conversation\n className="min-h-0 flex-1"\n initial={sessionId === undefined ? undefined : false}\n resize={activeSessionId === undefined ? "smooth" : "instant"}\n scrollRestorationKey={\n isEmpty || activeSessionId === undefined\n ? undefined\n : `eve:web-chat-scroll:${activeSessionId}`\n }\n >\n <ConversationTopFade className="top-14" />\n <ConversationContent className="mx-auto w-full max-w-3xl gap-6 px-4 pt-20 pb-36 sm:px-6">\n {agent.data.messages.map((message, index) =>\n showPendingThinking &&\n isPendingAssistantShell &&\n message.id === lastMessage.id ? null : (\n <AgentMessage\n canRespond={!isBusy && !isResuming}\n isStreaming={\n agent.status === "streaming" && index === agent.data.messages.length - 1\n }\n key={message.id}\n message={message}\n onInputResponses={(inputResponses) => {\n setCancellationError(undefined);\n return agent.respond(inputResponses);\n }}\n />\n ),\n )}\n {showPendingThinking ? <PendingThinking /> : null}\n {errorMessage ? <ErrorMessage message={errorMessage} /> : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n ) : null}\n\n <div\n className={cn(\n "mx-auto w-full px-4 sm:px-6",\n showConversationLayout\n ? "fixed bottom-0 left-1/2 z-20 max-w-3xl -translate-x-1/2 bg-gradient-to-t from-background via-background to-transparent pt-4 pb-6"\n : "flex max-w-xl flex-1 flex-col items-center justify-center gap-8 pb-[10vh]",\n )}\n >\n {showConversationLayout ? null : (\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 )}\n <div className="w-full">{composer}</div>\n </div>\n </main>\n );\n}\n\nfunction ComposerAction({\n hasInputText,\n isBusy,\n isResuming,\n onCancel,\n}: {\n readonly hasInputText: boolean;\n readonly isBusy: boolean;\n readonly isResuming: boolean;\n readonly onCancel: () => void;\n}) {\n const attachments = usePromptInputAttachments();\n const canSubmit = hasInputText || attachments.files.length > 0;\n\n if (!isBusy || canSubmit) {\n return <PromptInputSubmit disabled={isResuming} />;\n }\n\n return (\n <PromptInputButton\n aria-label="Stop"\n className="absolute right-2.5 bottom-2.5"\n onClick={onCancel}\n variant="outline"\n >\n <SquareIcon className="size-3 fill-current" />\n </PromptInputButton>\n );\n}\n\nfunction ErrorMessage({ message }: { readonly message: string }) {\n return (\n <Message className="max-w-full" from="assistant">\n <MessageContent>\n <div\n className="flex w-full items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm"\n role="alert"\n >\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">{message}</p>\n </div>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction ChatHeader({ canStartNewChat }: { readonly canStartNewChat: boolean }) {\n return (\n <header className="pointer-events-none fixed top-0 right-0 left-0 z-20 h-14">\n <div className="relative mx-auto flex h-full w-full max-w-3xl items-center justify-center bg-background px-24">\n <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>\n {canStartNewChat ? (\n <Button\n aria-label="Start a new chat"\n className="pointer-events-auto fixed top-3 right-6 pr-4"\n onClick={() => window.location.assign("/s")}\n size="sm"\n type="button"\n variant="ghost"\n >\n <PlusIcon className="size-4" />\n <span className="hidden font-normal text-sm sm:inline">New chat</span>\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n\nfunction PendingThinking() {\n return (\n <Message aria-live="polite" from="assistant">\n <MessageContent>\n <div className="mb-4 flex w-full items-center gap-2 text-muted-foreground text-sm">\n <BrainIcon className="size-4" />\n <Shimmer duration={1}>Thinking</Shimmer>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : "Unable to cancel the response.";\n}\n\nfunction getLatestTurnFailure(\n events: ReturnType<typeof useEveAgent>["events"],\n): string | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n\n if (event.type === "turn.failed") {\n return event.data.code === "MODEL_CALL_FAILED"\n ? "The model is temporarily unavailable. Please try again."\n : event.data.message;\n }\n\n if (event.type === "turn.completed" || event.type === "turn.cancelled") {\n return undefined;\n }\n\n if (event.type === "message.received") {\n return undefined;\n }\n }\n\n return undefined;\n}\n';
|
|
3
|
-
readonly "app/_components/agent-message.tsx": '"use client";\n\nimport type {\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageInputRequest,\n EveMessagePart,\n} from "eve/react";\nimport { useState } from "react";\nimport {\n ArrowRightIcon,\n CheckCircleIcon,\n CheckIcon,\n ExternalLinkIcon,\n FileIcon,\n ImageIcon,\n KeyRoundIcon,\n XCircleIcon,\n} from "lucide-react";\nimport { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";\nimport {\n Question,\n QuestionInput,\n QuestionOption,\n QuestionOptions,\n QuestionPrompt,\n type QuestionResponse,\n QuestionSubmit,\n type QuestionValue,\n} from "@/components/ai-elements/question";\nimport { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";\nimport {\n BashToolContent,\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 const hasAssistantText =\n message.role === "assistant" &&\n message.parts.some((part) => part.type === "text" && part.text.length > 0);\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 hasAssistantText && part.type === "reasoning" ? null : (\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 )}\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 const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (inputRequest?.kind === "question") {\n return (\n <QuestionRequest\n canRespond={canRespond}\n inputRequest={inputRequest}\n inputResponse={part.toolMetadata?.eve?.inputResponse}\n onInputResponses={onInputResponses}\n />\n );\n }\n\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 {part.toolName === "bash" ? (\n <BashToolContent errorText={part.errorText} input={part.input} output={part.output} />\n ) : (\n <ToolInput input={part.input} />\n )}\n <InputRequestActions\n canRespond={canRespond}\n part={part}\n onInputResponses={onInputResponses}\n />\n {part.toolName === "bash" ? null : (\n <ToolOutput errorText={part.errorText} output={part.output} />\n )}\n </ToolContent>\n </Tool>\n );\n }\n }\n}\n\nfunction QuestionRequest({\n canRespond,\n inputRequest,\n inputResponse,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly inputRequest: EveMessageInputRequest;\n readonly inputResponse?: AgentInputResponse;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const hasOptions = (inputRequest.options?.length ?? 0) > 0;\n const acceptsFreeform = inputRequest.allowFreeform === true || !hasOptions;\n const [questionValue, setQuestionValue] = useState<QuestionValue>({\n selectedValues: inputResponse?.optionId ? [inputResponse.optionId] : [],\n text: inputResponse?.text ?? "",\n });\n\n const submitOption = (optionId: string) => {\n setQuestionValue((value) => ({ ...value, selectedValues: [optionId] }));\n return onInputResponses([\n {\n optionId,\n requestId: inputRequest.requestId,\n },\n ]);\n };\n\n const submitResponse = ({ selectedValues, text }: QuestionResponse) =>\n onInputResponses([\n {\n optionId: selectedValues[0],\n requestId: inputRequest.requestId,\n text,\n },\n ]);\n\n return (\n <Question\n disabled={!canRespond || inputResponse !== undefined}\n onSubmit={submitResponse}\n onValueChange={setQuestionValue}\n value={questionValue}\n >\n <QuestionPrompt>{inputRequest.prompt}</QuestionPrompt>\n {hasOptions ? (\n <QuestionOptions className="flex-col items-stretch" aria-label={inputRequest.prompt}>\n {inputRequest.options?.map((option, index) => (\n <QuestionOption\n className="justify-start px-3 py-2 text-left"\n key={option.id}\n onClick={() => void submitOption(option.id)}\n value={option.id}\n >\n <span className="min-w-0 flex-1">\n <span className="block text-foreground text-sm leading-tight">{option.label}</span>\n {option.description ? (\n <span className="block text-sm text-muted-foreground leading-tight">\n {option.description}\n </span>\n ) : null}\n </span>\n {inputResponse === undefined ? (\n <span aria-hidden="true" className="relative size-6 shrink-0">\n <span className="absolute inset-0 flex items-center justify-center rounded-full bg-foreground/8 text-xs text-muted-foreground transition-opacity group-hover/option:opacity-0 group-focus-visible/option:opacity-0">\n {index + 1}\n </span>\n <ArrowRightIcon className="absolute top-1/2 left-1/2 size-4 -translate-x-1/2 -translate-y-1/2 text-muted-foreground opacity-0 transition-[color,opacity] group-hover/option:text-foreground group-hover/option:opacity-100 group-focus-visible/option:opacity-100" />\n </span>\n ) : (\n <CheckIcon className="size-4 shrink-0 opacity-0 transition-opacity group-data-[state=checked]/option:opacity-100" />\n )}\n </QuestionOption>\n ))}\n </QuestionOptions>\n ) : null}\n {acceptsFreeform ? (\n <div className="relative">\n <QuestionInput\n aria-label="Answer"\n className={inputResponse === undefined ? "pr-12 pb-12" : undefined}\n placeholder="Type your answer…"\n />\n {inputResponse === undefined && questionValue.text.trim().length > 0 ? (\n <QuestionSubmit\n aria-label="Answer"\n className="absolute right-2 bottom-2"\n size="icon-sm"\n >\n <ArrowRightIcon />\n </QuestionSubmit>\n ) : null}\n </div>\n ) : null}\n </Question>\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';
|
|
2
|
+
readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport type { UserContent } from "ai";\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon, BrainIcon, PlusIcon, SquareIcon } from "lucide-react";\nimport { useState } from "react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n ConversationTopFade,\n} from "@/components/ai-elements/conversation";\nimport { Message, MessageContent } from "@/components/ai-elements/message";\nimport {\n PromptInput,\n PromptInputButton,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n usePromptInputAttachments,\n} from "@/components/ai-elements/prompt-input";\nimport { Shimmer } from "@/components/ai-elements/shimmer";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\nimport { WEB_CHAT_AGENT } from "@/app/eve-agent";\n\nconst DEFAULT_AGENT_NAME = "__EVE_INIT_APP_NAME__";\nconst AGENT_NAME = WEB_CHAT_AGENT ?? DEFAULT_AGENT_NAME;\n\nexport function AgentChat({\n sessionId,\n sessionless = false,\n}: {\n readonly sessionId?: string;\n readonly sessionless?: boolean;\n}) {\n const [cancellationError, setCancellationError] = useState<string>();\n const [hasInputText, setHasInputText] = useState(false);\n const agent = useEveAgent({\n agent: WEB_CHAT_AGENT,\n initialSession:\n sessionId === undefined\n ? undefined\n : {\n sessionId,\n streamIndex: 0,\n },\n resume: sessionId !== undefined,\n onSessionChange(session) {\n if (sessionId === undefined && session !== undefined) {\n // Next patches window.history to navigate, which would detach the active stream.\n History.prototype.replaceState.call(\n window.history,\n window.history.state,\n "",\n `/s/${encodeURIComponent(session.sessionId)}`,\n );\n }\n },\n });\n\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isResuming = agent.status === "resuming";\n const isEmpty = agent.data.messages.length === 0;\n const lastMessage = agent.data.messages.at(-1);\n const isPendingAssistantShell =\n lastMessage?.role === "assistant" &&\n lastMessage.parts.every((part) => part.type === "step-start");\n const showPendingThinking =\n isBusy &&\n (agent.status === "submitted" || lastMessage?.role !== "assistant" || isPendingAssistantShell);\n const turnFailure = isBusy || isResuming ? undefined : getLatestTurnFailure(agent.events);\n const errorMessage = cancellationError ?? agent.error?.message ?? turnFailure;\n const hasConversationContent = sessionless || !isEmpty || errorMessage !== undefined;\n const showConversationLayout = isResuming || hasConversationContent;\n const activeSessionId = sessionId ?? agent.session?.sessionId;\n // Answers are accepted while work runs; the server\'s settlement closes each request.\n const canRespond = (requestId: string) =>\n !isResuming && agent.data.inputs[requestId]?.status === "open";\n\n const requestCancellation = () => {\n setCancellationError(undefined);\n void agent.cancel().catch((error: unknown) => {\n setCancellationError(toErrorMessage(error));\n });\n };\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if ((text.length === 0 && message.files.length === 0) || isResuming) return;\n\n setHasInputText(false);\n setCancellationError(undefined);\n const options = isBusy ? { turnPolicy: "steer" as const } : undefined;\n\n if (message.files.length === 0) {\n await agent.send(text, options);\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(parts, options);\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea\n disabled={isResuming}\n onChange={(event) => setHasInputText(event.currentTarget.value.trim().length > 0)}\n placeholder="Send a message…"\n />\n <ComposerAction\n hasInputText={hasInputText}\n isBusy={isBusy}\n isResuming={isResuming}\n onCancel={requestCancellation}\n />\n </PromptInput>\n );\n\n return (\n <main className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">\n {showConversationLayout ? (\n <ChatHeader canStartNewChat={activeSessionId !== undefined} />\n ) : null}\n\n {showConversationLayout ? (\n <Conversation\n className="min-h-0 flex-1"\n initial={sessionId === undefined ? undefined : false}\n resize={activeSessionId === undefined ? "smooth" : "instant"}\n scrollRestorationKey={\n isEmpty || activeSessionId === undefined\n ? undefined\n : `eve:web-chat-scroll:${activeSessionId}`\n }\n >\n <ConversationTopFade className="top-14" />\n <ConversationContent className="mx-auto w-full max-w-3xl gap-6 px-4 pt-20 pb-36 sm:px-6">\n {agent.data.messages.map((message, index) =>\n showPendingThinking &&\n isPendingAssistantShell &&\n message.id === lastMessage.id ? null : (\n <AgentMessage\n canRespond={canRespond}\n isStreaming={\n agent.status === "streaming" && index === agent.data.messages.length - 1\n }\n key={message.id}\n message={message}\n onInputResponses={(inputResponses) => {\n setCancellationError(undefined);\n return agent.respond(inputResponses);\n }}\n />\n ),\n )}\n {showPendingThinking ? <PendingThinking /> : null}\n {errorMessage ? <ErrorMessage message={errorMessage} /> : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n ) : null}\n\n <div\n className={cn(\n "mx-auto w-full px-4 sm:px-6",\n showConversationLayout\n ? "fixed bottom-0 left-1/2 z-20 max-w-3xl -translate-x-1/2 bg-gradient-to-t from-background via-background to-transparent pt-4 pb-6"\n : "flex max-w-xl flex-1 flex-col items-center justify-center gap-8 pb-[10vh]",\n )}\n >\n {showConversationLayout ? null : (\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 )}\n <div className="w-full">{composer}</div>\n </div>\n </main>\n );\n}\n\nfunction ComposerAction({\n hasInputText,\n isBusy,\n isResuming,\n onCancel,\n}: {\n readonly hasInputText: boolean;\n readonly isBusy: boolean;\n readonly isResuming: boolean;\n readonly onCancel: () => void;\n}) {\n const attachments = usePromptInputAttachments();\n const canSubmit = hasInputText || attachments.files.length > 0;\n\n if (!isBusy || canSubmit) {\n return <PromptInputSubmit disabled={isResuming} />;\n }\n\n return (\n <PromptInputButton\n aria-label="Stop"\n className="absolute right-2.5 bottom-2.5"\n onClick={onCancel}\n variant="outline"\n >\n <SquareIcon className="size-3 fill-current" />\n </PromptInputButton>\n );\n}\n\nfunction ErrorMessage({ message }: { readonly message: string }) {\n return (\n <Message className="max-w-full" from="assistant">\n <MessageContent>\n <div\n className="flex w-full items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm"\n role="alert"\n >\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">{message}</p>\n </div>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction ChatHeader({ canStartNewChat }: { readonly canStartNewChat: boolean }) {\n return (\n <header className="pointer-events-none fixed top-0 right-0 left-0 z-20 h-14">\n <div className="relative mx-auto flex h-full w-full max-w-3xl items-center justify-center bg-background px-24">\n <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>\n {canStartNewChat ? (\n <Button\n aria-label="Start a new chat"\n className="pointer-events-auto fixed top-3 right-6 pr-4"\n onClick={() => window.location.assign("/s")}\n size="sm"\n type="button"\n variant="ghost"\n >\n <PlusIcon className="size-4" />\n <span className="hidden font-normal text-sm sm:inline">New chat</span>\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n\nfunction PendingThinking() {\n return (\n <Message aria-live="polite" from="assistant">\n <MessageContent>\n <div className="mb-4 flex w-full items-center gap-2 text-muted-foreground text-sm">\n <BrainIcon className="size-4" />\n <Shimmer duration={1}>Thinking</Shimmer>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : "Unable to cancel the response.";\n}\n\nfunction getLatestTurnFailure(\n events: ReturnType<typeof useEveAgent>["events"],\n): string | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n\n if (event.type === "turn.failed") {\n return event.data.code === "MODEL_CALL_FAILED"\n ? "The model is temporarily unavailable. Please try again."\n : event.data.message;\n }\n\n if (event.type === "turn.completed" || event.type === "turn.cancelled") {\n return undefined;\n }\n\n if (event.type === "message.received") {\n return undefined;\n }\n }\n\n return undefined;\n}\n';
|
|
3
|
+
readonly "app/_components/agent-message.tsx": '"use client";\n\nimport type {\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageInputRequest,\n EveMessagePart,\n} from "eve/react";\nimport { useState } from "react";\nimport {\n ArrowRightIcon,\n CheckCircleIcon,\n CheckIcon,\n ExternalLinkIcon,\n FileIcon,\n ImageIcon,\n KeyRoundIcon,\n XCircleIcon,\n} from "lucide-react";\nimport { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";\nimport {\n Question,\n QuestionInput,\n QuestionOption,\n QuestionOptions,\n QuestionPrompt,\n type QuestionResponse,\n QuestionSubmit,\n type QuestionValue,\n} from "@/components/ai-elements/question";\nimport { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";\nimport {\n BashToolContent,\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: (requestId: string) => 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 const hasAssistantText =\n message.role === "assistant" &&\n message.parts.some((part) => part.type === "text" && part.text.length > 0);\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 hasAssistantText && part.type === "reasoning" ? null : (\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 )}\n </MessageContent>\n </Message>\n );\n}\n\nfunction AgentMessagePart({\n canRespond,\n onInputResponses,\n part,\n showCaret,\n}: {\n readonly canRespond: (requestId: string) => 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 const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (inputRequest?.kind === "question") {\n return (\n <QuestionRequest\n canRespond={canRespond(inputRequest.requestId)}\n inputRequest={inputRequest}\n inputResponse={part.toolMetadata?.eve?.inputResponse}\n onInputResponses={onInputResponses}\n />\n );\n }\n\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 {part.toolName === "bash" ? (\n <BashToolContent errorText={part.errorText} input={part.input} output={part.output} />\n ) : (\n <ToolInput input={part.input} />\n )}\n <InputRequestActions\n canRespond={canRespond}\n part={part}\n onInputResponses={onInputResponses}\n />\n {part.toolName === "bash" ? null : (\n <ToolOutput errorText={part.errorText} output={part.output} />\n )}\n </ToolContent>\n </Tool>\n );\n }\n }\n}\n\nfunction QuestionRequest({\n canRespond,\n inputRequest,\n inputResponse,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly inputRequest: EveMessageInputRequest;\n readonly inputResponse?: AgentInputResponse;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const hasOptions = (inputRequest.options?.length ?? 0) > 0;\n const acceptsFreeform = inputRequest.allowFreeform === true || !hasOptions;\n const [questionValue, setQuestionValue] = useState<QuestionValue>({\n selectedValues: inputResponse?.optionId ? [inputResponse.optionId] : [],\n text: inputResponse?.text ?? "",\n });\n\n const submitOption = (optionId: string) => {\n setQuestionValue((value) => ({ ...value, selectedValues: [optionId] }));\n return onInputResponses([\n {\n optionId,\n requestId: inputRequest.requestId,\n },\n ]);\n };\n\n const submitResponse = ({ selectedValues, text }: QuestionResponse) =>\n onInputResponses([\n {\n optionId: selectedValues[0],\n requestId: inputRequest.requestId,\n text,\n },\n ]);\n\n return (\n <Question\n disabled={!canRespond || inputResponse !== undefined}\n onSubmit={submitResponse}\n onValueChange={setQuestionValue}\n value={questionValue}\n >\n <QuestionPrompt>{inputRequest.prompt}</QuestionPrompt>\n {hasOptions ? (\n <QuestionOptions className="flex-col items-stretch" aria-label={inputRequest.prompt}>\n {inputRequest.options?.map((option, index) => (\n <QuestionOption\n className="justify-start px-3 py-2 text-left"\n key={option.id}\n onClick={() => void submitOption(option.id)}\n value={option.id}\n >\n <span className="min-w-0 flex-1">\n <span className="block text-foreground text-sm leading-tight">{option.label}</span>\n {option.description ? (\n <span className="block text-sm text-muted-foreground leading-tight">\n {option.description}\n </span>\n ) : null}\n </span>\n {inputResponse === undefined ? (\n <span aria-hidden="true" className="relative size-6 shrink-0">\n <span className="absolute inset-0 flex items-center justify-center rounded-full bg-foreground/8 text-xs text-muted-foreground transition-opacity group-hover/option:opacity-0 group-focus-visible/option:opacity-0">\n {index + 1}\n </span>\n <ArrowRightIcon className="absolute top-1/2 left-1/2 size-4 -translate-x-1/2 -translate-y-1/2 text-muted-foreground opacity-0 transition-[color,opacity] group-hover/option:text-foreground group-hover/option:opacity-100 group-focus-visible/option:opacity-100" />\n </span>\n ) : (\n <CheckIcon className="size-4 shrink-0 opacity-0 transition-opacity group-data-[state=checked]/option:opacity-100" />\n )}\n </QuestionOption>\n ))}\n </QuestionOptions>\n ) : null}\n {acceptsFreeform ? (\n <div className="relative">\n <QuestionInput\n aria-label="Answer"\n className={inputResponse === undefined ? "pr-12 pb-12" : undefined}\n placeholder="Type your answer…"\n />\n {inputResponse === undefined && questionValue.text.trim().length > 0 ? (\n <QuestionSubmit\n aria-label="Answer"\n className="absolute right-2 bottom-2"\n size="icon-sm"\n >\n <ArrowRightIcon />\n </QuestionSubmit>\n ) : null}\n </div>\n ) : null}\n </Question>\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: (requestId: string) => 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 // An approval can settle before its batch resolves, without an input response.\n const settledApproval =\n part.approval?.approved === undefined\n ? undefined\n : part.approval.approved\n ? "Approved"\n : "Denied";\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 || settledApproval ? (\n <p className="font-medium text-sm">\n Responded:{" "}\n {selectedOption?.label ??\n inputResponse?.text ??\n inputResponse?.optionId ??\n settledApproval}\n </p>\n ) : (\n <div className="flex flex-wrap gap-2">\n {inputRequest.options?.map((option) => (\n <Button\n disabled={!canRespond(inputRequest.requestId)}\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 part.attemptId === undefined\n ? `authorization:${part.turnId}:${part.stepIndex}:${part.name}`\n : `authorization:${part.attemptId}`;\n case "dynamic-tool":\n return part.toolCallId;\n case "reasoning":\n case "text":\n return `${part.type}:${part.id ?? index}`;\n default:\n return `${part.type}:${index}`;\n }\n}\n';
|
|
4
4
|
readonly "app/apple-icon.tsx": 'import { ImageResponse } from "next/og";\n\nexport const size = {\n width: 180,\n height: 180,\n};\n\nexport const contentType = "image/png";\n\nexport default function AppleIcon() {\n return new ImageResponse(\n <svg fill="none" viewBox="0 0 102 102" xmlns="http://www.w3.org/2000/svg">\n <path d="M0 0h102v102H0z" fill="#000" />\n <path\n d="M49.28 66.94 75.03 34.96h-6.89L47.91 60.11l-5.49 6.83h6.86ZM0 34.96h42.4v5.11H0zm0 13.32h27.66v5.11H0zm0 13.54h27.66v5.11H0zm69.63-26.86H102v5.11H69.63zm4.71 13.32H102v5.11H74.34zm0 13.54H102v5.11H74.34z"\n fill="#fff"\n />\n </svg>,\n size,\n );\n}\n';
|
|
5
5
|
readonly "app/eve-agent.ts": '/** Named workspace agent selected by the Web Chat installer. */\nexport const WEB_CHAT_AGENT: string | undefined = undefined;\n';
|
|
6
6
|
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 -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\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';
|
|
@@ -75,6 +75,9 @@ export function AgentChat({
|
|
|
75
75
|
const hasConversationContent = sessionless || !isEmpty || errorMessage !== undefined;
|
|
76
76
|
const showConversationLayout = isResuming || hasConversationContent;
|
|
77
77
|
const activeSessionId = sessionId ?? agent.session?.sessionId;
|
|
78
|
+
// Answers are accepted while work runs; the server's settlement closes each request.
|
|
79
|
+
const canRespond = (requestId: string) =>
|
|
80
|
+
!isResuming && agent.data.inputs[requestId]?.status === "open";
|
|
78
81
|
|
|
79
82
|
const requestCancellation = () => {
|
|
80
83
|
setCancellationError(undefined);
|
|
@@ -152,7 +155,7 @@ export function AgentChat({
|
|
|
152
155
|
isPendingAssistantShell &&
|
|
153
156
|
message.id === lastMessage.id ? null : (
|
|
154
157
|
<AgentMessage
|
|
155
|
-
canRespond={
|
|
158
|
+
canRespond={canRespond}
|
|
156
159
|
isStreaming={
|
|
157
160
|
agent.status === "streaming" && index === agent.data.messages.length - 1
|
|
158
161
|
}
|
|
@@ -360,7 +363,7 @@ export function AgentMessage({
|
|
|
360
363
|
message,
|
|
361
364
|
onInputResponses,
|
|
362
365
|
}: {
|
|
363
|
-
readonly canRespond: boolean;
|
|
366
|
+
readonly canRespond: (requestId: string) => boolean;
|
|
364
367
|
readonly isStreaming: boolean;
|
|
365
368
|
readonly message: EveMessage;
|
|
366
369
|
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
|
|
@@ -401,7 +404,7 @@ function AgentMessagePart({
|
|
|
401
404
|
part,
|
|
402
405
|
showCaret,
|
|
403
406
|
}: {
|
|
404
|
-
readonly canRespond: boolean;
|
|
407
|
+
readonly canRespond: (requestId: string) => boolean;
|
|
405
408
|
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
|
|
406
409
|
readonly part: EveMessagePart;
|
|
407
410
|
readonly showCaret: boolean;
|
|
@@ -431,7 +434,7 @@ function AgentMessagePart({
|
|
|
431
434
|
if (inputRequest?.kind === "question") {
|
|
432
435
|
return (
|
|
433
436
|
<QuestionRequest
|
|
434
|
-
canRespond={canRespond}
|
|
437
|
+
canRespond={canRespond(inputRequest.requestId)}
|
|
435
438
|
inputRequest={inputRequest}
|
|
436
439
|
inputResponse={part.toolMetadata?.eve?.inputResponse}
|
|
437
440
|
onInputResponses={onInputResponses}
|
|
@@ -710,7 +713,7 @@ function InputRequestActions({
|
|
|
710
713
|
onInputResponses,
|
|
711
714
|
part,
|
|
712
715
|
}: {
|
|
713
|
-
readonly canRespond: boolean;
|
|
716
|
+
readonly canRespond: (requestId: string) => boolean;
|
|
714
717
|
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
|
|
715
718
|
readonly part: EveDynamicToolPart;
|
|
716
719
|
}) {
|
|
@@ -723,19 +726,30 @@ function InputRequestActions({
|
|
|
723
726
|
const selectedOption = inputRequest.options?.find(
|
|
724
727
|
(option) => option.id === inputResponse?.optionId,
|
|
725
728
|
);
|
|
729
|
+
// An approval can settle before its batch resolves, without an input response.
|
|
730
|
+
const settledApproval =
|
|
731
|
+
part.approval?.approved === undefined
|
|
732
|
+
? undefined
|
|
733
|
+
: part.approval.approved
|
|
734
|
+
? "Approved"
|
|
735
|
+
: "Denied";
|
|
726
736
|
|
|
727
737
|
return (
|
|
728
738
|
<div className="space-y-3 rounded-md border border-yellow-500/30 bg-yellow-500/5 p-3">
|
|
729
739
|
<p className="text-muted-foreground text-sm">{inputRequest.prompt}</p>
|
|
730
|
-
{inputResponse ? (
|
|
740
|
+
{inputResponse || settledApproval ? (
|
|
731
741
|
<p className="font-medium text-sm">
|
|
732
|
-
Responded:
|
|
742
|
+
Responded:{" "}
|
|
743
|
+
{selectedOption?.label ??
|
|
744
|
+
inputResponse?.text ??
|
|
745
|
+
inputResponse?.optionId ??
|
|
746
|
+
settledApproval}
|
|
733
747
|
</p>
|
|
734
748
|
) : (
|
|
735
749
|
<div className="flex flex-wrap gap-2">
|
|
736
750
|
{inputRequest.options?.map((option) => (
|
|
737
751
|
<Button
|
|
738
|
-
disabled={!canRespond}
|
|
752
|
+
disabled={!canRespond(inputRequest.requestId)}
|
|
739
753
|
key={option.id}
|
|
740
754
|
onClick={() => {
|
|
741
755
|
void onInputResponses([
|
|
@@ -761,9 +775,14 @@ function InputRequestActions({
|
|
|
761
775
|
function partKey(part: EveMessagePart, index: number): string {
|
|
762
776
|
switch (part.type) {
|
|
763
777
|
case "authorization":
|
|
764
|
-
return
|
|
778
|
+
return part.attemptId === undefined
|
|
779
|
+
? \`authorization:\${part.turnId}:\${part.stepIndex}:\${part.name}\`
|
|
780
|
+
: \`authorization:\${part.attemptId}\`;
|
|
765
781
|
case "dynamic-tool":
|
|
766
782
|
return part.toolCallId;
|
|
783
|
+
case "reasoning":
|
|
784
|
+
case "text":
|
|
785
|
+
return \`\${part.type}:\${part.id ?? index}\`;
|
|
767
786
|
default:
|
|
768
787
|
return \`\${part.type}:\${index}\`;
|
|
769
788
|
}
|